// Tela de login — magic-link, acesso restrito à gestão CHARLES (por enquanto). // Produção: sem allowlist no cliente (o servidor decide; a resposta é sempre neutra — // anti-enumeração, padrão VoIP). Chama POST /api/auth/link e mostra sempre "Confira seu e-mail". // Os 3 estados de erro do link (ERROS_LINK) são acionados pelo App.jsx quando o POST do token // volta com {erro:...} — copy verbatim aprovada no design. const DSl = window.WiFi_eeb21e; const { Card: CardL, CardContent: CardContentL, Button: BtnL, Input: InputL, Label: LabelL, CharlesLogoMark: LogoL, Separator: SepL } = DSl; const { TermoDica: TermoL } = window; const ERROS_LINK = { vencido: { t: 'Este link já venceu.', d: 'Links valem por 15 minutos e funcionam uma vez. Peça outro abaixo — é rápido.' }, usado: { t: 'Este link já foi usado.', d: 'Cada link funciona uma única vez. Se não foi você quem usou, peça outro agora.' }, substituido: { t: 'Este link foi substituído.', d: 'Você pediu um link mais novo — só o mais recente vale. Confira o último e-mail ou peça outro.' } }; // map do rótulo do servidor (app.py) para a chave da copy: // expirado→vencido · ja_usado→usado · substituido→substituido const ERRO_SERVIDOR = { expirado: 'vencido', ja_usado: 'usado', substituido: 'substituido' }; function TelaLogin({ erroInicial }) { const [email, setEmail] = React.useState(''); const [fase, setFase] = React.useState(erroInicial ? (ERRO_SERVIDOR[erroInicial] || 'vencido') : 'pedir'); // pedir | enviado | vencido | usado | substituido const [enviando, setEnviando] = React.useState(false); const enviar = async e => { e.preventDefault(); setEnviando(true); try { await fetch('/api/auth/link', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email.trim().toLowerCase() }) }); } catch (err) { /* resposta é sempre neutra — nem erro de rede muda a tela */ } setEnviando(false); setFase('enviado'); // sempre — não revela se o e-mail existe }; return