website refactor

This commit is contained in:
2026-01-19 14:07:49 +01:00
parent 54f42bab9f
commit 6154d54435
88 changed files with 755 additions and 566 deletions

View File

@@ -1,5 +1,10 @@
import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO';
import type { DriverProfileViewData } from '@/lib/types/view-data/DriverProfileViewData';
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
import { RatingDisplay } from '@/lib/display-objects/RatingDisplay';
import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
import { FinishDisplay } from '@/lib/display-objects/FinishDisplay';
import { PercentDisplay } from '@/lib/display-objects/PercentDisplay';
/**
* DriverProfileViewDataBuilder
@@ -17,26 +22,38 @@ export class DriverProfileViewDataBuilder {
avatarUrl: apiDto.currentDriver.avatarUrl || '',
iracingId: typeof apiDto.currentDriver.iracingId === 'string' ? parseInt(apiDto.currentDriver.iracingId, 10) : (apiDto.currentDriver.iracingId ?? null),
joinedAt: apiDto.currentDriver.joinedAt,
joinedAtLabel: DateDisplay.formatMonthYear(apiDto.currentDriver.joinedAt),
rating: apiDto.currentDriver.rating ?? null,
ratingLabel: RatingDisplay.format(apiDto.currentDriver.rating),
globalRank: apiDto.currentDriver.globalRank ?? null,
globalRankLabel: apiDto.currentDriver.globalRank != null ? `#${apiDto.currentDriver.globalRank}` : '—',
consistency: apiDto.currentDriver.consistency ?? null,
bio: apiDto.currentDriver.bio ?? null,
totalDrivers: apiDto.currentDriver.totalDrivers ?? null,
} : null,
stats: apiDto.stats ? {
totalRaces: apiDto.stats.totalRaces,
totalRacesLabel: NumberDisplay.format(apiDto.stats.totalRaces),
wins: apiDto.stats.wins,
winsLabel: NumberDisplay.format(apiDto.stats.wins),
podiums: apiDto.stats.podiums,
podiumsLabel: NumberDisplay.format(apiDto.stats.podiums),
dnfs: apiDto.stats.dnfs,
dnfsLabel: NumberDisplay.format(apiDto.stats.dnfs),
avgFinish: apiDto.stats.avgFinish ?? null,
avgFinishLabel: FinishDisplay.formatAverage(apiDto.stats.avgFinish),
bestFinish: apiDto.stats.bestFinish ?? null,
bestFinishLabel: FinishDisplay.format(apiDto.stats.bestFinish),
worstFinish: apiDto.stats.worstFinish ?? null,
worstFinishLabel: FinishDisplay.format(apiDto.stats.worstFinish),
finishRate: apiDto.stats.finishRate ?? null,
winRate: apiDto.stats.winRate ?? null,
podiumRate: apiDto.stats.podiumRate ?? null,
percentile: apiDto.stats.percentile ?? null,
rating: apiDto.stats.rating ?? null,
ratingLabel: RatingDisplay.format(apiDto.stats.rating),
consistency: apiDto.stats.consistency ?? null,
consistencyLabel: PercentDisplay.formatWhole(apiDto.stats.consistency),
overallRank: apiDto.stats.overallRank ?? null,
} : null,
finishDistribution: apiDto.finishDistribution ? {
@@ -53,6 +70,7 @@ export class DriverProfileViewDataBuilder {
teamTag: m.teamTag ?? null,
role: m.role,
joinedAt: m.joinedAt,
joinedAtLabel: DateDisplay.formatMonthYear(m.joinedAt),
isCurrent: m.isCurrent,
})),
socialSummary: {
@@ -76,7 +94,9 @@ export class DriverProfileViewDataBuilder {
description: a.description,
icon: a.icon,
rarity: a.rarity,
rarityLabel: a.rarity,
earnedAt: a.earnedAt,
earnedAtLabel: DateDisplay.formatShort(a.earnedAt),
})),
racingStyle: apiDto.extendedProfile.racingStyle,
favoriteTrack: apiDto.extendedProfile.favoriteTrack,
@@ -88,4 +108,4 @@ export class DriverProfileViewDataBuilder {
} : null,
};
}
}
}

View File

@@ -1,5 +1,7 @@
import type { DriversLeaderboardDTO } from '@/lib/types/generated/DriversLeaderboardDTO';
import type { DriversViewData } from '@/lib/types/view-data/DriversViewData';
import { RatingDisplay } from '@/lib/display-objects/RatingDisplay';
import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
export class DriversViewDataBuilder {
static build(dto: DriversLeaderboardDTO): DriversViewData {
@@ -8,6 +10,7 @@ export class DriversViewDataBuilder {
id: driver.id,
name: driver.name,
rating: driver.rating,
ratingLabel: RatingDisplay.format(driver.rating),
skillLevel: driver.skillLevel,
category: driver.category,
nationality: driver.nationality,
@@ -19,8 +22,12 @@ export class DriversViewDataBuilder {
avatarUrl: driver.avatarUrl,
})),
totalRaces: dto.totalRaces,
totalRacesLabel: NumberDisplay.format(dto.totalRaces),
totalWins: dto.totalWins,
totalWinsLabel: NumberDisplay.format(dto.totalWins),
activeCount: dto.activeCount,
activeCountLabel: NumberDisplay.format(dto.activeCount),
totalDriversLabel: NumberDisplay.format(dto.drivers.length),
};
}
}

View File

@@ -1,6 +1,7 @@
import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO';
import type { LeagueRosterJoinRequestDTO } from '@/lib/types/generated/LeagueRosterJoinRequestDTO';
import type { LeagueRosterAdminViewData, RosterMemberData, JoinRequestData } from '@/lib/view-data/LeagueRosterAdminViewData';
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
/**
* LeagueRosterAdminViewDataBuilder
@@ -25,6 +26,7 @@ export class LeagueRosterAdminViewDataBuilder {
},
role: member.role,
joinedAt: member.joinedAt,
formattedJoinedAt: DateDisplay.formatShort(member.joinedAt),
}));
// Transform join requests
@@ -35,6 +37,7 @@ export class LeagueRosterAdminViewDataBuilder {
name: 'Unknown Driver', // driver field is unknown type
},
requestedAt: req.requestedAt,
formattedRequestedAt: DateDisplay.formatShort(req.requestedAt),
message: req.message,
}));

View File

@@ -1,5 +1,7 @@
import { LeagueSponsorshipsViewData } from '@/lib/view-data/leagues/LeagueSponsorshipsViewData';
import { LeagueSponsorshipsApiDto } from '@/lib/types/tbd/LeagueSponsorshipsApiDto';
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
import { StatusDisplay } from '@/lib/display-objects/StatusDisplay';
export class LeagueSponsorshipsViewDataBuilder {
static build(apiDto: LeagueSponsorshipsApiDto): LeagueSponsorshipsViewData {
@@ -9,7 +11,11 @@ export class LeagueSponsorshipsViewDataBuilder {
onTabChange: () => {},
league: apiDto.league,
sponsorshipSlots: apiDto.sponsorshipSlots,
sponsorshipRequests: apiDto.sponsorshipRequests,
sponsorshipRequests: apiDto.sponsorshipRequests.map(r => ({
...r,
formattedRequestedAt: DateDisplay.formatShort(r.requestedAt),
statusLabel: StatusDisplay.protestStatus(r.status), // Reusing protest status for now
})),
};
}
}

View File

@@ -1,13 +1,15 @@
import { LeagueWalletViewData, LeagueWalletTransactionViewData } from '@/lib/view-data/leagues/LeagueWalletViewData';
import { LeagueWalletApiDto } from '@/lib/types/tbd/LeagueWalletApiDto';
import { CurrencyDisplay } from '@/lib/display-objects/CurrencyDisplay';
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
export class LeagueWalletViewDataBuilder {
static build(apiDto: LeagueWalletApiDto): LeagueWalletViewData {
const transactions: LeagueWalletTransactionViewData[] = apiDto.transactions.map(t => ({
...t,
formattedAmount: `${t.amount} ${apiDto.currency}`,
formattedAmount: CurrencyDisplay.format(t.amount, apiDto.currency),
amountColor: t.amount >= 0 ? 'green' : 'red',
formattedDate: new Date(t.createdAt).toLocaleDateString(),
formattedDate: DateDisplay.formatShort(t.createdAt),
statusColor: t.status === 'completed' ? 'green' : t.status === 'pending' ? 'yellow' : 'red',
typeColor: 'blue',
}));
@@ -15,13 +17,13 @@ export class LeagueWalletViewDataBuilder {
return {
leagueId: apiDto.leagueId,
balance: apiDto.balance,
formattedBalance: `${apiDto.balance} ${apiDto.currency}`,
formattedBalance: CurrencyDisplay.format(apiDto.balance, apiDto.currency),
totalRevenue: apiDto.balance, // Mock
formattedTotalRevenue: `${apiDto.balance} ${apiDto.currency}`,
formattedTotalRevenue: CurrencyDisplay.format(apiDto.balance, apiDto.currency),
totalFees: 0, // Mock
formattedTotalFees: `0 ${apiDto.currency}`,
formattedTotalFees: CurrencyDisplay.format(0, apiDto.currency),
pendingPayouts: 0, // Mock
formattedPendingPayouts: `0 ${apiDto.currency}`,
formattedPendingPayouts: CurrencyDisplay.format(0, apiDto.currency),
currency: apiDto.currency,
transactions,
};

View File

@@ -2,6 +2,11 @@ import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverP
import type { ProfileViewData } from '@/lib/view-data/ProfileViewData';
import { mediaConfig } from '@/lib/config/mediaConfig';
import { CountryFlagDisplay } from '@/lib/display-objects/CountryFlagDisplay';
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
import { FinishDisplay } from '@/lib/display-objects/FinishDisplay';
import { PercentDisplay } from '@/lib/display-objects/PercentDisplay';
import { RatingDisplay } from '@/lib/display-objects/RatingDisplay';
import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
export class ProfileViewDataBuilder {
static build(apiDto: GetDriverProfileOutputDTO): ProfileViewData {
@@ -29,11 +34,6 @@ export class ProfileViewDataBuilder {
const socialSummary = apiDto.socialSummary;
const extended = apiDto.extendedProfile ?? null;
const joinedAtLabel = new Date(driver.joinedAt).toLocaleDateString('en-US', {
month: 'short',
year: 'numeric',
});
return {
driver: {
id: driver.id,
@@ -42,22 +42,22 @@ export class ProfileViewDataBuilder {
countryFlag: CountryFlagDisplay.fromCountryCode(driver.country).toString(),
avatarUrl: driver.avatarUrl || mediaConfig.avatars.defaultFallback,
bio: driver.bio || null,
iracingId: driver.iracingId || null,
joinedAtLabel,
iracingId: driver.iracingId ? String(driver.iracingId) : null,
joinedAtLabel: DateDisplay.formatMonthYear(driver.joinedAt),
},
stats: stats
? {
ratingLabel: stats.rating != null ? String(stats.rating) : '0',
ratingLabel: RatingDisplay.format(stats.rating),
globalRankLabel: driver.globalRank != null ? `#${driver.globalRank}` : '—',
totalRacesLabel: String(stats.totalRaces),
winsLabel: String(stats.wins),
podiumsLabel: String(stats.podiums),
dnfsLabel: String(stats.dnfs),
bestFinishLabel: stats.bestFinish != null ? `P${stats.bestFinish}` : '—',
worstFinishLabel: stats.worstFinish != null ? `P${stats.worstFinish}` : '—',
avgFinishLabel: stats.avgFinish != null ? `P${stats.avgFinish.toFixed(1)}` : '—',
consistencyLabel: stats.consistency != null ? `${stats.consistency}%` : '0%',
percentileLabel: stats.percentile != null ? `${stats.percentile}%` : '—',
totalRacesLabel: NumberDisplay.format(stats.totalRaces),
winsLabel: NumberDisplay.format(stats.wins),
podiumsLabel: NumberDisplay.format(stats.podiums),
dnfsLabel: NumberDisplay.format(stats.dnfs),
bestFinishLabel: FinishDisplay.format(stats.bestFinish),
worstFinishLabel: FinishDisplay.format(stats.worstFinish),
avgFinishLabel: FinishDisplay.formatAverage(stats.avgFinish),
consistencyLabel: PercentDisplay.formatWhole(stats.consistency),
percentileLabel: PercentDisplay.format(stats.percentile),
}
: null,
teamMemberships: apiDto.teamMemberships.map((m) => ({
@@ -65,10 +65,7 @@ export class ProfileViewDataBuilder {
teamName: m.teamName,
teamTag: m.teamTag || null,
roleLabel: m.role,
joinedAtLabel: new Date(m.joinedAt).toLocaleDateString('en-US', {
month: 'short',
year: 'numeric',
}),
joinedAtLabel: DateDisplay.formatMonthYear(m.joinedAt),
href: `/teams/${m.teamId}`,
})),
extendedProfile: extended
@@ -89,12 +86,8 @@ export class ProfileViewDataBuilder {
id: a.id,
title: a.title,
description: a.description,
earnedAtLabel: new Date(a.earnedAt).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
}),
icon: a.icon as NonNullable<ProfileViewData['extendedProfile']>['achievements'][number]['icon'],
earnedAtLabel: DateDisplay.formatShort(a.earnedAt),
icon: a.icon as any,
rarityLabel: a.rarity,
})),
friends: socialSummary.friends.slice(0, 8).map((f) => ({
@@ -104,7 +97,7 @@ export class ProfileViewDataBuilder {
avatarUrl: f.avatarUrl || mediaConfig.avatars.defaultFallback,
href: `/drivers/${f.id}`,
})),
friendsCountLabel: String(socialSummary.friendsCount),
friendsCountLabel: NumberDisplay.format(socialSummary.friendsCount),
}
: null,
};

View File

@@ -1,5 +1,7 @@
import type { SponsorDashboardDTO } from '@/lib/types/generated/SponsorDashboardDTO';
import type { SponsorDashboardViewData } from '@/lib/view-data/SponsorDashboardViewData';
import { CurrencyDisplay } from '@/lib/display-objects/CurrencyDisplay';
import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
/**
* Sponsor Dashboard ViewData Builder
@@ -9,26 +11,28 @@ import type { SponsorDashboardViewData } from '@/lib/view-data/SponsorDashboardV
*/
export class SponsorDashboardViewDataBuilder {
static build(apiDto: SponsorDashboardDTO): SponsorDashboardViewData {
const totalInvestmentValue = apiDto.investment.activeSponsorships * 1000;
return {
sponsorName: apiDto.sponsorName,
totalImpressions: apiDto.metrics.impressions.toString(),
totalInvestment: `$${apiDto.investment.activeSponsorships * 1000}`, // Mock calculation
totalImpressions: NumberDisplay.format(apiDto.metrics.impressions),
totalInvestment: CurrencyDisplay.format(totalInvestmentValue),
metrics: {
impressionsChange: apiDto.metrics.impressions > 1000 ? 15 : -5,
viewersChange: 8,
exposureChange: 12,
},
categoryData: {
leagues: { count: 2, impressions: 1500 },
teams: { count: 1, impressions: 800 },
drivers: { count: 3, impressions: 2200 },
races: { count: 1, impressions: 500 },
platform: { count: 0, impressions: 0 },
leagues: { count: 2, countLabel: '2 active', impressions: 1500, impressionsLabel: '1,500' },
teams: { count: 1, countLabel: '1 active', impressions: 800, impressionsLabel: '800' },
drivers: { count: 3, countLabel: '3 active', impressions: 2200, impressionsLabel: '2,200' },
races: { count: 1, countLabel: '1 active', impressions: 500, impressionsLabel: '500' },
platform: { count: 0, countLabel: '0 active', impressions: 0, impressionsLabel: '0' },
},
sponsorships: apiDto.sponsorships,
activeSponsorships: apiDto.investment.activeSponsorships,
formattedTotalInvestment: `$${apiDto.investment.activeSponsorships * 1000}`,
costPerThousandViews: '$50',
formattedTotalInvestment: CurrencyDisplay.format(totalInvestmentValue),
costPerThousandViews: CurrencyDisplay.format(50),
upcomingRenewals: [], // Mock empty for now
recentActivity: [], // Mock empty for now
};

View File

@@ -3,6 +3,7 @@ import type { TeamDetailViewData, TeamDetailData, TeamMemberData, SponsorMetric,
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
import { MemberDisplay } from '@/lib/display-objects/MemberDisplay';
import { LeagueDisplay } from '@/lib/display-objects/LeagueDisplay';
import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
/**
* TeamDetailViewDataBuilder - Transforms TeamDetailPageDto into ViewData
@@ -47,19 +48,19 @@ export class TeamDetailViewDataBuilder {
{
icon: 'users',
label: 'Members',
value: memberships.length,
value: NumberDisplay.format(memberships.length),
color: 'text-primary-blue',
},
{
icon: 'zap',
label: 'Est. Reach',
value: memberships.length * 15,
value: NumberDisplay.format(memberships.length * 15),
color: 'text-purple-400',
},
{
icon: 'calendar',
label: 'Races',
value: leagueCount,
value: NumberDisplay.format(leagueCount),
color: 'text-neon-aqua',
},
{

View File

@@ -1,6 +1,8 @@
import type { TeamsPageDto } from '@/lib/page-queries/TeamsPageQuery';
import type { TeamsViewData, TeamSummaryData } from '@/lib/view-data/TeamsViewData';
import type { TeamListItemDTO } from '@/lib/types/generated/TeamListItemDTO';
import { RatingDisplay } from '@/lib/display-objects/RatingDisplay';
import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
/**
* TeamsViewDataBuilder - Transforms TeamsPageDto into ViewData for TeamsTemplate
@@ -14,6 +16,9 @@ export class TeamsViewDataBuilder {
leagueName: team.leagues[0] || '',
memberCount: team.memberCount,
logoUrl: team.logoUrl,
ratingLabel: RatingDisplay.format(team.rating),
winsLabel: NumberDisplay.format(team.totalWins || 0),
racesLabel: NumberDisplay.format(team.totalRaces || 0),
}));
return { teams };