website refactor

This commit is contained in:
2026-01-14 12:52:30 +01:00
parent 02073f19ef
commit 5451b5b0e9
6 changed files with 0 additions and 1053 deletions

View File

@@ -1,212 +0,0 @@
'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<LeagueDetailInput, LeagueDetailViewData> {
/**
* 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 });
}
}

View File

@@ -1,103 +0,0 @@
'use client';
/**
* LeagueStandingsPresenter
* Pure client-side presenter for LeagueStandingsTemplate
* Converts ViewModels to ViewData
*/
import type { Presenter } from '@/lib/contracts/presenters/Presenter';
import type { LeagueMembership } from '@/lib/types/LeagueMembership';
import type { DriverData, LeagueMembershipData, LeagueStandingsViewData, StandingEntryData } from '@/lib/view-data/LeagueStandingsViewData';
import type { DriverViewModel } from '@/lib/view-models/DriverViewModel';
import type { StandingEntryViewModel } from '@/lib/view-models/StandingEntryViewModel';
interface LeagueStandingsInput {
standings: StandingEntryViewModel[];
drivers: DriverViewModel[];
memberships: LeagueMembership[];
leagueId: string;
currentDriverId: string | null;
isAdmin: boolean;
}
export class LeagueStandingsPresenter implements Presenter<LeagueStandingsInput, LeagueStandingsViewData> {
/**
* Convert StandingEntryViewModel to StandingEntryData
*/
private static convertStanding(standing: StandingEntryViewModel): StandingEntryData {
return {
driverId: standing.driverId,
position: standing.position,
totalPoints: standing.points,
racesFinished: standing.races,
racesStarted: standing.races,
avgFinish: null, // Not available in current ViewModel
penaltyPoints: 0, // Not available in current ViewModel
bonusPoints: 0, // Not available in current ViewModel
};
}
/**
* Convert DriverViewModel to DriverData
*/
private static convertDriver(driver: DriverViewModel): DriverData {
return {
id: driver.id,
name: driver.name,
avatarUrl: driver.avatarUrl,
iracingId: driver.iracingId,
rating: driver.rating,
country: driver.country,
};
}
/**
* Convert LeagueMembership to LeagueMembershipData
*/
private static convertMembership(membership: LeagueMembership): LeagueMembershipData {
return {
driverId: membership.driverId,
leagueId: membership.leagueId,
role: membership.role,
joinedAt: membership.joinedAt,
status: membership.status,
};
}
/**
* Transform input to output
*/
present(input: LeagueStandingsInput): LeagueStandingsViewData {
return {
standings: input.standings.map(s => LeagueStandingsPresenter.convertStanding(s)),
drivers: input.drivers.map(d => LeagueStandingsPresenter.convertDriver(d)),
memberships: input.memberships.map(m => LeagueStandingsPresenter.convertMembership(m)),
leagueId: input.leagueId,
currentDriverId: input.currentDriverId,
isAdmin: input.isAdmin,
};
}
/**
* Static helper for backward compatibility
*/
static createViewData(
standings: StandingEntryViewModel[],
drivers: DriverViewModel[],
memberships: LeagueMembership[],
leagueId: string,
currentDriverId: string | null,
isAdmin: boolean
): LeagueStandingsViewData {
const presenter = new LeagueStandingsPresenter();
return presenter.present({
standings,
drivers,
memberships,
leagueId,
currentDriverId,
isAdmin,
});
}
}

View File

@@ -1,23 +0,0 @@
'use client';
import type { AuthSessionDTO } from '@/lib/types/generated/AuthSessionDTO';
import { SessionViewModel } from '@/lib/view-models/SessionViewModel';
/**
* Session Presenter
*
* Converts AuthSessionDTO to SessionViewModel for client-side presentation.
* Pure and deterministic - no side effects.
*/
export class SessionPresenter {
/**
* Present session data as a view model
*/
present(sessionDto: AuthSessionDTO | null): SessionViewModel | null {
if (!sessionDto || !sessionDto.user) {
return null;
}
return new SessionViewModel(sessionDto.user);
}
}