website refactor

This commit is contained in:
2026-01-17 15:46:55 +01:00
parent 4d5ce9bfd6
commit 72a626ce71
346 changed files with 19308 additions and 8605 deletions

View File

@@ -1,35 +1,20 @@
'use client';
import React from 'react';
import { Breadcrumbs } from '@/ui/Breadcrumbs';
import { Button } from '@/ui/Button';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Container } from '@/ui/Container';
import { Icon } from '@/ui/Icon';
import { Stack } from '@/ui/Stack';
import { Grid } from '@/ui/Grid';
import { GridItem } from '@/ui/GridItem';
import { Skeleton } from '@/ui/Skeleton';
import { InfoBox } from '@/ui/InfoBox';
import { RaceJoinButton } from '@/ui/RaceJoinButton';
import { RaceHero } from '@/ui/RaceHeroWrapper';
import { Text } from '@/ui/Text';
import { Box } from '@/ui/Box';
import { RaceUserResult } from '@/ui/RaceUserResultWrapper';
import { RaceEntryList } from '@/components/races/RaceEntryList';
import { RaceDetailCard } from '@/ui/RaceDetailCard';
import { LeagueSummaryCard } from '@/components/leagues/LeagueSummaryCardWrapper';
import {
AlertTriangle,
ArrowLeft,
CheckCircle2,
Clock,
PlayCircle,
Trophy,
XCircle,
Scale,
} from 'lucide-react';
import { Surface } from '@/ui/Surface';
import { Card } from '@/ui/Card';
import { RaceActionBar } from '@/ui/RaceActionBar';
import { RaceDetailsHeader } from '@/components/races/RaceDetailsHeader';
import { TrackConditionsPanel } from '@/components/races/TrackConditionsPanel';
import { EntrantsTable } from '@/components/races/EntrantsTable';
import type { SessionStatus } from '@/components/races/SessionStatusBadge';
export interface RaceDetailEntryViewModel {
id: string;
@@ -127,7 +112,6 @@ export function RaceDetailTemplate({
onFileProtest,
onResultsClick,
onStewardingClick,
onDriverClick,
isOwnerOrAdmin = false,
animatedRatingChange,
mutationLoading = {},
@@ -152,174 +136,122 @@ export function RaceDetailTemplate({
if (error || !viewData || !viewData.race) {
return (
<Container size="md" py={8}>
<Stack gap={6}>
<Breadcrumbs items={[{ label: 'Races', href: '/races' }, { label: 'Error' }]} />
<Card>
<Stack align="center" gap={4} py={12}>
<Surface variant="muted" rounded="full" padding={4}>
<Icon icon={AlertTriangle} size={8} color="#f59e0b" />
</Surface>
<Box>
<Text weight="medium" color="text-white" block mb={1}>{error instanceof Error ? error.message : error || 'Race not found'}</Text>
<Text size="sm" color="text-gray-500">
The race you&apos;re looking for doesn&apos;t exist or has been removed.
</Text>
</Box>
<Button
variant="secondary"
onClick={onBack}
>
Back to Races
</Button>
</Stack>
</Card>
</Stack>
<Box bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={12} textAlign="center" rounded="xl">
<Stack alignItems="center" gap={4}>
<Text as="h2" size="xl" weight="bold" color="text-white">Race Not Found</Text>
<Text color="text-gray-400">{`The race you're looking for doesn't exist or has been removed.`}</Text>
<Box
as="button"
onClick={onBack}
mt={4}
px={6}
py={2}
bg="bg-primary-accent"
color="text-white"
weight="bold"
rounded="md"
hoverBg="bg-primary-accent"
bgOpacity={0.8}
transition
>
Back to Schedule
</Box>
</Stack>
</Box>
</Container>
);
}
const { race, league, entryList, userResult } = viewData;
const statusConfig = {
scheduled: {
icon: Clock,
variant: 'primary' as const,
label: 'Scheduled',
description: 'This race is scheduled and waiting to start',
},
running: {
icon: PlayCircle,
variant: 'success' as const,
label: 'LIVE NOW',
description: 'This race is currently in progress',
},
completed: {
icon: CheckCircle2,
variant: 'default' as const,
label: 'Completed',
description: 'This race has finished',
},
cancelled: {
icon: XCircle,
variant: 'warning' as const,
label: 'Cancelled',
description: 'This race has been cancelled',
},
};
const config = statusConfig[race.status] || statusConfig.scheduled;
const breadcrumbItems = [
{ label: 'Races', href: '/races' },
...(league ? [{ label: league.name, href: `/leagues/${league.id}` }] : []),
{ label: race.track },
];
return (
<Container size="lg" py={8}>
<Stack gap={6}>
{/* Navigation Row */}
<Stack direction="row" align="center" justify="between">
<Breadcrumbs items={breadcrumbItems} />
<Button
variant="secondary"
onClick={onBack}
size="sm"
icon={<Icon icon={ArrowLeft} size={4} />}
>
Back
</Button>
</Stack>
<Box as="main" minHeight="screen" bg="bg-base-black">
<RaceDetailsHeader
title={race.track}
leagueName={league?.name || 'Official'}
trackName={race.track}
scheduledAt={race.scheduledAt}
status={race.status as SessionStatus}
onBack={onBack}
/>
{/* User Result */}
{userResult && (
<RaceUserResult
{...userResult}
animatedRatingChange={animatedRatingChange}
/>
)}
<Container size="lg" py={8}>
<Stack gap={8}>
{userResult && (
<RaceUserResult
{...userResult}
animatedRatingChange={animatedRatingChange}
/>
)}
{/* Hero Header */}
<RaceHero
track={race.track}
scheduledAt={race.scheduledAt}
car={race.car}
status={race.status}
statusConfig={config}
/>
<Box bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={4}>
<RaceActionBar
status={race.status}
isUserRegistered={viewData.registration.isUserRegistered}
canRegister={viewData.registration.canRegister}
onRegister={onRegister}
onWithdraw={onWithdraw}
onResultsClick={onResultsClick}
onStewardingClick={onStewardingClick}
onFileProtest={onFileProtest}
isAdmin={isOwnerOrAdmin}
onCancel={onCancel}
onReopen={onReopen}
onEndRace={onEndRace}
isLoading={mutationLoading}
/>
</Box>
<Grid cols={12} gap={6}>
<GridItem lgSpan={8} colSpan={12}>
<Stack gap={6}>
<RaceDetailCard
track={race.track}
car={race.car}
sessionType={race.sessionType}
statusLabel={config.label}
statusColor={config.variant === 'success' ? '#10b981' : config.variant === 'primary' ? '#3b82f6' : '#9ca3af'}
/>
<Grid cols={12} gap={6}>
<GridItem lgSpan={8} colSpan={12}>
<Stack gap={6}>
<Box as="section" bg="bg-surface-charcoal" border borderColor="border-outline-steel" overflow="hidden">
<Box p={4} borderBottom borderColor="border-outline-steel" bg="bg-base-black" bgOpacity={0.2}>
<Text size="xs" weight="bold" color="text-gray-500" uppercase letterSpacing="widest">Entry List</Text>
</Box>
<EntrantsTable
entrants={entryList.map(entry => ({
id: entry.id,
name: entry.name,
carName: race.car,
rating: entry.rating || 0,
status: 'confirmed'
}))}
/>
</Box>
</Stack>
</GridItem>
<RaceEntryList
entries={entryList}
onDriverClick={onDriverClick}
/>
</Stack>
</GridItem>
<GridItem lgSpan={4} colSpan={12}>
<Stack gap={6}>
{league && <LeagueSummaryCard league={league} />}
<TrackConditionsPanel
airTemp="24°C"
trackTemp="31°C"
humidity="45%"
windSpeed="12 km/h NW"
weatherType="Partly Cloudy"
/>
<GridItem lgSpan={4} colSpan={12}>
<Stack gap={6}>
{league && <LeagueSummaryCard league={league} />}
{/* Actions Card */}
<Card>
<Stack gap={4}>
<Text size="xl" weight="bold" color="text-white">Actions</Text>
<Stack gap={3}>
<RaceJoinButton
raceStatus={race.status}
isUserRegistered={viewData.registration.isUserRegistered}
canRegister={viewData.registration.canRegister}
onRegister={onRegister}
onWithdraw={onWithdraw}
onCancel={onCancel}
onReopen={onReopen}
onEndRace={onEndRace}
canReopenRace={viewData.canReopenRace}
isOwnerOrAdmin={isOwnerOrAdmin}
isLoading={mutationLoading}
/>
{race.status === 'completed' && (
<>
<Button variant="primary" fullWidth onClick={onResultsClick} icon={<Icon icon={Trophy} size={4} />}>
View Results
</Button>
{userResult && (
<Button variant="secondary" fullWidth onClick={onFileProtest} icon={<Icon icon={Scale} size={4} />}>
File Protest
</Button>
)}
<Button variant="secondary" fullWidth onClick={onStewardingClick} icon={<Icon icon={Scale} size={4} />}>
Stewarding
</Button>
</>
)}
<Box as="section" bg="bg-surface-charcoal" border borderColor="border-outline-steel" p={4}>
<Text as="h3" size="xs" weight="bold" color="text-gray-500" uppercase letterSpacing="widest" mb={4}>Session Info</Text>
<Stack gap={4}>
<Stack gap={1}>
<Text size="xs" color="text-gray-500" uppercase weight="bold">Format</Text>
<Text size="sm" color="text-white">{race.sessionType}</Text>
</Stack>
<Stack gap={1}>
<Text size="xs" color="text-gray-500" uppercase weight="bold">Car Class</Text>
<Text size="sm" color="text-white">{race.car}</Text>
</Stack>
</Stack>
</Stack>
</Card>
{/* Status Info */}
<InfoBox
icon={config.icon}
title={config.label}
description={config.description}
variant={config.variant}
/>
</Stack>
</GridItem>
</Grid>
</Stack>
</Container>
</Box>
</Stack>
</GridItem>
</Grid>
</Stack>
</Container>
</Box>
);
}