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,8 +1,8 @@
import { Box } from '@/ui/Box';
import React from 'react';
import { Image } from '@/ui/Image';
import { Text } from '@/ui/Text';
import { Card } from '@/ui/Card';
import { Stack } from '@/ui/Stack';
interface ActiveDriverCardProps {
name: string;
@@ -24,25 +24,21 @@ export function ActiveDriverCard({
onClick,
}: ActiveDriverCardProps) {
return (
<Box
<Stack
as="button"
type="button"
{...({ type: 'button' } as any)}
onClick={onClick}
p={3}
rounded="xl"
bg="bg-iron-gray/40"
border
borderColor="border-charcoal-outline"
transition
cursor="pointer"
hoverBorderColor="performance-green/40"
group
textAlign="center"
className="transition-all hover:border-performance-green/40 group text-center"
>
<Box position="relative" w="12" h="12" mx="auto" rounded="full" overflow="hidden" border borderColor="border-charcoal-outline" mb={2}>
<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 />
<Box position="absolute" bottom="0" right="0" w="3" h="3" rounded="full" bg="bg-performance-green" border borderColor="border-iron-gray" style={{ borderWidth: '2px' }} />
</Box>
<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"
@@ -54,14 +50,14 @@ export function ActiveDriverCard({
>
{name}
</Text>
<Box display="flex" alignItems="center" justifyContent="center" gap={1}>
<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>
)}
</Box>
</Box>
</Stack>
</Stack>
);
}

View File

@@ -1,12 +1,10 @@
import { AchievementCard } from '@/components/achievements/AchievementCard';
import { Box } from '@/ui/Box';
import { Card } from '@/ui/Card';
import { GoalCard } from '@/ui/GoalCard';
import { Heading } from '@/ui/Heading';
import { MilestoneItem } from '@/components/achievements/MilestoneItem';
import { Stack } from '@/ui/Stack';
import { Grid } from '@/ui/Grid';
interface Achievement {
id: string;
@@ -69,7 +67,7 @@ export function CareerHighlights() {
<Card>
<Heading level={3} mb={4}>Achievements</Heading>
<Box display="grid" gridCols={{ base: 1, sm: 2 }} gap={3}>
<Grid cols={1} mdCols={2} gap={3}>
{mockAchievements.map((achievement) => (
<AchievementCard
key={achievement.id}
@@ -80,7 +78,7 @@ export function CareerHighlights() {
rarity={achievement.rarity}
/>
))}
</Box>
</Grid>
</Card>
<GoalCard

View File

@@ -3,9 +3,8 @@
import React, { useState, FormEvent } from 'react';
import { Input } from '@/ui/Input';
import { Button } from '@/ui/Button';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { TextArea } from '@/ui/TextArea';
import { InfoBox } from '@/ui/InfoBox';
import { AlertCircle } from 'lucide-react';
@@ -81,7 +80,7 @@ export function CreateDriverForm({ onSuccess, isPending }: CreateDriverFormProps
};
return (
<Box as="form" onSubmit={handleSubmit}>
<Stack as="form" onSubmit={handleSubmit}>
<Stack gap={6}>
<Input
label="Driver Name *"
@@ -95,7 +94,7 @@ export function CreateDriverForm({ onSuccess, isPending }: CreateDriverFormProps
disabled={isPending}
/>
<Box>
<Stack>
<Input
label="Country Code *"
id="country"
@@ -109,9 +108,9 @@ export function CreateDriverForm({ onSuccess, isPending }: CreateDriverFormProps
disabled={isPending}
/>
<Text size="xs" color="text-gray-500" mt={1} block>Use ISO 3166-1 alpha-2 or alpha-3 code</Text>
</Box>
</Stack>
<Box>
<Stack>
<TextArea
label="Bio (Optional)"
id="bio"
@@ -122,15 +121,15 @@ export function CreateDriverForm({ onSuccess, isPending }: CreateDriverFormProps
rows={4}
disabled={isPending}
/>
<Box display="flex" justifyContent="between" mt={1}>
<Stack display="flex" justifyContent="between" mt={1}>
{errors.bio ? (
<Text size="sm" color="text-warning-amber">{errors.bio}</Text>
) : <Box />}
) : <Stack />}
<Text size="xs" color="text-gray-500">
{formData.bio.length}/500
</Text>
</Box>
</Box>
</Stack>
</Stack>
{errors.submit && (
<InfoBox
@@ -150,6 +149,6 @@ export function CreateDriverForm({ onSuccess, isPending }: CreateDriverFormProps
{isPending ? 'Creating Profile...' : 'Create Profile'}
</Button>
</Stack>
</Box>
</Stack>
);
}

View File

@@ -3,10 +3,9 @@
import { CountryFlagDisplay } from '@/lib/display-objects/CountryFlagDisplay';
import { Zap } from 'lucide-react';
import { Badge } from '@/ui/Badge';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Icon } from '@/ui/Icon';
import { Image } from '@/ui/Image';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
interface DriverEntryRowProps {
@@ -29,7 +28,7 @@ export function DriverEntryRow({
onClick,
}: DriverEntryRowProps) {
return (
<Box
<Stack
onClick={onClick}
display="flex"
alignItems="center"
@@ -43,7 +42,7 @@ export function DriverEntryRow({
borderColor={isCurrentUser ? 'border-primary-blue/30' : 'transparent'}
hoverBorderColor={isCurrentUser ? 'primary-blue/40' : 'charcoal-outline/20'}
>
<Box
<Stack
display="flex"
alignItems="center"
justifyContent="center"
@@ -55,10 +54,10 @@ export function DriverEntryRow({
style={{ fontWeight: 'bold', fontSize: '0.875rem' }}
>
{index + 1}
</Box>
</Stack>
<Box position="relative" flexShrink={0}>
<Box
<Stack position="relative" flexShrink={0}>
<Stack
w="10"
h="10"
rounded="full"
@@ -74,8 +73,8 @@ export function DriverEntryRow({
objectFit="cover"
fill
/>
</Box>
<Box
</Stack>
<Stack
position="absolute"
bottom="-0.5"
right="-0.5"
@@ -89,10 +88,10 @@ export function DriverEntryRow({
style={{ fontSize: '0.625rem' }}
>
{CountryFlagDisplay.fromCountryCode(country).toString()}
</Box>
</Box>
</Stack>
</Stack>
<Box flexGrow={1} minWidth="0">
<Stack flexGrow={1} minWidth="0">
<Stack direction="row" align="center" gap={2}>
<Text
weight="semibold"
@@ -105,7 +104,7 @@ export function DriverEntryRow({
{isCurrentUser && <Badge variant="primary">You</Badge>}
</Stack>
<Text size="xs" color="text-gray-500">{country}</Text>
</Box>
</Stack>
{rating != null && (
<Badge variant="warning">
@@ -113,6 +112,6 @@ export function DriverEntryRow({
{rating}
</Badge>
)}
</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';
import { Image } from '@/ui/Image';
import { RatingBadge } from '@/components/drivers/RatingBadge';
@@ -27,7 +26,7 @@ export function DriverHeaderPanel({
const defaultAvatar = 'https://cdn.gridpilot.com/avatars/default.png';
return (
<Box
<Stack
bg="bg-panel-gray"
rounded="xl"
border
@@ -36,7 +35,7 @@ export function DriverHeaderPanel({
position="relative"
>
{/* Background Accent */}
<Box
<Stack
position="absolute"
top={0}
left={0}
@@ -46,10 +45,10 @@ export function DriverHeaderPanel({
opacity={0.5}
/>
<Box p={6} position="relative">
<Stack p={6} position="relative">
<Stack direction={{ base: 'col', md: 'row' }} gap={6} align="start" className="md:items-center">
{/* Avatar */}
<Box
<Stack
width="32"
height="32"
rounded="2xl"
@@ -65,10 +64,10 @@ export function DriverHeaderPanel({
fill
objectFit="cover"
/>
</Box>
</Stack>
{/* Info */}
<Box flexGrow={1}>
<Stack flexGrow={1}>
<Stack gap={2}>
<Stack direction="row" align="center" gap={3} wrap>
<Text as="h1" size="3xl" weight="bold" color="text-white">
@@ -94,16 +93,16 @@ export function DriverHeaderPanel({
</Text>
)}
</Stack>
</Box>
</Stack>
{/* Actions */}
{actions && (
<Box flexShrink={0}>
<Stack flexShrink={0}>
{actions}
</Box>
</Stack>
)}
</Stack>
</Box>
</Box>
</Stack>
</Stack>
);
}

View File

@@ -2,10 +2,9 @@
import type { DriverViewModel } from '@/lib/view-models/DriverViewModel';
import { Card } from '@/ui/Card';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
import { Stack } from '@/ui/Stack';
import { StatCard } from '@/ui/StatCard';
import { ProfileHeader } from '@/components/drivers/ProfileHeader';
import { ProfileStats } from './ProfileStats';
@@ -96,12 +95,12 @@ export function DriverProfile({ driver, isOwnProfile = false, onEditClick }: Dri
)}
{driverStats && (
<Box display="grid" responsiveGridCols={{ base: 1, lg: 3 }} gap={6}>
<Box responsiveColSpan={{ lg: 2 }}>
<Stack display="grid" responsiveGridCols={{ base: 1, lg: 3 }} gap={6}>
<Stack responsiveColSpan={{ lg: 2 }}>
<Stack gap={6}>
<Card>
<Heading level={3} mb={4}>Career Statistics</Heading>
<Box display="grid" gridCols={2} gap={4}>
<Stack display="grid" gridCols={2} gap={4}>
<StatCard
label="Rating"
value={driverStats.rating ?? 0}
@@ -110,26 +109,26 @@ export function DriverProfile({ driver, isOwnProfile = false, onEditClick }: Dri
<StatCard label="Total Races" value={driverStats.totalRaces} variant="blue" />
<StatCard label="Wins" value={driverStats.wins} variant="green" />
<StatCard label="Podiums" value={driverStats.podiums} variant="orange" />
</Box>
</Stack>
</Card>
{performanceStats && <PerformanceMetrics stats={performanceStats} />}
</Stack>
</Box>
</Stack>
<DriverRankings rankings={rankings} />
</Box>
</Stack>
)}
{!driverStats && (
<Box display="grid" responsiveGridCols={{ base: 1, lg: 3 }} gap={6}>
<Stack display="grid" responsiveGridCols={{ base: 1, lg: 3 }} gap={6}>
<Card responsiveColSpan={{ lg: 3 }}>
<Heading level={3} mb={4}>Career Statistics</Heading>
<Text color="text-gray-400" size="sm" block>
No statistics available yet. Compete in races to start building your record.
</Text>
</Card>
</Box>
</Stack>
)}
<Card>
@@ -154,20 +153,20 @@ export function DriverProfile({ driver, isOwnProfile = false, onEditClick }: Dri
<CareerHighlights />
<Card bg="bg-charcoal-200/50" borderColor="border-primary-blue/30">
<Box display="flex" alignItems="center" gap={3} mb={3}>
<Stack display="flex" alignItems="center" gap={3} mb={3}>
<Text size="2xl">🔒</Text>
<Heading level={3}>Private Information</Heading>
</Box>
</Stack>
<Text color="text-gray-400" size="sm" block>
Detailed race history, settings, and preferences are only visible to the driver.
</Text>
</Card>
<Card bg="bg-charcoal-200/50" borderColor="border-primary-blue/30">
<Box display="flex" alignItems="center" gap={3} mb={3}>
<Stack display="flex" alignItems="center" gap={3} mb={3}>
<Text size="2xl">📊</Text>
<Heading level={3}>Coming Soon</Heading>
</Box>
</Stack>
<Text color="text-gray-400" size="sm" block>
Per-car statistics, per-track performance, and head-to-head comparisons will be available in production.
</Text>

View File

@@ -6,7 +6,6 @@ import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Button } from '@/ui/Button';
import { RatingBadge } from '@/components/drivers/RatingBadge';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Image } from '@/ui/Image';
import { SafetyRatingBadge } from './SafetyRatingBadge';
@@ -37,34 +36,34 @@ export function DriverProfileHeader({
const defaultAvatar = 'https://cdn.gridpilot.com/avatars/default.png';
return (
<Box position="relative" overflow="hidden" rounded="2xl" border borderColor="border-charcoal-outline" bg="bg-deep-charcoal" p={{ base: 6, lg: 8 }}>
<Stack position="relative" overflow="hidden" rounded="2xl" border borderColor="border-charcoal-outline" bg="bg-deep-charcoal" p={{ base: 6, lg: 8 }}>
{/* Background Accents */}
<Box position="absolute" right="-24" top="-24" w="96" h="96" rounded="full" bg="bg-primary-blue/5" blur="3xl" />
<Stack position="absolute" right="-24" top="-24" w="96" h="96" rounded="full" bg="bg-primary-blue/5" blur="3xl" />
<Box position="relative" display="flex" flexDirection={{ base: 'col', lg: 'row' }} gap={8}>
<Stack position="relative" display="flex" flexDirection={{ base: 'col', lg: 'row' }} gap={8}>
{/* Avatar */}
<Box position="relative" h={{ base: '32', lg: '40' }} w={{ base: '32', lg: '40' }} flexShrink={0} overflow="hidden" rounded="2xl" border={true} borderWidth="2px" borderColor="border-charcoal-outline" bg="bg-deep-graphite" shadow="2xl">
<Stack position="relative" h={{ base: '32', lg: '40' }} w={{ base: '32', lg: '40' }} flexShrink={0} overflow="hidden" rounded="2xl" border={true} borderWidth="2px" borderColor="border-charcoal-outline" bg="bg-deep-graphite" shadow="2xl">
<Image
src={avatarUrl || defaultAvatar}
alt={name}
fill
objectFit="cover"
/>
</Box>
</Stack>
{/* Info */}
<Box display="flex" flexGrow={1} flexDirection="col" gap={4}>
<Box display="flex" flexDirection={{ base: 'col', lg: 'row' }} alignItems={{ lg: 'center' }} justifyContent="between" gap={2}>
<Box>
<Stack display="flex" flexGrow={1} flexDirection="col" gap={4}>
<Stack display="flex" flexDirection={{ base: 'col', lg: 'row' }} alignItems={{ lg: 'center' }} justifyContent="between" gap={2}>
<Stack>
<Stack direction="row" align="center" gap={3} mb={1}>
<Heading level={1}>{name}</Heading>
{globalRank && (
<Box display="flex" alignItems="center" gap={1} rounded="md" bg="bg-warning-amber/10" px={2} py={0.5} border borderColor="border-warning-amber/20">
<Stack display="flex" alignItems="center" gap={1} rounded="md" bg="bg-warning-amber/10" px={2} py={0.5} border borderColor="border-warning-amber/20">
<Trophy size={12} color="#FFBE4D" />
<Text size="xs" weight="bold" font="mono" color="text-warning-amber">
#{globalRank}
</Text>
</Box>
</Stack>
)}
</Stack>
<Stack direction="row" align="center" gap={4}>
@@ -72,15 +71,15 @@ export function DriverProfileHeader({
<Globe size={14} color="#6B7280" />
<Text size="sm" color="text-gray-400">{nationality}</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={2}>
<RatingBadge rating={rating} size="sm" />
<SafetyRatingBadge rating={safetyRating} size="sm" />
</Stack>
</Stack>
</Box>
</Stack>
<Box mt={{ base: 4, lg: 0 }}>
<Stack mt={{ base: 4, lg: 0 }}>
<Button
variant={friendRequestSent ? 'secondary' : 'primary'}
onClick={onAddFriend}
@@ -89,18 +88,18 @@ export function DriverProfileHeader({
>
{friendRequestSent ? 'Request Sent' : 'Add Friend'}
</Button>
</Box>
</Box>
</Stack>
</Stack>
{bio && (
<Box maxWidth="3xl">
<Stack maxWidth="3xl">
<Text size="sm" color="text-gray-400" leading="relaxed">
{bio}
</Text>
</Box>
</Stack>
)}
</Box>
</Box>
</Box>
</Stack>
</Stack>
</Stack>
);
}

View File

@@ -3,7 +3,6 @@
import React from 'react';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { MapPin, Car, Clock, Users2, MailCheck } from 'lucide-react';
@@ -32,45 +31,45 @@ export function DriverRacingProfile({
];
return (
<Box display="flex" flexDirection="col" gap={6} rounded="2xl" border borderColor="border-charcoal-outline" bg="bg-deep-charcoal/50" p={6}>
<Box display="flex" alignItems="center" justifyContent="between">
<Stack display="flex" flexDirection="col" gap={6} rounded="2xl" border borderColor="border-charcoal-outline" bg="bg-deep-charcoal/50" p={6}>
<Stack display="flex" alignItems="center" justifyContent="between">
<Heading level={3}>Racing Profile</Heading>
<Stack direction="row" gap={2}>
{lookingForTeam && (
<Box display="flex" alignItems="center" gap={1.5} rounded="full" bg="bg-primary-blue/10" border borderColor="border-primary-blue/20" px={3} py={1}>
<Stack display="flex" alignItems="center" gap={1.5} rounded="full" bg="bg-primary-blue/10" border borderColor="border-primary-blue/20" px={3} py={1}>
<Users2 size={12} color="#198CFF" />
<Text size="xs" weight="bold" color="text-primary-blue" uppercase letterSpacing="tight">Looking for Team</Text>
</Box>
</Stack>
)}
{openToRequests && (
<Box display="flex" alignItems="center" gap={1.5} rounded="full" bg="bg-performance-green/10" border borderColor="border-performance-green/20" px={3} py={1}>
<Stack display="flex" alignItems="center" gap={1.5} rounded="full" bg="bg-performance-green/10" border borderColor="border-performance-green/20" px={3} py={1}>
<MailCheck size={12} color="#22C55E" />
<Text size="xs" weight="bold" color="text-performance-green" uppercase letterSpacing="tight">Open to Requests</Text>
</Box>
</Stack>
)}
</Stack>
</Box>
</Stack>
<Box display="grid" gridCols={{ base: 1, sm: 2 }} gap={4}>
<Stack display="grid" gridCols={{ base: 1, sm: 2 }} gap={4}>
{details.map((detail, index) => {
const Icon = detail.icon;
return (
<Box key={index} display="flex" alignItems="center" gap={4} rounded="xl" border borderColor="border-charcoal-outline/50" bg="bg-deep-graphite/50" p={4}>
<Box display="flex" h="10" w="10" alignItems="center" justifyContent="center" rounded="lg" bg="bg-charcoal-outline/50" color="text-gray-400">
<Stack key={index} display="flex" alignItems="center" gap={4} rounded="xl" border borderColor="border-charcoal-outline/50" bg="bg-deep-graphite/50" p={4}>
<Stack display="flex" h="10" w="10" alignItems="center" justifyContent="center" rounded="lg" bg="bg-charcoal-outline/50" color="text-gray-400">
<Icon size={20} />
</Box>
<Box display="flex" flexDirection="col">
</Stack>
<Stack display="flex" flexDirection="col">
<Text size="xs" weight="bold" color="text-gray-500" uppercase letterSpacing="wider">
{detail.label}
</Text>
<Text size="sm" weight="semibold" color="text-white">
{detail.value}
</Text>
</Box>
</Box>
</Stack>
</Stack>
);
})}
</Box>
</Box>
</Stack>
</Stack>
);
}

View File

@@ -1,6 +1,5 @@
import React from 'react';
import { Stack } from '@/ui/Stack';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
interface DriverStatsProps {
@@ -13,22 +12,22 @@ interface DriverStatsProps {
export function DriverStats({ rating, wins, podiums, winRate }: DriverStatsProps) {
return (
<Stack direction="row" align="center" gap={8} textAlign="center">
<Box>
<Stack>
<Text size="2xl" weight="bold" color="text-primary-blue" block>{rating}</Text>
<Text size="xs" color="text-gray-400" block>Rating</Text>
</Box>
<Box>
</Stack>
<Stack>
<Text size="2xl" weight="bold" color="text-green-400" block>{wins}</Text>
<Text size="xs" color="text-gray-400" block>Wins</Text>
</Box>
<Box>
</Stack>
<Stack>
<Text size="2xl" weight="bold" color="text-warning-amber" block>{podiums}</Text>
<Text size="xs" color="text-gray-400" block>Podiums</Text>
</Box>
<Box>
</Stack>
<Stack>
<Text size="sm" color="text-gray-400" block>{winRate}%</Text>
<Text size="xs" color="text-gray-500" block>Win Rate</Text>
</Box>
</Stack>
</Stack>
);
}

View File

@@ -1,11 +1,10 @@
import React from 'react';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Image } from '@/ui/Image';
import { Link } from '@/ui/Link';
import { PlaceholderImage } from '@/ui/PlaceholderImage';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
interface DriverSummaryPillProps {
@@ -27,7 +26,7 @@ export function DriverSummaryPill({
}: DriverSummaryPillProps) {
const content = (
<>
<Box
<Stack
w="8"
h="8"
rounded="full"
@@ -51,7 +50,7 @@ export function DriverSummaryPill({
) : (
<PlaceholderImage size={32} />
)}
</Box>
</Stack>
<Stack direction="col" align="start" justify="center">
<Text
@@ -95,7 +94,7 @@ export function DriverSummaryPill({
if (onClick) {
return (
<Box
<Stack
as="button"
type="button"
onClick={onClick}
@@ -115,12 +114,12 @@ export function DriverSummaryPill({
className="hover:bg-iron-gray"
>
{content}
</Box>
</Stack>
);
}
return (
<Box
<Stack
display="flex"
alignItems="center"
gap={3}
@@ -132,6 +131,6 @@ export function DriverSummaryPill({
borderColor="border-charcoal-outline/80"
>
{content}
</Box>
</Stack>
);
}

View File

@@ -4,7 +4,6 @@ import React from 'react';
import { TrendingUp } from 'lucide-react';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
interface DriverTableProps {
@@ -15,31 +14,31 @@ export function DriverTable({ children }: DriverTableProps) {
return (
<Stack gap={4}>
<Stack direction="row" align="center" gap={3}>
<Box display="flex" h="10" w="10" alignItems="center" justifyContent="center" rounded="xl" bg="bg-primary-blue/10" border borderColor="border-primary-blue/20">
<Stack display="flex" h="10" w="10" alignItems="center" justifyContent="center" rounded="xl" bg="bg-primary-blue/10" border borderColor="border-primary-blue/20">
<TrendingUp size={20} color="#198CFF" />
</Box>
<Box>
</Stack>
<Stack>
<Heading level={2}>Driver Rankings</Heading>
<Text size="xs" color="text-gray-500">Top performers by skill rating</Text>
</Box>
</Stack>
</Stack>
<Box overflow="hidden" rounded="xl" border borderColor="border-charcoal-outline" bg="bg-deep-charcoal/50">
<Box as="table" w="full" textAlign="left">
<Box as="thead">
<Box as="tr" borderBottom borderColor="border-charcoal-outline" bg="bg-deep-charcoal/80">
<Box as="th" px={6} py={4} fontSize="xs" color="text-gray-500" textAlign="center" width="60px">#</Box>
<Box as="th" px={6} py={4} fontSize="xs" color="text-gray-500">Driver</Box>
<Box as="th" px={6} py={4} fontSize="xs" color="text-gray-500" width="150px">Nationality</Box>
<Box as="th" px={6} py={4} fontSize="xs" color="text-gray-500" textAlign="right" width="100px">Rating</Box>
<Box as="th" px={6} py={4} fontSize="xs" color="text-gray-500" textAlign="right" width="80px">Wins</Box>
</Box>
</Box>
<Box as="tbody">
<Stack overflow="hidden" rounded="xl" border borderColor="border-charcoal-outline" bg="bg-deep-charcoal/50">
<Stack as="table" w="full" textAlign="left">
<Stack as="thead">
<Stack as="tr" borderBottom borderColor="border-charcoal-outline" bg="bg-deep-charcoal/80">
<Stack as="th" px={6} py={4} fontSize="xs" color="text-gray-500" textAlign="center" width="60px">#</Stack>
<Stack as="th" px={6} py={4} fontSize="xs" color="text-gray-500">Driver</Stack>
<Stack as="th" px={6} py={4} fontSize="xs" color="text-gray-500" width="150px">Nationality</Stack>
<Stack as="th" px={6} py={4} fontSize="xs" color="text-gray-500" textAlign="right" width="100px">Rating</Stack>
<Stack as="th" px={6} py={4} fontSize="xs" color="text-gray-500" textAlign="right" width="80px">Wins</Stack>
</Stack>
</Stack>
<Stack as="tbody">
{children}
</Box>
</Box>
</Box>
</Stack>
</Stack>
</Stack>
</Stack>
);
}

View File

@@ -3,7 +3,6 @@
import React from 'react';
import { RatingBadge } from '@/components/drivers/RatingBadge';
import { Text } from '@/ui/Text';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Image } from '@/ui/Image';
@@ -29,7 +28,7 @@ export function DriverTableRow({
const defaultAvatar = 'https://cdn.gridpilot.com/avatars/default.png';
return (
<Box
<Stack
as="tr"
onClick={onClick}
cursor="pointer"
@@ -39,7 +38,7 @@ export function DriverTableRow({
borderBottom
borderColor="border-charcoal-outline/50"
>
<Box as="td" px={6} py={4} textAlign="center">
<Stack as="td" px={6} py={4} textAlign="center">
<Text
size="sm"
weight="bold"
@@ -48,17 +47,17 @@ export function DriverTableRow({
>
{rank}
</Text>
</Box>
<Box as="td" px={6} py={4}>
</Stack>
<Stack as="td" px={6} py={4}>
<Stack direction="row" align="center" gap={3}>
<Box position="relative" h="8" w="8" overflow="hidden" rounded="full" border borderColor="border-charcoal-outline" bg="bg-deep-charcoal">
<Stack position="relative" h="8" w="8" overflow="hidden" rounded="full" border borderColor="border-charcoal-outline" bg="bg-deep-charcoal">
<Image
src={avatarUrl || defaultAvatar}
alt={name}
fill
objectFit="cover"
/>
</Box>
</Stack>
<Text
size="sm"
weight="semibold"
@@ -69,18 +68,18 @@ export function DriverTableRow({
{name}
</Text>
</Stack>
</Box>
<Box as="td" px={6} py={4}>
</Stack>
<Stack as="td" px={6} py={4}>
<Text size="xs" color="text-gray-400">{nationality}</Text>
</Box>
<Box as="td" px={6} py={4} textAlign="right">
</Stack>
<Stack as="td" px={6} py={4} textAlign="right">
<RatingBadge rating={rating} size="sm" />
</Box>
<Box as="td" px={6} py={4} textAlign="right">
</Stack>
<Stack as="td" px={6} py={4} textAlign="right">
<Text size="sm" weight="semibold" font="mono" color="text-performance-green">
{wins}
</Text>
</Box>
</Box>
</Stack>
</Stack>
);
}

View File

@@ -5,7 +5,6 @@ import { Users, Trophy } from 'lucide-react';
import { Heading } from '@/ui/Heading';
import { Text } from '@/ui/Text';
import { Button } from '@/ui/Button';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
interface DriverStat {
@@ -38,7 +37,7 @@ export function DriversDirectoryHeader({
];
return (
<Box
<Stack
as="header"
position="relative"
overflow="hidden"
@@ -49,15 +48,15 @@ export function DriversDirectoryHeader({
p={{ base: 8, lg: 10 }}
>
{/* Background Accents */}
<Box position="absolute" right="-24" top="-24" w="96" h="96" rounded="full" bg="bg-primary-blue/5" blur="3xl" />
<Box position="absolute" bottom="-16" left="-16" w="64" h="64" rounded="full" bg="bg-neon-aqua/5" blur="3xl" />
<Stack position="absolute" right="-24" top="-24" w="96" h="96" rounded="full" bg="bg-primary-blue/5" blur="3xl" />
<Stack position="absolute" bottom="-16" left="-16" w="64" h="64" rounded="full" bg="bg-neon-aqua/5" blur="3xl" />
<Box position="relative" display="flex" flexDirection={{ base: 'col', lg: 'row' }} alignItems={{ lg: 'center' }} justifyContent="between" gap={8}>
<Box maxWidth="2xl">
<Stack position="relative" display="flex" flexDirection={{ base: 'col', lg: 'row' }} alignItems={{ lg: 'center' }} justifyContent="between" gap={8}>
<Stack maxWidth="2xl">
<Stack direction="row" align="center" gap={3} mb={4}>
<Box display="flex" h="12" w="12" alignItems="center" justifyContent="center" rounded="xl" border borderColor="border-charcoal-outline" bg="bg-deep-charcoal" shadow="lg">
<Stack display="flex" h="12" w="12" alignItems="center" justifyContent="center" rounded="xl" border borderColor="border-charcoal-outline" bg="bg-deep-charcoal" shadow="lg">
<Users size={24} color="#198CFF" />
</Box>
</Stack>
<Heading level={1}>Drivers</Heading>
</Stack>
@@ -65,10 +64,10 @@ export function DriversDirectoryHeader({
Meet the racers who make every lap count. From rookies to champions, track their journey and see who&apos;s dominating the grid.
</Text>
<Box display="flex" flexWrap="wrap" gap={6} mt={6}>
<Stack display="flex" flexWrap="wrap" gap={6} mt={6}>
{stats.map((stat, index) => (
<Stack key={index} direction="row" align="center" gap={2}>
<Box
<Stack
w="2"
h="2"
rounded="full"
@@ -80,8 +79,8 @@ export function DriversDirectoryHeader({
</Text>
</Stack>
))}
</Box>
</Box>
</Stack>
</Stack>
<Stack gap={2}>
<Button
@@ -95,7 +94,7 @@ export function DriversDirectoryHeader({
See full driver rankings
</Text>
</Stack>
</Box>
</Box>
</Stack>
</Stack>
);
}

View File

@@ -1,7 +1,6 @@
import { Card } from '@/ui/Card';
import { Button } from '@/ui/Button';
import { Car, Download, Trash2, Edit } from 'lucide-react';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
@@ -28,9 +27,9 @@ export function LiveryCard({ livery, onEdit, onDownload, onDelete }: LiveryCardP
return (
<Card className="overflow-hidden hover:border-primary-blue/50 transition-colors">
{/* Livery Preview */}
<Box height={48} backgroundColor="deep-graphite" rounded="lg" mb={4} display="flex" center border borderColor="charcoal-outline">
<Stack height={48} backgroundColor="deep-graphite" rounded="lg" mb={4} display="flex" center border borderColor="charcoal-outline">
<Icon icon={Car} size={16} color="text-gray-600" />
</Box>
</Stack>
{/* Livery Info */}
<Stack gap={3}>

View File

@@ -1,6 +1,6 @@
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Card } from '@/ui/Card';
import { CircularProgress } from '@/ui/CircularProgress';
import { Grid } from '@/ui/Grid';
@@ -8,7 +8,6 @@ import { GridItem } from '@/ui/GridItem';
import { Heading } from '@/ui/Heading';
import { HorizontalBarChart } from '@/ui/HorizontalBarChart';
import { Icon } from '@/ui/Icon';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Activity, BarChart3, Target, TrendingUp } from 'lucide-react';
@@ -27,11 +26,11 @@ interface PerformanceOverviewProps {
export function PerformanceOverview({ stats }: PerformanceOverviewProps) {
return (
<Card>
<Box mb={6}>
<Stack mb={6}>
<Heading level={2} icon={<Icon icon={Activity} size={5} color="#00f2ff" />}>
Performance Overview
</Heading>
</Box>
</Stack>
<Grid cols={12} gap={8}>
<GridItem colSpan={12} lgSpan={6}>
<Stack align="center" gap={4}>
@@ -67,11 +66,11 @@ export function PerformanceOverview({ stats }: PerformanceOverviewProps) {
</GridItem>
<GridItem colSpan={12} lgSpan={6}>
<Box mb={4}>
<Stack mb={4}>
<Heading level={3} icon={<Icon icon={BarChart3} size={4} color="#9ca3af" />}>
Results Breakdown
</Heading>
</Box>
</Stack>
<HorizontalBarChart
data={[
{ label: 'Wins', value: stats.wins, color: 'bg-performance-green' },
@@ -81,9 +80,9 @@ export function PerformanceOverview({ stats }: PerformanceOverviewProps) {
maxValue={stats.totalRaces}
/>
<Box mt={6}>
<Stack mt={6}>
<Grid cols={2} gap={4}>
<Box p={4} style={{ backgroundColor: '#0f1115', borderRadius: '0.75rem', border: '1px solid #262626' }}>
<Stack p={4} style={{ backgroundColor: '#0f1115', borderRadius: '0.75rem', border: '1px solid #262626' }}>
<Stack gap={2}>
<Stack direction="row" align="center" gap={2}>
<Icon icon={TrendingUp} size={4} color="#10b981" />
@@ -91,8 +90,8 @@ export function PerformanceOverview({ stats }: PerformanceOverviewProps) {
</Stack>
<Text size="2xl" weight="bold" color="text-performance-green">P{stats.bestFinish}</Text>
</Stack>
</Box>
<Box p={4} style={{ backgroundColor: '#0f1115', borderRadius: '0.75rem', border: '1px solid #262626' }}>
</Stack>
<Stack p={4} style={{ backgroundColor: '#0f1115', borderRadius: '0.75rem', border: '1px solid #262626' }}>
<Stack gap={2}>
<Stack direction="row" align="center" gap={2}>
<Icon icon={Target} size={4} color="#3b82f6" />
@@ -102,9 +101,9 @@ export function PerformanceOverview({ stats }: PerformanceOverviewProps) {
P{(stats.avgFinish ?? 0).toFixed(1)}
</Text>
</Stack>
</Box>
</Stack>
</Grid>
</Box>
</Stack>
</GridItem>
</Grid>
</Card>

View File

@@ -2,14 +2,13 @@
import type { DriverViewModel } from '@/lib/view-models/DriverViewModel';
import { Badge } from '@/ui/Badge';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Button } from '@/ui/Button';
import { CountryFlag } from '@/ui/CountryFlag';
import { DriverRatingPill } from '@/components/drivers/DriverRatingPill';
import { Heading } from '@/ui/Heading';
import { Image } from '@/ui/Image';
import { PlaceholderImage } from '@/ui/PlaceholderImage';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
interface ProfileHeaderProps {
@@ -32,9 +31,9 @@ export function ProfileHeader({
teamTag,
}: ProfileHeaderProps) {
return (
<Box display="flex" alignItems="start" justifyContent="between">
<Box display="flex" alignItems="start" gap={4}>
<Box
<Stack display="flex" alignItems="start" justifyContent="between">
<Stack display="flex" alignItems="start" gap={4}>
<Stack
w="20"
h="20"
rounded="full"
@@ -55,10 +54,10 @@ export function ProfileHeader({
) : (
<PlaceholderImage size={80} />
)}
</Box>
</Stack>
<Box>
<Box display="flex" alignItems="center" gap={3} mb={2}>
<Stack>
<Stack display="flex" alignItems="center" gap={3} mb={2}>
<Heading level={1}>{driver.name}</Heading>
{driver.country && <CountryFlag countryCode={driver.country} size="lg" />}
{teamTag && (
@@ -66,9 +65,9 @@ export function ProfileHeader({
{teamTag}
</Badge>
)}
</Box>
</Stack>
<Box display="flex" alignItems="center" gap={4}>
<Stack display="flex" alignItems="center" gap={4}>
<Text size="sm" color="text-gray-400">iRacing ID: {driver.iracingId}</Text>
{teamName && (
<Stack direction="row" align="center" gap={2}>
@@ -78,21 +77,21 @@ export function ProfileHeader({
</Text>
</Stack>
)}
</Box>
</Stack>
{(typeof rating === 'number' || typeof rank === 'number') && (
<Box mt={2}>
<Stack mt={2}>
<DriverRatingPill rating={rating ?? null} rank={rank ?? null} />
</Box>
</Stack>
)}
</Box>
</Box>
</Stack>
</Stack>
{isOwnProfile && (
<Button variant="secondary" onClick={onEditClick}>
Edit Profile
</Button>
)}
</Box>
</Stack>
);
}

View File

@@ -2,12 +2,11 @@
import { mediaConfig } from '@/lib/config/mediaConfig';
import { CountryFlagDisplay } from '@/lib/display-objects/CountryFlagDisplay';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Button } from '@/ui/Button';
import { Heading } from '@/ui/Heading';
import { Image } from '@/ui/Image';
import { Link } from '@/ui/Link';
import { Stack } from '@/ui/Stack';
import { Surface } from '@/ui/Surface';
import { Text } from '@/ui/Text';
import { Calendar, Clock, ExternalLink, Globe, Star, Trophy, UserPlus } from 'lucide-react';
@@ -58,9 +57,9 @@ export function ProfileHero({
<Surface variant="muted" rounded="2xl" border padding={6} style={{ background: 'linear-gradient(to bottom right, rgba(38, 38, 38, 0.8), rgba(38, 38, 38, 0.6), #0f1115)', borderColor: '#262626' }}>
<Stack direction="row" align="start" gap={6} wrap>
{/* Avatar */}
<Box style={{ position: 'relative' }}>
<Box style={{ width: '7rem', height: '7rem', borderRadius: '1rem', background: 'linear-gradient(to bottom right, #3b82f6, #9333ea)', padding: '0.25rem', boxShadow: '0 20px 25px -5px rgba(59, 130, 246, 0.2)' }}>
<Box style={{ width: '100%', height: '100%', borderRadius: '0.75rem', overflow: 'hidden', backgroundColor: '#262626' }}>
<Stack style={{ position: 'relative' }}>
<Stack style={{ width: '7rem', height: '7rem', borderRadius: '1rem', background: 'linear-gradient(to bottom right, #3b82f6, #9333ea)', padding: '0.25rem', boxShadow: '0 20px 25px -5px rgba(59, 130, 246, 0.2)' }}>
<Stack style={{ width: '100%', height: '100%', borderRadius: '0.75rem', overflow: 'hidden', backgroundColor: '#262626' }}>
<Image
src={driver.avatarUrl || mediaConfig.avatars.defaultFallback}
alt={driver.name}
@@ -68,12 +67,12 @@ export function ProfileHero({
height={144}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
</Box>
</Box>
</Box>
</Stack>
</Stack>
</Stack>
{/* Driver Info */}
<Box style={{ flex: 1, minWidth: 0 }}>
<Stack style={{ flex: 1, minWidth: 0 }}>
<Stack direction="row" align="center" gap={3} wrap mb={2}>
<Heading level={1}>{driver.name}</Heading>
<Text size="4xl" aria-label={`Country: ${driver.country}`}>
@@ -124,10 +123,10 @@ export function ProfileHero({
<Text size="sm">{timezone}</Text>
</Stack>
</Stack>
</Box>
</Stack>
{/* Action Buttons */}
<Box>
<Stack>
<Button
variant="primary"
onClick={onAddFriend}
@@ -136,18 +135,18 @@ export function ProfileHero({
>
{friendRequestSent ? 'Request Sent' : 'Add Friend'}
</Button>
</Box>
</Stack>
</Stack>
{/* Social Handles */}
{socialHandles.length > 0 && (
<Box mt={6} pt={6} style={{ borderTop: '1px solid rgba(38, 38, 38, 0.5)' }}>
<Stack mt={6} pt={6} style={{ borderTop: '1px solid rgba(38, 38, 38, 0.5)' }}>
<Stack direction="row" align="center" gap={2} wrap>
<Text size="sm" color="text-gray-500" style={{ marginRight: '0.5rem' }}>Connect:</Text>
{socialHandles.map((social) => {
const Icon = getSocialIcon(social.platform);
return (
<Box key={social.platform}>
<Stack key={social.platform}>
<Link
href={social.url}
target="_blank"
@@ -160,11 +159,11 @@ export function ProfileHero({
<ExternalLink style={{ width: '0.75rem', height: '0.75rem', opacity: 0.5 }} />
</Surface>
</Link>
</Box>
</Stack>
);
})}
</Stack>
</Box>
</Stack>
)}
</Surface>
);

View File

@@ -3,9 +3,8 @@
import { useState, useEffect } from 'react';
import { Card } from '@/ui/Card';
import { Button } from '@/ui/Button';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { LoadingWrapper } from '@/components/shared/state/LoadingWrapper';
import { EmptyState } from '@/components/shared/state/EmptyState';
import { Pagination } from '@/ui/Pagination';
@@ -43,11 +42,11 @@ export function ProfileRaceHistory({ driverId }: RaceHistoryProps) {
if (loading) {
return (
<Stack gap={4}>
<Box display="flex" alignItems="center" gap={2}>
<Stack display="flex" alignItems="center" gap={2}>
{[1, 2, 3].map(i => (
<Box key={i} h="9" w="24" bg="bg-iron-gray" rounded="md" animate="pulse" />
<Stack key={i} h="9" w="24" bg="bg-iron-gray" rounded="md" animate="pulse" />
))}
</Box>
</Stack>
<Card>
<LoadingWrapper variant="skeleton" skeletonCount={3} />
</Card>
@@ -67,7 +66,7 @@ export function ProfileRaceHistory({ driverId }: RaceHistoryProps) {
return (
<Stack gap={4}>
<Box display="flex" alignItems="center" gap={2}>
<Stack display="flex" alignItems="center" gap={2}>
<Button
variant={filter === 'all' ? 'primary' : 'secondary'}
onClick={() => { setFilter('all'); setPage(1); }}
@@ -89,13 +88,13 @@ export function ProfileRaceHistory({ driverId }: RaceHistoryProps) {
>
Podiums
</Button>
</Box>
</Stack>
<Card>
{/* No results until API provides driver results */}
<Box minHeight="100px" display="flex" center>
<Stack minHeight="100px" display="flex" center>
<Text color="text-gray-500">No results found for the selected filter.</Text>
</Box>
</Stack>
<Pagination
currentPage={page}

View File

@@ -5,9 +5,8 @@ import type { DriverProfileDriverSummaryViewModel } from '@/lib/view-models/Driv
import { Card } from '@/ui/Card';
import { Button } from '@/ui/Button';
import { Input } from '@/ui/Input';
import { Box } from '@/ui/Box';
import { Heading } from '@/ui/Heading';
import { Stack } from '@/ui/Stack';
import { Heading } from '@/ui/Heading';
import { Select } from '@/ui/Select';
import { Toggle } from '@/ui/Toggle';
import { TextArea } from '@/ui/TextArea';
@@ -142,14 +141,14 @@ export function ProfileSettings({ driver, onSave }: ProfileSettingsProps) {
</Stack>
</Card>
<Box display="flex" gap={3}>
<Stack display="flex" gap={3}>
<Button variant="primary" onClick={handleSave} fullWidth>
Save Changes
</Button>
<Button variant="secondary" fullWidth>
Cancel
</Button>
</Box>
</Stack>
</Stack>
);
}

View File

@@ -3,10 +3,9 @@
import { useDriverProfile } from "@/hooks/driver/useDriverProfile";
import { useMemo } from 'react';
import { Card } from '@/ui/Card';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
import { Stack } from '@/ui/Stack';
import { StatCard } from '@/ui/StatCard';
import { RankBadge } from '@/components/leaderboards/RankBadge';
@@ -86,18 +85,18 @@ export function ProfileStats({ driverId, stats }: ProfileStatsProps) {
<Heading level={2} mb={6}>Rankings Dashboard</Heading>
<Stack gap={4}>
<Box p={4} rounded="lg" bg="bg-deep-graphite" border borderColor="border-charcoal-outline">
<Box display="flex" alignItems="center" justifyContent="between" mb={3}>
<Box display="flex" alignItems="center" gap={3}>
<Stack p={4} rounded="lg" bg="bg-deep-graphite" border borderColor="border-charcoal-outline">
<Stack display="flex" alignItems="center" justifyContent="between" mb={3}>
<Stack display="flex" alignItems="center" gap={3}>
<RankBadge rank={driverStats.overallRank ?? 0} size="lg" />
<Box>
<Stack>
<Text color="text-white" weight="medium" size="lg" block>Overall Ranking</Text>
<Text size="sm" color="text-gray-400" block>
{driverStats.overallRank ?? 0} of {totalDrivers} drivers
</Text>
</Box>
</Box>
<Box textAlign="right">
</Stack>
</Stack>
<Stack textAlign="right">
<Text
size="sm"
weight="medium"
@@ -107,36 +106,36 @@ export function ProfileStats({ driverId, stats }: ProfileStatsProps) {
{getPercentileLabel(driverStats.percentile ?? 0)}
</Text>
<Text size="xs" color="text-gray-500" block>Global Percentile</Text>
</Box>
</Box>
</Stack>
</Stack>
<Box display="grid" gridCols={3} gap={4} pt={3} borderTop borderColor="border-charcoal-outline">
<Box textAlign="center">
<Stack display="grid" gridCols={3} gap={4} pt={3} borderTop borderColor="border-charcoal-outline">
<Stack textAlign="center">
<Text size="2xl" weight="bold" color="text-primary-blue" block>
{driverStats.rating ?? 0}
</Text>
<Text size="xs" color="text-gray-400" block>Rating</Text>
</Box>
<Box textAlign="center">
</Stack>
<Stack textAlign="center">
<Text size="lg" weight="bold" color="text-green-400" block>
{getTrendIndicator(5)} {winRate}%
</Text>
<Text size="xs" color="text-gray-400" block>Win Rate</Text>
</Box>
<Box textAlign="center">
</Stack>
<Stack textAlign="center">
<Text size="lg" weight="bold" color="text-warning-amber" block>
{getTrendIndicator(2)} {podiumRate}%
</Text>
<Text size="xs" color="text-gray-400" block>Podium Rate</Text>
</Box>
</Box>
</Box>
</Stack>
</Stack>
</Stack>
</Stack>
</Card>
)}
{defaultStats ? (
<Box display="grid" responsiveGridCols={{ base: 2, md: 4 }} gap={4}>
<Stack display="grid" responsiveGridCols={{ base: 2, md: 4 }} gap={4}>
<StatCard label="Total Races" value={defaultStats.totalRaces} variant="blue" />
<StatCard label="Wins" value={defaultStats.wins} variant="green" />
<StatCard label="Podiums" value={defaultStats.podiums} variant="orange" />
@@ -145,7 +144,7 @@ export function ProfileStats({ driverId, stats }: ProfileStatsProps) {
<StatCard label="Completion" value={`${defaultStats.completionRate.toFixed(1)}%`} variant="green" />
<StatCard label="Win Rate" value={`${winRate}%`} variant="blue" />
<StatCard label="Podium Rate" value={`${podiumRate}%`} variant="orange" />
</Box>
</Stack>
) : (
<Card>
<Heading level={3} mb={2}>Career Statistics</Heading>
@@ -156,10 +155,10 @@ export function ProfileStats({ driverId, stats }: ProfileStatsProps) {
)}
<Card bg="bg-charcoal-200/50" borderColor="border-primary-blue/30">
<Box display="flex" alignItems="center" gap={3} mb={3}>
<Stack display="flex" alignItems="center" gap={3} mb={3}>
<Text size="2xl">📊</Text>
<Heading level={3}>Performance by Car Class</Heading>
</Box>
</Stack>
<Text color="text-gray-400" size="sm" block>
Detailed per-car and per-class performance breakdowns will be available in a future
version once more race history data is tracked.
@@ -167,10 +166,10 @@ export function ProfileStats({ driverId, stats }: ProfileStatsProps) {
</Card>
<Card bg="bg-charcoal-200/50" borderColor="border-primary-blue/30">
<Box display="flex" alignItems="center" gap={3} mb={3}>
<Stack display="flex" alignItems="center" gap={3} mb={3}>
<Text size="2xl">📈</Text>
<Heading level={3}>Coming Soon</Heading>
</Box>
</Stack>
<Text color="text-gray-400" size="sm" block>
Performance trends, track-specific stats, head-to-head comparisons vs friends, and
league member comparisons will be available in production.

View File

@@ -1,11 +1,9 @@
import { Box } from '@/ui/Box';
import { Card } from '@/ui/Card';
import { Stack } from '@/ui/Stack';
import { Card , Card as Surface } from '@/ui/Card';
import { Heading } from '@/ui/Heading';
import { Icon } from '@/ui/Icon';
import { Stack } from '@/ui/Stack';
import { Surface } from '@/ui/Surface';
import { Text } from '@/ui/Text';
import { Flag, UserPlus, Users } from 'lucide-react';
@@ -28,31 +26,31 @@ export function RacingProfile({
}: RacingProfileProps) {
return (
<Card>
<Box mb={4}>
<Stack mb={4}>
<Heading level={2} icon={<Icon icon={Flag} size={5} color="#00f2ff" />}>
Racing Profile
</Heading>
</Box>
</Stack>
<Stack gap={4}>
<Box>
<Stack>
<Text size="xs" color="text-gray-500" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }} block>Racing Style</Text>
<Text color="text-white" weight="medium">{racingStyle}</Text>
</Box>
<Box>
</Stack>
<Stack>
<Text size="xs" color="text-gray-500" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }} block>Favorite Track</Text>
<Text color="text-white" weight="medium">{favoriteTrack}</Text>
</Box>
<Box>
</Stack>
<Stack>
<Text size="xs" color="text-gray-500" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }} block>Favorite Car</Text>
<Text color="text-white" weight="medium">{favoriteCar}</Text>
</Box>
<Box>
</Stack>
<Stack>
<Text size="xs" color="text-gray-500" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }} block>Available</Text>
<Text color="text-white" weight="medium">{availableHours}</Text>
</Box>
</Stack>
{/* Status badges */}
<Box mt={4} pt={4} style={{ borderTop: '1px solid rgba(38, 38, 38, 0.5)' }}>
<Stack mt={4} pt={4} style={{ borderTop: '1px solid rgba(38, 38, 38, 0.5)' }}>
<Stack gap={2}>
{lookingForTeam && (
<Surface variant="muted" rounded="lg" padding={2} style={{ backgroundColor: 'rgba(16, 185, 129, 0.1)', border: '1px solid rgba(16, 185, 129, 0.3)' }}>
@@ -71,7 +69,7 @@ export function RacingProfile({
</Surface>
)}
</Stack>
</Box>
</Stack>
</Stack>
</Card>
);

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { Badge } from '@/ui/Badge';
interface RatingBadgeProps {
rating: number;
@@ -8,22 +9,29 @@ interface RatingBadgeProps {
export function RatingBadge({ rating, size = 'md', className = '' }: RatingBadgeProps) {
const getColor = (val: number) => {
if (val >= 2500) return 'text-yellow-400 bg-yellow-400/10 border-yellow-400/20';
if (val >= 2000) return 'text-purple-400 bg-purple-400/10 border-purple-400/20';
if (val >= 1500) return 'text-primary-blue bg-primary-blue/10 border-primary-blue/20';
if (val >= 1000) return 'text-performance-green bg-performance-green/10 border-performance-green/20';
return 'text-gray-400 bg-gray-400/10 border-gray-400/20';
if (val >= 2500) return { variant: 'warning' as const };
if (val >= 2000) return { color: 'text-purple-400', bg: 'bg-purple-400/10', borderColor: 'border-purple-400/20' };
if (val >= 1500) return { variant: 'primary' as const };
if (val >= 1000) return { variant: 'success' as const };
return { variant: 'default' as const };
};
const sizeMap = {
sm: 'px-1.5 py-0.5 text-[10px]',
md: 'px-2 py-1 text-xs',
lg: 'px-3 py-1.5 text-sm',
const sizeMap: Record<string, 'xs' | 'sm' | 'md'> = {
sm: 'xs',
md: 'sm',
lg: 'md',
};
const config = getColor(rating);
return (
<div className={`inline-flex items-center justify-center font-mono font-bold rounded border ${sizeMap[size]} ${getColor(rating)} ${className}`}>
<Badge
{...config}
size={sizeMap[size]}
className={`font-mono ${className}`}
rounded="sm"
>
{rating.toLocaleString()}
</div>
</Badge>
);
}

View File

@@ -1,11 +1,10 @@
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Card } from '@/ui/Card';
import { Heading } from '@/ui/Heading';
import { RatingComponent } from '@/components/drivers/RatingComponent';
import { RatingHistoryItem } from '@/components/drivers/RatingHistoryItem';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
interface RatingBreakdownProps {
@@ -94,23 +93,23 @@ export function RatingBreakdown({
</Card>
<Card borderColor="border-primary-blue/30" bg="bg-charcoal-outline/20">
<Box display="flex" alignItems="center" gap={3} mb={3}>
<Stack display="flex" alignItems="center" gap={3} mb={3}>
<Text size="2xl">📈</Text>
<Heading level={3}>Rating Insights</Heading>
</Box>
</Stack>
<Stack as="ul" gap={2}>
<Box as="li" display="flex" alignItems="start" gap={2}>
<Stack as="li" display="flex" alignItems="start" gap={2}>
<Text color="text-performance-green" mt={0.5}></Text>
<Text size="sm" color="text-gray-400">Strong safety rating - keep up the clean racing!</Text>
</Box>
<Box as="li" display="flex" alignItems="start" gap={2}>
</Stack>
<Stack as="li" display="flex" alignItems="start" gap={2}>
<Text color="text-warning-amber" mt={0.5}></Text>
<Text size="sm" color="text-gray-400">Skill rating improving - competitive against higher-rated drivers</Text>
</Box>
<Box as="li" display="flex" alignItems="start" gap={2}>
</Stack>
<Stack as="li" display="flex" alignItems="start" gap={2}>
<Text color="text-primary-blue" mt={0.5}>i</Text>
<Text size="sm" color="text-gray-400">Complete more races to stabilize your ratings</Text>
</Box>
</Stack>
</Stack>
</Card>
</Stack>

View File

@@ -1,8 +1,7 @@
import { Box } from '@/ui/Box';
import { ProgressBar } from '@/ui/ProgressBar';
import { Stack } from '@/ui/Stack';
import { ProgressBar } from '@/ui/ProgressBar';
import { Text } from '@/ui/Text';
interface RatingComponentProps {
@@ -27,13 +26,13 @@ export function RatingComponent({
const percentage = (value / maxValue) * 100;
return (
<Box>
<Box display="flex" alignItems="center" justifyContent="between" mb={2}>
<Stack>
<Stack display="flex" alignItems="center" justifyContent="between" mb={2}>
<Text weight="medium" color="text-white">{label}</Text>
<Text size="2xl" weight="bold" color={color}>
{value}{suffix}
</Text>
</Box>
</Stack>
<ProgressBar value={percentage} max={100} color={color} mb={3} />
@@ -41,12 +40,12 @@ export function RatingComponent({
<Stack gap={1}>
{breakdown.map((item, index) => (
<Box key={index} display="flex" alignItems="center" justifyContent="between">
<Stack key={index} display="flex" alignItems="center" justifyContent="between">
<Text size="xs" color="text-gray-500">{item.label}</Text>
<Text size="xs" color="text-gray-400">{item.percentage}%</Text>
</Box>
</Stack>
))}
</Stack>
</Box>
</Stack>
);
}

View File

@@ -1,6 +1,5 @@
import React from 'react';
import { LucideIcon, ChevronRight, UserPlus } from 'lucide-react';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
@@ -35,9 +34,9 @@ export function SkillLevelHeader({
showToggle,
}: SkillLevelHeaderProps) {
return (
<Box display="flex" alignItems="center" justifyContent="between" mb={4}>
<Stack display="flex" alignItems="center" justifyContent="between" mb={4}>
<Stack direction="row" align="center" gap={3}>
<Box
<Stack
display="flex"
center
width="11"
@@ -46,8 +45,8 @@ export function SkillLevelHeader({
className={`${bgColor} border ${borderColor}`}
>
<Icon icon={icon} size={5} className={color} />
</Box>
<Box>
</Stack>
<Stack>
<Stack direction="row" align="center" gap={2}>
<Heading level={2}>{label}</Heading>
<Badge variant="default">
@@ -60,11 +59,11 @@ export function SkillLevelHeader({
)}
</Stack>
<Text size="sm" color="text-gray-500">{description}</Text>
</Box>
</Stack>
</Stack>
{showToggle && (
<Box
<Stack
as="button"
type="button"
onClick={onToggle}
@@ -78,8 +77,8 @@ export function SkillLevelHeader({
>
<Text size="sm">{isExpanded ? 'Show less' : `View all ${teamCount}`}</Text>
<Icon icon={ChevronRight} size={4} className={`transition-transform ${isExpanded ? 'rotate-90' : ''}`} />
</Box>
</Stack>
)}
</Box>
</Stack>
);
}