114 lines
3.5 KiB
TypeScript
114 lines
3.5 KiB
TypeScript
|
|
|
||
|
|
'use client';
|
||
|
|
|
||
|
|
import { useForm } from 'react-hook-form';
|
||
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||
|
|
import * as z from 'zod';
|
||
|
|
import { Button } from '@/components/ui/button';
|
||
|
|
import { Input } from '@/components/ui/input';
|
||
|
|
import { Label } from '@/components/ui/label';
|
||
|
|
import { useToast } from '@/hooks/use-toast';
|
||
|
|
import {
|
||
|
|
Card,
|
||
|
|
CardContent,
|
||
|
|
CardDescription,
|
||
|
|
CardFooter,
|
||
|
|
CardHeader,
|
||
|
|
CardTitle,
|
||
|
|
} from '@/components/ui/card';
|
||
|
|
import { useRouter } from 'next/navigation';
|
||
|
|
|
||
|
|
const loginSchema = z.object({
|
||
|
|
email: z.string().email({ message: 'Invalid email address.' }),
|
||
|
|
password: z.string().min(1, { message: 'Password is required.' }),
|
||
|
|
});
|
||
|
|
|
||
|
|
type LoginFormValues = z.infer<typeof loginSchema>;
|
||
|
|
|
||
|
|
export default function LoginPage() {
|
||
|
|
const router = useRouter();
|
||
|
|
const { toast } = useToast();
|
||
|
|
const {
|
||
|
|
register,
|
||
|
|
handleSubmit,
|
||
|
|
formState: { errors, isSubmitting },
|
||
|
|
} = useForm<LoginFormValues>({
|
||
|
|
resolver: zodResolver(loginSchema),
|
||
|
|
});
|
||
|
|
|
||
|
|
const onSubmit = async (data: LoginFormValues) => {
|
||
|
|
try {
|
||
|
|
// This is a mock login. In a real app, you'd call an authentication service.
|
||
|
|
console.log('Attempting to log in with:', data.email);
|
||
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||
|
|
|
||
|
|
if (data.email === 'admin@example.com' && data.password === 'password') {
|
||
|
|
toast({
|
||
|
|
title: 'Login Successful',
|
||
|
|
description: 'Redirecting to your dashboard...',
|
||
|
|
});
|
||
|
|
router.push('/admin');
|
||
|
|
} else {
|
||
|
|
throw new Error('Invalid email or password.');
|
||
|
|
}
|
||
|
|
} catch (error: any) {
|
||
|
|
toast({
|
||
|
|
variant: 'destructive',
|
||
|
|
title: 'Login Failed',
|
||
|
|
description: error.message || 'An unexpected error occurred.',
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<main className="flex min-h-screen flex-col items-center justify-center bg-background p-8">
|
||
|
|
<Card className="w-full max-w-sm">
|
||
|
|
<CardHeader className="text-center">
|
||
|
|
<CardTitle className="text-2xl font-bold tracking-tight">
|
||
|
|
EstimateFlow Admin
|
||
|
|
</CardTitle>
|
||
|
|
<CardDescription>
|
||
|
|
Welcome back! Please sign in to continue.
|
||
|
|
</CardDescription>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="email">Email</Label>
|
||
|
|
<Input
|
||
|
|
id="email"
|
||
|
|
type="email"
|
||
|
|
placeholder="admin@example.com"
|
||
|
|
{...register('email')}
|
||
|
|
/>
|
||
|
|
{errors.email && (
|
||
|
|
<p className="text-sm text-destructive">{errors.email.message}</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="password">Password</Label>
|
||
|
|
<Input
|
||
|
|
id="password"
|
||
|
|
type="password"
|
||
|
|
placeholder="password"
|
||
|
|
{...register('password')}
|
||
|
|
/>
|
||
|
|
{errors.password && (
|
||
|
|
<p className="text-sm text-destructive">
|
||
|
|
{errors.password.message}
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
<Button type="submit" className="w-full" disabled={isSubmitting}>
|
||
|
|
{isSubmitting ? 'Signing In...' : 'Sign In'}
|
||
|
|
</Button>
|
||
|
|
</form>
|
||
|
|
</CardContent>
|
||
|
|
<CardFooter>
|
||
|
|
<p className="text-xs text-center w-full text-muted-foreground">Use admin@example.com and 'password' to sign in.</p>
|
||
|
|
</CardFooter>
|
||
|
|
</Card>
|
||
|
|
</main>
|
||
|
|
);
|
||
|
|
}
|