117 lines
3.5 KiB
TypeScript
117 lines
3.5 KiB
TypeScript
import { useState } from 'react';
|
|
import { Link, useNavigate, useLocation } from 'react-router-dom';
|
|
import { useForm } from 'react-hook-form';
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { z } from 'zod';
|
|
import { Mail, Lock, Eye, EyeOff } from 'lucide-react';
|
|
import { AuthLayout } from '@app/layouts/AuthLayout';
|
|
import { Button } from '@components/atoms/Button';
|
|
import { FormField } from '@components/molecules/FormField';
|
|
import { Alert } from '@components/molecules/Alert';
|
|
import { useAuthStore } from '@stores/useAuthStore';
|
|
|
|
const loginSchema = z.object({
|
|
email: z.string().email('Email inválido'),
|
|
password: z.string().min(1, 'La contraseña es requerida'),
|
|
});
|
|
|
|
type LoginFormData = z.infer<typeof loginSchema>;
|
|
|
|
export default function LoginPage() {
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
const { login, isLoading, error, clearError } = useAuthStore();
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
|
|
const from = (location.state as { from?: Location })?.from?.pathname || '/dashboard';
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
formState: { errors },
|
|
} = useForm<LoginFormData>({
|
|
resolver: zodResolver(loginSchema),
|
|
});
|
|
|
|
const onSubmit = async (data: LoginFormData) => {
|
|
try {
|
|
await login(data);
|
|
navigate(from, { replace: true });
|
|
} catch {
|
|
// Error is handled in the store
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AuthLayout
|
|
title="Iniciar sesión"
|
|
subtitle="Ingresa tus credenciales para acceder a tu cuenta"
|
|
>
|
|
{error && (
|
|
<Alert variant="danger" onClose={clearError} className="mb-4">
|
|
{error}
|
|
</Alert>
|
|
)}
|
|
|
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
|
<FormField
|
|
label="Email"
|
|
type="email"
|
|
placeholder="tu@email.com"
|
|
error={errors.email?.message}
|
|
leftIcon={<Mail className="h-5 w-5" />}
|
|
{...register('email')}
|
|
/>
|
|
|
|
<FormField
|
|
label="Contraseña"
|
|
type={showPassword ? 'text' : 'password'}
|
|
placeholder="••••••••"
|
|
error={errors.password?.message}
|
|
leftIcon={<Lock className="h-5 w-5" />}
|
|
rightIcon={
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
className="text-gray-400 hover:text-gray-600"
|
|
>
|
|
{showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
|
|
</button>
|
|
}
|
|
{...register('password')}
|
|
/>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<label className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
className="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
|
/>
|
|
<span className="ml-2 text-sm text-gray-600">Recordarme</span>
|
|
</label>
|
|
<Link
|
|
to="/forgot-password"
|
|
className="text-sm font-medium text-primary-600 hover:text-primary-500"
|
|
>
|
|
¿Olvidaste tu contraseña?
|
|
</Link>
|
|
</div>
|
|
|
|
<Button type="submit" fullWidth isLoading={isLoading}>
|
|
Iniciar sesión
|
|
</Button>
|
|
</form>
|
|
|
|
<p className="mt-6 text-center text-sm text-gray-600">
|
|
¿No tienes cuenta?{' '}
|
|
<Link
|
|
to="/register"
|
|
className="font-medium text-primary-600 hover:text-primary-500"
|
|
>
|
|
Regístrate aquí
|
|
</Link>
|
|
</p>
|
|
</AuthLayout>
|
|
);
|
|
}
|