stock-bot/apps/wcag-ada/dashboard/src/pages/auth/login.tsx
2025-06-28 11:11:34 -04:00

112 lines
No EOL
3.7 KiB
TypeScript

import { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { apiClient } from '@/lib/api-client';
import { useAuthStore } from '@/store/auth-store';
import { Button } from '@/components/ui/button';
import { Loader2 } from 'lucide-react';
const loginSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});
type LoginForm = z.infer<typeof loginSchema>;
export function LoginPage() {
const navigate = useNavigate();
const { setAuth } = useAuthStore();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const {
register,
handleSubmit,
formState: { errors },
} = useForm<LoginForm>({
resolver: zodResolver(loginSchema),
});
const onSubmit = async (data: LoginForm) => {
setIsLoading(true);
setError(null);
try {
const response = await apiClient.login(data.email, data.password);
setAuth(response.user, response.token);
navigate('/dashboard');
} catch (err: any) {
setError(err.response?.data?.error || 'Login failed. Please try again.');
} finally {
setIsLoading(false);
}
};
return (
<form className="mt-8 space-y-6" onSubmit={handleSubmit(onSubmit)}>
<div className="rounded-md shadow-sm -space-y-px">
<div>
<label htmlFor="email" className="sr-only">
Email address
</label>
<input
{...register('email')}
type="email"
autoComplete="email"
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-primary focus:border-primary focus:z-10 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-white"
placeholder="Email address"
/>
{errors.email && (
<p className="mt-1 text-sm text-red-600">{errors.email.message}</p>
)}
</div>
<div>
<label htmlFor="password" className="sr-only">
Password
</label>
<input
{...register('password')}
type="password"
autoComplete="current-password"
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-primary focus:border-primary focus:z-10 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:text-white"
placeholder="Password"
/>
{errors.password && (
<p className="mt-1 text-sm text-red-600">{errors.password.message}</p>
)}
</div>
</div>
{error && (
<div className="rounded-md bg-red-50 dark:bg-red-900/20 p-4">
<p className="text-sm text-red-800 dark:text-red-400">{error}</p>
</div>
)}
<div>
<Button
type="submit"
className="w-full"
disabled={isLoading}
>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Sign in
</Button>
</div>
<div className="text-center">
<span className="text-sm text-gray-600 dark:text-gray-400">
Don't have an account?{' '}
<Link
to="/register"
className="font-medium text-primary hover:text-primary/80"
>
Sign up
</Link>
</span>
</div>
</form>
);
}