'use client'; import { useState, useEffect, useMemo } from 'react'; import { useRouter, useParams } from 'next/navigation'; import Button from '@/components/ui/Button'; import Card from '@/components/ui/Card'; import JoinLeagueButton from '@/components/leagues/JoinLeagueButton'; import LeagueActivityFeed from '@/components/leagues/LeagueActivityFeed'; import DriverIdentity from '@/components/drivers/DriverIdentity'; import DriverSummaryPill from '@/components/profile/DriverSummaryPill'; import SponsorInsightsCard, { useSponsorMode, MetricBuilders, SlotTemplates, type SponsorMetric, } from '@/components/sponsors/SponsorInsightsCard'; import { League, Driver, EntityMappers, type DriverDTO, type LeagueScoringConfigDTO, } from '@gridpilot/racing'; import { getLeagueRepository, getRaceRepository, getDriverRepository, getGetLeagueScoringConfigUseCase, getDriverStats, getAllDriverRankings, getGetLeagueStatsUseCase, getSeasonRepository, getSponsorRepository, getSeasonSponsorshipRepository, } from '@/lib/di-container'; import { Trophy, Star, ExternalLink } from 'lucide-react'; import { getMembership, getLeagueMembers } from '@/lib/leagueMembership'; import { useEffectiveDriverId } from '@/lib/currentDriver'; import { getLeagueRoleDisplay } from '@/lib/leagueRoles'; // Sponsor info type interface SponsorInfo { id: string; name: string; logoUrl?: string; websiteUrl?: string; tier: 'main' | 'secondary'; tagline?: string; } export default function LeagueDetailPage() { const router = useRouter(); const params = useParams(); const leagueId = params.id as string; const isSponsor = useSponsorMode(); const [league, setLeague] = useState(null); const [owner, setOwner] = useState(null); const [drivers, setDrivers] = useState([]); const [scoringConfig, setScoringConfig] = useState(null); const [averageSOF, setAverageSOF] = useState(null); const [completedRacesCount, setCompletedRacesCount] = useState(0); const [sponsors, setSponsors] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [refreshKey, setRefreshKey] = useState(0); const currentDriverId = useEffectiveDriverId(); const membership = getMembership(leagueId, currentDriverId); const leagueMemberships = getLeagueMembers(leagueId); // Sponsor insights data - uses leagueMemberships and averageSOF const sponsorInsights = useMemo(() => { const memberCount = leagueMemberships?.length || 20; const mainSponsorTaken = sponsors.some(s => s.tier === 'main'); const secondaryTaken = sponsors.filter(s => s.tier === 'secondary').length; return { avgViewsPerRace: 5400 + memberCount * 50, totalImpressions: 45000 + memberCount * 500, engagementRate: (3.5 + (memberCount / 50)).toFixed(1), estimatedReach: memberCount * 150, mainSponsorAvailable: !mainSponsorTaken, secondarySlotsAvailable: Math.max(0, 2 - secondaryTaken), mainSponsorPrice: 800 + Math.floor(memberCount * 10), secondaryPrice: 250 + Math.floor(memberCount * 3), tier: (averageSOF && averageSOF > 3000 ? 'premium' : averageSOF && averageSOF > 2000 ? 'standard' : 'starter') as 'premium' | 'standard' | 'starter', trustScore: Math.min(100, 60 + memberCount + completedRacesCount), discordMembers: memberCount * 3, monthlyActivity: Math.min(100, 40 + completedRacesCount * 2), }; }, [averageSOF, leagueMemberships?.length, sponsors, completedRacesCount]); // Build metrics for SponsorInsightsCard const leagueMetrics: SponsorMetric[] = useMemo(() => [ MetricBuilders.views(sponsorInsights.avgViewsPerRace, 'Avg Views/Race'), MetricBuilders.engagement(sponsorInsights.engagementRate), MetricBuilders.reach(sponsorInsights.estimatedReach), MetricBuilders.sof(averageSOF ?? '—'), ], [sponsorInsights, averageSOF]); const loadLeagueData = async () => { try { const leagueRepo = getLeagueRepository(); const raceRepo = getRaceRepository(); const driverRepo = getDriverRepository(); const leagueStatsUseCase = getGetLeagueStatsUseCase(); const seasonRepo = getSeasonRepository(); const sponsorRepo = getSponsorRepository(); const sponsorshipRepo = getSeasonSponsorshipRepository(); const leagueData = await leagueRepo.findById(leagueId); if (!leagueData) { setError('League not found'); setLoading(false); return; } setLeague(leagueData); // Load owner data const ownerData = await driverRepo.findById(leagueData.ownerId); setOwner(ownerData); // Load scoring configuration for the active season const getLeagueScoringConfigUseCase = getGetLeagueScoringConfigUseCase(); await getLeagueScoringConfigUseCase.execute({ leagueId }); const scoringViewModel = getLeagueScoringConfigUseCase.presenter.getViewModel(); setScoringConfig(scoringViewModel as unknown as LeagueScoringConfigDTO); // Load all drivers for standings and map to DTOs for UI components const allDrivers = await driverRepo.findAll(); const driverDtos: DriverDTO[] = allDrivers .map((driver) => EntityMappers.toDriverDTO(driver)) .filter((dto): dto is DriverDTO => dto !== null); setDrivers(driverDtos); // Load league stats including average SOF from application use case await leagueStatsUseCase.execute({ leagueId }); const leagueStatsViewModel = leagueStatsUseCase.presenter.getViewModel(); if (leagueStatsViewModel) { setAverageSOF(leagueStatsViewModel.averageSOF); setCompletedRacesCount(leagueStatsViewModel.completedRaces); } else { // Fallback: count completed races manually const leagueRaces = await raceRepo.findByLeagueId(leagueId); const completedRaces = leagueRaces.filter(r => r.status === 'completed'); setCompletedRacesCount(completedRaces.length); } // Load sponsors for this league try { const seasons = await seasonRepo.findByLeagueId(leagueId); const activeSeason = seasons.find((s: { status: string }) => s.status === 'active') ?? seasons[0]; if (activeSeason) { const sponsorships = await sponsorshipRepo.findBySeasonId(activeSeason.id); const activeSponsorships = sponsorships.filter((s) => s.status === 'active'); const sponsorInfos: SponsorInfo[] = []; for (const sponsorship of activeSponsorships) { const sponsor = await sponsorRepo.findById(sponsorship.sponsorId); if (sponsor) { const testingSupportModule = await import('@gridpilot/testing-support'); const demoSponsors = testingSupportModule.sponsors as Array<{ id: string; tagline?: string }>; const demoSponsor = demoSponsors.find((demo) => demo.id === sponsor.id); sponsorInfos.push({ id: sponsor.id, name: sponsor.name, logoUrl: sponsor.logoUrl ?? '', websiteUrl: sponsor.websiteUrl ?? '', tier: sponsorship.tier, tagline: demoSponsor?.tagline ?? '', }); } } // Sort: main sponsors first, then secondary sponsorInfos.sort((a, b) => { if (a.tier === 'main' && b.tier !== 'main') return -1; if (a.tier !== 'main' && b.tier === 'main') return 1; return 0; }); setSponsors(sponsorInfos); } } catch (sponsorError) { console.warn('Failed to load sponsors:', sponsorError); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load league'); } finally { setLoading(false); } }; useEffect(() => { loadLeagueData(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [leagueId]); const handleMembershipChange = () => { setRefreshKey(prev => prev + 1); loadLeagueData(); }; const driversById = useMemo(() => { const map: Record = {}; for (const d of drivers) { map[d.id] = d; } return map; }, [drivers]); const ownerMembership = leagueMemberships.find((m) => m.role === 'owner') ?? null; const adminMemberships = leagueMemberships.filter((m) => m.role === 'admin'); const stewardMemberships = leagueMemberships.filter((m) => m.role === 'steward'); const buildDriverSummary = (driverId: string) => { const driverDto = driversById[driverId]; if (!driverDto) { return null; } const stats = getDriverStats(driverDto.id); const allRankings = getAllDriverRankings(); let rating: number | null = stats?.rating ?? null; let rank: number | null = null; if (stats) { if (typeof stats.overallRank === 'number' && stats.overallRank > 0) { rank = stats.overallRank; } else { const indexInGlobal = allRankings.findIndex( (stat) => stat.driverId === stats.driverId, ); if (indexInGlobal !== -1) { rank = indexInGlobal + 1; } } if (rating === null) { const globalEntry = allRankings.find( (stat) => stat.driverId === stats.driverId, ); if (globalEntry) { rating = globalEntry.rating; } } } return { driver: driverDto, rating, rank, }; }; return loading ? (
Loading league...
) : error || !league ? (
{error || 'League not found'}
) : ( <> {/* Sponsor Insights Card - Only shown to sponsors, at top of page */} {isSponsor && league && ( )} {/* Action Card */} {!membership && !isSponsor && (

Join This League

Become a member to participate in races and track your progress

)} {/* League Overview - Activity Center with Info Sidebar */}
{/* Center - Activity Feed */}

Recent Activity

{/* Right Sidebar - League Info */}
{/* League Info - Combined */}

About

{/* Stats Grid */}
{leagueMemberships.length}
Members
{completedRacesCount}
Races
{averageSOF ?? '—'}
Avg SOF
{/* Details */}
Structure Solo • {league.settings.maxDrivers ?? 32} max
Scoring {scoringConfig?.scoringPresetName ?? 'Standard'}
Created {new Date(league.createdAt).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}
{league.socialLinks && (
{league.socialLinks.discordUrl && ( Discord )} {league.socialLinks.youtubeUrl && ( YouTube )} {league.socialLinks.websiteUrl && ( Website )}
)}
{/* Sponsors Section - Show sponsor logos */} {sponsors.length > 0 && (

{sponsors.find(s => s.tier === 'main') ? 'Presented by' : 'Sponsors'}

{/* Main Sponsor - Featured prominently */} {sponsors.filter(s => s.tier === 'main').map(sponsor => (
{sponsor.logoUrl ? (
{sponsor.name}
) : (
)}
{sponsor.name} Main
{sponsor.tagline && (

{sponsor.tagline}

)}
{sponsor.websiteUrl && ( )}
))} {/* Secondary Sponsors - Smaller display */} {sponsors.filter(s => s.tier === 'secondary').length > 0 && (
{sponsors.filter(s => s.tier === 'secondary').map(sponsor => (
{sponsor.logoUrl ? (
{sponsor.name}
) : (
)}
{sponsor.name}
{sponsor.websiteUrl && ( )}
))}
)}
)} {/* Management */} {(ownerMembership || adminMemberships.length > 0 || stewardMemberships.length > 0) && (

Management

{ownerMembership && (() => { const driverDto = driversById[ownerMembership.driverId]; const summary = buildDriverSummary(ownerMembership.driverId); const roleDisplay = getLeagueRoleDisplay('owner'); const meta = summary && summary.rating !== null ? `Rating ${summary.rating}${summary.rank ? ` • Rank ${summary.rank}` : ''}` : null; return driverDto ? (
{roleDisplay.text}
) : null; })()} {adminMemberships.slice(0, 3).map((membership) => { const driverDto = driversById[membership.driverId]; const summary = buildDriverSummary(membership.driverId); const roleDisplay = getLeagueRoleDisplay('admin'); const meta = summary && summary.rating !== null ? `Rating ${summary.rating}${summary.rank ? ` • Rank ${summary.rank}` : ''}` : null; return driverDto ? (
{roleDisplay.text}
) : null; })} {stewardMemberships.slice(0, 3).map((membership) => { const driverDto = driversById[membership.driverId]; const summary = buildDriverSummary(membership.driverId); const roleDisplay = getLeagueRoleDisplay('steward'); const meta = summary && summary.rating !== null ? `Rating ${summary.rating}${summary.rank ? ` • Rank ${summary.rank}` : ''}` : null; return driverDto ? (
{roleDisplay.text}
) : null; })}
)}
); }