veza/apps/web/src/features/auth/components/LoginForm.tsx

120 lines
3.7 KiB
TypeScript
Raw Normal View History

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useNavigate } from 'react-router-dom';
import { useAuthStore } from '../store/authStore';
import { formatErrorMessage } from '@/utils/apiErrorHandler';
import type { LoginRequest } from '@/services/api/auth';
import type { ApiError } from '@/types/api';
const loginSchema = z.object({
email: z.string().email('Email invalide'),
password: z.string().min(1, 'Le mot de passe est requis'),
remember_me: z.boolean().optional(),
});
type LoginFormData = z.infer<typeof loginSchema>;
export const LoginForm = () => {
const navigate = useNavigate();
const { login: loginStore, isLoading, error } = useAuthStore();
2025-12-13 02:34:34 +00:00
const {
register,
handleSubmit,
formState: { errors },
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
defaultValues: {
email: '',
password: '',
remember_me: false,
},
});
const onSubmit = async (data: LoginFormData) => {
try {
const loginRequest: LoginRequest = {
email: data.email,
password: data.password,
remember_me: data.remember_me || false,
};
await loginStore(loginRequest);
// Redirection après succès
navigate('/dashboard');
} catch (err) {
// L'erreur est déjà gérée par le store
console.error('Login error:', err);
}
};
return (
2025-12-13 02:34:34 +00:00
<form
onSubmit={handleSubmit(onSubmit)}
className="space-y-4 max-w-sm mx-auto p-4 border rounded shadow"
>
<h2 className="text-2xl font-bold mb-4 text-center">Connexion</h2>
2025-12-13 02:34:34 +00:00
{error && (
2025-12-13 02:34:34 +00:00
<div
className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
role="alert"
>
<span className="block sm:inline">
{formatErrorMessage(error as ApiError)}
2025-12-13 02:34:34 +00:00
</span>
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-700">Email</label>
<input
type="email"
{...register('email')}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 p-2 border"
/>
2025-12-13 02:34:34 +00:00
{errors.email && (
<p className="text-red-500 text-xs mt-1">{errors.email.message}</p>
)}
</div>
<div>
2025-12-13 02:34:34 +00:00
<label className="block text-sm font-medium text-gray-700">
Mot de passe
</label>
<input
type="password"
{...register('password')}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 p-2 border"
/>
2025-12-13 02:34:34 +00:00
{errors.password && (
<p className="text-red-500 text-xs mt-1">{errors.password.message}</p>
)}
</div>
<div className="flex items-center">
<input
type="checkbox"
{...register('remember_me')}
id="remember_me"
className="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
/>
2025-12-13 02:34:34 +00:00
<label
htmlFor="remember_me"
className="ml-2 block text-sm text-gray-900"
>
Se souvenir de moi
</label>
</div>
<button
type="submit"
disabled={isLoading}
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:bg-gray-400"
>
{isLoading ? 'Connexion...' : 'Se connecter'}
</button>
</form>
);
2025-12-13 02:34:34 +00:00
};