website refactor

This commit is contained in:
2026-01-19 18:01:30 +01:00
parent 6154d54435
commit 61b5cf3b64
120 changed files with 2226 additions and 2021 deletions

View File

@@ -5,13 +5,12 @@ import { AuthFooterLinks } from '@/components/auth/AuthFooterLinks';
import { AuthForm } from '@/components/auth/AuthForm';
import { ForgotPasswordViewData } from '@/lib/builders/view-data/types/ForgotPasswordViewData';
import { routes } from '@/lib/routing/RouteConfig';
import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';
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 { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { AlertCircle, ArrowLeft, CheckCircle2, Mail, Shield } from 'lucide-react';
import React from 'react';
@@ -54,12 +53,10 @@ export function ForgotPasswordTemplate({ viewData, formActions, mutationState }:
/>
{mutationState.error && (
<Box p={4} bg="critical-red/10" border borderColor="critical-red/30" rounded="md">
<Stack direction="row" align="start" gap={3}>
<Icon icon={AlertCircle} size={4.5} color="var(--color-critical)" />
<Text size="sm" color="text-critical-red">{mutationState.error}</Text>
</Stack>
</Box>
<Group direction="row" align="start" gap={3} fullWidth>
<Icon icon={AlertCircle} size={4.5} color="var(--color-critical)" />
<Text size="sm" color="text-critical-red">{mutationState.error}</Text>
</Group>
)}
<Button
@@ -72,36 +69,34 @@ export function ForgotPasswordTemplate({ viewData, formActions, mutationState }:
{isSubmitting ? 'Sending...' : 'Send Reset Link'}
</Button>
<Box textAlign="center">
<Group justify="center" fullWidth>
<Link href={routes.auth.login}>
<Stack direction="row" align="center" justify="center" gap={2} group>
<Icon icon={ArrowLeft} size={3.5} color="var(--color-primary)" groupHoverScale />
<Group direction="row" align="center" justify="center" gap={2}>
<Icon icon={ArrowLeft} size={3.5} color="var(--color-primary)" />
<Text size="sm" weight="bold" color="text-primary-accent">Back to Login</Text>
</Stack>
</Group>
</Link>
</Box>
</Group>
</AuthForm>
) : (
<Stack gap={6}>
<Box p={4} bg="success-green/10" border borderColor="success-green/30" rounded="md">
<Stack direction="row" align="start" gap={3}>
<Icon icon={CheckCircle2} size={5} color="var(--color-success)" />
<Box>
<Text size="sm" color="text-success-green" weight="bold" block>Check your email</Text>
<Text size="xs" color="text-gray-400" block mt={1}>{viewData.successMessage}</Text>
</Box>
</Stack>
</Box>
<Group direction="column" gap={6} fullWidth>
<Group direction="row" align="start" gap={3} fullWidth>
<Icon icon={CheckCircle2} size={5} color="var(--color-success)" />
<Group direction="column" gap={1}>
<Text size="sm" color="text-success-green" weight="bold" block>Check your email</Text>
<Text size="xs" color="text-gray-400" block>{viewData.successMessage}</Text>
</Group>
</Group>
{viewData.magicLink && (
<Box p={3} bg="surface-charcoal" border borderColor="outline-steel" rounded="md">
<Text size="xs" color="text-gray-500" block mb={2} weight="bold">DEVELOPMENT MAGIC LINK</Text>
<Group direction="column" gap={2} fullWidth>
<Text size="xs" color="text-gray-500" block weight="bold">DEVELOPMENT MAGIC LINK</Text>
<Link href={viewData.magicLink}>
<Text size="xs" color="text-primary-accent" block>
{viewData.magicLink}
</Text>
</Link>
</Box>
</Group>
)}
<Button
@@ -112,7 +107,7 @@ export function ForgotPasswordTemplate({ viewData, formActions, mutationState }:
>
Return to Login
</Button>
</Stack>
</Group>
)}
<AuthFooterLinks>

View File

@@ -7,14 +7,14 @@ 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 { Box } from '@/ui/Box';
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 { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { AlertCircle, LogIn, Mail } from 'lucide-react';
import React from 'react';
@@ -43,7 +43,7 @@ export function LoginTemplate({ viewData, formActions, mutationState }: LoginTem
description="Sign in to access your racing dashboard"
>
<AuthForm onSubmit={formActions.handleSubmit}>
<Stack gap={4}>
<Group direction="column" gap={4} fullWidth>
<Input
label="Email Address"
id="email"
@@ -58,7 +58,7 @@ export function LoginTemplate({ viewData, formActions, mutationState }: LoginTem
icon={<Mail size={16} />}
/>
<Stack gap={1.5}>
<Group direction="column" gap={1.5} fullWidth>
<PasswordField
label="Password"
id="password"
@@ -72,50 +72,43 @@ export function LoginTemplate({ viewData, formActions, mutationState }: LoginTem
showPassword={viewData.showPassword}
onTogglePassword={() => formActions.setShowPassword(!viewData.showPassword)}
/>
<Box textAlign="right">
<Group justify="end" fullWidth>
<Link href={routes.auth.forgotPassword}>
<Text size="xs" color="text-primary-accent">
Forgot password?
</Text>
</Link>
</Box>
</Stack>
</Group>
</Group>
<Stack direction="row" align="center" gap={2}>
<Box
as="input"
id="rememberMe"
name="rememberMe"
type="checkbox"
rounded="sm"
borderColor="outline-steel"
bg="surface-charcoal"
color="text-primary-accent"
ring="focus:ring-primary-accent/50"
w="1rem"
h="1rem"
checked={viewData.formState.fields.rememberMe.value as boolean}
onChange={formActions.handleChange}
disabled={isSubmitting}
/>
<Text as="label" htmlFor="rememberMe" size="sm" color="text-med" cursor="pointer">
Keep me signed in
</Text>
</Stack>
</Stack>
<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 && (
<Box p={4} bg="warning-amber/10" border borderColor="warning-amber/30" rounded="md">
<Stack direction="row" align="start" gap={3}>
<Icon icon={AlertCircle} size={5} color="var(--color-warning)" />
<Box>
<Text weight="bold" color="text-warning-amber" block size="sm">Insufficient Permissions</Text>
<Text size="xs" color="text-gray-400" block mt={1}>
Please log in with an account that has the required role.
</Text>
</Box>
</Stack>
</Box>
<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 && (
@@ -149,14 +142,14 @@ export function LoginTemplate({ viewData, formActions, mutationState }: LoginTem
</Link>
</Text>
<Box mt={2}>
<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>
</Box>
</Group>
</AuthFooterLinks>
</AuthCard>
);

View File

@@ -5,13 +5,12 @@ import { AuthFooterLinks } from '@/components/auth/AuthFooterLinks';
import { AuthForm } from '@/components/auth/AuthForm';
import { ResetPasswordViewData } from '@/lib/builders/view-data/types/ResetPasswordViewData';
import { routes } from '@/lib/routing/RouteConfig';
import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';
import { Group } from '@/ui/Group';
import { Icon } from '@/ui/Icon';
import { Link } from '@/ui/Link';
import { LoadingSpinner } from '@/ui/LoadingSpinner';
import { PasswordField } from '@/ui/PasswordField';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { AlertCircle, ArrowLeft, CheckCircle2, Shield } from 'lucide-react';
import React from 'react';
@@ -50,7 +49,7 @@ export function ResetPasswordTemplate({
>
{!viewData.showSuccess ? (
<AuthForm onSubmit={formActions.handleSubmit}>
<Stack gap={4}>
<Group direction="column" gap={4} fullWidth>
<PasswordField
label="New Password"
id="newPassword"
@@ -78,15 +77,13 @@ export function ResetPasswordTemplate({
showPassword={uiState.showConfirmPassword}
onTogglePassword={() => formActions.setShowConfirmPassword(!uiState.showConfirmPassword)}
/>
</Stack>
</Group>
{mutationState.error && (
<Box p={4} bg="critical-red/10" border borderColor="critical-red/30" rounded="md">
<Stack direction="row" align="start" gap={3}>
<Icon icon={AlertCircle} size={4.5} color="var(--color-critical)" />
<Text size="sm" color="text-critical-red">{mutationState.error}</Text>
</Stack>
</Box>
<Group direction="row" align="start" gap={3} fullWidth>
<Icon icon={AlertCircle} size={4.5} color="var(--color-critical)" />
<Text size="sm" color="text-critical-red">{mutationState.error}</Text>
</Group>
)}
<Button
@@ -99,26 +96,24 @@ export function ResetPasswordTemplate({
{isSubmitting ? 'Resetting...' : 'Reset Password'}
</Button>
<Box textAlign="center">
<Group justify="center" fullWidth>
<Link href={routes.auth.login}>
<Stack direction="row" align="center" justify="center" gap={2} group>
<Icon icon={ArrowLeft} size={3.5} color="var(--color-primary)" groupHoverScale />
<Group direction="row" align="center" justify="center" gap={2}>
<Icon icon={ArrowLeft} size={3.5} color="var(--color-primary)" />
<Text size="sm" weight="bold" color="text-primary-accent">Back to Login</Text>
</Stack>
</Group>
</Link>
</Box>
</Group>
</AuthForm>
) : (
<Stack gap={6}>
<Box p={4} bg="success-green/10" border borderColor="success-green/30" rounded="md">
<Stack direction="row" align="start" gap={3}>
<Icon icon={CheckCircle2} size={5} color="var(--color-success)" />
<Box>
<Text size="sm" color="text-success-green" weight="bold" block>Password Reset</Text>
<Text size="xs" color="text-gray-400" block mt={1}>{viewData.successMessage}</Text>
</Box>
</Stack>
</Box>
<Group direction="column" gap={6} fullWidth>
<Group direction="row" align="start" gap={3} fullWidth>
<Icon icon={CheckCircle2} size={5} color="var(--color-success)" />
<Group direction="column" gap={1}>
<Text size="sm" color="text-success-green" weight="bold" block>Password Reset</Text>
<Text size="xs" color="text-gray-400" block>{viewData.successMessage}</Text>
</Group>
</Group>
<Button
type="button"
@@ -128,7 +123,7 @@ export function ResetPasswordTemplate({
>
Return to Login
</Button>
</Stack>
</Group>
)}
<AuthFooterLinks>

View File

@@ -5,15 +5,16 @@ import { AuthFooterLinks } from '@/components/auth/AuthFooterLinks';
import { AuthForm } from '@/components/auth/AuthForm';
import { SignupViewData } from '@/lib/builders/view-data/types/SignupViewData';
import { checkPasswordStrength } from '@/lib/utils/validation';
import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';
import { Grid } from '@/ui/Grid';
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 { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { ProgressBar } from '@/ui/ProgressBar';
import { AlertCircle, Check, Mail, User, UserPlus, X } from 'lucide-react';
import React from 'react';
@@ -47,16 +48,22 @@ export function SignupTemplate({ viewData, formActions, uiState, mutationState }
{ met: /[^a-zA-Z\d]/.test(passwordValue), label: 'Special' },
];
const getStrengthIntent = () => {
if (passwordStrength.score <= 2) return 'critical';
if (passwordStrength.score <= 4) return 'warning';
return 'success';
};
return (
<AuthCard
title="Create Account"
description="Join the GridPilot racing community"
>
<AuthForm onSubmit={formActions.handleSubmit}>
<Stack gap={6}>
<Stack gap={4}>
<Group direction="column" gap={6} fullWidth>
<Group direction="column" gap={4} fullWidth>
<Text size="xs" weight="bold" color="text-low" uppercase letterSpacing="wide" block>Personal Information</Text>
<Box display="grid" gridCols={{ base: 1, md: 2 }} gap={4}>
<Grid cols={{ base: 1, md: 2 }} gap={4}>
<Input
label="First Name"
id="firstName"
@@ -81,16 +88,14 @@ export function SignupTemplate({ viewData, formActions, uiState, mutationState }
autoComplete="family-name"
icon={<User size={16} />}
/>
</Box>
</Grid>
<Box p={3} bg="warning-amber/5" border borderColor="warning-amber/20" rounded="md">
<Stack direction="row" align="start" gap={2}>
<Icon icon={AlertCircle} size={3.5} color="var(--color-warning)" mt={0.5} />
<Text size="xs" color="text-med">
<Text weight="bold" color="text-warning-amber">Note:</Text> Your name cannot be changed after signup.
</Text>
</Stack>
</Box>
<Group direction="row" align="start" gap={2} fullWidth>
<Icon icon={AlertCircle} size={3.5} color="var(--color-warning)" />
<Text size="xs" color="text-med">
<Text weight="bold" color="text-warning-amber">Note:</Text> Your name cannot be changed after signup.
</Text>
</Group>
<Input
label="Email Address"
@@ -105,9 +110,9 @@ export function SignupTemplate({ viewData, formActions, uiState, mutationState }
autoComplete="email"
icon={<Mail size={16} />}
/>
</Stack>
</Group>
<Stack gap={4}>
<Group direction="column" gap={4} fullWidth>
<Text size="xs" weight="bold" color="text-low" uppercase letterSpacing="wide" block>Security</Text>
<PasswordField
label="Password"
@@ -124,34 +129,30 @@ export function SignupTemplate({ viewData, formActions, uiState, mutationState }
/>
{passwordValue && (
<Stack gap={3}>
<Stack direction="row" align="center" gap={2}>
<Box flexGrow={1} h="1px" bg="outline-steel" rounded="full" overflow="hidden">
<Box
h="full"
transition
w={`${(passwordStrength.score / 5) * 100}%`}
bg={
passwordStrength.score <= 2 ? 'critical-red' :
passwordStrength.score <= 4 ? 'warning-amber' : 'success-green'
}
<Group direction="column" gap={3} fullWidth>
<Group direction="row" align="center" gap={2} fullWidth>
<Group fullWidth>
<ProgressBar
value={(passwordStrength.score / 5) * 100}
intent={getStrengthIntent()}
size="sm"
/>
</Box>
</Group>
<Text size="xs" weight="bold" color="text-low" uppercase>
{passwordStrength.label}
</Text>
</Stack>
<Box display="grid" gridCols={2} gap={2}>
</Group>
<Grid cols={2} gap={2}>
{passwordRequirements.map((req, index) => (
<Stack key={index} direction="row" align="center" gap={1.5}>
<Group key={index} direction="row" align="center" gap={1.5}>
<Icon icon={req.met ? Check : X} size={3} color={req.met ? 'var(--color-success)' : 'var(--color-text-low)'} />
<Text size="xs" color={req.met ? 'text-med' : 'text-low'}>
{req.label}
</Text>
</Stack>
</Group>
))}
</Box>
</Stack>
</Grid>
</Group>
)}
<PasswordField
@@ -167,16 +168,14 @@ export function SignupTemplate({ viewData, formActions, uiState, mutationState }
showPassword={uiState.showConfirmPassword}
onTogglePassword={() => formActions.setShowConfirmPassword(!uiState.showConfirmPassword)}
/>
</Stack>
</Stack>
</Group>
</Group>
{mutationState.error && (
<Box p={4} bg="critical-red/10" border borderColor="critical-red/30" rounded="md">
<Stack direction="row" align="start" gap={3}>
<Icon icon={AlertCircle} size={4.5} color="var(--color-critical)" />
<Text size="sm" color="text-critical-red">{mutationState.error}</Text>
</Stack>
</Box>
<Group direction="row" align="start" gap={3} fullWidth>
<Icon icon={AlertCircle} size={4.5} color="var(--color-critical)" />
<Text size="sm" color="text-critical-red">{mutationState.error}</Text>
</Group>
)}
<Button
@@ -200,14 +199,14 @@ export function SignupTemplate({ viewData, formActions, uiState, mutationState }
</Link>
</Text>
<Box mt={2}>
<Group direction="column" gap={1} align="center" fullWidth>
<Text size="xs" color="text-gray-600">
By creating an account, you agree to our{' '}
<Link href="/terms">Terms</Link>
{' '}and{' '}
<Link href="/privacy">Privacy</Link>
</Text>
</Box>
</Group>
</AuthFooterLinks>
</AuthCard>
);