All files / pages LoginPage.tsx

100% Statements 14/14
100% Branches 4/4
100% Functions 3/3
100% Lines 13/13

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>
    );
}