fix error login

This commit is contained in:
David 2026-01-27 19:33:51 +01:00
parent 94039598d9
commit 4c7b07a911
3 changed files with 272 additions and 946 deletions

1035
CLAUDE.md

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,66 @@ import Link from 'next/link';
import Image from 'next/image'; import Image from 'next/image';
import { useAuth } from '@/lib/context/auth-context'; import { useAuth } from '@/lib/context/auth-context';
interface FieldErrors {
email?: string;
password?: string;
}
// Map backend error messages to French user-friendly messages
function getErrorMessage(error: any): { message: string; field?: 'email' | 'password' | 'general' } {
const errorMessage = error?.message || error?.response?.message || '';
// Network or server errors
if (error?.name === 'TypeError' || errorMessage.includes('fetch')) {
return {
message: 'Impossible de se connecter au serveur. Vérifiez votre connexion internet.',
field: 'general'
};
}
// Backend error messages
if (errorMessage.includes('Invalid credentials') || errorMessage.includes('Identifiants')) {
return {
message: 'Email ou mot de passe incorrect',
field: 'general'
};
}
if (errorMessage.includes('inactive') || errorMessage.includes('désactivé')) {
return {
message: 'Votre compte a été désactivé. Contactez le support pour plus d\'informations.',
field: 'general'
};
}
if (errorMessage.includes('not found') || errorMessage.includes('introuvable')) {
return {
message: 'Aucun compte trouvé avec cet email',
field: 'email'
};
}
if (errorMessage.includes('password') || errorMessage.includes('mot de passe')) {
return {
message: 'Mot de passe incorrect',
field: 'password'
};
}
if (errorMessage.includes('Too many') || errorMessage.includes('rate limit')) {
return {
message: 'Trop de tentatives de connexion. Veuillez réessayer dans quelques minutes.',
field: 'general'
};
}
// Default error
return {
message: errorMessage || 'Une erreur est survenue. Veuillez réessayer.',
field: 'general'
};
}
export default function LoginPage() { export default function LoginPage() {
const { login } = useAuth(); const { login } = useAuth();
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
@ -20,17 +80,64 @@ export default function LoginPage() {
const [rememberMe, setRememberMe] = useState(false); const [rememberMe, setRememberMe] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
const [fieldErrors, setFieldErrors] = useState<FieldErrors>({});
// Validate form fields
const validateForm = (): boolean => {
const errors: FieldErrors = {};
// Email validation
if (!email.trim()) {
errors.email = 'L\'adresse email est requise';
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
errors.email = 'L\'adresse email n\'est pas valide';
}
// Password validation
if (!password) {
errors.password = 'Le mot de passe est requis';
} else if (password.length < 6) {
errors.password = 'Le mot de passe doit contenir au moins 6 caractères';
}
setFieldErrors(errors);
return Object.keys(errors).length === 0;
};
// Handle input changes - keep errors visible until successful login
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value);
};
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
};
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setError(''); setError('');
setFieldErrors({});
// Validate form before submission
if (!validateForm()) {
return;
}
setIsLoading(true); setIsLoading(true);
try { try {
await login(email, password); await login(email, password);
// Navigation is handled by the login function in auth context // Navigation is handled by the login function in auth context
} catch (err: any) { } catch (err: any) {
setError(err.message || 'Identifiants incorrects'); const { message, field } = getErrorMessage(err);
if (field === 'email') {
setFieldErrors({ email: message });
} else if (field === 'password') {
setFieldErrors({ password: message });
} else {
setError(message);
}
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@ -65,7 +172,20 @@ export default function LoginPage() {
{/* Error Message */} {/* Error Message */}
{error && ( {error && (
<div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-lg"> <div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-lg flex items-start gap-3">
<svg
className="w-5 h-5 text-red-600 flex-shrink-0 mt-0.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<p className="text-body-sm text-red-800">{error}</p> <p className="text-body-sm text-red-800">{error}</p>
</div> </div>
)} )}
@ -74,38 +194,74 @@ export default function LoginPage() {
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
{/* Email */} {/* Email */}
<div> <div>
<label htmlFor="email" className="label"> <label htmlFor="email" className={`label ${fieldErrors.email ? 'text-red-600' : ''}`}>
Adresse email Adresse email
</label> </label>
<input <input
id="email" id="email"
type="email" type="email"
required
value={email} value={email}
onChange={e => setEmail(e.target.value)} onChange={handleEmailChange}
className="input w-full" className={`input w-full ${
fieldErrors.email
? 'border-red-500 focus:border-red-500 focus:ring-red-500 bg-red-50'
: ''
}`}
placeholder="votre.email@entreprise.com" placeholder="votre.email@entreprise.com"
autoComplete="email" autoComplete="email"
disabled={isLoading} disabled={isLoading}
aria-invalid={!!fieldErrors.email}
aria-describedby={fieldErrors.email ? 'email-error' : undefined}
/> />
{fieldErrors.email && (
<p id="email-error" className="mt-1.5 text-body-sm text-red-600 flex items-center gap-1">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
{fieldErrors.email}
</p>
)}
</div> </div>
{/* Password */} {/* Password */}
<div> <div>
<label htmlFor="password" className="label"> <label htmlFor="password" className={`label ${fieldErrors.password ? 'text-red-600' : ''}`}>
Mot de passe Mot de passe
</label> </label>
<input <input
id="password" id="password"
type="password" type="password"
required
value={password} value={password}
onChange={e => setPassword(e.target.value)} onChange={handlePasswordChange}
className="input w-full" className={`input w-full ${
fieldErrors.password
? 'border-red-500 focus:border-red-500 focus:ring-red-500 bg-red-50'
: ''
}`}
placeholder="••••••••••" placeholder="••••••••••"
autoComplete="current-password" autoComplete="current-password"
disabled={isLoading} disabled={isLoading}
aria-invalid={!!fieldErrors.password}
aria-describedby={fieldErrors.password ? 'password-error' : undefined}
/> />
{fieldErrors.password && (
<p id="password-error" className="mt-1.5 text-body-sm text-red-600 flex items-center gap-1">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
{fieldErrors.password}
</p>
)}
</div> </div>
{/* Remember Me & Forgot Password */} {/* Remember Me & Forgot Password */}

View File

@ -165,7 +165,12 @@ export async function apiRequest<T>(
}); });
// Handle 401 Unauthorized - token expired // Handle 401 Unauthorized - token expired
if (response.status === 401 && !isRetry && !endpoint.includes('/auth/refresh')) { // Skip auto-redirect for auth endpoints (login, register, refresh) - they handle their own errors
const isAuthEndpoint = endpoint.includes('/auth/login') ||
endpoint.includes('/auth/register') ||
endpoint.includes('/auth/refresh');
if (response.status === 401 && !isRetry && !isAuthEndpoint) {
// Check if we have a refresh token // Check if we have a refresh token
const refreshToken = getRefreshToken(); const refreshToken = getRefreshToken();
if (!refreshToken) { if (!refreshToken) {