'use client'; import { useState, useEffect } from 'react'; import { useRouter, useParams } from 'next/navigation'; import Link from 'next/link'; import Button from '@/components/ui/Button'; import Card from '@/components/ui/Card'; import Heading from '@/components/ui/Heading'; import Breadcrumbs from '@/components/layout/Breadcrumbs'; import type { Race } from '@gridpilot/racing/domain/entities/Race'; import type { League } from '@gridpilot/racing/domain/entities/League'; import type { Driver } from '@gridpilot/racing/domain/entities/Driver'; import { getRaceRepository, getLeagueRepository, getDriverRepository, getGetRaceRegistrationsQuery, getIsDriverRegisteredForRaceQuery, getRegisterForRaceUseCase, getWithdrawFromRaceUseCase, getTrackRepository, getCarRepository, getGetRaceWithSOFQuery, } from '@/lib/di-container'; import { getMembership } from '@/lib/leagueMembership'; import { useEffectiveDriverId } from '@/lib/currentDriver'; import { Calendar, Clock, MapPin, Car, Trophy, Users, Zap, PlayCircle, CheckCircle2, XCircle, ChevronRight, Flag, Timer, UserPlus, UserMinus, AlertTriangle, ArrowRight, ArrowLeft, ExternalLink, Award, } from 'lucide-react'; import { getDriverStats, getAllDriverRankings } from '@/lib/di-container'; export default function RaceDetailPage() { const router = useRouter(); const params = useParams(); const raceId = params.id as string; const [race, setRace] = useState(null); const [league, setLeague] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [cancelling, setCancelling] = useState(false); const [registering, setRegistering] = useState(false); const [entryList, setEntryList] = useState([]); const [isUserRegistered, setIsUserRegistered] = useState(false); const [canRegister, setCanRegister] = useState(false); const [raceSOF, setRaceSOF] = useState(null); const currentDriverId = useEffectiveDriverId(); const loadRaceData = async () => { try { const raceRepo = getRaceRepository(); const leagueRepo = getLeagueRepository(); const raceWithSOFQuery = getGetRaceWithSOFQuery(); const raceData = await raceRepo.findById(raceId); if (!raceData) { setError('Race not found'); setLoading(false); return; } setRace(raceData); // Load race with SOF from application query const raceWithSOF = await raceWithSOFQuery.execute({ raceId }); if (raceWithSOF) { setRaceSOF(raceWithSOF.strengthOfField); } // Load league data const leagueData = await leagueRepo.findById(raceData.leagueId); setLeague(leagueData); // Load entry list await loadEntryList(raceData.id, raceData.leagueId); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load race'); } finally { setLoading(false); } }; const loadEntryList = async (raceId: string, leagueId: string) => { try { const driverRepo = getDriverRepository(); const raceRegistrationsQuery = getGetRaceRegistrationsQuery(); const registeredDriverIds = await raceRegistrationsQuery.execute({ raceId }); const drivers = await Promise.all( registeredDriverIds.map((id: string) => driverRepo.findById(id)), ); const validDrivers = drivers.filter((d: Driver | null): d is Driver => d !== null); setEntryList(validDrivers); const isRegisteredQuery = getIsDriverRegisteredForRaceQuery(); const userIsRegistered = await isRegisteredQuery.execute({ raceId, driverId: currentDriverId, }); setIsUserRegistered(userIsRegistered); const membership = getMembership(leagueId, currentDriverId); const isUpcoming = race?.status === 'scheduled'; setCanRegister(!!membership && membership.status === 'active' && !!isUpcoming); } catch (err) { console.error('Failed to load entry list:', err); } }; useEffect(() => { loadRaceData(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [raceId]); const handleCancelRace = async () => { if (!race || race.status !== 'scheduled') return; const confirmed = window.confirm( 'Are you sure you want to cancel this race? This action cannot be undone.' ); if (!confirmed) return; setCancelling(true); try { const raceRepo = getRaceRepository(); const cancelledRace = race.cancel(); await raceRepo.update(cancelledRace); setRace(cancelledRace); } catch (err) { alert(err instanceof Error ? err.message : 'Failed to cancel race'); } finally { setCancelling(false); } }; const handleRegister = async () => { if (!race || !league) return; const confirmed = window.confirm( `Register for ${race.track}?\n\nYou'll be added to the entry list for this race.` ); if (!confirmed) return; setRegistering(true); try { const useCase = getRegisterForRaceUseCase(); await useCase.execute({ raceId: race.id, leagueId: league.id, driverId: currentDriverId, }); await loadEntryList(race.id, league.id); } catch (err) { alert(err instanceof Error ? err.message : 'Failed to register for race'); } finally { setRegistering(false); } }; const handleWithdraw = async () => { if (!race || !league) return; const confirmed = window.confirm( 'Withdraw from this race?\n\nYou can register again later if you change your mind.' ); if (!confirmed) return; setRegistering(true); try { const useCase = getWithdrawFromRaceUseCase(); await useCase.execute({ raceId: race.id, driverId: currentDriverId, }); await loadEntryList(race.id, league.id); } catch (err) { alert(err instanceof Error ? err.message : 'Failed to withdraw from race'); } finally { setRegistering(false); } }; const formatDate = (date: Date) => { return new Date(date).toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric', }); }; const formatTime = (date: Date) => { return new Date(date).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', timeZoneName: 'short', }); }; const getTimeUntil = (date: Date) => { const now = new Date(); const target = new Date(date); const diffMs = target.getTime() - now.getTime(); if (diffMs < 0) return null; const days = Math.floor(diffMs / (1000 * 60 * 60 * 24)); const hours = Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); if (days > 0) return `${days}d ${hours}h`; if (hours > 0) return `${hours}h ${minutes}m`; return `${minutes}m`; }; const statusConfig = { scheduled: { icon: Clock, color: 'text-primary-blue', bg: 'bg-primary-blue/10', border: 'border-primary-blue/30', label: 'Scheduled', description: 'This race is scheduled and waiting to start', }, running: { icon: PlayCircle, color: 'text-performance-green', bg: 'bg-performance-green/10', border: 'border-performance-green/30', label: 'LIVE NOW', description: 'This race is currently in progress', }, completed: { icon: CheckCircle2, color: 'text-gray-400', bg: 'bg-gray-500/10', border: 'border-gray-500/30', label: 'Completed', description: 'This race has finished', }, cancelled: { icon: XCircle, color: 'text-warning-amber', bg: 'bg-warning-amber/10', border: 'border-warning-amber/30', label: 'Cancelled', description: 'This race has been cancelled', }, }; if (loading) { return (
); } if (error || !race) { return (

{error || 'Race not found'}

The race you're looking for doesn't exist or has been removed.

); } const config = statusConfig[race.status]; const StatusIcon = config.icon; const timeUntil = race.status === 'scheduled' ? getTimeUntil(race.scheduledAt) : null; const breadcrumbItems = [ { label: 'Races', href: '/races' }, ...(league ? [{ label: league.name, href: `/leagues/${league.id}` }] : []), { label: race.track }, ]; // Build driver rankings for entry list display const getDriverRank = (driverId: string): { rating: number | null; rank: number | null } => { const stats = getDriverStats(driverId); if (!stats) return { rating: null, rank: null }; const allRankings = getAllDriverRankings(); let rank = stats.overallRank; if (!rank || rank <= 0) { const indexInGlobal = allRankings.findIndex(s => s.driverId === driverId); if (indexInGlobal !== -1) { rank = indexInGlobal + 1; } } return { rating: stats.rating, rank }; }; return (
{/* Navigation Row: Breadcrumbs left, Back button right */}
{/* Hero Header */}
{/* Live indicator */} {race.status === 'running' && (
)}
{/* Status Badge */}
{race.status === 'running' && ( )} {config.label}
{timeUntil && ( Starts in {timeUntil} )}
{/* Title */} {race.track} {/* Meta */}
{formatDate(race.scheduledAt)} {formatTime(race.scheduledAt)} {race.car} {raceSOF && ( SOF {raceSOF} )}
{/* League Banner */} {league && (

Part of

{league.name}

View League
)}
{/* Main Content */}
{/* Race Details */}

Race Details

Track

{race.track}

Car

{race.car}

Session Type

{race.sessionType}

Status

{config.label}

Strength of Field

{raceSOF ?? '—'}

{race.registeredCount !== undefined && (

Registered

{race.registeredCount} {race.maxParticipants && ` / ${race.maxParticipants}`}

)}
{/* Entry List */}

Entry List

{entryList.length} driver{entryList.length !== 1 ? 's' : ''}
{entryList.length === 0 ? (

No drivers registered yet

Be the first to sign up!

) : (
{entryList.map((driver, index) => { const driverRankInfo = getDriverRank(driver.id); return (
router.push(`/drivers/${driver.id}`)} className="flex items-center gap-2 p-2 bg-deep-graphite rounded-lg hover:bg-charcoal-outline/50 cursor-pointer transition-colors" > #{index + 1}
{driver.name.charAt(0)}

{driver.name}

{driverRankInfo.rating && ( {driverRankInfo.rating} )} {driver.id === currentDriverId && ( You )}
); })}
)}
{/* Sidebar - Actions */}
{/* Quick Actions Card */}

Actions

{/* Registration Actions */} {race.status === 'scheduled' && canRegister && !isUserRegistered && ( )} {race.status === 'scheduled' && isUserRegistered && ( <>
You're Registered
)} {race.status === 'completed' && ( )} {race.status === 'scheduled' && ( )}
{/* Status Info */}

{config.label}

{config.description}

{/* Quick Links */}

Quick Links

All Races {league && ( {league.name} )} {league && ( League Standings )}
); }