import { useState, useEffect, useRef } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { useAuth } from '../contexts/AuthContext'; import { useLanguage } from '../contexts/LanguageContext'; import { useBranding } from '../contexts/BrandingContext'; import { Mail, Lock, ArrowRight, Loader2, AlertTriangle, RefreshCw, LogIn, ShieldCheck } from 'lucide-react'; import BrandLogo from '../components/BrandLogo'; import api from '../services/api'; import toast from 'react-hot-toast'; export default function Login() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [loading, setLoading] = useState(false); const [needsVerification, setNeedsVerification] = useState(false); const [resendCooldown, setResendCooldown] = useState(0); const [resending, setResending] = useState(false); // 2FA state const [needs2FA, setNeeds2FA] = useState(false); const [tempToken, setTempToken] = useState(''); const [totpCode, setTotpCode] = useState(''); const [verifying2FA, setVerifying2FA] = useState(false); const totpInputRef = useRef(null); const { login, verify2FA } = useAuth(); const { t } = useLanguage(); const { registrationMode, oauthEnabled, oauthDisplayName } = useBranding(); const navigate = useNavigate(); useEffect(() => { if (resendCooldown <= 0) return; const timer = setTimeout(() => setResendCooldown(c => c - 1), 1000); return () => clearTimeout(timer); }, [resendCooldown]); // Auto-focus TOTP input when 2FA screen appears useEffect(() => { if (needs2FA && totpInputRef.current) { totpInputRef.current.focus(); } }, [needs2FA]); const handleResend = async () => { if (resendCooldown > 0 || resending) return; setResending(true); try { await api.post('/auth/resend-verification', { email }); toast.success(t('auth.emailVerificationResendSuccess')); setResendCooldown(60); } catch (err) { const wait = err.response?.data?.waitSeconds; if (wait) { setResendCooldown(wait); } toast.error(err.response?.data?.error || t('auth.emailVerificationResendFailed')); } finally { setResending(false); } }; const handleSubmit = async (e) => { e.preventDefault(); setLoading(true); try { const result = await login(email, password); if (result?.requires2FA) { setTempToken(result.tempToken); setNeeds2FA(true); setLoading(false); return; } toast.success(t('auth.loginSuccess')); navigate('/dashboard'); } catch (err) { if (err.response?.data?.needsVerification) { setNeedsVerification(true); } else { toast.error(err.response?.data?.error || t('auth.loginFailed')); } } finally { setLoading(false); } }; const handle2FASubmit = async (e) => { e.preventDefault(); setVerifying2FA(true); try { await verify2FA(tempToken, totpCode); toast.success(t('auth.loginSuccess')); navigate('/dashboard'); } catch (err) { toast.error(err.response?.data?.error || t('auth.2fa.verifyFailed')); setTotpCode(''); } finally { setVerifying2FA(false); } }; const handleBack = () => { setNeeds2FA(false); setTempToken(''); setTotpCode(''); }; return (
{/* Animated background */}
{/* Login card */}
{/* Logo */}
{needs2FA ? ( <> {/* 2FA verification step */}

{t('auth.2fa.title')}

{t('auth.2fa.prompt')}

setTotpCode(e.target.value.replace(/[^0-9\s]/g, '').slice(0, 7))} className="input-field pl-11 text-center text-lg tracking-[0.3em] font-mono" placeholder="000 000" required maxLength={7} />
) : ( <>

{t('auth.welcomeBack')}

{t('auth.loginSubtitle')}

setEmail(e.target.value)} className="input-field pl-11" placeholder={t('auth.emailPlaceholder')} required />
setPassword(e.target.value)} className="input-field pl-11" placeholder={t('auth.passwordPlaceholder')} required />
{oauthEnabled && ( <>
{t('auth.orContinueWith')}
{t('auth.loginWithOAuth').replace('{provider}', oauthDisplayName || 'SSO')} )} {needsVerification && (

{t('auth.emailVerificationBanner')}

)} {registrationMode !== 'invite' && (

{t('auth.noAccount')}{' '} {t('auth.signUpNow')}

)} {t('auth.backToHome')} )}
); }