website refactor

This commit is contained in:
2026-01-18 13:26:35 +01:00
parent 350c78504d
commit 0b301feb61
225 changed files with 1678 additions and 26666 deletions

View File

@@ -1,91 +1,171 @@
'use client';
import React from 'react';
import { Clock, MapPin, Users } from 'lucide-react';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { Icon } from '@/ui/Icon';
import { ArrowRight, Car, ChevronRight, LucideIcon, Trophy, Zap } from 'lucide-react';
import { Box } from '@/ui/Box';
import { SessionStatusBadge, type SessionStatus } from './SessionStatusBadge';
import { Heading } from '@/ui/Heading';
import { Icon } from '@/ui/Icon';
import { Link } from '@/ui/Link';
import { Stack } from '@/ui/Stack';
import { Surface } from '@/ui/Surface';
import { Text } from '@/ui/Text';
import { routes } from '@/lib/routing/RouteConfig';
interface RaceCardProps {
id: string;
title: string;
leagueName: string;
trackName: string;
track: string;
car: string;
scheduledAt: string;
entrantCount: number;
status: SessionStatus;
onClick: (id: string) => void;
status: string;
leagueName: string;
leagueId?: string;
strengthOfField?: number | null;
onClick?: () => void;
statusConfig: {
border: string;
bg: string;
color: string;
icon: LucideIcon | null;
label: string;
};
}
export function RaceCard({
id,
title,
leagueName,
trackName,
track,
car,
scheduledAt,
entrantCount,
status,
leagueName,
leagueId,
strengthOfField,
onClick,
statusConfig,
}: RaceCardProps) {
const scheduledAtDate = new Date(scheduledAt);
return (
<Box
as="article"
onClick={() => onClick(id)}
<Surface
bg="bg-surface-charcoal"
rounded="xl"
border
borderColor="border-outline-steel"
p={4}
padding={4}
onClick={onClick}
cursor={onClick ? 'pointer' : 'default'}
hoverBorderColor="border-primary-accent"
transition
cursor="pointer"
position="relative"
overflow="hidden"
group
>
{/* Hover Glow */}
<Box
position="absolute"
inset="0"
bg="bg-primary-accent"
bgOpacity={0.05}
opacity={0}
groupHoverOpacity={1}
transition
/>
<Stack gap={4}>
<Stack direction="row" justifyContent="between" alignItems="start">
<Stack gap={1}>
<Text size="xs" color="text-gray-500" weight="bold" uppercase>
{leagueName}
</Text>
<Text size="lg" weight="bold" groupHoverTextColor="text-primary-accent">
{title}
</Text>
</Stack>
<SessionStatusBadge status={status} />
</Stack>
{/* Live indicator */}
{status === 'running' && (
<Box
position="absolute"
top="0"
left="0"
right="0"
h="1"
bg="bg-success-green"
animate="pulse"
/>
)}
<Box display="grid" gridCols={2} gap={4}>
<Stack direction="row" alignItems="center" gap={2}>
<Icon icon={MapPin} size={3} color="#6b7280" />
<Text size="xs" color="text-gray-400">{trackName}</Text>
</Stack>
<Stack direction="row" alignItems="center" gap={2}>
<Icon icon={Clock} size={3} color="#6b7280" />
<Text size="xs" color="text-gray-400">{scheduledAt}</Text>
</Stack>
<Stack direction="row" align="start" gap={4}>
{/* Time Column */}
<Box textAlign="center" flexShrink={0} width="16">
<Text size="lg" weight="bold" color="text-white" block>
{scheduledAtDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</Text>
<Text size="xs" color={statusConfig.color} block>
{status === 'running' ? 'LIVE' : scheduledAtDate.toLocaleDateString()}
</Text>
</Box>
<Stack direction="row" alignItems="center" gap={2} pt={2} borderTop borderColor="border-outline-steel" bgOpacity={0.5}>
<Icon icon={Users} size={3} color="#4ED4E0" />
<Text size="xs" color="text-gray-400">
<Text as="span" color="text-telemetry-aqua" weight="bold">{entrantCount}</Text> ENTRANTS
</Text>
</Stack>
{/* Divider */}
<Box
w="px"
bg="border-outline-steel"
alignSelf="stretch"
/>
{/* Main Content */}
<Box flex={1} minWidth="0">
<Stack direction="row" align="start" justify="between" gap={4}>
<Box minWidth="0">
<Heading
level={3}
truncate
groupHoverTextColor="text-primary-accent"
transition
>
{track}
</Heading>
<Stack direction="row" align="center" gap={3} mt={1}>
<Stack direction="row" align="center" gap={1}>
<Icon icon={Car} size={3.5} color="var(--text-gray-400)" />
<Text size="sm" color="text-gray-400">
{car}
</Text>
</Stack>
{strengthOfField && (
<Stack direction="row" align="center" gap={1}>
<Icon icon={Zap} size={3.5} color="var(--warning-amber)" />
<Text size="sm" color="text-gray-400">
SOF {strengthOfField}
</Text>
</Stack>
)}
</Stack>
</Box>
{/* Status Badge */}
<Box
display="flex"
alignItems="center"
gap={1.5}
px={2.5}
py={1}
rounded="full"
border
borderColor="border-outline-steel"
bg="bg-base-black"
bgOpacity={0.5}
>
{statusConfig.icon && (
<Icon icon={statusConfig.icon} size={3.5} color={statusConfig.color} />
)}
<Text size="xs" weight="medium" color={statusConfig.color}>
{statusConfig.label}
</Text>
</Box>
</Stack>
{/* League Link */}
<Box mt={3} pt={3} borderTop borderColor="border-outline-steel" borderOpacity={0.3}>
<Link
href={routes.league.detail(leagueId ?? '')}
onClick={(e) => e.stopPropagation()}
>
<Stack direction="row" align="center" gap={2}>
<Icon icon={Trophy} size={3.5} color="var(--primary-accent)" />
<Text size="sm" color="text-primary-accent">
{leagueName}
</Text>
<Icon icon={ArrowRight} size={3} color="var(--primary-accent)" />
</Stack>
</Link>
</Box>
</Box>
{/* Arrow */}
<Icon
icon={ChevronRight}
size={5}
color="var(--text-gray-500)"
groupHoverTextColor="text-primary-accent"
transition
flexShrink={0}
/>
</Stack>
</Box>
</Surface>
);
}