Files
zeekay ea03b3eefc newsletter: double opt-in in every footer, on cloud's subscribe
POST /v1/marketing/subscribe answers pending whatever the address and
mails a link to /newsletter/confirm#t=<token>; that page spends the
token only on a press, so a mail scanner subscribes nobody, and drops
the fragment from the address. Every page's footer, /newsletter and
/subscribe draw the one form; the footer's chunk loads at low priority.

The old form filed /v1/marketing/leads, mailing sales for every
subscriber, and said subscribed when the request failed; it is gone,
with the unused NewsletterSignup stub whose track was a no-op.
2026-10-29 23:13:08 -07:00

90 lines
3.5 KiB
TypeScript

'use client'
import { useEffect, useState, useSyncExternalStore } from 'react'
import { useI18n } from '@hanzo/i18n/react'
import { Text, YStack } from '@hanzo/ui'
import { Action } from '@hanzo/ui/marketing'
import { track } from '@hanzo/app/lib/tags'
import { BAD } from '@hanzo/app/lib/mix'
import { Focus, LARGE } from '@/components/shell/focus'
import { Subscribe } from '@/components/newsletter/Subscribe'
import { confirm, Refused } from '@/lib/newsletter'
/**
* Where the newsletter's confirmation mail lands: `/newsletter/confirm#t=<token>`.
* The token rides the fragment, so it reaches neither a server log nor a
* Referer, and it is spent only by the press below: a mail scanner that opens
* the link subscribes nobody. Once read, the fragment leaves the address.
*
* Events: newsletter_confirmed, newsletter_confirm_refused { status }.
*/
type State = { at: 'ready' | 'busy' | 'done' } | { at: 'refused'; status: number; detail: string }
/** The link's token, read off the fragment the first time it is asked for and kept: the fragment leaves the address once read. */
let kept: string | null = null
const fromHash = () => (kept ??= new URLSearchParams(window.location.hash.slice(1)).get('t') ?? '')
/** Nothing changes the token while the page is open. */
const still = () => () => {}
export default function ConfirmNewsletter() {
const { t } = useI18n()
// Null until the page runs: the export cannot know the token.
const token = useSyncExternalStore(still, fromHash, () => null)
const [state, setState] = useState<State>({ at: 'ready' })
useEffect(() => {
if (window.location.hash) history.replaceState(history.state, '', window.location.pathname)
}, [])
const press = async () => {
setState({ at: 'busy' })
try {
await confirm(token ?? '')
setState({ at: 'done' })
track('newsletter_confirmed', {})
} catch (err) {
const status = err instanceof Refused ? err.status : 0
track('newsletter_confirm_refused', { status })
setState({ at: 'refused', status, detail: err instanceof Refused ? err.detail : '' })
}
}
if (state.at === 'done') {
return (
<Focus id="confirm-title" title={t('You are subscribed.')} line={t('The next issue comes to your inbox. Every one has a one-click unsubscribe.')}>
<Action href="/" fill {...LARGE}>
{t('Back to hanzo.ai')}
</Action>
</Focus>
)
}
if (token === '' || (state.at === 'refused' && (state.status === 400 || state.status === 410))) {
const expired = state.at === 'refused' && state.status === 410
return (
<Focus
id="confirm-title"
title={expired ? t('This link has expired.') : t('This link does not work.')}
line={t('Enter your address again and we will send a new one.')}
>
<Subscribe source="hanzo.ai/newsletter/confirm" id="again" />
</Focus>
)
}
return (
<Focus id="confirm-title" title={t('Confirm your subscription')} line={t('One press and the Hanzo newsletter starts coming to your inbox.')}>
<YStack gap="$3">
<Action render="button" fill disabled={!token || state.at === 'busy'} onClick={press} {...LARGE}>
{state.at === 'busy' ? t('Confirming…') : t('Confirm subscription')}
</Action>
{state.at === 'refused' ? (
<Text role="alert" text="center" fontSize="$2" lineHeight="1.25rem" color={BAD}>
{state.detail || t('We could not reach our mail service. Try again in a minute.')}
</Text>
) : null}
</YStack>
</Focus>
)
}