diff --git a/apps/website/components/drivers/DriverCard.tsx b/apps/website/components/drivers/DriverCard.tsx index 538a027bd..606120996 100644 --- a/apps/website/components/drivers/DriverCard.tsx +++ b/apps/website/components/drivers/DriverCard.tsx @@ -1,70 +1,66 @@ -import { DriverIdentity } from '@/ui/DriverIdentity'; -import { DriverStats } from '@/components/drivers/DriverStats'; -import { RankBadge } from '@/components/leaderboards/RankBadge'; -import { routes } from '@/lib/routing/RouteConfig'; -import { DriverViewModel } from '@/lib/view-models/DriverViewModel'; -import { Card } from '@/ui/Card'; -import { Stack } from '@/ui/Stack'; +'use client'; -export interface DriverCardProps { - id: string; - name: string; - rating: number; - skillLevel: 'beginner' | 'intermediate' | 'advanced' | 'pro'; - nationality: string; - racesCompleted: number; - wins: number; - podiums: number; - rank: number; - onClick?: () => void; +import { DriverIdentity } from '@/ui/DriverIdentity'; +import { ProfileCard } from '@/ui/ProfileCard'; +import { StatGrid } from '@/ui/StatGrid'; +import { Badge } from '@/ui/Badge'; +import { Flag, Trophy, Medal } from 'lucide-react'; + +interface DriverCardProps { + driver: { + id: string; + name: string; + avatarUrl?: string; + rating: number; + ratingLabel: string; + nationality: string; + racesCompleted: number; + wins: number; + podiums: number; + rank: number; + }; + onClick: (id: string) => void; } -export function DriverCard(props: DriverCardProps) { - const { - id, - name, - rating, - nationality, - racesCompleted, - wins, - podiums, - rank, - onClick, - } = props; - - // Create a proper DriverViewModel instance - const driverViewModel = new DriverViewModel({ - id, - name, - avatarUrl: null, - }); - - const winRate = racesCompleted > 0 ? ((wins / racesCompleted) * 100).toFixed(0) : '0'; +export function DriverCard({ driver, onClick }: DriverCardProps) { + const stats = [ + { label: 'Races', value: driver.racesCompleted, intent: 'low', icon: Flag }, + { label: 'Wins', value: driver.wins, intent: 'primary', icon: Trophy }, + { label: 'Podiums', value: driver.podiums, intent: 'warning', icon: Medal }, + ]; return ( - - - - - - - - - onClick(driver.id)} + variant="muted" + identity={ + - - + } + actions={ + + {driver.ratingLabel} + + } + stats={ + ({ + label: s.label, + value: s.value, + intent: s.intent as any, + icon: s.icon + }))} + columns={3} + variant="box" + /> + } + /> ); } diff --git a/apps/website/components/drivers/DriverSearchBar.tsx b/apps/website/components/drivers/DriverSearchBar.tsx index bc9361471..c15543fc5 100644 --- a/apps/website/components/drivers/DriverSearchBar.tsx +++ b/apps/website/components/drivers/DriverSearchBar.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Group } from '@/ui/Group'; +import { Box } from '@/ui/Box'; import { Input } from '@/ui/Input'; import { Search } from 'lucide-react'; @@ -11,13 +11,14 @@ interface DriverSearchBarProps { export function DriverSearchBar({ query, onChange }: DriverSearchBarProps) { return ( - + onChange(e.target.value)} - placeholder="Search drivers by name or nationality..." - icon={} + placeholder="Search competitors..." + icon={} + size="sm" /> - + ); } diff --git a/apps/website/components/drivers/DriverStatsHeader.tsx b/apps/website/components/drivers/DriverStatsHeader.tsx new file mode 100644 index 000000000..db082e2e1 --- /dev/null +++ b/apps/website/components/drivers/DriverStatsHeader.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { StatGrid } from '@/ui/StatGrid'; +import { Users, Trophy, Activity } from 'lucide-react'; + +interface DriverStatsHeaderProps { + totalDrivers: string; + activeDrivers: string; + totalRaces: string; +} + +export function DriverStatsHeader({ totalDrivers, activeDrivers, totalRaces }: DriverStatsHeaderProps) { + return ( + + ); +} diff --git a/apps/website/components/drivers/DriverTable.tsx b/apps/website/components/drivers/DriverTable.tsx index 1bd4f0bce..0626c7ced 100644 --- a/apps/website/components/drivers/DriverTable.tsx +++ b/apps/website/components/drivers/DriverTable.tsx @@ -2,11 +2,12 @@ import { Heading } from '@/ui/Heading'; import { Text } from '@/ui/Text'; -import { Group } from '@/ui/Group'; +import { Box } from '@/ui/Box'; import { Table, TableHead, TableBody, TableRow, TableHeaderCell } from '@/ui/Table'; import { TrendingUp } from 'lucide-react'; -import { Card } from '@/ui/Card'; import { Icon } from '@/ui/Icon'; +import { Surface } from '@/ui/Surface'; +import { Stack } from '@/ui/Stack'; import React from 'react'; interface DriverTableProps { @@ -15,31 +16,31 @@ interface DriverTableProps { export function DriverTable({ children }: DriverTableProps) { return ( - - - + + + - - - Driver Rankings - Top performers by skill rating - - + + + Driver Rankings + Performance metrics based on sanctioned race results + + - # - Driver - Nationality - Rating - Wins + Rank + Competitor + Nationality + Skill Rating + Victories {children}
-
+ ); } diff --git a/apps/website/components/drivers/DriverTableRow.tsx b/apps/website/components/drivers/DriverTableRow.tsx index b7d96e1f2..965a6c259 100644 --- a/apps/website/components/drivers/DriverTableRow.tsx +++ b/apps/website/components/drivers/DriverTableRow.tsx @@ -2,9 +2,10 @@ import { RatingBadge } from '@/components/drivers/RatingBadge'; import { Text } from '@/ui/Text'; -import { Group } from '@/ui/Group'; +import { Box } from '@/ui/Box'; import { TableRow, TableCell } from '@/ui/Table'; import { Avatar } from '@/ui/Avatar'; +import { CountryFlag } from '@/ui/CountryFlag'; interface DriverTableRowProps { rank: number; @@ -28,19 +29,19 @@ export function DriverTableRow({ onClick, }: DriverTableRowProps) { return ( - + - {rank} + {rank.toString().padStart(2, '0')} - + {name} - + - {nationality} + + + {nationality} + - + + {rating} + + - + {winsLabel} diff --git a/apps/website/components/drivers/DriversDirectoryHeader.tsx b/apps/website/components/drivers/DriversDirectoryHeader.tsx index 21f8debf2..8a8f32ebd 100644 --- a/apps/website/components/drivers/DriversDirectoryHeader.tsx +++ b/apps/website/components/drivers/DriversDirectoryHeader.tsx @@ -2,19 +2,15 @@ import { Button } from '@/ui/Button'; import { Heading } from '@/ui/Heading'; -import { Group } from '@/ui/Group'; import { Text } from '@/ui/Text'; import { Section } from '@/ui/Section'; import { Container } from '@/ui/Container'; -import { Card } from '@/ui/Card'; import { Icon } from '@/ui/Icon'; -import { Trophy, Users } from 'lucide-react'; - -interface DriverStat { - label: string; - value: string | number; - intent?: 'primary' | 'success' | 'warning' | 'telemetry'; -} +import { MetricCard } from '@/ui/MetricCard'; +import { Trophy, Users, Zap, Flag } from 'lucide-react'; +import { Grid } from '@/ui/Grid'; +import { Box } from '@/ui/Box'; +import { Stack } from '@/ui/Stack'; interface DriversDirectoryHeaderProps { totalDriversLabel: string; @@ -31,53 +27,68 @@ export function DriversDirectoryHeader({ totalRacesLabel, onViewLeaderboard, }: DriversDirectoryHeaderProps) { - const stats: DriverStat[] = [ - { label: 'drivers', value: totalDriversLabel, intent: 'primary' }, - { label: 'active', value: activeDriversLabel, intent: 'success' }, - { label: 'total wins', value: totalWinsLabel, intent: 'warning' }, - { label: 'races', value: totalRacesLabel, intent: 'telemetry' }, - ]; - return ( -
+
- - - - + + + + - - Drivers - - - - Meet the racers who make every lap count. From rookies to champions, track their journey and see who's dominating the grid. - + + Driver Directory + + + + The official registry of GridPilot competitors. Tracking performance, + reliability, and championship progress across all sanctioned events. + + - - {stats.map((stat, index) => ( - - - {stat.value} {stat.label} - - - ))} - - - - - - See full driver rankings - - - + + + + + + + + + + + + + + + + +
); diff --git a/apps/website/templates/DriversTemplate.tsx b/apps/website/templates/DriversTemplate.tsx index 2590e584b..2bde53b83 100644 --- a/apps/website/templates/DriversTemplate.tsx +++ b/apps/website/templates/DriversTemplate.tsx @@ -1,18 +1,16 @@ 'use client'; -import { DriversDirectoryHeader } from '@/components/drivers/DriversDirectoryHeader'; -import { DriverSearchBar } from '@/components/drivers/DriverSearchBar'; -import { DriverTable } from '@/components/drivers/DriverTable'; -import { DriverTableRow } from '@/components/drivers/DriverTableRow'; +import { DriversViewData } from '@/lib/types/view-data/DriversViewData'; +import { DriverCard } from '@/components/drivers/DriverCard'; +import { DriverStatsHeader } from '@/components/drivers/DriverStatsHeader'; +import { PageHeader } from '@/ui/PageHeader'; +import { Input } from '@/ui/Input'; +import { Box } from '@/ui/Box'; +import { Search, Users } from 'lucide-react'; import { EmptyState } from '@/ui/EmptyState'; -import type { DriversViewData } from '@/lib/types/view-data/DriversViewData'; -import { Container } from '@/ui/Container'; -import { Group } from '@/ui/Group'; -import { Search } from 'lucide-react'; -import React from 'react'; interface DriversTemplateProps { - viewData: DriversViewData | null; + viewData: DriversViewData; searchQuery: string; onSearchChange: (query: string) => void; filteredDrivers: DriversViewData['drivers']; @@ -20,7 +18,7 @@ interface DriversTemplateProps { onViewLeaderboard: () => void; } -export function DriversTemplate({ +export function DriversTemplate({ viewData, searchQuery, onSearchChange, @@ -29,47 +27,59 @@ export function DriversTemplate({ onViewLeaderboard }: DriversTemplateProps) { return ( - - - + + + Leaderboard + + } /> + - + + + - - {filteredDrivers.map((driver, index) => ( - + onSearchChange(e.target.value)} + icon={Search} + variant="search" + /> + + + {filteredDrivers.length > 0 ? ( + + {filteredDrivers.map(driver => ( + onDriverClick(driver.id)} + driver={driver} + onClick={onDriverClick} /> ))} - - - {filteredDrivers.length === 0 && ( - onSearchChange(''), - variant: 'secondary' - }} - /> - )} - - + + ) : ( + + )} + ); } diff --git a/apps/website/ui/ProfileCard.tsx b/apps/website/ui/ProfileCard.tsx new file mode 100644 index 000000000..a84d0aeff --- /dev/null +++ b/apps/website/ui/ProfileCard.tsx @@ -0,0 +1,38 @@ +import { ReactNode } from 'react'; +import { Card } from './Card'; +import { Box } from './Box'; + +export interface ProfileCardProps { + identity: ReactNode; + stats?: ReactNode; + actions?: ReactNode; + variant?: 'default' | 'muted' | 'outline' | 'glass'; + onClick?: () => void; +} + +export const ProfileCard = ({ identity, stats, actions, variant = 'default', onClick }: ProfileCardProps) => { + return ( + + + + {identity} + + {actions && ( + + {actions} + + )} + + {stats && ( + + {stats} + + )} + + ); +};