Files
gridpilot.gg/apps/website/templates/auth/LoginTemplate.tsx
2026-01-19 18:01:30 +01:00

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