'use client'; import { useState, type FormEvent } from 'react'; /** * THE VYNDR REPORT capture (Session S7, a1) — email field for the daily * newsletter. Dark terminal surface, mono for data (the email IS data), * VOICE v1.1 copy: deadpan, no exclamation points. * * States: * idle → busy → sent ("Confirmation sent. Check your inbox.") * soon (backend not configured → "Signups open soon.") * error (bad email / transient upstream) * * Double opt-in is real: the backend asks Listmonk to send a confirmation; * nothing lands on the list until the reader clicks it. The note under the * field says exactly that. */ export default function NewsletterCapture({ compact = false }: { compact?: boolean }) { const [email, setEmail] = useState(''); const [website, setWebsite] = useState(''); // honeypot — humans never see it const [state, setState] = useState<'idle' | 'busy' | 'sent' | 'soon' | 'error'>('idle'); const [message, setMessage] = useState(''); const submit = async (e: FormEvent) => { e.preventDefault(); if (state === 'busy') return; setState('busy'); setMessage(''); try { const res = await fetch('/api/newsletter/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email.trim(), website }), }); const data = await res.json().catch(() => ({ ok: false, reason: 'not configured' })); if (data.ok) { setState('sent'); } else if (res.status === 400) { setState('error'); setMessage(data.error || 'Enter a valid email.'); } else { // 'not configured' or transient upstream — calm either way. setState('soon'); } } catch { setState('soon'); } }; if (state === 'sent') { return (
Confirmation sent. Check your inbox — nothing arrives until you click it.
The slate, the signals, the settle. Daily. Free.
{state === 'soon' ? (Signups open soon.
) : ( )} {state === 'error' && message && ({message}
)} {state !== 'soon' && (Double opt-in — a confirmation email lands first; nothing sends until you click it. Unsubscribe is one click, forever. 21+.
)}