website refactor

This commit is contained in:
2026-01-18 16:18:18 +01:00
parent 0b301feb61
commit 13567d51af
329 changed files with 4701 additions and 4750 deletions

View File

@@ -1,6 +1,5 @@
import React from 'react';
import { Search, Filter } from 'lucide-react';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Icon } from '@/ui/Icon';
import { Text } from '@/ui/Text';
@@ -19,7 +18,7 @@ export function LeaderboardFiltersBar({
children,
}: LeaderboardFiltersBarProps) {
return (
<Box
<Stack
mb={6}
p={3}
bg="bg-deep-charcoal/40"
@@ -29,8 +28,8 @@ export function LeaderboardFiltersBar({
blur="sm"
>
<Stack direction="row" align="center" justify="between" gap={4}>
<Box position="relative" flexGrow={1} maxWidth="md">
<Box
<Stack position="relative" flexGrow={1} maxWidth="md">
<Stack
position="absolute"
left="3"
top="1/2"
@@ -39,8 +38,8 @@ export function LeaderboardFiltersBar({
zIndex={10}
>
<Icon icon={Search} size={4} color="text-gray-500" />
</Box>
<Box
</Stack>
<Stack
as="input"
type="text"
value={searchQuery}
@@ -59,11 +58,11 @@ export function LeaderboardFiltersBar({
transition
hoverBorderColor="border-primary-blue/50"
/>
</Box>
</Stack>
<Stack direction="row" align="center" gap={4}>
{children}
<Box
<Stack
display="flex"
alignItems="center"
gap={2}
@@ -80,9 +79,9 @@ export function LeaderboardFiltersBar({
>
<Icon icon={Filter} size={3.5} color="text-gray-400" />
<Text size="xs" weight="bold" color="text-gray-400" uppercase letterSpacing="wider">Filters</Text>
</Box>
</Stack>
</Stack>
</Stack>
</Box>
</Stack>
);
}

View File

@@ -1,9 +1,8 @@
import React from 'react';
import { ArrowLeft, LucideIcon } from 'lucide-react';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { Button } from '@/ui/Button';
import { Icon } from '@/ui/Icon';
@@ -25,9 +24,9 @@ export function LeaderboardHeader({
children,
}: LeaderboardHeaderProps) {
return (
<Box mb={8}>
<Stack mb={8}>
{onBack && (
<Box mb={6}>
<Stack mb={6}>
<Button
variant="secondary"
onClick={onBack}
@@ -35,13 +34,13 @@ export function LeaderboardHeader({
>
{backLabel}
</Button>
</Box>
</Stack>
)}
<Stack direction="row" align="center" justify="between" gap={4}>
<Stack direction="row" align="center" gap={4}>
{icon && (
<Box
<Stack
p={3}
bg="linear-gradient(to bottom right, rgba(25, 140, 255, 0.15), rgba(25, 140, 255, 0.05))"
border
@@ -52,21 +51,21 @@ export function LeaderboardHeader({
justifyContent="center"
>
<Icon icon={icon} size={6} color="text-primary-blue" />
</Box>
</Stack>
)}
<Box>
<Stack>
<Heading level={1} weight="bold" letterSpacing="tight">{title}</Heading>
{description && (
<Text color="text-gray-400" block mt={1} size="sm">
{description}
</Text>
)}
</Box>
</Stack>
</Stack>
<Box>
<Stack>
{children}
</Box>
</Stack>
</Stack>
</Box>
</Stack>
);
}

View File

@@ -1,9 +1,8 @@
import React from 'react';
import { ArrowLeft, LucideIcon } from 'lucide-react';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { Button } from '@/ui/Button';
import { Icon } from '@/ui/Icon';
import { Surface } from '@/ui/Surface';
@@ -26,9 +25,9 @@ export function LeaderboardHeaderPanel({
children,
}: LeaderboardHeaderPanelProps) {
return (
<Box mb={8}>
<Stack mb={8}>
{onBack && (
<Box mb={6}>
<Stack mb={6}>
<Button
variant="secondary"
onClick={onBack}
@@ -36,7 +35,7 @@ export function LeaderboardHeaderPanel({
>
{backLabel}
</Button>
</Box>
</Stack>
)}
<Stack direction="row" align="center" justify="between" gap={4}>
@@ -53,17 +52,17 @@ export function LeaderboardHeaderPanel({
<Icon icon={icon} size={7} color="text-primary-blue" />
</Surface>
)}
<Box>
<Stack>
<Heading level={1}>{title}</Heading>
{description && (
<Text color="text-gray-400" block mt={1}>
{description}
</Text>
)}
</Box>
</Stack>
</Stack>
{children}
</Stack>
</Box>
</Stack>
);
}

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { Box } from '@/ui/Box';
import { Image } from '@/ui/Image';
import { Stack } from '@/ui/Stack';
import { Image } from '@/ui/Image';
import { Text } from '@/ui/Text';
import { RatingDisplay } from '@/lib/display-objects/RatingDisplay';
import { MedalDisplay } from '@/lib/display-objects/MedalDisplay';
@@ -25,11 +24,11 @@ export function LeaderboardPodium({ podium, onDriverClick }: LeaderboardPodiumPr
const displayOrder = [1, 0, 2];
return (
<Box mb={12}>
<Box display="flex" alignItems="end" justifyContent="center" gap={4} maxWidth="4xl" mx="auto">
<Stack mb={12}>
<Stack display="flex" alignItems="end" justifyContent="center" gap={4} maxWidth="4xl" mx="auto">
{displayOrder.map((index) => {
const driver = podium[index];
if (!driver) return <Box key={index} flexGrow={1} />;
if (!driver) return <Stack key={index} flexGrow={1} />;
const position = index + 1;
const isFirst = position === 1;
@@ -41,7 +40,7 @@ export function LeaderboardPodium({ podium, onDriverClick }: LeaderboardPodiumPr
}[position as 1 | 2 | 3];
return (
<Box
<Stack
key={driver.id}
as="button"
type="button"
@@ -56,8 +55,8 @@ export function LeaderboardPodium({ podium, onDriverClick }: LeaderboardPodiumPr
shadow={config.shadow}
zIndex={config.zIndex}
>
<Box position="relative" mb={4} transform={`scale(${config.scale})`}>
<Box
<Stack position="relative" mb={4} transform={`scale(${config.scale})`}>
<Stack
position="relative"
w={isFirst ? '32' : '24'}
h={isFirst ? '32' : '24'}
@@ -79,8 +78,8 @@ export function LeaderboardPodium({ podium, onDriverClick }: LeaderboardPodiumPr
fullHeight
objectFit="cover"
/>
</Box>
<Box
</Stack>
<Stack
position="absolute"
bottom="-2"
left="50%"
@@ -98,8 +97,8 @@ export function LeaderboardPodium({ podium, onDriverClick }: LeaderboardPodiumPr
shadow="lg"
>
<Text size="sm" weight="bold">{position}</Text>
</Box>
</Box>
</Stack>
</Stack>
<Text
weight="bold"
@@ -132,14 +131,14 @@ export function LeaderboardPodium({ podium, onDriverClick }: LeaderboardPodiumPr
<Text size="xs" color="text-gray-500" weight="bold" uppercase letterSpacing="wider">Wins</Text>
<Text size="xs" weight="bold" color="text-performance-green">{driver.wins}</Text>
</Stack>
<Box w="1" h="1" rounded="full" bg="bg-gray-700" />
<Stack w="1" h="1" rounded="full" bg="bg-gray-700" />
<Stack direction="row" align="center" gap={1}>
<Text size="xs" color="text-gray-500" weight="bold" uppercase letterSpacing="wider">Podiums</Text>
<Text size="xs" weight="bold" color="text-white">{driver.podiums}</Text>
</Stack>
</Stack>
<Box
<Stack
mt={6}
w="full"
h={config.height}
@@ -163,11 +162,11 @@ export function LeaderboardPodium({ podium, onDriverClick }: LeaderboardPodiumPr
>
{position}
</Text>
</Box>
</Box>
</Stack>
</Stack>
);
})}
</Box>
</Box>
</Stack>
</Stack>
);
}

View File

@@ -2,7 +2,6 @@
import React from 'react';
import { Award, Trophy, Users } from 'lucide-react';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
@@ -31,9 +30,9 @@ export function LeaderboardsHero({ onNavigateToDrivers, onNavigateToTeams }: Lea
<DecorativeBlur color="blue" size="lg" position="top-right" opacity={10} />
<DecorativeBlur color="purple" size="md" position="bottom-left" opacity={5} />
<Box position="relative" zIndex={10}>
<Stack position="relative" zIndex={10}>
<Stack direction="row" align="center" gap={4} mb={4}>
<Box
<Stack
p={3}
bg="linear-gradient(to bottom right, rgba(25, 140, 255, 0.2), rgba(25, 140, 255, 0.05))"
border
@@ -44,11 +43,11 @@ export function LeaderboardsHero({ onNavigateToDrivers, onNavigateToTeams }: Lea
justifyContent="center"
>
<Icon icon={Award} size={7} color="text-primary-blue" />
</Box>
<Box>
</Stack>
<Stack>
<Heading level={1} weight="bold" letterSpacing="tight">Leaderboards</Heading>
<Text color="text-gray-400" block mt={1} size="sm" uppercase letterSpacing="widest" weight="bold">Precision Performance Tracking</Text>
</Box>
</Stack>
</Stack>
<Text
@@ -80,7 +79,7 @@ export function LeaderboardsHero({ onNavigateToDrivers, onNavigateToTeams }: Lea
Team Rankings
</Button>
</Stack>
</Box>
</Stack>
</Surface>
);
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Crown } from 'lucide-react';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Icon } from '@/ui/Icon';
import { Text } from '@/ui/Text';
@@ -21,12 +21,11 @@ export function MedalBadge({ position }: MedalBadgeProps) {
const isMedal = position <= 3;
return (
<Box
display="flex"
<Stack
h="10"
w="10"
alignItems="center"
justifyContent="center"
align="center"
justify="center"
rounded="full"
bg={isMedal ? 'bg-gradient-to-br from-yellow-400/20 to-amber-600/10' : 'bg-iron-gray'}
>
@@ -35,6 +34,6 @@ export function MedalBadge({ position }: MedalBadgeProps) {
) : (
<Text size="lg" weight="bold" color="text-gray-400">#{position}</Text>
)}
</Box>
</Stack>
);
}

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
interface RankingListItemProps {
name: string;
@@ -21,7 +20,7 @@ export function RankingListItem({
rating,
}: RankingListItemProps) {
return (
<Box
<Stack
display="flex"
alignItems="center"
justifyContent="between"
@@ -39,32 +38,32 @@ export function RankingListItem({
</Text>
</Stack>
<Box display="flex" alignItems="center" gap={6} textAlign="right">
<Box>
<Stack display="flex" alignItems="center" gap={6} textAlign="right">
<Stack>
<Text color="text-primary-blue" size="base" weight="semibold" block>
#{rank}
</Text>
<Text color="text-gray-500" size="xs" block>Position</Text>
</Box>
<Box>
</Stack>
<Stack>
<Text color="text-white" size="sm" weight="semibold" block>
{totalDrivers}
</Text>
<Text color="text-gray-500" size="xs" block>Drivers</Text>
</Box>
<Box>
</Stack>
<Stack>
<Text color="text-green-400" size="sm" weight="semibold" block>
{percentile.toFixed(1)}%
</Text>
<Text color="text-gray-500" size="xs" block>Percentile</Text>
</Box>
<Box>
</Stack>
<Stack>
<Text color="text-warning-amber" size="sm" weight="semibold" block>
{rating}
</Text>
<Text color="text-gray-500" size="xs" block>Rating</Text>
</Box>
</Box>
</Box>
</Stack>
</Stack>
</Stack>
);
}

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { Box } from '@/ui/Box';
import { Image } from '@/ui/Image';
import { Stack } from '@/ui/Stack';
import { Image } from '@/ui/Image';
import { TableCell, TableRow } from '@/ui/Table';
import { Text } from '@/ui/Text';
import { RankMedal } from './RankMedal';
@@ -42,20 +41,20 @@ export function RankingRow({
>
<TableCell>
<Stack direction="row" align="center" gap={4}>
<Box w="8" display="flex" justifyContent="center">
<Stack w="8" display="flex" justifyContent="center">
<RankMedal rank={rank} size="md" />
</Box>
</Stack>
{rankDelta !== undefined && (
<Box w="10">
<Stack w="10">
<DeltaChip value={rankDelta} type="rank" />
</Box>
</Stack>
)}
</Stack>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center" gap={3}>
<Box
<Stack display="flex" alignItems="center" gap={3}>
<Stack
position="relative"
w="10"
h="10"
@@ -75,8 +74,8 @@ export function RankingRow({
fullHeight
objectFit="cover"
/>
</Box>
<Box minWidth="0">
</Stack>
<Stack minWidth="0">
<Text
weight="semibold"
color="text-white"
@@ -89,11 +88,11 @@ export function RankingRow({
</Text>
<Stack direction="row" align="center" gap={2} mt={0.5}>
<Text size="xs" color="text-gray-500">{nationality}</Text>
<Box w="1" h="1" rounded="full" bg="bg-gray-700" />
<Stack w="1" h="1" rounded="full" bg="bg-gray-700" />
<Text size="xs" color="text-gray-500">{skillLevel}</Text>
</Stack>
</Box>
</Box>
</Stack>
</Stack>
</TableCell>
<TableCell textAlign="center">

View File

@@ -1,8 +1,7 @@
import { Box } from '@/ui/Box';
import { Image } from '@/ui/Image';
import { Stack } from '@/ui/Stack';
import { Image } from '@/ui/Image';
import { Text } from '@/ui/Text';
interface PodiumDriver {
@@ -21,8 +20,8 @@ interface RankingsPodiumProps {
export function RankingsPodium({ podium, onDriverClick }: RankingsPodiumProps) {
return (
<Box mb={10}>
<Box display="flex" alignItems="end" justifyContent="center" gap={4}>
<Stack mb={10}>
<Stack display="flex" alignItems="end" justifyContent="center" gap={4}>
{[1, 0, 2].map((index) => {
const driver = podium[index];
if (!driver) return null;
@@ -35,7 +34,7 @@ export function RankingsPodium({ podium, onDriverClick }: RankingsPodiumProps) {
}[position] || { height: '6rem', color: 'rgba(217, 119, 6, 0.2)', borderColor: 'rgba(217, 119, 6, 0.4)', crown: '#d97706' };
return (
<Box
<Stack
key={driver.id}
as="button"
type="button"
@@ -47,8 +46,8 @@ export function RankingsPodium({ podium, onDriverClick }: RankingsPodiumProps) {
border={false}
cursor="pointer"
>
<Box position="relative" mb={4}>
<Box
<Stack position="relative" mb={4}>
<Stack
position="relative"
w={position === 1 ? '24' : '20'}
h={position === 1 ? '24' : '20'}
@@ -65,8 +64,8 @@ export function RankingsPodium({ podium, onDriverClick }: RankingsPodiumProps) {
height={112}
objectFit="cover"
/>
</Box>
<Box
</Stack>
<Stack
position="absolute"
bottom="-2"
left="50%"
@@ -88,8 +87,8 @@ export function RankingsPodium({ podium, onDriverClick }: RankingsPodiumProps) {
}}
>
{position}
</Box>
</Box>
</Stack>
</Stack>
<Text weight="semibold" color="text-white" size={position === 1 ? 'lg' : 'base'} mb={1} block>
{driver.name}
@@ -111,7 +110,7 @@ export function RankingsPodium({ podium, onDriverClick }: RankingsPodiumProps) {
</Stack>
</Stack>
<Box
<Stack
mt={4}
w={position === 1 ? '28' : '24'}
h={config.height}
@@ -131,11 +130,11 @@ export function RankingsPodium({ podium, onDriverClick }: RankingsPodiumProps) {
<Text weight="bold" size={position === 1 ? '4xl' : '3xl'} color={config.crown === '#facc15' ? 'text-warning-amber' : config.crown === '#d1d5db' ? 'text-gray-300' : 'text-orange-600'}>
{position}
</Text>
</Box>
</Box>
</Stack>
</Stack>
);
})}
</Box>
</Box>
</Stack>
</Stack>
);
}

View File

@@ -1,6 +1,5 @@
import { Box } from '@/ui/Box';
import { Icon } from '@/ui/Icon';
import { Image } from '@/ui/Image';
import { Stack } from '@/ui/Stack';
@@ -30,16 +29,16 @@ interface RankingsTableProps {
export function RankingsTable({ drivers, onDriverClick }: RankingsTableProps) {
if (drivers.length === 0) {
return (
<Box py={16} textAlign="center" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline" rounded="xl">
<Stack py={16} align="center" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline" rounded="xl">
<Text size="4xl" block mb={4}>🔍</Text>
<Text color="text-gray-400" block mb={2}>No drivers found</Text>
<Text size="sm" color="text-gray-500">There are no drivers in the system yet</Text>
</Box>
</Stack>
);
}
return (
<Box rounded="xl" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline" overflow="hidden">
<Stack rounded="xl" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline" overflow="hidden" gap={0}>
<Table>
<TableHead>
<TableRow>
@@ -58,29 +57,31 @@ export function RankingsTable({ drivers, onDriverClick }: RankingsTableProps) {
onClick={() => onDriverClick?.(driver.id)}
>
<TableCell className="text-center">
<Box
display="inline-flex"
<Stack
direction="row"
h="9"
w="9"
alignItems="center"
justifyContent="center"
align="center"
justify="center"
rounded="full"
border
borderColor="border-charcoal-outline"
bg={driver.medalBg}
color={driver.medalColor}
className="text-sm font-bold"
fontSize="sm"
fontWeight="bold"
>
{driver.rank <= 3 ? <Icon icon={Medal} size={4} /> : driver.rank}
</Box>
{driver.rank <= 3 ? <Icon icon={Medal} size={4} color={driver.medalColor} /> : (
<Text color={driver.medalColor}>{driver.rank}</Text>
)}
</Stack>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center" gap={3}>
<Box position="relative" w="10" h="10" rounded="full" overflow="hidden" border borderColor="border-charcoal-outline" borderTop={false} borderBottom={false} borderLeft={false} borderRight={false}>
<Image src={driver.avatarUrl} alt={driver.name} width={40} height={40} className="w-full h-full object-cover" />
</Box>
<Box minWidth="0">
<Stack direction="row" align="center" gap={3}>
<Stack position="relative" w="10" h="10" rounded="full" overflow="hidden" border borderColor="border-charcoal-outline" gap={0}>
<Image src={driver.avatarUrl} alt={driver.name} width={40} height={40} objectFit="cover" fullWidth fullHeight />
</Stack>
<Stack minWidth="0" gap={0}>
<Text weight="semibold" color="text-white" block truncate>
{driver.name}
</Text>
@@ -88,8 +89,8 @@ export function RankingsTable({ drivers, onDriverClick }: RankingsTableProps) {
<Text size="xs" color="text-gray-500">{driver.nationality}</Text>
<Text size="xs" color="text-gray-500">{driver.skillLevel}</Text>
</Stack>
</Box>
</Box>
</Stack>
</Stack>
</TableCell>
<TableCell className="text-center">
@@ -111,6 +112,6 @@ export function RankingsTable({ drivers, onDriverClick }: RankingsTableProps) {
))}
</TableBody>
</Table>
</Box>
</Stack>
);
}