Reapply "Add Turnstile captcha to login"

This reverts commit a7c732e139.
This commit is contained in:
Cauê Faleiros
2026-07-28 12:42:27 -03:00
parent a7c732e139
commit 252c2447a8
9 changed files with 198 additions and 15 deletions

View File

@@ -1,29 +1,134 @@
import { useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Lock } from 'lucide-react';
import { login } from '../dataService';
declare global {
interface Window {
turnstile?: {
render: (
container: HTMLElement,
options: {
sitekey: string;
callback: (token: string) => void;
'expired-callback': () => void;
'error-callback': () => void;
theme: 'dark' | 'light' | 'auto';
}
) => string;
reset: (widgetId?: string) => void;
};
}
}
const turnstileSiteKey = import.meta.env.VITE_TURNSTILE_SITE_KEY;
const turnstileScriptId = 'turnstile-api-script';
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [captchaToken, setCaptchaToken] = useState('');
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [captchaReady, setCaptchaReady] = useState(!turnstileSiteKey);
const captchaContainerRef = useRef<HTMLDivElement | null>(null);
const captchaWidgetIdRef = useRef<string | null>(null);
const navigate = useNavigate();
const captchaEnabled = Boolean(turnstileSiteKey);
useEffect(() => {
if (!turnstileSiteKey || !captchaContainerRef.current || captchaWidgetIdRef.current) return;
const siteKey = turnstileSiteKey;
const renderCaptcha = () => {
if (!window.turnstile || !captchaContainerRef.current || captchaWidgetIdRef.current) return;
captchaWidgetIdRef.current = window.turnstile.render(captchaContainerRef.current, {
sitekey: siteKey,
theme: 'dark',
callback: (token) => {
setCaptchaToken(token);
setCaptchaReady(true);
},
'expired-callback': () => {
setCaptchaToken('');
setCaptchaReady(true);
},
'error-callback': () => {
setCaptchaToken('');
setCaptchaReady(false);
setError('Não foi possível carregar a verificação anti-bot.');
},
});
setCaptchaReady(true);
};
if (window.turnstile) {
renderCaptcha();
return;
}
const existingScript = document.getElementById(turnstileScriptId);
if (existingScript) {
existingScript.addEventListener('load', renderCaptcha, { once: true });
return () => existingScript.removeEventListener('load', renderCaptcha);
}
const script = document.createElement('script');
script.id = turnstileScriptId;
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
script.async = true;
script.defer = true;
script.addEventListener('load', renderCaptcha, { once: true });
script.addEventListener('error', () => {
setCaptchaReady(false);
setError('Não foi possível carregar a verificação anti-bot.');
}, { once: true });
document.head.appendChild(script);
return () => script.removeEventListener('load', renderCaptcha);
}, [captchaEnabled]);
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
if (captchaEnabled && !captchaToken) {
setError('Confirme a verificação anti-bot antes de entrar.');
return;
}
setIsLoading(true);
setError('');
try {
const success = await login(email, password);
if (success) {
const result = await login(email, password, captchaToken);
if (result === 'success') {
navigate('/graph');
} else {
} else if (result === 'captcha_failed') {
setError('A verificação anti-bot falhou. Tente novamente.');
if (captchaEnabled) {
setCaptchaToken('');
window.turnstile?.reset(captchaWidgetIdRef.current ?? undefined);
}
} else if (result === 'invalid_credentials') {
setError('E-mail ou senha incorretos.');
if (captchaEnabled) {
setCaptchaToken('');
window.turnstile?.reset(captchaWidgetIdRef.current ?? undefined);
}
} else {
setError('Erro ao conectar ao servidor.');
if (captchaEnabled) {
setCaptchaToken('');
window.turnstile?.reset(captchaWidgetIdRef.current ?? undefined);
}
}
} catch {
setError('Erro ao conectar ao servidor.');
if (captchaEnabled) {
setCaptchaToken('');
window.turnstile?.reset(captchaWidgetIdRef.current ?? undefined);
}
} finally {
setIsLoading(false);
}
@@ -64,11 +169,20 @@ const Login = () => {
/>
</div>
{captchaEnabled && (
<div className="min-h-[70px] rounded-xl border border-dark-border bg-dark-input/60 p-3">
<div ref={captchaContainerRef} className="flex justify-center" />
{!captchaReady && (
<p className="mt-2 text-center text-xs font-medium text-red-400">Verificação anti-bot indisponível.</p>
)}
</div>
)}
{error && <p className="text-red-500 text-sm font-medium">{error}</p>}
<button
type="submit"
disabled={isLoading}
disabled={isLoading || !captchaReady || (captchaEnabled && !captchaToken)}
className="w-full bg-brand-primary hover:bg-opacity-90 hover:scale-[1.02] active:scale-[0.98] text-zinc-900 font-bold py-3 rounded-xl transition-all duration-200 disabled:opacity-50 disabled:hover:scale-100 disabled:active:scale-100 mt-4 cursor-pointer"
>
{isLoading ? 'Entrando...' : 'Entrar'}