fix error login
This commit is contained in:
parent
94039598d9
commit
4c7b07a911
@ -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 */}
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user