website refactor
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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'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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user