Files
hanzo.ai/components/marketing/NewsletterSignup.tsx
zeekay cb6db9e659 design: every page and component outside the landing and beta paths is at zero lint
Inline style objects became gui props (Box for a div, View or Text rendering the
element for the rest, since Box styles only a div), raw colours and sizes became
the design's tokens, the brand swatches read @hanzo/design's own values, and the
project and creation hues moved to palette.json, which is data. The newsletter's
subscribe button is the standard filled Action. connect-steps.tsx, which nothing
imports, is deleted with the brand constants nobody read.

Pages compared before and after at 1440x900 and 390x844: blockchain, brand,
directory, projects, settings, unsubscribe identical; founder 0.03% of pixels;
buying specialist's title snaps from 28 to the ramp's 26px and the newsletter's
button is now filled.

Claude-Session: https://claude.ai/code/session_017vrVus6PPBgP5YRzKV1iqW
2026-10-26 03:49:42 -07:00

128 lines
3.9 KiB
TypeScript

'use client'
import React, { useState } from 'react'
import { Mail, Check, ArrowRight } from 'lucide-react'
import { Button, Input, Text, toast, View, XStack, YStack } from '@hanzo/ui'
const api = () => 'https://api.hanzo.ai'
const track = (_event: string, _props?: Record<string, any>) => {}
const visit = () => ({})
interface NewsletterSignupProps {
title?: string
description?: string
source?: string
}
export function NewsletterSignup({
title = 'Stay on the frontier',
description = 'Weekly updates on new model releases, benchmarks, and agent architectures.',
source = 'hanzo.ai/newsletter',
}: NewsletterSignupProps) {
const [email, setEmail] = useState('')
const [busy, setBusy] = useState(false)
const [subscribed, setSubscribed] = useState(false)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
const trimmed = email.trim()
if (!trimmed || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
toast.error('Please enter a valid email address')
return
}
setBusy(true)
track('newsletter_submitting', { source })
try {
const res = await fetch(`${api()}/v1/marketing/leads`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: trimmed,
source,
consent: true,
program: 'newsletter',
stage: 'subscriber',
visit: visit(),
}),
})
if (res.ok) {
setSubscribed(true)
track('newsletter_subscribed', { source, email: trimmed })
toast.success('Subscribed! Welcome to the Hanzo developer dispatch.')
} else {
toast.error('Unable to subscribe right now. Please try again.')
}
} catch {
toast.error('Network error. Check your connection.')
} finally {
setBusy(false)
}
}
return (
<YStack gap={12} width="100%" maxW={460}>
{title && (
<Text fontSize="$4" fontWeight="600" color="var(--foreground)">
{title}
</Text>
)}
{description && (
<Text fontSize="$2" color="var(--muted-foreground)">
{description}
</Text>
)}
{subscribed ? (
<XStack items="center" gap={8} py={8}>
<Check size={16} color="var(--state-success)" />
<Text fontSize="$2" color="var(--state-success)" fontWeight="500">
You're subscribed to the Hanzo newsletter.
</Text>
</XStack>
) : (
<View render={<form onSubmit={handleSubmit} />} display="block" width="100%">
<XStack gap={8} width="100%">
<Input
type="email"
placeholder="Enter your email"
value={email}
onChange={(e: any) => setEmail(e.target.value)}
height={42}
rounded={10}
bg="var(--surface-1)"
borderColor="var(--border)"
flex={1}
/>
<Button
type="submit"
height={42}
px={16}
rounded={10}
bg="var(--foreground)"
color="var(--background)"
disabled={busy}
gap={6}
>
{busy ? '...' : 'Subscribe'}
{!busy && <ArrowRight size={14} />}
</Button>
</XStack>
<Text fontSize="$1" color="var(--muted-foreground)" mt={8} lineHeight={14}>
By subscribing, you agree to receive product updates and engineering dispatches from Hanzo AI Inc. (995 Market St, SF CA). You can unsubscribe anytime via the 1-click link in every email. View our{' '}
<Text
render={<a href="/legal/privacy" />}
color="var(--foreground)"
textDecorationLine="underline"
>Privacy Policy</Text>.
</Text>
</View>
)}
</YStack>
)
}
export default NewsletterSignup