website refactor

This commit is contained in:
2026-01-18 22:55:55 +01:00
parent b43a23a48c
commit aeaa43f4d3
179 changed files with 4736 additions and 6832 deletions

View File

@@ -1,52 +0,0 @@
import { Image } from '@/ui/Image';
import { Stack } from '@/ui/primitives/Stack';
import { Text } from '@/ui/Text';
export function ActiveDriverCard({
name,
avatarUrl,
categoryLabel,
categoryColor,
skillLevelLabel,
skillLevelColor,
onClick,
}: ActiveDriverCardProps) {
return (
<Stack
as="button"
{...({ type: 'button' } as any)}
onClick={onClick}
p={3}
rounded="xl"
bg="bg-iron-gray/40"
border
borderColor="border-charcoal-outline"
className="transition-all hover:border-performance-green/40 group text-center"
>
<Stack position="relative" w="12" h="12" mx="auto" rounded="full" overflow="hidden" border borderColor="border-charcoal-outline" mb={2}>
<Image src={avatarUrl || '/default-avatar.png'} alt={name} objectFit="cover" fill />
<Stack position="absolute" bottom="0" right="0" w="3" h="3" rounded="full" bg="bg-performance-green" border borderColor="border-iron-gray" style={{ borderWidth: '2px' }}>{null}</Stack>
</Stack>
<Text
size="sm"
weight="medium"
color="text-white"
truncate
block
groupHoverTextColor="performance-green"
transition
>
{name}
</Text>
<Stack direction="row" align="center" justify="center" gap={1}>
{categoryLabel && (
<Text size="xs" color={categoryColor}>{categoryLabel}</Text>
)}
{skillLevelLabel && (
<Text size="xs" color={skillLevelColor}>{skillLevelLabel}</Text>
)}
</Stack>
</Stack>
);
}

View File

@@ -1,85 +0,0 @@
import { Badge } from '@/ui/Badge';
import { Image } from '@/ui/Image';
import { Link } from '@/ui/Link';
import { PlaceholderImage } from '@/ui/PlaceholderImage';
import { Box } from '@/ui/primitives/Box';
import { Text } from '@/ui/Text';
export interface DriverIdentityProps {
driver: {
id: string;
name: string;
avatarUrl: string | null;
};
href?: string;
contextLabel?: React.ReactNode;
meta?: React.ReactNode;
size?: 'sm' | 'md';
}
export function DriverIdentity(props: DriverIdentityProps) {
const { driver, href, contextLabel, meta, size = 'md' } = props;
const avatarSize = size === 'sm' ? 40 : 48;
const nameSize = size === 'sm' ? 'sm' : 'base';
const avatarUrl = driver.avatarUrl;
const content = (
<Box display="flex" alignItems="center" gap={{ base: 3, md: 4 }} flexGrow={1} minWidth="0">
<Box
rounded="full"
bg="bg-primary-blue/20"
overflow="hidden"
display="flex"
alignItems="center"
justifyContent="center"
flexShrink={0}
w={`${avatarSize}px`}
h={`${avatarSize}px`}
>
{avatarUrl ? (
<Image
src={avatarUrl}
alt={driver.name}
width={avatarSize}
height={avatarSize}
fullWidth
fullHeight
objectFit="cover"
/>
) : (
<PlaceholderImage size={avatarSize} />
)}
</Box>
<Box flexGrow={1} minWidth="0">
<Box display="flex" alignItems="center" gap={2} minWidth="0">
<Text size={nameSize} weight="medium" color="text-white" truncate>
{driver.name}
</Text>
{contextLabel && (
<Badge variant="default" bg="bg-charcoal-outline/60" size="xs">
{contextLabel}
</Badge>
)}
</Box>
{meta && (
<Text size="xs" color="text-gray-400" mt={0.5} truncate block>
{meta}
</Text>
)}
</Box>
</Box>
);
if (href) {
return (
<Link href={href} block variant="ghost">
{content}
</Link>
);
}
return <Box display="flex" alignItems="center" gap={{ base: 3, md: 4 }} flexGrow={1} minWidth="0">{content}</Box>;
}

View File

@@ -1,9 +1,10 @@
'use client';
import React from 'react';
import { Card } from '@/ui/Card';
import { Heading } from '@/ui/Heading';
import { Panel } from '@/ui/Panel';
import { RankingListItem } from '@/components/leaderboards/RankingListItem';
import { RankingList } from '@/components/leaderboards/RankingList';
import { MinimalEmptyState } from '@/components/shared/state/EmptyState';
import { EmptyState } from '@/ui/EmptyState';
export interface DriverRanking {
type: 'overall' | 'league';
@@ -21,19 +22,18 @@ interface DriverRankingsProps {
export function DriverRankings({ rankings }: DriverRankingsProps) {
if (!rankings || rankings.length === 0) {
return (
<Card bg="bg-iron-gray/60" borderColor="border-charcoal-outline/80" p={4}>
<Heading level={3} mb={2}>Rankings</Heading>
<MinimalEmptyState
<Panel title="Rankings">
<EmptyState
title="No ranking data available yet"
description="Compete in leagues to earn your first results."
variant="minimal"
/>
</Card>
</Panel>
);
}
return (
<Card bg="bg-iron-gray/60" borderColor="border-charcoal-outline/80" p={4}>
<Heading level={3} mb={4}>Rankings</Heading>
<Panel title="Rankings">
<RankingList>
{rankings.map((ranking, index) => (
<RankingListItem
@@ -47,6 +47,6 @@ export function DriverRankings({ rankings }: DriverRankingsProps) {
/>
))}
</RankingList>
</Card>
</Panel>
);
}

View File

@@ -1,14 +1,15 @@
'use client';
import { EmptyState } from '@/components/shared/state/EmptyState';
import { LoadingWrapper } from '@/components/shared/state/LoadingWrapper';
import { EmptyState } from '@/ui/EmptyState';
import { LoadingWrapper } from '@/ui/LoadingWrapper';
import { Button } from '@/ui/Button';
import { Card } from '@/ui/Card';
import { Pagination } from '@/ui/Pagination';
import { Stack } from '@/ui/primitives/Stack';
import { Text } from '@/ui/Text';
import { ControlBar } from '@/ui/ControlBar';
import { Trophy } from 'lucide-react';
import { useEffect, useState } from 'react';
import React from 'react';
interface RaceHistoryProps {
driverId: string;
@@ -24,7 +25,6 @@ export function ProfileRaceHistory({ driverId }: RaceHistoryProps) {
async function loadRaceHistory() {
try {
// Driver race history is not exposed via API yet.
// Keep as placeholder until an endpoint exists.
} catch (err) {
console.error('Failed to load race history:', err);
} finally {
@@ -40,18 +40,7 @@ export function ProfileRaceHistory({ driverId }: RaceHistoryProps) {
const totalPages = Math.ceil(filteredResults.length / resultsPerPage);
if (loading) {
return (
<Stack gap={4}>
<Stack display="flex" alignItems="center" gap={2}>
{[1, 2, 3].map(i => (
<Stack key={i} h="9" w="24" bg="bg-iron-gray" rounded="md" animate="pulse" />
))}
</Stack>
<Card>
<LoadingWrapper variant="skeleton" skeletonCount={3} />
</Card>
</Stack>
);
return <LoadingWrapper variant="spinner" message="Loading race history..." />;
}
if (filteredResults.length === 0) {
@@ -60,50 +49,50 @@ export function ProfileRaceHistory({ driverId }: RaceHistoryProps) {
icon={Trophy}
title="No race history yet"
description="Complete races to build your racing record"
variant="minimal"
/>
);
}
return (
<Stack gap={4}>
<Stack display="flex" alignItems="center" gap={2}>
<Button
variant={filter === 'all' ? 'primary' : 'secondary'}
onClick={() => { setFilter('all'); setPage(1); }}
size="sm"
>
All Races
</Button>
<Button
variant={filter === 'wins' ? 'primary' : 'secondary'}
onClick={() => { setFilter('wins'); setPage(1); }}
size="sm"
>
Wins Only
</Button>
<Button
variant={filter === 'podiums' ? 'primary' : 'secondary'}
onClick={() => { setFilter('podiums'); setPage(1); }}
size="sm"
>
Podiums
</Button>
</Stack>
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<ControlBar>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<Button
variant={filter === 'all' ? 'primary' : 'secondary'}
onClick={() => { setFilter('all'); setPage(1); }}
size="sm"
>
All Races
</Button>
<Button
variant={filter === 'wins' ? 'primary' : 'secondary'}
onClick={() => { setFilter('wins'); setPage(1); }}
size="sm"
>
Wins Only
</Button>
<Button
variant={filter === 'podiums' ? 'primary' : 'secondary'}
onClick={() => { setFilter('podiums'); setPage(1); }}
size="sm"
>
Podiums
</Button>
</div>
</ControlBar>
<Card>
{/* No results until API provides driver results */}
<Stack minHeight="100px" display="flex" center>
<Text color="text-gray-500">No results found for the selected filter.</Text>
</Stack>
<div style={{ minHeight: '10rem', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Text variant="low">No results found for the selected filter.</Text>
</div>
<Pagination
currentPage={page}
totalPages={totalPages}
totalItems={filteredResults.length}
itemsPerPage={resultsPerPage}
onPageChange={setPage}
/>
</Card>
</Stack>
</div>
);
}