"use client"; /* Newsletter sign-up. * * Deliberately kept away from the contact form, which promises "No mailing list, no follow-up * sequence" at the point of collection. That promise is about people who write to you; this is a * separate, explicit act by someone who wants to hear about releases. Keeping them visually and * physically apart is what makes both statements true. * * The copy says what it is and what it isn't, because a hospital coordinator deciding whether to * hand over a work address deserves to know the frequency and the exit before they type it. */ import { useState } from "react"; import Turnstile, { awaitTurnstile, resetTurnstile, turnstileOn } from "@/components/Turnstile"; import { track } from "@/lib/analytics"; export default function Subscribe() { const [email, setEmail] = useState(""); const [company, setCompany] = useState(""); // honeypot const [cfToken, setCfToken] = useState(""); const [busy, setBusy] = useState(false); const [done, setDone] = useState(false); const [err, setErr] = useState(""); async function submit(e: React.FormEvent) { e.preventDefault(); if (!email.trim()) { setErr("Add an email address."); return; } setBusy(true); setErr(""); const token = cfToken || (turnstileOn() ? await awaitTurnstile() : ""); const r = await fetch("/api/subscribe", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email, company, cfToken: token }), }).catch(() => null); const j = r ? await r.json().catch(() => ({})) : {}; setBusy(false); setCfToken(""); resetTurnstile(); // A rejected fetch used to leave the button disabled on "Sending…" with nothing said, so the // form looked like it had eaten the address. Sign-up is confirmed by email either way, so a // second attempt costs nothing. if (!r) { setErr("Couldn’t reach the server. Check the connection and try again."); return; } if (!r.ok) { setErr(j.error || "That didn’t go through. Try again in a moment."); return; } track("newsletter_signup"); setDone(true); } if (done) { return (
There’s a confirmation link on its way from hello@threadcount.tech. You’re not on the list until you click it.