'use client'; /** * LeagueDetailPresenter * Pure client-side presenter for LeagueDetailTemplate * Converts ViewModels to ViewData and removes DisplayObject usage */ import type { Presenter } from '@/lib/contracts/presenters/Presenter'; import type { DriverSummaryData, LeagueDetailViewData, LeagueInfoData, LiveRaceData, SponsorInfo, SponsorMetric } from '@/lib/view-data/LeagueDetailViewData'; import type { DriverSummary, LeagueDetailPageViewModel } from '@/lib/view-models/LeagueDetailPageViewModel'; import type { RaceViewModel } from '@/lib/view-models/RaceViewModel'; import { Eye, TrendingUp, Users, Zap } from 'lucide-react'; interface SponsorshipSlot { tier: 'main' | 'secondary'; available: boolean; price: number; benefits: string[]; } interface LeagueDetailInput { viewModel: LeagueDetailPageViewModel; leagueId: string; isSponsor: boolean; } // League role display data (moved from LeagueRoleDisplay) const leagueRoleDisplay = { owner: { text: 'Owner', badgeClasses: 'bg-yellow-500/10 text-yellow-500 border-yellow-500/30', }, admin: { text: 'Admin', badgeClasses: 'bg-purple-500/10 text-purple-400 border-purple-500/30', }, steward: { text: 'Steward', badgeClasses: 'bg-blue-500/10 text-blue-400 border-blue-500/30', }, member: { text: 'Member', badgeClasses: 'bg-primary-blue/10 text-primary-blue border-primary-blue/30', }, } as const; export class LeagueDetailPresenter implements Presenter { /** * Convert RaceViewModel[] to LiveRaceData[] */ private static convertRunningRaces(races: RaceViewModel[]): LiveRaceData[] { return races.map(race => ({ id: race.id, name: race.name, date: race.date, registeredCount: race.registeredCount, strengthOfField: race.strengthOfField, })); } /** * Convert DriverSummary to DriverSummaryData with role badge info */ private static convertDriverSummary( summary: DriverSummary | null, role: 'owner' | 'admin' | 'steward' | 'member', leagueId: string ): DriverSummaryData | null { if (!summary) return null; const roleDisplay = leagueRoleDisplay[role]; return { driverId: summary.driver.id, driverName: summary.driver.name, avatarUrl: summary.driver.avatarUrl, rating: summary.rating, rank: summary.rank, roleBadgeText: roleDisplay.text, roleBadgeClasses: roleDisplay.badgeClasses, profileUrl: `/drivers/${summary.driver.id}?from=league-management&leagueId=${leagueId}`, }; } /** * Transform input to output */ present(input: LeagueDetailInput): LeagueDetailViewData { const { viewModel, leagueId, isSponsor } = input; // Build info data const info: LeagueInfoData = { name: viewModel.name, description: viewModel.description ?? '', membersCount: viewModel.memberships.length, racesCount: viewModel.completedRacesCount, avgSOF: viewModel.averageSOF, structure: `Solo • ${viewModel.settings.maxDrivers ?? 32} max`, scoring: viewModel.scoringConfig?.scoringPresetName ?? 'Standard', createdAt: viewModel.createdAt, discordUrl: viewModel.socialLinks?.discordUrl, youtubeUrl: viewModel.socialLinks?.youtubeUrl, websiteUrl: viewModel.socialLinks?.websiteUrl, }; // Convert running races const runningRaces = LeagueDetailPresenter.convertRunningRaces(viewModel.runningRaces); // Convert sponsors const sponsors: SponsorInfo[] = viewModel.sponsors.map(s => ({ id: s.id, name: s.name, tier: s.tier, logoUrl: s.logoUrl, websiteUrl: s.websiteUrl, tagline: s.tagline, })); // Convert driver summaries with role badges const ownerSummary = LeagueDetailPresenter.convertDriverSummary(viewModel.ownerSummary, 'owner', leagueId); const adminSummaries = viewModel.adminSummaries .map(s => LeagueDetailPresenter.convertDriverSummary(s, 'admin', leagueId)) .filter((s): s is DriverSummaryData => s !== null); const stewardSummaries = viewModel.stewardSummaries .map(s => LeagueDetailPresenter.convertDriverSummary(s, 'steward', leagueId)) .filter((s): s is DriverSummaryData => s !== null); // Sponsor insights (only if sponsor mode) const sponsorInsights = isSponsor ? { avgViewsPerRace: viewModel.sponsorInsights.avgViewsPerRace, engagementRate: viewModel.sponsorInsights.engagementRate, estimatedReach: viewModel.sponsorInsights.estimatedReach, tier: viewModel.sponsorInsights.tier, trustScore: viewModel.sponsorInsights.trustScore, discordMembers: viewModel.sponsorInsights.discordMembers, monthlyActivity: viewModel.sponsorInsights.monthlyActivity, mainSponsorAvailable: viewModel.sponsorInsights.mainSponsorAvailable, secondarySlotsAvailable: viewModel.sponsorInsights.secondarySlotsAvailable, mainSponsorPrice: viewModel.sponsorInsights.mainSponsorPrice, secondaryPrice: viewModel.sponsorInsights.secondaryPrice, totalImpressions: viewModel.sponsorInsights.totalImpressions, metrics: [ { icon: Eye, label: 'Avg Views/Race', value: viewModel.sponsorInsights.avgViewsPerRace, color: 'text-primary-blue', }, { icon: TrendingUp, label: 'Engagement', value: viewModel.sponsorInsights.engagementRate, color: 'text-performance-green', }, { icon: Users, label: 'Est. Reach', value: viewModel.sponsorInsights.estimatedReach, color: 'text-purple-400', }, { icon: Zap, label: 'Avg SOF', value: viewModel.averageSOF ?? '—', color: 'text-warning-amber', }, ], slots: [ { tier: 'main' as const, available: viewModel.sponsorInsights.mainSponsorAvailable, price: viewModel.sponsorInsights.mainSponsorPrice, benefits: ['Hood placement', 'League banner', 'Prominent logo'], }, { tier: 'secondary' as const, available: viewModel.sponsorInsights.secondarySlotsAvailable > 0, price: viewModel.sponsorInsights.secondaryPrice, benefits: ['Side logo placement', 'League page listing'], }, { tier: 'secondary' as const, available: viewModel.sponsorInsights.secondarySlotsAvailable > 1, price: viewModel.sponsorInsights.secondaryPrice, benefits: ['Side logo placement', 'League page listing'], }, ], } : null; return { leagueId: viewModel.id, name: viewModel.name, description: viewModel.description ?? '', info, runningRaces, sponsors, ownerSummary, adminSummaries, stewardSummaries, sponsorInsights, }; } /** * Static helper for backward compatibility */ static createViewData(viewModel: LeagueDetailPageViewModel, leagueId: string, isSponsor: boolean): LeagueDetailViewData { const presenter = new LeagueDetailPresenter(); return presenter.present({ viewModel, leagueId, isSponsor }); } }