import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Link, useSearchParams } from 'react-router-dom'; import { apiPost } from '../../lib/api'; import { useDocumentTitle } from '../../lib/use-document-title'; type VerifyState = 'pending' | 'success' | 'error'; export function VerifyEmailPage(): React.JSX.Element { const { t } = useTranslation(); useDocumentTitle(t('auth:verify.title')); const [params] = useSearchParams(); const [state, setState] = useState('pending'); const [email, setEmail] = useState(''); const [resent, setResent] = useState(false); const token = params.get('token'); // Tokens are single-use: the effect must fire exactly once per token, // also under React StrictMode's double-invocation in development. const firedFor = useRef(null); useEffect(() => { if (!token) { setState('error'); return; } if (firedFor.current === token) return; firedFor.current = token; apiPost('/auth/verify-email', { token }) .then(() => setState('success')) .catch(() => setState('error')); }, [token]); if (state === 'pending') { return (

{t('auth:verify.title')}

); } if (state === 'success') { return (

{t('auth:verify.success.title')}

{t('auth:verify.success.body')}

{t('auth:verify.success.login')}
); } return (

{t('auth:verify.error.title')}

{t('errors:token_invalid')}

{resent ? (

{t('auth:verify.resent')}

) : (
{ event.preventDefault(); void apiPost('/auth/resend-verification', { email }).then(() => setResent(true)); }} >

{t('auth:verify.error.resendPrompt')}

)}
); }