// The contact form — an ISLAND inside an otherwise-static page. The page is // `interactive: 'islands'`, so only THIS component hydrates; the surrounding // marketing copy stays inert HTML. // // `hydrate: 'visible'` defers hydration until the form scrolls into view — // it's below the fold, so there's no reason to spend JS on it at first paint. // // i18n note: islands hydrate INDEPENDENTLY of the layout's URL , // so this component must NOT import @voltro/i18n (``/useT would crash / show // raw keys at hydration). Instead the PAGE resolves every UI string per-locale // (inside the provider) and passes them in as `labels` — the framework // serialises those props into the island's HTML, so it hydrates already // translated. Dynamic runtime data (the server's error detail) stays as-is. // // On submit it POSTs JSON to the serverless function (CONTACT_ENDPOINT). The // function lives on a different origin, but the framework's node runner + // the edge hosts both send CORS headers, so the cross-origin call works. import type { FormEvent, ReactNode } from 'react' import { useState } from 'react' import { island } from '@voltro/web' import { CONTACT_ENDPOINT } from '../config' // Every user-facing string the island shows, resolved per-locale by the page. export interface ContactFormLabels { readonly name: string readonly email: string readonly message: string readonly send: string readonly sending: string readonly sentNotice: string readonly errorPrefix: string readonly networkError: string } // Object-literal type (not an interface) so it satisfies the island() // generic's `Record` constraint. type ContactFormProps = { readonly labels: ContactFormLabels } type Status = | { readonly kind: 'idle' } | { readonly kind: 'sending' } | { readonly kind: 'sent' } | { readonly kind: 'error'; readonly message: string } function ContactForm({ labels }: ContactFormProps): ReactNode { const [name, setName] = useState('') const [email, setEmail] = useState('') const [message, setMessage] = useState('') const [status, setStatus] = useState({ kind: 'idle' }) const onSubmit = async (e: FormEvent): Promise => { e.preventDefault() setStatus({ kind: 'sending' }) try { const res = await fetch(CONTACT_ENDPOINT, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name, email, message }), }) const body = (await res.json().catch(() => ({}))) as { error?: string; detail?: string } if (!res.ok) { setStatus({ kind: 'error', message: body.detail ?? body.error ?? `HTTP ${res.status}` }) return } setStatus({ kind: 'sent' }) setName('') setEmail('') setMessage('') } catch (err) { setStatus({ kind: 'error', message: err instanceof Error ? err.message : labels.networkError }) } } if (status.kind === 'sent') { return

{labels.sentNotice}

} const sending = status.kind === 'sending' return (
setName(e.target.value)} /> setEmail(e.target.value)} />