Add email verification handling and resend functionality to login
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m2s

This commit is contained in:
2026-02-27 16:49:24 +01:00
parent 15eed76ab4
commit ab52ca4529

View File

@@ -1,19 +1,43 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import { useLanguage } from '../contexts/LanguageContext';
import { Mail, Lock, ArrowRight, Loader2 } from 'lucide-react';
import { Mail, Lock, ArrowRight, Loader2, AlertTriangle, RefreshCw } 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);
const { login } = useAuth();
const { t } = useLanguage();
const navigate = useNavigate();
useEffect(() => {
if (resendCooldown <= 0) return;
const timer = setTimeout(() => setResendCooldown(c => c - 1), 1000);
return () => clearTimeout(timer);
}, [resendCooldown]);
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 {
toast.error(t('auth.emailVerificationResendFailed'));
} finally {
setResending(false);
}
};
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
@@ -22,7 +46,11 @@ export default function Login() {
toast.success(t('auth.loginSuccess'));
navigate('/dashboard');
} catch (err) {
toast.error(err.response?.data?.error || t('auth.loginFailed'));
if (err.response?.data?.needsVerification) {
setNeedsVerification(true);
} else {
toast.error(err.response?.data?.error || t('auth.loginFailed'));
}
} finally {
setLoading(false);
}
@@ -101,6 +129,25 @@ export default function Login() {
</button>
</form>
{needsVerification && (
<div className="mt-4 p-4 rounded-xl bg-amber-500/10 border border-amber-500/30 space-y-2">
<div className="flex items-start gap-2">
<AlertTriangle size={16} className="text-amber-400 flex-shrink-0 mt-0.5" />
<p className="text-sm text-amber-200">{t('auth.emailVerificationBanner')}</p>
</div>
<button
onClick={handleResend}
disabled={resendCooldown > 0 || resending}
className="flex items-center gap-1.5 text-sm text-amber-400 hover:text-amber-300 underline underline-offset-2 transition-colors disabled:opacity-60 disabled:no-underline disabled:cursor-not-allowed"
>
<RefreshCw size={13} className={resending ? 'animate-spin' : ''} />
{resendCooldown > 0
? t('auth.emailVerificationResendCooldown').replace('{seconds}', resendCooldown)
: t('auth.emailVerificationResend')}
</button>
</div>
)}
<p className="mt-6 text-center text-sm text-th-text-s">
{t('auth.noAccount')}{' '}
<Link to="/register" className="text-th-accent hover:underline font-medium">