'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 FileProtestModal from '@/components/races/FileProtestModal'; import SponsorInsightsCard, { useSponsorMode, MetricBuilders, SlotTemplates } from '@/components/sponsors/SponsorInsightsCard'; 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 type { Result } from '@gridpilot/racing/domain/entities/Result'; import { getRaceRepository, getLeagueRepository, getDriverRepository, getGetRaceRegistrationsQuery, getIsDriverRegisteredForRaceQuery, getRegisterForRaceUseCase, getWithdrawFromRaceUseCase, getGetRaceWithSOFQuery, getResultRepository, getImageService, } from '@/lib/di-container'; import { getMembership } from '@/lib/leagueMembership'; import { useEffectiveDriverId } from '@/lib/currentDriver'; import { getDriverStats } from '@/lib/di-container'; import { Calendar, Clock, MapPin, Car, Trophy, Users, Zap, PlayCircle, CheckCircle2, XCircle, ChevronRight, Flag, Timer, UserPlus, UserMinus, AlertTriangle, ArrowRight, ArrowLeft, ExternalLink, Award, Scale, } from 'lucide-react'; import { 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 [userResult, setUserResult] = useState(null); const [ratingChange, setRatingChange] = useState(null); const [animatedRatingChange, setAnimatedRatingChange] = useState(0); const [showProtestModal, setShowProtestModal] = useState(false); const currentDriverId = useEffectiveDriverId(); const isSponsorMode = useSponsorMode(); 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); // Load user's result if race is completed if (raceData.status === 'completed') { const resultRepo = getResultRepository(); const results = await resultRepo.findByRaceId(raceData.id); const result = results.find(r => r.driverId === currentDriverId); setUserResult(result || null); // Get rating change from driver stats (mock based on position) if (result) { const stats = getDriverStats(currentDriverId); if (stats) { // Calculate rating change based on position - simplified domain logic const baseChange = result.position <= 3 ? 25 : result.position <= 10 ? 10 : -5; const positionBonus = Math.max(0, (20 - result.position) * 2); const change = baseChange + positionBonus; setRatingChange(change); } } } } 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]); // Animate rating change when it changes useEffect(() => { if (ratingChange !== null) { let start = 0; const end = ratingChange; const duration = 1000; const startTime = performance.now(); const animate = (currentTime: number) => { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); // Ease out cubic const eased = 1 - Math.pow(1 - progress, 3); const current = Math.round(start + (end - start) * eased); setAnimatedRatingChange(current); if (progress < 1) { requestAnimationFrame(animate); } }; requestAnimationFrame(animate); } }, [ratingChange]); 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 }, ]; // Country code to flag emoji converter const getCountryFlag = (countryCode: string): string => { const codePoints = countryCode .toUpperCase() .split('') .map(char => 127397 + char.charCodeAt(0)); return String.fromCodePoint(...codePoints); }; // 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 }; }; // Build sponsor insights for race const sponsorInsights = { tier: 'gold' as const, trustScore: 92, discordMembers: league ? 1847 : undefined, monthlyActivity: 156, }; const raceMetrics = [ MetricBuilders.views(entryList.length * 12), MetricBuilders.engagement(78), { label: 'SOF', value: raceSOF?.toString() ?? '—', icon: Zap, color: 'text-warning-amber' as const }, MetricBuilders.reach(entryList.length * 45), ]; return (
{/* Navigation Row: Breadcrumbs left, Back button right */}
{/* Sponsor Insights Card - Consistent placement at top */} {isSponsorMode && race && league && ( )} {/* User Result - Premium Achievement Card */} {userResult && (
{/* Decorative elements */}
{/* Victory confetti effect for P1 */} {userResult.position === 1 && (
)}
{/* Main content grid */}
{/* Left: Position and achievement */}
{/* Giant position badge */}
{userResult.position === 1 && ( )} P{userResult.position}
{/* Achievement text */}

{userResult.position === 1 ? '🏆 VICTORY!' : userResult.position === 2 ? '🥈 Second Place' : userResult.position === 3 ? '🥉 Podium Finish' : userResult.position <= 5 ? '⭐ Top 5 Finish' : userResult.position <= 10 ? 'Points Finish' : `P${userResult.position} Finish`}

Started P{userResult.startPosition} {userResult.incidents}x incidents {userResult.isClean() && ' ✨'}
{/* Right: Stats cards */}
{/* Position change */} {userResult.getPositionChange() !== 0 && (
0 ? 'bg-gradient-to-br from-performance-green/30 to-performance-green/10 border border-performance-green/40' : 'bg-gradient-to-br from-red-500/30 to-red-500/10 border border-red-500/40'} `}>
0 ? 'text-performance-green' : 'text-red-400'} `}> {userResult.getPositionChange() > 0 ? ( ) : ( )} {Math.abs(userResult.getPositionChange())}
{userResult.getPositionChange() > 0 ? 'Gained' : 'Lost'}
)} {/* Rating change */} {ratingChange !== null && (
0 ? 'bg-gradient-to-br from-warning-amber/30 to-warning-amber/10 border border-warning-amber/40' : 'bg-gradient-to-br from-red-500/30 to-red-500/10 border border-red-500/40'} `}>
0 ? 'text-warning-amber' : 'text-red-400'} `}> {animatedRatingChange > 0 ? '+' : ''}{animatedRatingChange}
iRating
)} {/* Clean race bonus */} {userResult.isClean() && (
Clean Race
)}
)} {/* 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}
{/* Prominent SOF Badge - Electric Design */} {raceSOF && (
{/* Glow effect */}
{/* Electric bolt with animation */}
Strength of Field
{raceSOF} SOF
)}
{/* 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' : ''}
{(() => { const imageService = getImageService(); return entryList.length === 0 ? (

No drivers registered yet

Be the first to sign up!

) : (
{entryList.map((driver, index) => { const driverRankInfo = getDriverRank(driver.id); const isCurrentUser = driver.id === currentDriverId; const avatarUrl = imageService.getDriverAvatar(driver.id); const countryFlag = getCountryFlag(driver.country); return (
router.push(`/drivers/${driver.id}`)} className={` flex items-center gap-3 p-3 rounded-xl cursor-pointer transition-all duration-200 ${isCurrentUser ? 'bg-gradient-to-r from-primary-blue/20 via-primary-blue/10 to-transparent border border-primary-blue/40 shadow-lg shadow-primary-blue/10' : 'bg-deep-graphite hover:bg-charcoal-outline/50 border border-transparent'} `} > {/* Position number */}
{index + 1}
{/* Avatar with nation flag */}
{driver.name} {/* Nation flag */}
{countryFlag}
{/* Driver info */}

{driver.name}

{isCurrentUser && ( You )}

{driver.country}

{/* Rating badge */} {driverRankInfo.rating && (
{driverRankInfo.rating}
)}
); })}
); })()}
{/* Sidebar */}
{/* League Card - Premium Design */} {league && (
{league.name}

League

{league.name}

{league.description && (

{league.description}

)}

Max Drivers

{league.settings.maxDrivers ?? 32}

Format

{league.settings.qualifyingFormat ?? 'Open'}

View League
)} {/* Quick Actions Card */}

Actions

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

{config.label}

{config.description}

{/* Protest Filing Modal */} setShowProtestModal(false)} raceId={race.id} leagueId={league?.id} protestingDriverId={currentDriverId} participants={entryList} />
); }