'use client'; import { useEffect, useState } from 'react'; import Link from 'next/link'; import Breadcrumbs from '@/components/layout/Breadcrumbs'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import Heading from '@/components/ui/Heading'; import { RaceJoinButton } from '@/components/races/RaceJoinButton'; import { AlertTriangle, ArrowLeft, ArrowRight, Calendar, Car, CheckCircle2, Clock, Flag, PlayCircle, Scale, Trophy, UserMinus, UserPlus, Users, XCircle, Zap, } from 'lucide-react'; export interface RaceDetailEntryViewModel { id: string; name: string; avatarUrl: string; country: string; rating?: number | null; isCurrentUser: boolean; } export interface RaceDetailUserResultViewModel { position: number; startPosition: number; positionChange: number; incidents: number; isClean: boolean; isPodium: boolean; ratingChange?: number; } export interface RaceDetailLeague { id: string; name: string; description?: string; settings: { maxDrivers: number; qualifyingFormat: string; }; } export interface RaceDetailRace { id: string; track: string; car: string; scheduledAt: string; status: 'scheduled' | 'running' | 'completed' | 'cancelled'; sessionType: string; } export interface RaceDetailRegistration { isUserRegistered: boolean; canRegister: boolean; } export interface RaceDetailViewModel { race: RaceDetailRace; league?: RaceDetailLeague; entryList: RaceDetailEntryViewModel[]; registration: RaceDetailRegistration; userResult?: RaceDetailUserResultViewModel; canReopenRace: boolean; } export interface RaceDetailTemplateProps { viewModel?: RaceDetailViewModel; isLoading: boolean; error?: Error | null; // Actions onBack: () => void; onRegister: () => void; onWithdraw: () => void; onCancel: () => void; onReopen: () => void; onEndRace: () => void; onFileProtest: () => void; onResultsClick: () => void; onStewardingClick: () => void; onLeagueClick: (leagueId: string) => void; onDriverClick: (driverId: string) => void; // User state currentDriverId?: string; isOwnerOrAdmin?: boolean; // UI State showProtestModal: boolean; setShowProtestModal: (show: boolean) => void; showEndRaceModal: boolean; setShowEndRaceModal: (show: boolean) => void; // Loading states mutationLoading?: { register?: boolean; withdraw?: boolean; cancel?: boolean; reopen?: boolean; complete?: boolean; }; } export function RaceDetailTemplate({ viewModel, isLoading, error, onBack, onRegister, onWithdraw, onCancel, onReopen, onEndRace, onFileProtest, onResultsClick, onStewardingClick, onLeagueClick, onDriverClick, currentDriverId, isOwnerOrAdmin = false, showProtestModal, setShowProtestModal, showEndRaceModal, setShowEndRaceModal, mutationLoading = {}, }: RaceDetailTemplateProps) { const [ratingChange, setRatingChange] = useState(null); const [animatedRatingChange, setAnimatedRatingChange] = useState(0); // Set rating change when viewModel changes useEffect(() => { if (viewModel?.userResult?.ratingChange !== undefined) { setRatingChange(viewModel.userResult.ratingChange); } }, [viewModel?.userResult?.ratingChange]); // 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); 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 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', }, } as const; const getCountryFlag = (countryCode: string): string => { const codePoints = countryCode .toUpperCase() .split('') .map(char => 127397 + char.charCodeAt(0)); return String.fromCodePoint(...codePoints); }; if (isLoading) { return (
); } if (error || !viewModel || !viewModel.race) { return (

{error instanceof Error ? error.message : error || 'Race not found'}

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

); } const race = viewModel.race; const league = viewModel.league; const entryList = viewModel.entryList; const userResult = viewModel.userResult; const raceSOF = null; // TODO: Add strength of field to race details response const config = statusConfig[race.status as keyof typeof statusConfig]; const StatusIcon = config.icon; const timeUntil = race.status === 'scheduled' ? getTimeUntil(new Date(race.scheduledAt)) : null; const breadcrumbItems = [ { label: 'Races', href: '/races' }, ...(league ? [{ label: league.name, href: `/leagues/${league.id}` }] : []), { label: race.track }, ]; return (
{/* Navigation Row: Breadcrumbs left, Back button right */}
{/* 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.positionChange !== 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.positionChange > 0 ? ( ) : ( )} {Math.abs(userResult.positionChange)}
{userResult.positionChange > 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}
Rating
)} {/* 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(new Date(race.scheduledAt))} {formatTime(new Date(race.scheduledAt))} {race.car}
{/* Prominent SOF Badge - Electric Design */} {raceSOF != null && (
{/* 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 ?? '—'}

{/* 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 isCurrentUser = driver.isCurrentUser; const countryFlag = getCountryFlag(driver.country); return (
onDriverClick(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 */} {driver.rating != null && (
{driver.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 */} {/* Results and Stewarding for completed races */} {race.status === 'completed' && ( <> {userResult && ( )} )}
{/* Status Info */}

{config.label}

{config.description}

{/* Modals would be rendered by parent */}
); }