'use client' import { useEffect, useRef, useState } from 'react' import { useTranslations } from 'next-intl' const ERROR_KEYS = ['invalidLink', 'expiredLink', 'emailExists'] as const export function ActivateClient({ hash, realm }: { hash: string; realm: string }) { const t = useTranslations('Activate') // Sin hash el error se sabe YA, al renderizar (y 'invalidLink' es justo el // errorKey por defecto): se deriva del prop en vez de ponerlo con setState // dentro del efecto, que encadena un render de más. const [state, setState] = useState<'loading' | 'ok' | 'error'>(hash ? 'loading' : 'error') const [errorKey, setErrorKey] = useState('invalidLink') const [email, setEmail] = useState('') const ran = useRef(false) useEffect(() => { if (!hash || ran.current) return // evita doble POST en StrictMode ran.current = true fetch('/api/auth/activate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ hash }), }) .then((r) => r.json()) .then((d: { success?: boolean; error?: string; email?: string }) => { if (d.success) { setEmail(d.email ?? '') setState('ok') } else { setErrorKey((ERROR_KEYS as readonly string[]).includes(d.error ?? '') ? d.error! : 'invalidLink') setState('error') } }) .catch(() => setState('error')) }, [hash]) // El envoltorio ya lo pone la página (`body-box-content centered`), así que aquí // van los

sueltos: es la estructura que tenían las plantillas del tema. return ( <> {state === 'loading' &&

{t('activating')}

} {state === 'ok' && ( <>

{t('welcome', { realm })}

{t.rich('activated', { email, account: (c) => {c} })}


{t('canPlay')}


)} {state === 'error' && ( <>

{t(errorKey)}


{t('needHelp', { realm })}

)} ) }