veza/apps/web/src/features/auth/pages/VerifyEmailPage.tsx
senke 0a29c544af fix(web): resolve all 568 TypeScript errors — tsc --noEmit now passes with zero errors
Major categories fixed:
- TS6133 (188): Remove unused imports (React, icons, types) and variables
- TS2322 (222): Fix type mismatches in stories (satisfies Meta -> const meta: Meta),
  add nullish coalescing for optional values, fix component prop types
- TS2345 (43): Fix argument type mismatches with proper null checks and type narrowing
- TS2741 (21): Add missing required properties to mock/story data
- TS2339 (19): Fix property access on incorrect types, add type guards
- TS2353 (13): Remove extra properties from object literals or extend interfaces
- TS2352 (11): Fix type conversion chains
- TS2307 (9): Fix import paths and module references
- Other (42): Fix implicit any, possibly undefined, export declarations

Vite build and tsc --noEmit both pass cleanly.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 00:32:08 +01:00

244 lines
7.3 KiB
TypeScript

import { useState, useEffect, useRef } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { AuthLayout } from '../components/AuthLayout';
import { AuthButton } from '../components/AuthButton';
import { authApi } from '@/services/api/auth';
import type { ApiError } from '@/schemas/apiSchemas';
export function VerifyEmailPage() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const [status, setStatus] = useState<'verifying' | 'success' | 'error'>(
'verifying',
);
const [message, setMessage] = useState(
'Vérification de votre email en cours...',
);
const [loading, setLoading] = useState(false);
const [resendLoading, setResendLoading] = useState(false);
const [resendCooldown, setResendCooldown] = useState(0);
const cooldownIntervalRef = useRef<NodeJS.Timeout | null>(null);
const [token, setToken] = useState<string | null>(null);
// Extraire le token depuis l'URL
useEffect(() => {
const tokenParam = searchParams.get('token');
if (tokenParam) {
setToken(tokenParam);
// Vérifier l'email automatiquement si le token est présent
handleVerifyEmail(tokenParam);
} else {
setStatus('error');
setMessage('Lien de vérification invalide ou manquant');
}
}, [searchParams]);
// Cleanup cooldown interval on unmount
useEffect(() => {
return () => {
if (cooldownIntervalRef.current) {
clearInterval(cooldownIntervalRef.current);
}
};
}, []);
// Gérer le cooldown pour le renvoi d'email
useEffect(() => {
if (resendCooldown > 0) {
cooldownIntervalRef.current = setInterval(() => {
setResendCooldown((prev) => {
if (prev <= 1) {
if (cooldownIntervalRef.current) {
clearInterval(cooldownIntervalRef.current);
}
return 0;
}
return prev - 1;
});
}, 1000);
} else {
if (cooldownIntervalRef.current) {
clearInterval(cooldownIntervalRef.current);
cooldownIntervalRef.current = null;
}
}
return () => {
if (cooldownIntervalRef.current) {
clearInterval(cooldownIntervalRef.current);
}
};
}, [resendCooldown]);
// Rediriger vers login après succès
useEffect(() => {
if (status === 'success') {
const timer = setTimeout(() => {
navigate('/login', { replace: true });
}, 3000);
return () => clearTimeout(timer);
}
return undefined;
}, [status, navigate]);
const handleVerifyEmail = async (emailToken: string) => {
try {
setLoading(true);
setStatus('verifying');
setMessage('Vérification de votre email en cours...');
await authApi.verifyEmail({ token: emailToken });
setStatus('success');
setMessage('Votre email a été vérifié avec succès !');
} catch (error) {
setStatus('error');
const apiError = error as ApiError;
setMessage(apiError.message || 'La vérification a échoué');
} finally {
setLoading(false);
}
};
const handleResendVerificationEmail = async () => {
if (resendCooldown > 0 || resendLoading) {
return;
}
try {
setResendLoading(true);
// Récupérer l'email depuis localStorage (stocké lors de l'inscription)
const email = localStorage.getItem('pendingVerificationEmail');
if (!email) {
setMessage(
'Email non trouvé. Veuillez vous réinscrire ou contacter le support.',
);
return;
}
await authApi.resendVerification({ email });
// Définir cooldown de 60 secondes
setResendCooldown(60);
// Afficher message de confirmation
setMessage(
'Email de vérification envoyé ! Veuillez vérifier votre boîte mail.',
);
} catch (error) {
const apiError = error as ApiError;
setMessage(apiError.message || "Échec de l'envoi de l'email");
} finally {
setResendLoading(false);
}
};
// Afficher le statut de vérification
if (status === 'verifying') {
return (
<AuthLayout
title="Vérification de l'email"
subtitle="Vérification en cours..."
footerLinks={[{ label: 'Retour à la connexion', to: '/login' }]}
>
<div
className="text-center space-y-4"
role="status"
aria-live="polite"
aria-busy="true"
>
<div className="flex justify-center" aria-hidden="true">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
</div>
<p className="text-muted-foreground">{message}</p>
<span className="sr-only">
Vérification de votre email en cours, veuillez patienter
</span>
</div>
</AuthLayout>
);
}
// Afficher le message de succès
if (status === 'success') {
return (
<AuthLayout
title="Email vérifié"
subtitle="Votre email a été vérifié avec succès"
footerLinks={[{ label: 'Retour à la connexion', to: '/login' }]}
>
<div className="text-center space-y-4" role="status" aria-live="polite">
<div
className="bg-success/10 border border-success text-success px-4 py-4 rounded"
role="alert"
>
<p className="font-medium">Succès !</p>
<p className="text-sm mt-1">{message}</p>
<p className="text-xs mt-2 text-muted-foreground">
Vous allez être redirigé vers la page de connexion...
</p>
</div>
</div>
</AuthLayout>
);
}
// Afficher le message d'erreur avec options de retry/resend
return (
<AuthLayout
title="Vérification de l'email"
subtitle="Une erreur s'est produite"
footerLinks={[{ label: 'Retour à la connexion', to: '/login' }]}
>
<div className="text-center space-y-4">
<div
className="bg-destructive/10 border border-destructive text-destructive px-4 py-4 rounded"
role="alert"
aria-live="assertive"
>
<p className="font-medium">Erreur</p>
<p className="text-sm mt-1">{message}</p>
</div>
<div className="space-y-2">
{token && (
<AuthButton
onClick={() => handleVerifyEmail(token)}
loading={loading}
type="button"
>
Réessayer
</AuthButton>
)}
<AuthButton
onClick={handleResendVerificationEmail}
loading={resendLoading}
disabled={resendCooldown > 0}
type="button"
variant="secondary"
aria-label={
resendCooldown > 0
? `Renvoyer l'email de vérification dans ${resendCooldown} secondes`
: "Renvoyer l'email de vérification"
}
>
{resendCooldown > 0 ? (
<>
<span className="sr-only">
Renvoyer dans {resendCooldown} secondes
</span>
<span aria-hidden="true">Renvoyer dans {resendCooldown}s</span>
</>
) : (
"Renvoyer l'email de vérification"
)}
</AuthButton>
</div>
</div>
</AuthLayout>
);
}
export default VerifyEmailPage;