Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | 1x 68x 226x 68x 68x 68x 11x 11x 11x 7x 4x 4x 68x | import { useState } from 'react';
import { useNavigate } from 'react-router';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import {
Field,
FieldGroup,
FieldLabel,
FieldError,
} from '@/components/ui/field';
import { useAuthStore } from '@/stores/auth.store';
import { handleApiError } from '@/lib/api';
// Validation schema
const loginSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(1, 'Password is required'),
});
type LoginFormData = z.infer<typeof loginSchema>;
export default function LoginPage() {
const navigate = useNavigate();
const login = useAuthStore((state) => state.login);
const [error, setError] = useState<string | null>(null);
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
});
const onSubmit = async (data: LoginFormData) => {
setError(null);
try {
await login(data.email, data.password);
navigate('/admin/dashboard');
} catch (err) {
const errorMessage = handleApiError(err);
setError(errorMessage);
}
};
return (
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
<div className="w-full max-w-sm">
<Card>
<CardHeader className="text-left py-4">
<CardTitle className="text-2xl font-semibold tracking-tight">
Login to your account
</CardTitle>
<CardDescription className="text-muted-foreground">
Enter your email below to login to your account
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit(onSubmit)}>
<FieldGroup>
<Field>
<FieldLabel htmlFor="email" className="text-sm font-medium">
Email
</FieldLabel>
<Input
id="email"
type="email"
placeholder="m@example.com"
{...register('email')}
disabled={isSubmitting}
/>
<FieldError errors={[errors.email]} />
</Field>
<Field>
<div className="flex items-center">
<FieldLabel
htmlFor="password"
className="text-sm font-medium"
>
Password
</FieldLabel>
</div>
<Input
id="password"
type="password"
{...register('password')}
disabled={isSubmitting}
/>
<FieldError errors={[errors.password]} />
</Field>
{error && (
<div className="rounded-md bg-destructive/10 border border-destructive/20 p-3">
<p className="text-sm text-destructive">{error}</p>
</div>
)}
<Field className="gap-4">
<Button type="submit" className="w-full" disabled={isSubmitting}>
{isSubmitting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Logging in...
</>
) : (
'Login'
)}
</Button>
</Field>
</FieldGroup>
</form>
</CardContent>
</Card>
<div className="text-balance text-muted-foreground text-center text-xs mt-6 px-8 leading-relaxed">
This is the superadmin login for SnackBase. Only authorized personnel should
access this area.
</div>
</div>
</div>
);
}
|