157 lines
5.6 KiB
TypeScript
157 lines
5.6 KiB
TypeScript
'use client';
|
|
|
|
import { AuthCard } from '@/components/auth/AuthCard';
|
|
import { AuthFooterLinks } from '@/components/auth/AuthFooterLinks';
|
|
import { AuthForm } from '@/components/auth/AuthForm';
|
|
import { EnhancedFormError } from '@/components/errors/EnhancedFormError';
|
|
import { FormState } from '@/lib/builders/view-data/types/FormState';
|
|
import { LoginViewData } from '@/lib/builders/view-data/types/LoginViewData';
|
|
import { routes } from '@/lib/routing/RouteConfig';
|
|
import { Button } from '@/ui/Button';
|
|
import { Checkbox } from '@/ui/Checkbox';
|
|
import { Group } from '@/ui/Group';
|
|
import { Icon } from '@/ui/Icon';
|
|
import { Input } from '@/ui/Input';
|
|
import { Link } from '@/ui/Link';
|
|
import { LoadingSpinner } from '@/ui/LoadingSpinner';
|
|
import { PasswordField } from '@/ui/PasswordField';
|
|
import { Text } from '@/ui/Text';
|
|
import { AlertCircle, LogIn, Mail } from 'lucide-react';
|
|
import React from 'react';
|
|
|
|
interface LoginTemplateProps {
|
|
viewData: LoginViewData;
|
|
formActions: {
|
|
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => Promise<void>;
|
|
setFormState: React.Dispatch<React.SetStateAction<FormState>>;
|
|
setShowPassword: (show: boolean) => void;
|
|
setShowErrorDetails: (show: boolean) => void;
|
|
};
|
|
mutationState: {
|
|
isPending: boolean;
|
|
error: string | null;
|
|
};
|
|
}
|
|
|
|
export function LoginTemplate({ viewData, formActions, mutationState }: LoginTemplateProps) {
|
|
const isSubmitting = viewData.formState.isSubmitting || mutationState.isPending;
|
|
|
|
return (
|
|
<AuthCard
|
|
title="Welcome Back"
|
|
description="Sign in to access your racing dashboard"
|
|
>
|
|
<AuthForm onSubmit={formActions.handleSubmit}>
|
|
<Group direction="column" gap={4} fullWidth>
|
|
<Input
|
|
label="Email Address"
|
|
id="email"
|
|
name="email"
|
|
type="email"
|
|
value={viewData.formState.fields.email.value as string}
|
|
onChange={formActions.handleChange}
|
|
errorMessage={viewData.formState.fields.email.error}
|
|
placeholder="you@example.com"
|
|
disabled={isSubmitting}
|
|
autoComplete="email"
|
|
icon={<Mail size={16} />}
|
|
/>
|
|
|
|
<Group direction="column" gap={1.5} fullWidth>
|
|
<PasswordField
|
|
label="Password"
|
|
id="password"
|
|
name="password"
|
|
value={viewData.formState.fields.password.value as string}
|
|
onChange={formActions.handleChange}
|
|
errorMessage={viewData.formState.fields.password.error}
|
|
placeholder="••••••••"
|
|
disabled={isSubmitting}
|
|
autoComplete="current-password"
|
|
showPassword={viewData.showPassword}
|
|
onTogglePassword={() => formActions.setShowPassword(!viewData.showPassword)}
|
|
/>
|
|
<Group justify="end" fullWidth>
|
|
<Link href={routes.auth.forgotPassword}>
|
|
<Text size="xs" color="text-primary-accent">
|
|
Forgot password?
|
|
</Text>
|
|
</Link>
|
|
</Group>
|
|
</Group>
|
|
|
|
<Checkbox
|
|
label="Keep me signed in"
|
|
checked={viewData.formState.fields.rememberMe.value as boolean}
|
|
onChange={(checked) => {
|
|
const event = {
|
|
target: {
|
|
name: 'rememberMe',
|
|
value: checked,
|
|
type: 'checkbox',
|
|
checked
|
|
}
|
|
} as any;
|
|
formActions.handleChange(event);
|
|
}}
|
|
disabled={isSubmitting}
|
|
/>
|
|
</Group>
|
|
|
|
{viewData.hasInsufficientPermissions && (
|
|
<Group direction="row" align="start" gap={3} fullWidth>
|
|
<Icon icon={AlertCircle} size={5} color="var(--color-warning)" />
|
|
<Group direction="column" gap={1}>
|
|
<Text weight="bold" color="text-warning-amber" block size="sm">Insufficient Permissions</Text>
|
|
<Text size="xs" color="text-gray-400" block>
|
|
Please log in with an account that has the required role.
|
|
</Text>
|
|
</Group>
|
|
</Group>
|
|
)}
|
|
|
|
{viewData.submitError && (
|
|
<EnhancedFormError
|
|
error={new Error(viewData.submitError)}
|
|
onDismiss={() => {
|
|
formActions.setFormState((prev: FormState) => ({ ...prev, submitError: undefined }));
|
|
}}
|
|
showDeveloperDetails={viewData.showErrorDetails}
|
|
/>
|
|
)}
|
|
|
|
<Button
|
|
type="submit"
|
|
variant="primary"
|
|
disabled={isSubmitting}
|
|
fullWidth
|
|
icon={isSubmitting ? <LoadingSpinner size={4} /> : <LogIn size={16} />}
|
|
>
|
|
{isSubmitting ? 'Signing in...' : 'Sign In'}
|
|
</Button>
|
|
</AuthForm>
|
|
|
|
<AuthFooterLinks>
|
|
<Text size="sm" color="text-gray-400">
|
|
Don't have an account?{' '}
|
|
<Link
|
|
href={viewData.returnTo && viewData.returnTo !== '/dashboard' ? `/auth/signup?returnTo=${encodeURIComponent(viewData.returnTo)}` : '/auth/signup'}
|
|
>
|
|
<Text as="span" color="text-primary-accent" weight="bold">Create one</Text>
|
|
</Link>
|
|
</Text>
|
|
|
|
<Group direction="column" gap={1} align="center" fullWidth>
|
|
<Text size="xs" color="text-gray-600">
|
|
By signing in, you agree to our{' '}
|
|
<Link href="/terms">Terms</Link>
|
|
{' '}and{' '}
|
|
<Link href="/privacy">Privacy</Link>
|
|
</Text>
|
|
</Group>
|
|
</AuthFooterLinks>
|
|
</AuthCard>
|
|
);
|
|
}
|