'use client'; import { useState, useEffect } from 'react'; import { useParams } from 'next/navigation'; import Card from '@/components/ui/Card'; import { getLeagueRepository, getGetLeagueScoringConfigUseCase } from '@/lib/di-container'; import type { LeagueScoringConfigDTO } from '@gridpilot/racing/application/dto/LeagueScoringConfigDTO'; import type { League } from '@gridpilot/racing/domain/entities/League'; type RulebookSection = 'scoring' | 'conduct' | 'protests' | 'penalties'; export default function LeagueRulebookPage() { const params = useParams(); const leagueId = params.id as string; const [league, setLeague] = useState(null); const [scoringConfig, setScoringConfig] = useState(null); const [loading, setLoading] = useState(true); const [activeSection, setActiveSection] = useState('scoring'); useEffect(() => { async function loadData() { try { const leagueRepo = getLeagueRepository(); const scoringUseCase = getGetLeagueScoringConfigUseCase(); const leagueData = await leagueRepo.findById(leagueId); if (!leagueData) { setLoading(false); return; } setLeague(leagueData); await scoringUseCase.execute({ leagueId }); const scoringViewModel = scoringUseCase.presenter.getViewModel(); setScoringConfig(scoringViewModel as unknown as LeagueScoringConfigDTO); } catch (err) { console.error('Failed to load scoring config:', err); } finally { setLoading(false); } } loadData(); }, [leagueId]); if (loading) { return (
Loading rulebook...
); } if (!league || !scoringConfig) { return (
Unable to load rulebook
); } const primaryChampionship = scoringConfig.championships.find(c => c.type === 'driver') ?? scoringConfig.championships[0]; const positionPoints = primaryChampionship?.pointsPreview .filter(p => p.sessionType === primaryChampionship.sessionTypes[0]) .map(p => ({ position: p.position, points: p.points })) .sort((a, b) => a.position - b.position) || []; const sections: { id: RulebookSection; label: string }[] = [ { id: 'scoring', label: 'Scoring' }, { id: 'conduct', label: 'Conduct' }, { id: 'protests', label: 'Protests' }, { id: 'penalties', label: 'Penalties' }, ]; return (
{/* Header */}

Rulebook

Official rules and regulations

{scoringConfig.scoringPresetName || 'Custom Rules'}
{/* Navigation Tabs */}
{sections.map((section) => ( ))}
{/* Content Sections */} {activeSection === 'scoring' && (
{/* Quick Stats */}

Platform

{scoringConfig.gameName}

Championships

{scoringConfig.championships.length}

Sessions Scored

{primaryChampionship?.sessionTypes.join(', ') || 'Main'}

Drop Policy

{scoringConfig.dropPolicySummary.includes('All') ? 'None' : 'Active'}

{/* Points Table */}

Points Distribution

{positionPoints.map(({ position, points }) => ( ))}
Position Points
{position}
{position === 1 ? '1st' : position === 2 ? '2nd' : position === 3 ? '3rd' : `${position}th`}
{points} pts
{/* Bonus Points */} {primaryChampionship?.bonusSummary && primaryChampionship.bonusSummary.length > 0 && (

Bonus Points

{primaryChampionship.bonusSummary.map((bonus, idx) => (
+

{bonus}

))}
)} {/* Drop Policy */} {!scoringConfig.dropPolicySummary.includes('All results count') && (

Drop Policy

{scoringConfig.dropPolicySummary}

Drop rules are applied automatically when calculating championship standings.

)}
)} {activeSection === 'conduct' && (

Driver Conduct

1. Respect

All drivers must treat each other with respect. Abusive language, harassment, or unsportsmanlike behavior will not be tolerated.

2. Clean Racing

Intentional wrecking, blocking, or dangerous driving is prohibited. Leave space for other drivers and race cleanly.

3. Track Limits

Drivers must stay within track limits. Gaining a lasting advantage by exceeding track limits may result in penalties.

4. Blue Flags

Lapped cars must yield to faster traffic within a reasonable time. Failure to do so may result in penalties.

5. Communication

Drivers are expected to communicate respectfully in voice and text chat during sessions.

)} {activeSection === 'protests' && (

Protest Process

Filing a Protest

Protests can be filed within 48 hours of the race conclusion. Include the lap number, drivers involved, and a clear description of the incident.

Evidence

Video evidence is highly recommended but not required. Stewards will review available replay data.

Review Process

League stewards will review protests and make decisions within 72 hours. Decisions are final unless new evidence is presented.

Outcomes

Protests may result in no action, warnings, time penalties, position penalties, or points deductions depending on severity.

)} {activeSection === 'penalties' && (

Penalty Guidelines

Infraction Typical Penalty
Causing avoidable contact 5-10 second time penalty
Unsafe rejoin 5 second time penalty
Blocking Warning or 3 second penalty
Repeated track limit violations 5 second penalty
Intentional wrecking Disqualification
Unsportsmanlike conduct Points deduction or ban

Penalties are applied at steward discretion based on incident severity and driver history.

)}
); }