From 891b3cf0eed6d9eecb4606a37e83c1d1752bbb75 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Sat, 24 Jan 2026 01:07:43 +0100 Subject: [PATCH] do to formatters --- apps/website/app/leagues/[id]/roster/page.tsx | 14 +- apps/website/app/sponsor/campaigns/page.tsx | 86 ---- .../client-wrapper/RosterAdminPage.tsx | 24 +- .../achievements/AchievementCard.tsx | 6 +- .../achievements/AchievementGrid.tsx | 8 +- .../components/admin/AdminUsersTable.tsx | 24 +- .../components/drivers/DriverEntryRow.tsx | 4 +- .../components/drivers/ProfileHero.tsx | 8 +- .../errors/ErrorAnalyticsDashboard.tsx | 57 ++- apps/website/components/feed/FeedItemCard.tsx | 8 +- apps/website/components/feed/FeedLayout.tsx | 10 +- .../leaderboards/DriverLeaderboardPreview.tsx | 12 +- .../leaderboards/LeaderboardPodium.tsx | 12 +- .../components/leaderboards/RankMedal.tsx | 11 +- .../components/leaderboards/RankingRow.tsx | 13 +- .../leaderboards/RankingsPodium.tsx | 9 +- .../leagues/EnhancedLeagueSchedulePanel.tsx | 26 +- .../components/leagues/LeagueActivityFeed.tsx | 6 +- .../components/leagues/LeagueMemberRow.tsx | 8 +- .../leagues/LeagueReviewSummary.tsx | 54 ++- .../components/leagues/RaceDetailModal.tsx | 39 +- .../profile/ProfileDetailsPanel.tsx | 14 +- .../components/profile/ProfileHeader.tsx | 14 +- .../profile/SponsorshipRequestsPanel.tsx | 4 +- .../components/races/RaceCardWrapper.tsx | 13 +- .../components/races/RaceHeroWrapper.tsx | 6 +- .../components/races/RaceListItemWrapper.tsx | 14 +- .../races/RaceResultCardWrapper.tsx | 4 +- .../components/races/RaceResultRow.tsx | 11 +- .../components/social/FriendsPreview.tsx | 4 +- apps/website/components/teams/TeamRoster.tsx | 18 +- .../website/hooks/league/useLeagueSchedule.ts | 12 +- .../league/useLeagueScheduleAdminPageData.ts | 14 +- .../view-data/DashboardViewDataBuilder.ts | 42 +- .../view-data/DriverProfileViewDataBuilder.ts | 38 +- .../DriverRankingsViewDataBuilder.ts | 12 +- .../view-data/DriversViewDataBuilder.ts | 16 +- .../view-data/HealthViewDataBuilder.ts | 54 +-- .../builders/view-data/HomeViewDataBuilder.ts | 6 +- .../LeagueRosterAdminViewDataBuilder.ts | 12 +- .../LeagueSponsorshipsViewDataBuilder.ts | 8 +- .../view-data/ProfileViewDataBuilder.ts | 48 +-- .../view-data/RacesViewDataBuilder.ts | 18 +- .../view-data/TeamDetailViewDataBuilder.ts | 24 +- .../view-data/TeamsViewDataBuilder.ts | 12 +- .../leagues/LeagueWizardCommandModel.ts | 2 +- .../{DisplayObject.ts => Formatter.ts} | 11 +- .../DashboardCountDisplay.test.ts | 38 -- .../DashboardViewDataConsistency.test.ts | 369 ------------------ .../AchievementFormatter.ts} | 2 +- .../ActivityLevelFormatter.ts} | 2 +- .../AvatarFormatter.ts} | 2 +- .../CountryFlagFormatter.ts} | 10 +- .../CurrencyFormatter.ts} | 2 +- .../DashboardConsistencyFormatter.test.ts} | 4 +- .../DashboardConsistencyFormatter.ts} | 2 +- .../DashboardCountFormatter.test.ts | 38 ++ .../DashboardCountFormatter.ts} | 2 +- .../DashboardDateFormatter.test.ts} | 0 .../DashboardDateFormatter.ts} | 2 +- .../DashboardLeaguePositionFormatter.test.ts} | 4 +- .../DashboardLeaguePositionFormatter.ts} | 2 +- .../DashboardRankFormatter.test.ts} | 4 +- .../DashboardRankFormatter.ts} | 2 +- .../DateFormatter.ts} | 2 +- .../DriverRegistrationStatusFormatter.tsx} | 2 +- .../DurationFormatter.ts} | 2 +- .../FinishFormatter.ts} | 2 +- .../HealthAlertFormatter.ts} | 2 +- .../HealthComponentFormatter.ts} | 2 +- .../HealthMetricFormatter.ts} | 2 +- .../HealthStatusFormatter.ts} | 2 +- .../LeagueCreationStatusFormatter.ts} | 2 +- .../LeagueFormatter.ts} | 2 +- .../LeagueRoleFormatter.ts} | 2 +- .../LeagueTierFormatter.ts} | 2 +- .../LeagueWizardValidationMessages.ts | 1 + .../MedalFormatter.ts} | 2 +- .../MemberFormatter.ts} | 2 +- .../MembershipFeeTypeFormatter.ts} | 2 +- .../MemoryFormatter.ts} | 2 +- .../NumberFormatter.ts} | 2 +- .../OnboardingStatusFormatter.ts} | 2 +- .../PayerTypeFormatter.ts} | 2 +- .../PaymentTypeFormatter.ts} | 2 +- .../PercentFormatter.ts} | 2 +- .../PrizeTypeFormatter.ts} | 2 +- .../ProfileFormatter.ts} | 12 +- .../RaceStatusFormatter.ts} | 2 +- .../RatingFormatter.test.ts} | 0 .../RatingFormatter.ts} | 6 +- .../RatingTrendFormatter.ts} | 2 +- .../RelativeTimeFormatter.ts} | 2 +- .../SeasonStatusFormatter.ts} | 2 +- .../SkillLevelFormatter.ts} | 2 +- .../SkillLevelIconFormatter.ts} | 2 +- .../StatusFormatter.ts} | 2 +- .../TeamCreationStatusFormatter.ts} | 2 +- .../TimeFormatter.ts} | 2 +- .../TransactionTypeFormatter.ts} | 2 +- .../UserRoleFormatter.ts} | 2 +- .../UserStatusFormatter.ts} | 2 +- .../WinRateFormatter.ts} | 2 +- apps/website/lib/services/home/HomeService.ts | 10 +- .../lib/view-models/AdminUserViewModel.ts | 16 +- .../view-models/AnalyticsMetricsViewModel.ts | 16 +- .../view-models/AvailableLeaguesViewModel.ts | 18 +- .../lib/view-models/AvatarViewModel.ts | 8 +- .../CompleteOnboardingViewModel.ts | 10 +- .../lib/view-models/CreateLeagueViewModel.ts | 6 +- .../lib/view-models/CreateTeamViewModel.ts | 6 +- .../view-models/DashboardStatsViewModel.ts | 8 +- .../DriverLeaderboardItemViewModel.ts | 18 +- .../DriverProfileDriverSummaryViewModel.ts | 14 +- .../DriverRegistrationStatusViewModel.ts | 8 +- .../lib/view-models/DriverSummaryViewModel.ts | 8 +- .../lib/view-models/DriverTeamViewModel.ts | 4 +- .../lib/view-models/DriverViewModel.ts | 4 +- .../lib/view-models/LeagueMemberViewModel.ts | 4 +- .../lib/view-models/LeagueViewModel.ts | 8 +- .../lib/view-models/LeagueWalletViewModel.ts | 12 +- .../lib/view-models/MembershipFeeViewModel.ts | 14 +- .../lib/view-models/PaymentViewModel.ts | 24 +- .../website/lib/view-models/PrizeViewModel.ts | 18 +- .../lib/view-models/ProtestViewModel.ts | 10 +- .../RaceDetailUserResultViewModel.ts | 4 +- .../lib/view-models/RaceListItemViewModel.ts | 8 +- .../lib/view-models/RaceResultViewModel.ts | 12 +- .../lib/view-models/RenewalAlertViewModel.ts | 8 +- .../view-models/SponsorshipDetailViewModel.ts | 4 +- .../SponsorshipPricingViewModel.ts | 8 +- .../SponsorshipRequestViewModel.ts | 8 +- .../lib/view-models/SponsorshipViewModel.ts | 14 +- .../lib/view-models/StandingEntryViewModel.ts | 4 +- .../view-models/TeamJoinRequestViewModel.ts | 4 +- .../lib/view-models/TeamMemberViewModel.ts | 6 +- .../view-models/UpcomingRaceCardViewModel.ts | 4 +- .../view-models/WalletTransactionViewModel.ts | 12 +- .../lib/view-models/WalletViewModel.ts | 6 +- .../templates/LeagueScheduleTemplate.tsx | 25 +- 140 files changed, 656 insertions(+), 1159 deletions(-) rename apps/website/lib/contracts/formatters/{DisplayObject.ts => Formatter.ts} (61%) delete mode 100644 apps/website/lib/display-objects/DashboardCountDisplay.test.ts delete mode 100644 apps/website/lib/display-objects/DashboardViewDataConsistency.test.ts rename apps/website/lib/{display-objects/AchievementDisplay.ts => formatters/AchievementFormatter.ts} (96%) rename apps/website/lib/{display-objects/ActivityLevelDisplay.ts => formatters/ActivityLevelFormatter.ts} (94%) rename apps/website/lib/{display-objects/AvatarDisplay.ts => formatters/AvatarFormatter.ts} (96%) rename apps/website/lib/{display-objects/CountryFlagDisplay.ts => formatters/CountryFlagFormatter.ts} (61%) rename apps/website/lib/{display-objects/CurrencyDisplay.ts => formatters/CurrencyFormatter.ts} (97%) rename apps/website/lib/{display-objects/DashboardConsistencyDisplay.test.ts => formatters/DashboardConsistencyFormatter.test.ts} (92%) rename apps/website/lib/{display-objects/DashboardConsistencyDisplay.ts => formatters/DashboardConsistencyFormatter.ts} (80%) create mode 100644 apps/website/lib/formatters/DashboardCountFormatter.test.ts rename apps/website/lib/{display-objects/DashboardCountDisplay.ts => formatters/DashboardCountFormatter.ts} (87%) rename apps/website/lib/{display-objects/DashboardDateDisplay.test.ts => formatters/DashboardDateFormatter.test.ts} (100%) rename apps/website/lib/{display-objects/DashboardDateDisplay.ts => formatters/DashboardDateFormatter.ts} (97%) rename apps/website/lib/{display-objects/DashboardLeaguePositionDisplay.test.ts => formatters/DashboardLeaguePositionFormatter.test.ts} (93%) rename apps/website/lib/{display-objects/DashboardLeaguePositionDisplay.ts => formatters/DashboardLeaguePositionFormatter.ts} (85%) rename apps/website/lib/{display-objects/DashboardRankDisplay.test.ts => formatters/DashboardRankFormatter.test.ts} (83%) rename apps/website/lib/{display-objects/DashboardRankDisplay.ts => formatters/DashboardRankFormatter.ts} (81%) rename apps/website/lib/{display-objects/DateDisplay.ts => formatters/DateFormatter.ts} (98%) rename apps/website/lib/{display-objects/DriverRegistrationStatusDisplay.tsx => formatters/DriverRegistrationStatusFormatter.tsx} (91%) rename apps/website/lib/{display-objects/DurationDisplay.ts => formatters/DurationFormatter.ts} (93%) rename apps/website/lib/{display-objects/FinishDisplay.ts => formatters/FinishFormatter.ts} (94%) rename apps/website/lib/{display-objects/HealthAlertDisplay.ts => formatters/HealthAlertFormatter.ts} (97%) rename apps/website/lib/{display-objects/HealthComponentDisplay.ts => formatters/HealthComponentFormatter.ts} (97%) rename apps/website/lib/{display-objects/HealthMetricDisplay.ts => formatters/HealthMetricFormatter.ts} (97%) rename apps/website/lib/{display-objects/HealthStatusDisplay.ts => formatters/HealthStatusFormatter.ts} (98%) rename apps/website/lib/{display-objects/LeagueCreationStatusDisplay.ts => formatters/LeagueCreationStatusFormatter.ts} (87%) rename apps/website/lib/{display-objects/LeagueDisplay.ts => formatters/LeagueFormatter.ts} (90%) rename apps/website/lib/{display-objects/LeagueRoleDisplay.ts => formatters/LeagueRoleFormatter.ts} (96%) rename apps/website/lib/{display-objects/LeagueTierDisplay.ts => formatters/LeagueTierFormatter.ts} (96%) rename apps/website/lib/{display-objects => formatters}/LeagueWizardValidationMessages.ts (96%) rename apps/website/lib/{display-objects/MedalDisplay.ts => formatters/MedalFormatter.ts} (96%) rename apps/website/lib/{display-objects/MemberDisplay.ts => formatters/MemberFormatter.ts} (93%) rename apps/website/lib/{display-objects/MembershipFeeTypeDisplay.ts => formatters/MembershipFeeTypeFormatter.ts} (84%) rename apps/website/lib/{display-objects/MemoryDisplay.ts => formatters/MemoryFormatter.ts} (92%) rename apps/website/lib/{display-objects/NumberDisplay.ts => formatters/NumberFormatter.ts} (96%) rename apps/website/lib/{display-objects/OnboardingStatusDisplay.ts => formatters/OnboardingStatusFormatter.ts} (95%) rename apps/website/lib/{display-objects/PayerTypeDisplay.ts => formatters/PayerTypeFormatter.ts} (75%) rename apps/website/lib/{display-objects/PaymentTypeDisplay.ts => formatters/PaymentTypeFormatter.ts} (76%) rename apps/website/lib/{display-objects/PercentDisplay.ts => formatters/PercentFormatter.ts} (94%) rename apps/website/lib/{display-objects/PrizeTypeDisplay.ts => formatters/PrizeTypeFormatter.ts} (86%) rename apps/website/lib/{display-objects/ProfileDisplay.ts => formatters/ProfileFormatter.ts} (90%) rename apps/website/lib/{display-objects/RaceStatusDisplay.ts => formatters/RaceStatusFormatter.ts} (96%) rename apps/website/lib/{display-objects/RatingDisplay.test.ts => formatters/RatingFormatter.test.ts} (100%) rename apps/website/lib/{display-objects/RatingDisplay.ts => formatters/RatingFormatter.ts} (63%) rename apps/website/lib/{display-objects/RatingTrendDisplay.ts => formatters/RatingTrendFormatter.ts} (93%) rename apps/website/lib/{display-objects/RelativeTimeDisplay.ts => formatters/RelativeTimeFormatter.ts} (97%) rename apps/website/lib/{display-objects/SeasonStatusDisplay.ts => formatters/SeasonStatusFormatter.ts} (95%) rename apps/website/lib/{display-objects/SkillLevelDisplay.ts => formatters/SkillLevelFormatter.ts} (97%) rename apps/website/lib/{display-objects/SkillLevelIconDisplay.ts => formatters/SkillLevelIconFormatter.ts} (86%) rename apps/website/lib/{display-objects/StatusDisplay.ts => formatters/StatusFormatter.ts} (96%) rename apps/website/lib/{display-objects/TeamCreationStatusDisplay.ts => formatters/TeamCreationStatusFormatter.ts} (88%) rename apps/website/lib/{display-objects/TimeDisplay.ts => formatters/TimeFormatter.ts} (94%) rename apps/website/lib/{display-objects/TransactionTypeDisplay.ts => formatters/TransactionTypeFormatter.ts} (72%) rename apps/website/lib/{display-objects/UserRoleDisplay.ts => formatters/UserRoleFormatter.ts} (91%) rename apps/website/lib/{display-objects/UserStatusDisplay.ts => formatters/UserStatusFormatter.ts} (96%) rename apps/website/lib/{display-objects/WinRateDisplay.ts => formatters/WinRateFormatter.ts} (91%) diff --git a/apps/website/app/leagues/[id]/roster/page.tsx b/apps/website/app/leagues/[id]/roster/page.tsx index d2ed39763..0f0476b67 100644 --- a/apps/website/app/leagues/[id]/roster/page.tsx +++ b/apps/website/app/leagues/[id]/roster/page.tsx @@ -1,11 +1,11 @@ -import { LeagueDetailPageQuery } from '@/lib/page-queries/LeagueDetailPageQuery'; -import { notFound } from 'next/navigation'; -import { Box } from '@/ui/Box'; -import { Text } from '@/ui/Text'; -import { Stack } from '@/ui/Stack'; import { RosterTable } from '@/components/leagues/RosterTable'; +import { LeagueDetailPageQuery } from '@/lib/page-queries/LeagueDetailPageQuery'; +import { Box } from '@/ui/Box'; +import { Stack } from '@/ui/Stack'; +import { Text } from '@/ui/Text'; +import { notFound } from 'next/navigation'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; interface Props { params: Promise<{ id: string }>; @@ -25,7 +25,7 @@ export default async function LeagueRosterPage({ params }: Props) { driverName: m.driver.name, role: m.role, joinedAt: m.joinedAt, - joinedAtLabel: DateDisplay.formatShort(m.joinedAt) + joinedAtLabel: DateFormatter.formatShort(m.joinedAt) })); return ( diff --git a/apps/website/app/sponsor/campaigns/page.tsx b/apps/website/app/sponsor/campaigns/page.tsx index 4f1475435..d417415e1 100644 --- a/apps/website/app/sponsor/campaigns/page.tsx +++ b/apps/website/app/sponsor/campaigns/page.tsx @@ -1,89 +1,3 @@ 'use client'; -import { useSponsorSponsorships } from "@/hooks/sponsor/useSponsorSponsorships"; -import { SponsorCampaignsTemplate, SponsorshipType, SponsorCampaignsViewData } from "@/templates/SponsorCampaignsTemplate"; -import { Box } from "@/ui/Box"; -import { Button } from "@/ui/Button"; -import { Text } from "@/ui/Text"; -import { useState } from 'react'; -import { CurrencyDisplay } from "@/lib/display-objects/CurrencyDisplay"; -import { NumberDisplay } from "@/lib/display-objects/NumberDisplay"; -import { DateDisplay } from "@/lib/display-objects/DateDisplay"; -import { StatusDisplay } from "@/lib/display-objects/StatusDisplay"; -export default function SponsorCampaignsPage() { - const [typeFilter, setTypeFilter] = useState('all'); - const [searchQuery, setSearchQuery] = useState(''); - const { data: sponsorshipsData, isLoading, error, retry } = useSponsorSponsorships('demo-sponsor-1'); - - if (isLoading) { - return ( - - - - Loading sponsorships... - - - ); - } - - if (error || !sponsorshipsData) { - return ( - - - {error?.getUserMessage() || 'No sponsorships data available'} - {error && ( - - )} - - - ); - } - - // Calculate stats - const totalInvestment = sponsorshipsData.sponsorships.filter((s: any) => s.status === 'active').reduce((sum: number, s: any) => sum + s.price, 0); - const totalImpressions = sponsorshipsData.sponsorships.reduce((sum: number, s: any) => sum + s.impressions, 0); - - const stats = { - total: sponsorshipsData.sponsorships.length, - active: sponsorshipsData.sponsorships.filter((s: any) => s.status === 'active').length, - pending: sponsorshipsData.sponsorships.filter((s: any) => s.status === 'pending_approval').length, - approved: sponsorshipsData.sponsorships.filter((s: any) => s.status === 'approved').length, - rejected: sponsorshipsData.sponsorships.filter((s: any) => s.status === 'rejected').length, - formattedTotalInvestment: CurrencyDisplay.format(totalInvestment), - formattedTotalImpressions: NumberDisplay.formatCompact(totalImpressions), - }; - - const sponsorships = sponsorshipsData.sponsorships.map((s: any) => ({ - ...s, - formattedInvestment: CurrencyDisplay.format(s.price), - formattedImpressions: NumberDisplay.format(s.impressions), - formattedStartDate: s.seasonStartDate ? DateDisplay.formatShort(s.seasonStartDate) : undefined, - formattedEndDate: s.seasonEndDate ? DateDisplay.formatShort(s.seasonEndDate) : undefined, - })); - - const viewData: SponsorCampaignsViewData = { - sponsorships, - stats: stats as any, - }; - - const filteredSponsorships = sponsorships.filter((s: any) => { - // For now, we only have leagues in the DTO - if (typeFilter !== 'all' && typeFilter !== 'leagues') return false; - if (searchQuery && !s.leagueName.toLowerCase().includes(searchQuery.toLowerCase())) return false; - return true; - }); - - return ( - - ); -} \ No newline at end of file diff --git a/apps/website/client-wrapper/RosterAdminPage.tsx b/apps/website/client-wrapper/RosterAdminPage.tsx index 5deaa0975..5ecf35c12 100644 --- a/apps/website/client-wrapper/RosterAdminPage.tsx +++ b/apps/website/client-wrapper/RosterAdminPage.tsx @@ -1,27 +1,27 @@ 'use client'; -import type { MembershipRole } from '@/lib/types/MembershipRole'; -import { useParams } from 'next/navigation'; -import { useMemo } from 'react'; import { + useApproveJoinRequest, useLeagueJoinRequests, useLeagueRosterAdmin, - useApproveJoinRequest, useRejectJoinRequest, - useUpdateMemberRole, useRemoveMember, + useUpdateMemberRole, } from "@/hooks/league/useLeagueRosterAdmin"; -import { RosterAdminTemplate } from '@/templates/RosterAdminTemplate'; -import type { JoinRequestData, RosterMemberData, LeagueRosterAdminViewData } from '@/lib/view-data/LeagueRosterAdminViewData'; +import { ClientWrapperProps } from '@/lib/contracts/components/ComponentContracts'; import type { LeagueRosterJoinRequestDTO } from '@/lib/types/generated/LeagueRosterJoinRequestDTO'; import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO'; -import { ClientWrapperProps } from '@/lib/contracts/components/ComponentContracts'; +import type { MembershipRole } from '@/lib/types/MembershipRole'; +import type { JoinRequestData, LeagueRosterAdminViewData, RosterMemberData } from '@/lib/view-data/LeagueRosterAdminViewData'; +import { RosterAdminTemplate } from '@/templates/RosterAdminTemplate'; +import { useParams } from 'next/navigation'; +import { useMemo } from 'react'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; const ROLE_OPTIONS: MembershipRole[] = ['owner', 'admin', 'steward', 'member']; -export function RosterAdminPage({ viewData: initialViewData }: Partial>) { +export function RosterAdminPage({ }: Partial>) { const params = useParams(); const leagueId = params.id as string; @@ -83,7 +83,7 @@ export function RosterAdminPage({ viewData: initialViewData }: Partial ({ @@ -91,7 +91,7 @@ export function RosterAdminPage({ viewData: initialViewData }: Partial{title} {description} - {DateDisplay.formatShort(unlockedAt)} + {DateFormatter.formatShort(unlockedAt)} diff --git a/apps/website/components/achievements/AchievementGrid.tsx b/apps/website/components/achievements/AchievementGrid.tsx index 27e6925ce..f306b75ae 100644 --- a/apps/website/components/achievements/AchievementGrid.tsx +++ b/apps/website/components/achievements/AchievementGrid.tsx @@ -1,15 +1,13 @@ -import { AchievementDisplay } from '@/lib/display-objects/AchievementDisplay'; +import { AchievementFormatter } from '@/lib/formatters/AchievementFormatter'; import { Card } from '@/ui/Card'; import { Grid } from '@/ui/Grid'; +import { Group } from '@/ui/Group'; import { Heading } from '@/ui/Heading'; import { Icon } from '@/ui/Icon'; -import { Group } from '@/ui/Group'; import { Stack } from '@/ui/Stack'; -import { Box } from '@/ui/Box'; import { Surface } from '@/ui/Surface'; import { Text } from '@/ui/Text'; import { Award, Crown, Medal, Star, Target, Trophy, Zap } from 'lucide-react'; -import React from 'react'; interface Achievement { id: string; @@ -53,7 +51,7 @@ export function AchievementGrid({ achievements }: AchievementGridProps) { {achievements.map((achievement) => { const AchievementIcon = getAchievementIcon(achievement.icon); - const rarity = AchievementDisplay.getRarityVariant(achievement.rarity); + const rarity = AchievementFormatter.getRarityVariant(achievement.rarity); return ( - {user.lastLoginAt ? DateDisplay.formatShort(user.lastLoginAt) : 'Never'} + {user.lastLoginAt ? DateFormatter.formatShort(user.lastLoginAt) : 'Never'} diff --git a/apps/website/components/drivers/DriverEntryRow.tsx b/apps/website/components/drivers/DriverEntryRow.tsx index 16653ff64..f433534db 100644 --- a/apps/website/components/drivers/DriverEntryRow.tsx +++ b/apps/website/components/drivers/DriverEntryRow.tsx @@ -1,6 +1,6 @@ -import { CountryFlagDisplay } from '@/lib/display-objects/CountryFlagDisplay'; +import { CountryFlagFormatter } from '@/lib/formatters/CountryFlagFormatter'; import { Badge } from '@/ui/Badge'; import { Icon } from '@/ui/Icon'; import { Image } from '@/ui/Image'; @@ -88,7 +88,7 @@ export function DriverEntryRow({ justifyContent="center" fontSize="0.625rem" > - {CountryFlagDisplay.fromCountryCode(country).toString()} + {CountryFlagFormatter.fromCountryCode(country).toString()} diff --git a/apps/website/components/drivers/ProfileHero.tsx b/apps/website/components/drivers/ProfileHero.tsx index 6e76ef343..979113919 100644 --- a/apps/website/components/drivers/ProfileHero.tsx +++ b/apps/website/components/drivers/ProfileHero.tsx @@ -1,16 +1,16 @@ import { mediaConfig } from '@/lib/config/mediaConfig'; -import { CountryFlagDisplay } from '@/lib/display-objects/CountryFlagDisplay'; +import { CountryFlagFormatter } from '@/lib/formatters/CountryFlagFormatter'; +import { Box } from '@/ui/Box'; import { Button } from '@/ui/Button'; import { Heading } from '@/ui/Heading'; +import { Icon } from '@/ui/Icon'; import { Image } from '@/ui/Image'; import { Link } from '@/ui/Link'; import { Stack } from '@/ui/Stack'; import { Surface } from '@/ui/Surface'; import { Text } from '@/ui/Text'; -import { Box } from '@/ui/Box'; -import { Icon } from '@/ui/Icon'; import { Calendar, Clock, ExternalLink, Globe, Star, Trophy, UserPlus } from 'lucide-react'; interface ProfileHeroProps { @@ -93,7 +93,7 @@ export function ProfileHero({ {driver.name} - {CountryFlagDisplay.fromCountryCode(driver.country).toString()} + {CountryFlagFormatter.fromCountryCode(driver.country).toString()} diff --git a/apps/website/components/errors/ErrorAnalyticsDashboard.tsx b/apps/website/components/errors/ErrorAnalyticsDashboard.tsx index 9e8f2d4ca..2cf7307b9 100644 --- a/apps/website/components/errors/ErrorAnalyticsDashboard.tsx +++ b/apps/website/components/errors/ErrorAnalyticsDashboard.tsx @@ -11,52 +11,41 @@ import { Input } from '@/ui/Input'; import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; import { - Activity, - AlertTriangle, - Bug, - ChevronDown, - Clock, - Copy, - Cpu, - Download, - FileText, - Globe, - RefreshCw, - Search, - Terminal, - Trash2, - Zap + Activity, + AlertTriangle, + Bug, + ChevronDown, + Clock, + Copy, + Cpu, + Download, + FileText, + Globe, + RefreshCw, + Search, + Terminal, + Trash2, + Zap } from 'lucide-react'; import { useEffect, useState } from 'react'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; -import { DurationDisplay } from '@/lib/display-objects/DurationDisplay'; -import { MemoryDisplay } from '@/lib/display-objects/MemoryDisplay'; -import { PercentDisplay } from '@/lib/display-objects/PercentDisplay'; -import { TimeDisplay } from '@/lib/display-objects/TimeDisplay'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; +import { DurationFormatter } from '@/lib/formatters/DurationFormatter'; +import { MemoryFormatter } from '@/lib/formatters/MemoryFormatter'; +import { PercentFormatter } from '@/lib/formatters/PercentFormatter'; -interface ErrorAnalyticsDashboardProps { - /** - * Auto-refresh interval in milliseconds - */ - refreshInterval?: number; - /** - * Whether to show in production (default: false) - */ - showInProduction?: boolean; -} function formatDuration(duration: number): string { - return DurationDisplay.formatMs(duration); + return DurationFormatter.formatMs(duration); } function formatPercentage(value: number, total: number): string { if (total === 0) return '0%'; - return PercentDisplay.format(value / total); + return PercentFormatter.format(value / total); } function formatMemory(bytes: number): string { - return MemoryDisplay.formatMB(bytes); + return MemoryFormatter.formatMB(bytes); } interface PerformanceWithMemory extends Performance { @@ -327,7 +316,7 @@ export function ErrorAnalyticsDashboard({ {error.type} - {DateDisplay.formatTime(error.timestamp)} + {DateFormatter.formatTime(error.timestamp)} {error.message} diff --git a/apps/website/components/feed/FeedItemCard.tsx b/apps/website/components/feed/FeedItemCard.tsx index a91639eae..e632852d2 100644 --- a/apps/website/components/feed/FeedItemCard.tsx +++ b/apps/website/components/feed/FeedItemCard.tsx @@ -1,11 +1,11 @@ 'use client'; -import React, { useEffect, useState } from 'react'; +import { TimeFormatter } from '@/lib/formatters/TimeFormatter'; import { Button } from '@/ui/Button'; import { FeedItem } from '@/ui/FeedItem'; -import { Text } from '@/ui/Text'; import { Stack } from '@/ui/Stack'; -import { TimeDisplay } from '@/lib/display-objects/TimeDisplay'; +import { Text } from '@/ui/Text'; +import { useEffect, useState } from 'react'; interface FeedItemData { id: string; @@ -50,7 +50,7 @@ export function FeedItemCard({ item }: FeedItemCardProps) { name: actor?.name || 'Unknown', avatar: actor?.avatarUrl }} - timestamp={TimeDisplay.timeAgo(item.timestamp)} + timestamp={TimeFormatter.timeAgo(item.timestamp)} content={ {item.headline} diff --git a/apps/website/components/feed/FeedLayout.tsx b/apps/website/components/feed/FeedLayout.tsx index f88aed306..313d34b93 100644 --- a/apps/website/components/feed/FeedLayout.tsx +++ b/apps/website/components/feed/FeedLayout.tsx @@ -1,14 +1,14 @@ import { FeedList } from '@/components/feed/FeedList'; import { LatestResultsSidebar } from '@/components/races/LatestResultsSidebar'; import { UpcomingRacesSidebar } from '@/components/races/UpcomingRacesSidebar'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; import { Card } from '@/ui/Card'; import { Container } from '@/ui/Container'; -import { Heading } from '@/ui/Heading'; import { Grid } from '@/ui/Grid'; -import { Stack } from '@/ui/Stack'; +import { Heading } from '@/ui/Heading'; import { Section } from '@/ui/Section'; +import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; interface FeedItemData { id: string; @@ -49,12 +49,12 @@ export function FeedLayout({ }: FeedLayoutProps) { const formattedUpcomingRaces = upcomingRaces.map(r => ({ ...r, - formattedDate: DateDisplay.formatShort(r.scheduledAt), + formattedDate: DateFormatter.formatShort(r.scheduledAt), })); const formattedLatestResults = latestResults.map(r => ({ ...r, - formattedDate: DateDisplay.formatShort(r.scheduledAt), + formattedDate: DateFormatter.formatShort(r.scheduledAt), })); return ( diff --git a/apps/website/components/leaderboards/DriverLeaderboardPreview.tsx b/apps/website/components/leaderboards/DriverLeaderboardPreview.tsx index ae5ae214a..ef4bdda05 100644 --- a/apps/website/components/leaderboards/DriverLeaderboardPreview.tsx +++ b/apps/website/components/leaderboards/DriverLeaderboardPreview.tsx @@ -1,11 +1,11 @@ import { RankBadge } from '@/components/leaderboards/RankBadge'; -import { RatingDisplay } from '@/lib/display-objects/RatingDisplay'; -import { SkillLevelDisplay } from '@/lib/display-objects/SkillLevelDisplay'; +import { RatingFormatter } from '@/lib/formatters/RatingFormatter'; +import { SkillLevelFormatter } from '@/lib/formatters/SkillLevelFormatter'; import { Avatar } from '@/ui/Avatar'; +import { Group } from '@/ui/Group'; import { LeaderboardList } from '@/ui/LeaderboardList'; import { LeaderboardPreviewShell } from '@/ui/LeaderboardPreviewShell'; import { LeaderboardRow } from '@/ui/LeaderboardRow'; -import { Group } from '@/ui/Group'; import { Text } from '@/ui/Text'; import { Trophy } from 'lucide-react'; @@ -69,8 +69,8 @@ export function DriverLeaderboardPreview({ {driver.nationality} - - {SkillLevelDisplay.getLabel(driver.skillLevel)} + + {SkillLevelFormatter.getLabel(driver.skillLevel)} @@ -80,7 +80,7 @@ export function DriverLeaderboardPreview({ - {RatingDisplay.format(driver.rating)} + {RatingFormatter.format(driver.rating)} Rating diff --git a/apps/website/components/leaderboards/LeaderboardPodium.tsx b/apps/website/components/leaderboards/LeaderboardPodium.tsx index 5e2569e39..fda52aa14 100644 --- a/apps/website/components/leaderboards/LeaderboardPodium.tsx +++ b/apps/website/components/leaderboards/LeaderboardPodium.tsx @@ -1,5 +1,5 @@ -import { MedalDisplay } from '@/lib/display-objects/MedalDisplay'; -import { RatingDisplay } from '@/lib/display-objects/RatingDisplay'; +import { MedalFormatter } from '@/lib/formatters/MedalFormatter'; +import { RatingFormatter } from '@/lib/formatters/RatingFormatter'; import { Image } from '@/ui/Image'; import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; @@ -91,8 +91,8 @@ export function LeaderboardPodium({ podium, onDriverClick }: LeaderboardPodiumPr border transform="translateX(-50%)" borderWidth="2px" - bg={MedalDisplay.getBg(position)} - color={MedalDisplay.getColor(position)} + bg={MedalFormatter.getBg(position)} + color={MedalFormatter.getColor(position)} shadow="lg" > {position} @@ -122,7 +122,7 @@ export function LeaderboardPodium({ podium, onDriverClick }: LeaderboardPodiumPr block color={isFirst ? 'text-warning-amber' : 'text-primary-blue'} > - {RatingDisplay.format(driver.rating)} + {RatingFormatter.format(driver.rating)} @@ -155,7 +155,7 @@ export function LeaderboardPodium({ podium, onDriverClick }: LeaderboardPodiumPr diff --git a/apps/website/components/leaderboards/RankMedal.tsx b/apps/website/components/leaderboards/RankMedal.tsx index a4588a70b..e04d0e1fd 100644 --- a/apps/website/components/leaderboards/RankMedal.tsx +++ b/apps/website/components/leaderboards/RankMedal.tsx @@ -1,11 +1,10 @@ -import { MedalDisplay } from '@/lib/display-objects/MedalDisplay'; -import { RankMedal as UiRankMedal, RankMedalProps } from '@/ui/RankMedal'; -import React from 'react'; +import { MedalFormatter } from '@/lib/formatters/MedalFormatter'; +import { RankMedalProps, RankMedal as UiRankMedal } from '@/ui/RankMedal'; export function RankMedal(props: RankMedalProps) { - const variant = MedalDisplay.getVariant(props.rank); - const bg = MedalDisplay.getBg(props.rank); - const color = MedalDisplay.getColor(props.rank); + const variant = MedalFormatter.getVariant(props.rank); + const bg = MedalFormatter.getBg(props.rank); + const color = MedalFormatter.getColor(props.rank); return ( {nationality} - - {SkillLevelDisplay.getLabel(skillLevel)} + + {SkillLevelFormatter.getLabel(skillLevel)} @@ -84,7 +83,7 @@ export function RankingRow({ - {RatingDisplay.format(rating)} + {RatingFormatter.format(rating)} Rating diff --git a/apps/website/components/leaderboards/RankingsPodium.tsx b/apps/website/components/leaderboards/RankingsPodium.tsx index bc5853a98..b1d7afb59 100644 --- a/apps/website/components/leaderboards/RankingsPodium.tsx +++ b/apps/website/components/leaderboards/RankingsPodium.tsx @@ -1,10 +1,7 @@ +import { RatingFormatter } from '@/lib/formatters/RatingFormatter'; import { Avatar } from '@/ui/Avatar'; import { Group } from '@/ui/Group'; -import { Text } from '@/ui/Text'; -import { RatingDisplay } from '@/lib/display-objects/RatingDisplay'; -import { MedalDisplay } from '@/lib/display-objects/MedalDisplay'; import { Surface } from '@/ui/Surface'; -import React from 'react'; interface PodiumDriver { id: string; @@ -20,7 +17,7 @@ interface RankingsPodiumProps { onDriverClick?: (id: string) => void; } -export function RankingsPodium({ podium, onDriverClick }: RankingsPodiumProps) { +export function RankingsPodium({ podium }: RankingsPodiumProps) { return ( {[1, 0, 2].map((index) => { @@ -57,7 +54,7 @@ export function RankingsPodium({ podium, onDriverClick }: RankingsPodiumProps) { {driver.name} - {RatingDisplay.format(driver.rating)} + {RatingFormatter.format(driver.rating)} diff --git a/apps/website/components/leagues/EnhancedLeagueSchedulePanel.tsx b/apps/website/components/leagues/EnhancedLeagueSchedulePanel.tsx index d7464b4f7..25d73ba49 100644 --- a/apps/website/components/leagues/EnhancedLeagueSchedulePanel.tsx +++ b/apps/website/components/leagues/EnhancedLeagueSchedulePanel.tsx @@ -1,18 +1,16 @@ 'use client'; -import React, { useState } from 'react'; -import { useRouter } from 'next/navigation'; -import { routes } from '@/lib/routing/RouteConfig'; -import { Box } from '@/ui/Box'; -import { Text } from '@/ui/Text'; -import { Stack } from '@/ui/Stack'; -import { Button } from '@/ui/Button'; -import { Icon } from '@/ui/Icon'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; import { Badge } from '@/ui/Badge'; +import { Box } from '@/ui/Box'; +import { Button } from '@/ui/Button'; import { Group } from '@/ui/Group'; +import { Icon } from '@/ui/Icon'; +import { Stack } from '@/ui/Stack'; import { Surface } from '@/ui/Surface'; -import { ChevronDown, ChevronUp, Calendar, CheckCircle, Trophy, Edit, Clock } from 'lucide-react'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; +import { Text } from '@/ui/Text'; +import { Calendar, CheckCircle, ChevronDown, ChevronUp, Clock, Edit, Trophy } from 'lucide-react'; +import { useState } from 'react'; interface RaceEvent { id: string; @@ -50,9 +48,6 @@ interface MonthGroup { export function EnhancedLeagueSchedulePanel({ events, - leagueId, - currentDriverId, - isAdmin, onRegister, onWithdraw, onEdit, @@ -60,7 +55,6 @@ export function EnhancedLeagueSchedulePanel({ onRaceDetail, onResultsClick, }: EnhancedLeagueSchedulePanelProps) { - const router = useRouter(); const [expandedMonths, setExpandedMonths] = useState>(new Set()); // Group races by month @@ -109,7 +103,7 @@ export function EnhancedLeagueSchedulePanel({ }; const formatTime = (scheduledAt: string) => { - return DateDisplay.formatDateTime(scheduledAt); + return DateFormatter.formatDateTime(scheduledAt); }; const groups = groupRacesByMonth(); @@ -158,7 +152,7 @@ export function EnhancedLeagueSchedulePanel({ {isExpanded && ( - {group.races.map((race, raceIndex) => ( + {group.races.map((race) => ( ); } diff --git a/apps/website/components/leagues/LeagueMemberRow.tsx b/apps/website/components/leagues/LeagueMemberRow.tsx index 0b7bc9aa4..17dfdeabb 100644 --- a/apps/website/components/leagues/LeagueMemberRow.tsx +++ b/apps/website/components/leagues/LeagueMemberRow.tsx @@ -1,11 +1,11 @@ -import { DriverIdentity } from '@/ui/DriverIdentity'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; import { DriverViewModel } from '@/lib/view-models/DriverViewModel'; import { Badge } from '@/ui/Badge'; import { Box } from '@/ui/Box'; +import { DriverIdentity } from '@/ui/DriverIdentity'; import { TableCell, TableRow } from '@/ui/Table'; import { Text } from '@/ui/Text'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; -import React, { ReactNode } from 'react'; +import { ReactNode } from 'react'; interface LeagueMemberRowProps { driver?: DriverViewModel; @@ -84,7 +84,7 @@ export function LeagueMemberRow({ - {DateDisplay.formatShort(joinedAt)} + {DateFormatter.formatShort(joinedAt)} {actions && ( diff --git a/apps/website/components/leagues/LeagueReviewSummary.tsx b/apps/website/components/leagues/LeagueReviewSummary.tsx index 1daf03fe7..0cc2d3210 100644 --- a/apps/website/components/leagues/LeagueReviewSummary.tsx +++ b/apps/website/components/leagues/LeagueReviewSummary.tsx @@ -1,39 +1,33 @@ 'use client'; -import { - Users, - Calendar, - Trophy, - Award, - Rocket, - Gamepad2, - User, - UsersRound, - Clock, - Flag, - Zap, - Timer, - Check, - Globe, - Medal, - type LucideIcon, -} from 'lucide-react'; import type { LeagueConfigFormModel } from '@/lib/types/LeagueConfigFormModel'; -import type { LeagueScoringPresetViewModel } from '@/lib/view-models/LeagueScoringPresetViewModel'; -import { Stack } from '@/ui/Stack'; -import { Text } from '@/ui/Text'; -import { Heading } from '@/ui/Heading'; -import { Icon } from '@/ui/Icon'; import { Card } from '@/ui/Card'; import { Grid } from '@/ui/Grid'; +import { Heading } from '@/ui/Heading'; +import { Icon } from '@/ui/Icon'; +import { Stack } from '@/ui/Stack'; +import { Text } from '@/ui/Text'; +import { + Award, + Calendar, + Check, + Clock, + Flag, + Gamepad2, + Globe, + Medal, + Rocket, + Timer, + Trophy, + User, + Users, + UsersRound, + Zap, + type LucideIcon, +} from 'lucide-react'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; -import { DurationDisplay } from '@/lib/display-objects/DurationDisplay'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; -interface LeagueReviewSummaryProps { - form: LeagueConfigFormModel; - presets: LeagueScoringPresetViewModel[]; -} // Individual review card component function ReviewCard({ @@ -142,7 +136,7 @@ export function LeagueReviewSummary({ form, presets }: LeagueReviewSummaryProps) const seasonStartLabel = timings.seasonStartDate - ? DateDisplay.formatShort(timings.seasonStartDate) + ? DateFormatter.formatShort(timings.seasonStartDate) : null; const stewardingLabel = (() => { diff --git a/apps/website/components/leagues/RaceDetailModal.tsx b/apps/website/components/leagues/RaceDetailModal.tsx index 3430dd474..f4f1c75d5 100644 --- a/apps/website/components/leagues/RaceDetailModal.tsx +++ b/apps/website/components/leagues/RaceDetailModal.tsx @@ -1,28 +1,27 @@ 'use client'; -import React from 'react'; -import { Box } from '@/ui/Box'; -import { Text } from '@/ui/Text'; -import { Stack } from '@/ui/Stack'; -import { Group } from '@/ui/Group'; -import { Surface } from '@/ui/Surface'; -import { Icon } from '@/ui/Icon'; -import { Button } from '@/ui/Button'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; import { Badge } from '@/ui/Badge'; -import { - Calendar, - Clock, - Car, - MapPin, - Thermometer, - Droplets, - Wind, +import { Box } from '@/ui/Box'; +import { Button } from '@/ui/Button'; +import { Group } from '@/ui/Group'; +import { Icon } from '@/ui/Icon'; +import { Stack } from '@/ui/Stack'; +import { Surface } from '@/ui/Surface'; +import { Text } from '@/ui/Text'; +import { + Calendar, + Car, + CheckCircle, + Clock, Cloud, - X, + Droplets, + MapPin, + Thermometer, Trophy, - CheckCircle + Wind, + X } from 'lucide-react'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; interface RaceDetailModalProps { race: { @@ -55,7 +54,7 @@ export function RaceDetailModal({ if (!isOpen) return null; const formatTime = (scheduledAt: string) => { - return DateDisplay.formatDateTime(scheduledAt); + return DateFormatter.formatDateTime(scheduledAt); }; const getStatusBadge = (status: 'scheduled' | 'completed') => { diff --git a/apps/website/components/profile/ProfileDetailsPanel.tsx b/apps/website/components/profile/ProfileDetailsPanel.tsx index 5a64d7787..9269c1873 100644 --- a/apps/website/components/profile/ProfileDetailsPanel.tsx +++ b/apps/website/components/profile/ProfileDetailsPanel.tsx @@ -1,15 +1,11 @@ 'use client'; -import { CountryFlagDisplay } from '@/lib/display-objects/CountryFlagDisplay'; -import { Panel } from '@/ui/Panel'; -import { Input } from '@/ui/Input'; -import { Text } from '@/ui/Text'; -import { TextArea } from '@/ui/TextArea'; -import { Box } from '@/ui/Box'; +import { CountryFlagFormatter } from '@/lib/formatters/CountryFlagFormatter'; import { Group } from '@/ui/Group'; +import { Input } from '@/ui/Input'; +import { Panel } from '@/ui/Panel'; import { Stack } from '@/ui/Stack'; -import { ProfileStat } from '@/ui/ProfileHero'; -import React from 'react'; +import { TextArea } from '@/ui/TextArea'; interface ProfileDetailsPanelProps { driver: { @@ -50,7 +46,7 @@ export function ProfileDetailsPanel({ driver, isEditing, onUpdate }: ProfileDeta Nationality - {CountryFlagDisplay.fromCountryCode(driver.country).toString()} + {CountryFlagFormatter.fromCountryCode(driver.country).toString()} {driver.country} diff --git a/apps/website/components/profile/ProfileHeader.tsx b/apps/website/components/profile/ProfileHeader.tsx index 133161e60..6bc9a08eb 100644 --- a/apps/website/components/profile/ProfileHeader.tsx +++ b/apps/website/components/profile/ProfileHeader.tsx @@ -1,16 +1,16 @@ 'use client'; import { mediaConfig } from '@/lib/config/mediaConfig'; -import { CountryFlagDisplay } from '@/lib/display-objects/CountryFlagDisplay'; +import { CountryFlagFormatter } from '@/lib/formatters/CountryFlagFormatter'; +import { Box } from '@/ui/Box'; import { Button } from '@/ui/Button'; +import { Group } from '@/ui/Group'; import { Heading } from '@/ui/Heading'; import { Image } from '@/ui/Image'; -import { ProfileHero, ProfileAvatar, ProfileStatsGroup, ProfileStat } from '@/ui/ProfileHero'; -import { Text } from '@/ui/Text'; -import { Box } from '@/ui/Box'; -import { Group } from '@/ui/Group'; +import { ProfileAvatar, ProfileHero, ProfileStat, ProfileStatsGroup } from '@/ui/ProfileHero'; import { Stack } from '@/ui/Stack'; -import { Calendar, Globe, Star, Trophy, UserPlus } from 'lucide-react'; +import { Text } from '@/ui/Text'; +import { Calendar, Globe, UserPlus } from 'lucide-react'; import React from 'react'; interface ProfileHeaderProps { @@ -56,7 +56,7 @@ export function ProfileHeader({ {driver.name} - {CountryFlagDisplay.fromCountryCode(driver.country).toString()} + {CountryFlagFormatter.fromCountryCode(driver.country).toString()} diff --git a/apps/website/components/profile/SponsorshipRequestsPanel.tsx b/apps/website/components/profile/SponsorshipRequestsPanel.tsx index d2d7b4f86..ba3876a96 100644 --- a/apps/website/components/profile/SponsorshipRequestsPanel.tsx +++ b/apps/website/components/profile/SponsorshipRequestsPanel.tsx @@ -1,6 +1,6 @@ 'use client'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; import { Button } from '@/ui/Button'; import { Card, Card as Surface } from '@/ui/Card'; import { Stack } from '@/ui/Stack'; @@ -64,7 +64,7 @@ export function SponsorshipRequestsPanel({ {request.message} )} - {DateDisplay.formatShort(request.createdAtIso)} + {DateFormatter.formatShort(request.createdAtIso)} diff --git a/apps/website/components/races/RaceCardWrapper.tsx b/apps/website/components/races/RaceCardWrapper.tsx index cca31c46e..ae3c5c576 100644 --- a/apps/website/components/races/RaceCardWrapper.tsx +++ b/apps/website/components/races/RaceCardWrapper.tsx @@ -1,6 +1,5 @@ -import React from 'react'; -import { RaceStatusDisplay } from '@/lib/display-objects/RaceStatusDisplay'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; +import { RaceStatusFormatter } from '@/lib/formatters/RaceStatusFormatter'; import { RaceCard as UiRaceCard } from './RaceCard'; interface RaceCardProps { @@ -23,11 +22,11 @@ export function RaceCard({ race, onClick }: RaceCardProps) { track={race.track} car={race.car} scheduledAt={race.scheduledAt} - scheduledAtLabel={DateDisplay.formatShort(race.scheduledAt)} - timeLabel={DateDisplay.formatTime(race.scheduledAt)} + scheduledAtLabel={DateFormatter.formatShort(race.scheduledAt)} + timeLabel={DateFormatter.formatTime(race.scheduledAt)} status={race.status} - statusLabel={RaceStatusDisplay.getLabel(race.status)} - statusVariant={RaceStatusDisplay.getVariant(race.status) as any} + statusLabel={RaceStatusFormatter.getLabel(race.status)} + statusVariant={RaceStatusFormatter.getVariant(race.status) as any} leagueName={race.leagueName} leagueId={race.leagueId} strengthOfField={race.strengthOfField} diff --git a/apps/website/components/races/RaceHeroWrapper.tsx b/apps/website/components/races/RaceHeroWrapper.tsx index 59d5691bd..df8bc0035 100644 --- a/apps/website/components/races/RaceHeroWrapper.tsx +++ b/apps/website/components/races/RaceHeroWrapper.tsx @@ -1,8 +1,8 @@ import { RaceHero as UiRaceHero } from '@/components/races/RaceHero'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; import { LucideIcon } from 'lucide-react'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; interface RaceHeroProps { track: string; @@ -34,8 +34,8 @@ export function RaceHero(props: RaceHeroProps) { return ( ); diff --git a/apps/website/components/races/RaceListItemWrapper.tsx b/apps/website/components/races/RaceListItemWrapper.tsx index b6e8519d5..1cf9cdadc 100644 --- a/apps/website/components/races/RaceListItemWrapper.tsx +++ b/apps/website/components/races/RaceListItemWrapper.tsx @@ -1,9 +1,9 @@ -import { routes } from '@/lib/routing/RouteConfig'; import { RaceListItem as UiRaceListItem } from '@/components/races/RaceListItem'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; -import { StatusDisplay } from '@/lib/display-objects/StatusDisplay'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; +import { StatusFormatter } from '@/lib/formatters/StatusFormatter'; +import { routes } from '@/lib/routing/RouteConfig'; interface Race { id: string; @@ -48,11 +48,11 @@ export function RaceListItem({ race, onClick }: RaceListItemProps) { - {CountryFlagDisplay.fromCountryCode(country).toString()} + {CountryFlagFormatter.fromCountryCode(country).toString()} diff --git a/apps/website/components/social/FriendsPreview.tsx b/apps/website/components/social/FriendsPreview.tsx index cbf0639e2..d5295f3d3 100644 --- a/apps/website/components/social/FriendsPreview.tsx +++ b/apps/website/components/social/FriendsPreview.tsx @@ -1,7 +1,7 @@ 'use client'; import { mediaConfig } from '@/lib/config/mediaConfig'; -import { CountryFlagDisplay } from '@/lib/display-objects/CountryFlagDisplay'; +import { CountryFlagFormatter } from '@/lib/formatters/CountryFlagFormatter'; import { routes } from '@/lib/routing/RouteConfig'; import { Card, Card as Surface } from '@/ui/Card'; import { Heading } from '@/ui/Heading'; @@ -68,7 +68,7 @@ export function FriendsPreview({ friends }: FriendsPreviewProps) { /> {friend.name} - {CountryFlagDisplay.fromCountryCode(friend.country).toString()} + {CountryFlagFormatter.fromCountryCode(friend.country).toString()} diff --git a/apps/website/components/teams/TeamRoster.tsx b/apps/website/components/teams/TeamRoster.tsx index b2e54674a..96c85b279 100644 --- a/apps/website/components/teams/TeamRoster.tsx +++ b/apps/website/components/teams/TeamRoster.tsx @@ -1,6 +1,5 @@ 'use client'; -import { MinimalEmptyState } from '@/ui/EmptyState'; import { TeamRosterItem } from '@/components/teams/TeamRosterItem'; import { TeamRosterList } from '@/components/teams/TeamRosterList'; import { useTeamRoster } from "@/hooks/team/useTeamRoster"; @@ -9,15 +8,16 @@ import { sortMembers } from '@/lib/utilities/roster-utils'; import type { DriverViewModel } from '@/lib/view-models/DriverViewModel'; import { Button } from '@/ui/Button'; import { Card } from '@/ui/Card'; +import { MinimalEmptyState } from '@/ui/EmptyState'; import { Heading } from '@/ui/Heading'; -import { Stack } from '@/ui/Stack'; import { Select } from '@/ui/Select'; +import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; import { useMemo, useState } from 'react'; -import { MemberDisplay } from '@/lib/display-objects/MemberDisplay'; -import { RatingDisplay } from '@/lib/display-objects/RatingDisplay'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; +import { MemberFormatter } from '@/lib/formatters/MemberFormatter'; +import { RatingFormatter } from '@/lib/formatters/RatingFormatter'; export type TeamRole = 'owner' | 'admin' | 'member'; export type TeamMemberRole = 'owner' | 'manager' | 'member'; @@ -74,7 +74,7 @@ export function TeamRoster({ const teamAverageRatingLabel = useMemo(() => { if (teamMembers.length === 0) return '—'; const avg = teamMembers.reduce((sum: number, m: { rating?: number | null }) => sum + (m.rating || 0), 0) / teamMembers.length; - return RatingDisplay.format(avg); + return RatingFormatter.format(avg); }, [teamMembers]); if (loading) { @@ -93,7 +93,7 @@ export function TeamRoster({ Team Roster - {MemberDisplay.formatCount(memberships.length)} • Avg Rating:{' '} + {MemberFormatter.formatCount(memberships.length)} • Avg Rating:{' '} {teamAverageRatingLabel} @@ -129,8 +129,8 @@ export function TeamRoster({ driver={driver as DriverViewModel} href={`${routes.driver.detail(driver.id)}?from=team&teamId=${teamId}`} roleLabel={getRoleLabel(role)} - joinedAtLabel={DateDisplay.formatShort(joinedAt)} - ratingLabel={RatingDisplay.format(rating)} + joinedAtLabel={DateFormatter.formatShort(joinedAt)} + ratingLabel={RatingFormatter.format(rating)} overallRankLabel={overallRank !== null ? `#${overallRank}` : null} actions={canManageMembership ? ( <> diff --git a/apps/website/hooks/league/useLeagueSchedule.ts b/apps/website/hooks/league/useLeagueSchedule.ts index 492a51192..a911ef7a8 100644 --- a/apps/website/hooks/league/useLeagueSchedule.ts +++ b/apps/website/hooks/league/useLeagueSchedule.ts @@ -1,10 +1,10 @@ -import { useQuery } from '@tanstack/react-query'; import { useInject } from '@/lib/di/hooks/useInject'; -import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens'; import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError'; -import { LeagueScheduleViewModel, LeagueScheduleRaceViewModel } from '@/lib/view-models/LeagueScheduleViewModel'; +import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; import type { RaceDTO } from '@/lib/types/generated/RaceDTO'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; +import { LeagueScheduleRaceViewModel, LeagueScheduleViewModel } from '@/lib/view-models/LeagueScheduleViewModel'; +import { useQuery } from '@tanstack/react-query'; function mapRaceDtoToViewModel(race: RaceDTO): LeagueScheduleRaceViewModel { const scheduledAt = race.date ? new Date(race.date) : new Date(0); @@ -15,8 +15,8 @@ function mapRaceDtoToViewModel(race: RaceDTO): LeagueScheduleRaceViewModel { id: race.id, name: race.name, scheduledAt, - formattedDate: DateDisplay.formatShort(scheduledAt), - formattedTime: DateDisplay.formatTime(scheduledAt), + formattedDate: DateFormatter.formatShort(scheduledAt), + formattedTime: DateFormatter.formatTime(scheduledAt), isPast, isUpcoming: !isPast, status: isPast ? 'completed' : 'scheduled', diff --git a/apps/website/hooks/league/useLeagueScheduleAdminPageData.ts b/apps/website/hooks/league/useLeagueScheduleAdminPageData.ts index 392a0ca31..4834dec6a 100644 --- a/apps/website/hooks/league/useLeagueScheduleAdminPageData.ts +++ b/apps/website/hooks/league/useLeagueScheduleAdminPageData.ts @@ -1,13 +1,13 @@ -import { usePageData } from '@/lib/page/usePageData'; import { useInject } from '@/lib/di/hooks/useInject'; -import { LEAGUE_SERVICE_TOKEN, LEAGUE_MEMBERSHIP_SERVICE_TOKEN } from '@/lib/di/tokens'; +import { LEAGUE_MEMBERSHIP_SERVICE_TOKEN, LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; +import { usePageData } from '@/lib/page/usePageData'; +import type { LeagueSeasonSummaryDTO } from '@/lib/types/generated/LeagueSeasonSummaryDTO'; +import type { RaceDTO } from '@/lib/types/generated/RaceDTO'; import { LeagueRoleUtility } from '@/lib/utilities/LeagueRoleUtility'; import { LeagueAdminScheduleViewModel } from '@/lib/view-models/LeagueAdminScheduleViewModel'; import { LeagueScheduleRaceViewModel } from '@/lib/view-models/LeagueScheduleViewModel'; import { LeagueSeasonSummaryViewModel } from '@/lib/view-models/LeagueSeasonSummaryViewModel'; -import type { RaceDTO } from '@/lib/types/generated/RaceDTO'; -import type { LeagueSeasonSummaryDTO } from '@/lib/types/generated/LeagueSeasonSummaryDTO'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; function mapRaceDtoToViewModel(race: RaceDTO): LeagueScheduleRaceViewModel { const scheduledAt = race.date ? new Date(race.date) : new Date(0); @@ -18,8 +18,8 @@ function mapRaceDtoToViewModel(race: RaceDTO): LeagueScheduleRaceViewModel { id: race.id, name: race.name, scheduledAt, - formattedDate: DateDisplay.formatShort(scheduledAt), - formattedTime: DateDisplay.formatTime(scheduledAt), + formattedDate: DateFormatter.formatShort(scheduledAt), + formattedTime: DateFormatter.formatTime(scheduledAt), isPast, isUpcoming: !isPast, status: isPast ? 'completed' : 'scheduled', diff --git a/apps/website/lib/builders/view-data/DashboardViewDataBuilder.ts b/apps/website/lib/builders/view-data/DashboardViewDataBuilder.ts index 5b7765f79..9a5353592 100644 --- a/apps/website/lib/builders/view-data/DashboardViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/DashboardViewDataBuilder.ts @@ -1,14 +1,14 @@ 'use client'; +import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; +import { DashboardConsistencyFormatter } from '@/lib/formatters/DashboardConsistencyFormatter'; +import { DashboardCountFormatter } from '@/lib/formatters/DashboardCountFormatter'; +import { DashboardDateFormatter } from '@/lib/formatters/DashboardDateFormatter'; +import { DashboardLeaguePositionFormatter } from '@/lib/formatters/DashboardLeaguePositionFormatter'; +import { DashboardRankFormatter } from '@/lib/formatters/DashboardRankFormatter'; +import { RatingFormatter } from '@/lib/formatters/RatingFormatter'; import type { DashboardOverviewDTO } from '@/lib/types/generated/DashboardOverviewDTO'; import type { DashboardViewData } from '@/lib/view-data/DashboardViewData'; -import { DashboardDateDisplay } from '@/lib/display-objects/DashboardDateDisplay'; -import { RatingDisplay } from '@/lib/display-objects/RatingDisplay'; -import { DashboardRankDisplay } from '@/lib/display-objects/DashboardRankDisplay'; -import { DashboardConsistencyDisplay } from '@/lib/display-objects/DashboardConsistencyDisplay'; -import { DashboardCountDisplay } from '@/lib/display-objects/DashboardCountDisplay'; -import { DashboardLeaguePositionDisplay } from '@/lib/display-objects/DashboardLeaguePositionDisplay'; -import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; export class DashboardViewDataBuilder { public static build(apiDto: DashboardOverviewDTO): DashboardViewData { @@ -17,21 +17,21 @@ export class DashboardViewDataBuilder { name: apiDto.currentDriver?.name || '', avatarUrl: apiDto.currentDriver?.avatarUrl || '', country: apiDto.currentDriver?.country || '', - rating: apiDto.currentDriver ? RatingDisplay.format(apiDto.currentDriver.rating ?? 0) : '0.0', - rank: apiDto.currentDriver ? DashboardRankDisplay.format(apiDto.currentDriver.globalRank ?? 0) : '0', - totalRaces: apiDto.currentDriver ? DashboardCountDisplay.format(apiDto.currentDriver.totalRaces ?? 0) : '0', - wins: apiDto.currentDriver ? DashboardCountDisplay.format(apiDto.currentDriver.wins ?? 0) : '0', - podiums: apiDto.currentDriver ? DashboardCountDisplay.format(apiDto.currentDriver.podiums ?? 0) : '0', - consistency: apiDto.currentDriver ? DashboardConsistencyDisplay.format(apiDto.currentDriver.consistency ?? 0) : '0%', + rating: apiDto.currentDriver ? RatingFormatter.format(apiDto.currentDriver.rating ?? 0) : '0.0', + rank: apiDto.currentDriver ? DashboardRankFormatter.format(apiDto.currentDriver.globalRank ?? 0) : '0', + totalRaces: apiDto.currentDriver ? DashboardCountFormatter.format(apiDto.currentDriver.totalRaces ?? 0) : '0', + wins: apiDto.currentDriver ? DashboardCountFormatter.format(apiDto.currentDriver.wins ?? 0) : '0', + podiums: apiDto.currentDriver ? DashboardCountFormatter.format(apiDto.currentDriver.podiums ?? 0) : '0', + consistency: apiDto.currentDriver ? DashboardConsistencyFormatter.format(apiDto.currentDriver.consistency ?? 0) : '0%', }, nextRace: apiDto.nextRace ? DashboardViewDataBuilder.buildNextRace(apiDto.nextRace) : null, upcomingRaces: apiDto.upcomingRaces.map((race) => DashboardViewDataBuilder.buildRace(race)), leagueStandings: apiDto.leagueStandingsSummaries.map((standing) => ({ leagueId: standing.leagueId, leagueName: standing.leagueName, - position: DashboardLeaguePositionDisplay.format(standing.position), - points: DashboardCountDisplay.format(standing.points), - totalDrivers: DashboardCountDisplay.format(standing.totalDrivers), + position: DashboardLeaguePositionFormatter.format(standing.position), + points: DashboardCountFormatter.format(standing.points), + totalDrivers: DashboardCountFormatter.format(standing.totalDrivers), })), feedItems: apiDto.feedSummary.items.map((item) => ({ id: item.id, @@ -39,7 +39,7 @@ export class DashboardViewDataBuilder { headline: item.headline, body: item.body ?? undefined, timestamp: item.timestamp, - formattedTime: DashboardDateDisplay.format(new Date(item.timestamp)).relative, + formattedTime: DashboardDateFormatter.format(new Date(item.timestamp)).relative, ctaHref: item.ctaHref ?? undefined, ctaLabel: item.ctaLabel ?? undefined, })), @@ -49,8 +49,8 @@ export class DashboardViewDataBuilder { avatarUrl: friend.avatarUrl || '', country: friend.country, })), - activeLeaguesCount: DashboardCountDisplay.format(apiDto.activeLeaguesCount), - friendCount: DashboardCountDisplay.format(apiDto.friends.length), + activeLeaguesCount: DashboardCountFormatter.format(apiDto.activeLeaguesCount), + friendCount: DashboardCountFormatter.format(apiDto.friends.length), hasUpcomingRaces: apiDto.upcomingRaces.length > 0, hasLeagueStandings: apiDto.leagueStandingsSummaries.length > 0, hasFeedItems: apiDto.feedSummary.items.length > 0, @@ -59,7 +59,7 @@ export class DashboardViewDataBuilder { } private static buildNextRace(race: NonNullable) { - const dateInfo = DashboardDateDisplay.format(new Date(race.scheduledAt)); + const dateInfo = DashboardDateFormatter.format(new Date(race.scheduledAt)); return { id: race.id, track: race.track, @@ -73,7 +73,7 @@ export class DashboardViewDataBuilder { } private static buildRace(race: DashboardOverviewDTO['upcomingRaces'][number]) { - const dateInfo = DashboardDateDisplay.format(new Date(race.scheduledAt)); + const dateInfo = DashboardDateFormatter.format(new Date(race.scheduledAt)); return { id: race.id, track: race.track, diff --git a/apps/website/lib/builders/view-data/DriverProfileViewDataBuilder.ts b/apps/website/lib/builders/view-data/DriverProfileViewDataBuilder.ts index 02cec790a..e69562b39 100644 --- a/apps/website/lib/builders/view-data/DriverProfileViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/DriverProfileViewDataBuilder.ts @@ -1,13 +1,13 @@ 'use client'; +import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; +import { FinishFormatter } from '@/lib/formatters/FinishFormatter'; +import { NumberFormatter } from '@/lib/formatters/NumberFormatter'; +import { PercentFormatter } from '@/lib/formatters/PercentFormatter'; +import { RatingFormatter } from '@/lib/formatters/RatingFormatter'; import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO'; import type { DriverProfileViewData } from '@/lib/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'; -import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; export class DriverProfileViewDataBuilder { public static build(apiDto: GetDriverProfileOutputDTO): DriverProfileViewData { @@ -19,9 +19,9 @@ 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), + joinedAtLabel: DateFormatter.formatMonthYear(apiDto.currentDriver.joinedAt), rating: apiDto.currentDriver.rating ?? null, - ratingLabel: RatingDisplay.format(apiDto.currentDriver.rating), + ratingLabel: RatingFormatter.format(apiDto.currentDriver.rating), globalRank: apiDto.currentDriver.globalRank ?? null, globalRankLabel: apiDto.currentDriver.globalRank != null ? `#${apiDto.currentDriver.globalRank}` : '—', consistency: apiDto.currentDriver.consistency ?? null, @@ -30,27 +30,27 @@ export class DriverProfileViewDataBuilder { } : null, stats: apiDto.stats ? { totalRaces: apiDto.stats.totalRaces, - totalRacesLabel: NumberDisplay.format(apiDto.stats.totalRaces), + totalRacesLabel: NumberFormatter.format(apiDto.stats.totalRaces), wins: apiDto.stats.wins, - winsLabel: NumberDisplay.format(apiDto.stats.wins), + winsLabel: NumberFormatter.format(apiDto.stats.wins), podiums: apiDto.stats.podiums, - podiumsLabel: NumberDisplay.format(apiDto.stats.podiums), + podiumsLabel: NumberFormatter.format(apiDto.stats.podiums), dnfs: apiDto.stats.dnfs, - dnfsLabel: NumberDisplay.format(apiDto.stats.dnfs), + dnfsLabel: NumberFormatter.format(apiDto.stats.dnfs), avgFinish: apiDto.stats.avgFinish ?? null, - avgFinishLabel: FinishDisplay.formatAverage(apiDto.stats.avgFinish), + avgFinishLabel: FinishFormatter.formatAverage(apiDto.stats.avgFinish), bestFinish: apiDto.stats.bestFinish ?? null, - bestFinishLabel: FinishDisplay.format(apiDto.stats.bestFinish), + bestFinishLabel: FinishFormatter.format(apiDto.stats.bestFinish), worstFinish: apiDto.stats.worstFinish ?? null, - worstFinishLabel: FinishDisplay.format(apiDto.stats.worstFinish), + worstFinishLabel: FinishFormatter.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), + ratingLabel: RatingFormatter.format(apiDto.stats.rating), consistency: apiDto.stats.consistency ?? null, - consistencyLabel: PercentDisplay.formatWhole(apiDto.stats.consistency), + consistencyLabel: PercentFormatter.formatWhole(apiDto.stats.consistency), overallRank: apiDto.stats.overallRank ?? null, } : null, finishDistribution: apiDto.finishDistribution ? { @@ -67,7 +67,7 @@ export class DriverProfileViewDataBuilder { teamTag: m.teamTag ?? null, role: m.role, joinedAt: m.joinedAt, - joinedAtLabel: DateDisplay.formatMonthYear(m.joinedAt), + joinedAtLabel: DateFormatter.formatMonthYear(m.joinedAt), isCurrent: m.isCurrent, })), socialSummary: { @@ -93,7 +93,7 @@ export class DriverProfileViewDataBuilder { rarity: a.rarity, rarityLabel: a.rarity, earnedAt: a.earnedAt, - earnedAtLabel: DateDisplay.formatShort(a.earnedAt), + earnedAtLabel: DateFormatter.formatShort(a.earnedAt), })), racingStyle: apiDto.extendedProfile.racingStyle, favoriteTrack: apiDto.extendedProfile.favoriteTrack, diff --git a/apps/website/lib/builders/view-data/DriverRankingsViewDataBuilder.ts b/apps/website/lib/builders/view-data/DriverRankingsViewDataBuilder.ts index 2c446597f..afde22b1b 100644 --- a/apps/website/lib/builders/view-data/DriverRankingsViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/DriverRankingsViewDataBuilder.ts @@ -1,10 +1,10 @@ 'use client'; +import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; +import { MedalFormatter } from '@/lib/formatters/MedalFormatter'; +import { WinRateFormatter } from '@/lib/formatters/WinRateFormatter'; import type { DriverLeaderboardItemDTO } from '@/lib/types/generated/DriverLeaderboardItemDTO'; import type { DriverRankingsViewData } from '@/lib/view-data/DriverRankingsViewData'; -import { WinRateDisplay } from '@/lib/display-objects/WinRateDisplay'; -import { MedalDisplay } from '@/lib/display-objects/MedalDisplay'; -import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; export class DriverRankingsViewDataBuilder { public static build(apiDto: DriverLeaderboardItemDTO[]): DriverRankingsViewData { @@ -31,9 +31,9 @@ export class DriverRankingsViewDataBuilder { podiums: driver.podiums, rank: driver.rank, avatarUrl: driver.avatarUrl || '', - winRate: WinRateDisplay.calculate(driver.racesCompleted, driver.wins), - medalBg: MedalDisplay.getBg(driver.rank), - medalColor: MedalDisplay.getColor(driver.rank), + winRate: WinRateFormatter.calculate(driver.racesCompleted, driver.wins), + medalBg: MedalFormatter.getBg(driver.rank), + medalColor: MedalFormatter.getColor(driver.rank), })), podium: apiDto.slice(0, 3).map((driver, index) => { const positions = [2, 1, 3]; // Display order: 2nd, 1st, 3rd diff --git a/apps/website/lib/builders/view-data/DriversViewDataBuilder.ts b/apps/website/lib/builders/view-data/DriversViewDataBuilder.ts index 39a63dd71..d36f202ef 100644 --- a/apps/website/lib/builders/view-data/DriversViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/DriversViewDataBuilder.ts @@ -1,10 +1,10 @@ 'use client'; +import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; +import { NumberFormatter } from '@/lib/formatters/NumberFormatter'; +import { RatingFormatter } from '@/lib/formatters/RatingFormatter'; import type { DriversLeaderboardDTO } from '@/lib/types/generated/DriversLeaderboardDTO'; import type { DriversViewData } from '@/lib/view-data/DriversViewData'; -import { RatingDisplay } from '@/lib/display-objects/RatingDisplay'; -import { NumberDisplay } from '@/lib/display-objects/NumberDisplay'; -import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; export class DriversViewDataBuilder { public static build(apiDto: DriversLeaderboardDTO): DriversViewData { @@ -13,7 +13,7 @@ export class DriversViewDataBuilder { id: driver.id, name: driver.name, rating: driver.rating, - ratingLabel: RatingDisplay.format(driver.rating), + ratingLabel: RatingFormatter.format(driver.rating), skillLevel: driver.skillLevel, category: driver.category ?? undefined, nationality: driver.nationality, @@ -25,12 +25,12 @@ export class DriversViewDataBuilder { avatarUrl: driver.avatarUrl ?? undefined, })), totalRaces: apiDto.totalRaces, - totalRacesLabel: NumberDisplay.format(apiDto.totalRaces), + totalRacesLabel: NumberFormatter.format(apiDto.totalRaces), totalWins: apiDto.totalWins, - totalWinsLabel: NumberDisplay.format(apiDto.totalWins), + totalWinsLabel: NumberFormatter.format(apiDto.totalWins), activeCount: apiDto.activeCount, - activeCountLabel: NumberDisplay.format(apiDto.activeCount), - totalDriversLabel: NumberDisplay.format(apiDto.drivers.length), + activeCountLabel: NumberFormatter.format(apiDto.activeCount), + totalDriversLabel: NumberFormatter.format(apiDto.drivers.length), }; } } diff --git a/apps/website/lib/builders/view-data/HealthViewDataBuilder.ts b/apps/website/lib/builders/view-data/HealthViewDataBuilder.ts index 21062096a..b27c11910 100644 --- a/apps/website/lib/builders/view-data/HealthViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/HealthViewDataBuilder.ts @@ -1,12 +1,12 @@ 'use client'; -import type { HealthDTO } from '@/lib/types/generated/HealthDTO'; -import type { HealthViewData, HealthStatus, HealthMetrics, HealthComponent, HealthAlert } from '@/lib/view-data/HealthViewData'; -import { HealthStatusDisplay } from '@/lib/display-objects/HealthStatusDisplay'; -import { HealthMetricDisplay } from '@/lib/display-objects/HealthMetricDisplay'; -import { HealthComponentDisplay } from '@/lib/display-objects/HealthComponentDisplay'; -import { HealthAlertDisplay } from '@/lib/display-objects/HealthAlertDisplay'; import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; +import { HealthAlertFormatter } from '@/lib/formatters/HealthAlertFormatter'; +import { HealthComponentFormatter } from '@/lib/formatters/HealthComponentFormatter'; +import { HealthMetricFormatter } from '@/lib/formatters/HealthMetricFormatter'; +import { HealthStatusFormatter } from '@/lib/formatters/HealthStatusFormatter'; +import type { HealthDTO } from '@/lib/types/generated/HealthDTO'; +import type { HealthAlert, HealthComponent, HealthMetrics, HealthStatus, HealthViewData } from '@/lib/view-data/HealthViewData'; export class HealthViewDataBuilder { public static build(apiDto: HealthDTO): HealthViewData { @@ -17,37 +17,37 @@ export class HealthViewDataBuilder { const overallStatus: HealthStatus = { status: apiDto.status, timestamp: apiDto.timestamp, - formattedTimestamp: HealthStatusDisplay.formatTimestamp(apiDto.timestamp), - relativeTime: HealthStatusDisplay.formatRelativeTime(apiDto.timestamp), - statusLabel: HealthStatusDisplay.formatStatusLabel(apiDto.status), - statusColor: HealthStatusDisplay.formatStatusColor(apiDto.status), - statusIcon: HealthStatusDisplay.formatStatusIcon(apiDto.status), + formattedTimestamp: HealthStatusFormatter.formatTimestamp(apiDto.timestamp), + relativeTime: HealthStatusFormatter.formatRelativeTime(apiDto.timestamp), + statusLabel: HealthStatusFormatter.formatStatusLabel(apiDto.status), + statusColor: HealthStatusFormatter.formatStatusColor(apiDto.status), + statusIcon: HealthStatusFormatter.formatStatusIcon(apiDto.status), }; // Build metrics const metrics: HealthMetrics = { - uptime: HealthMetricDisplay.formatUptime(apiDto.uptime), - responseTime: HealthMetricDisplay.formatResponseTime(apiDto.responseTime), - errorRate: HealthMetricDisplay.formatErrorRate(apiDto.errorRate), + uptime: HealthMetricFormatter.formatUptime(apiDto.uptime), + responseTime: HealthMetricFormatter.formatResponseTime(apiDto.responseTime), + errorRate: HealthMetricFormatter.formatErrorRate(apiDto.errorRate), lastCheck: apiDto.lastCheck || lastUpdated, - formattedLastCheck: HealthMetricDisplay.formatTimestamp(apiDto.lastCheck || lastUpdated), + formattedLastCheck: HealthMetricFormatter.formatTimestamp(apiDto.lastCheck || lastUpdated), checksPassed: apiDto.checksPassed || 0, checksFailed: apiDto.checksFailed || 0, totalChecks: (apiDto.checksPassed || 0) + (apiDto.checksFailed || 0), - successRate: HealthMetricDisplay.formatSuccessRate(apiDto.checksPassed, apiDto.checksFailed), + successRate: HealthMetricFormatter.formatSuccessRate(apiDto.checksPassed, apiDto.checksFailed), }; // Build components const components: HealthComponent[] = (apiDto.components || []).map((component) => ({ name: component.name, status: component.status, - statusLabel: HealthComponentDisplay.formatStatusLabel(component.status), - statusColor: HealthComponentDisplay.formatStatusColor(component.status), - statusIcon: HealthComponentDisplay.formatStatusIcon(component.status), + statusLabel: HealthComponentFormatter.formatStatusLabel(component.status), + statusColor: HealthComponentFormatter.formatStatusColor(component.status), + statusIcon: HealthComponentFormatter.formatStatusIcon(component.status), lastCheck: component.lastCheck || lastUpdated, - formattedLastCheck: HealthComponentDisplay.formatTimestamp(component.lastCheck || lastUpdated), - responseTime: HealthMetricDisplay.formatResponseTime(component.responseTime), - errorRate: HealthMetricDisplay.formatErrorRate(component.errorRate), + formattedLastCheck: HealthComponentFormatter.formatTimestamp(component.lastCheck || lastUpdated), + responseTime: HealthMetricFormatter.formatResponseTime(component.responseTime), + errorRate: HealthMetricFormatter.formatErrorRate(component.errorRate), })); // Build alerts @@ -57,10 +57,10 @@ export class HealthViewDataBuilder { title: alert.title, message: alert.message, timestamp: alert.timestamp, - formattedTimestamp: HealthAlertDisplay.formatTimestamp(alert.timestamp), - relativeTime: HealthAlertDisplay.formatRelativeTime(alert.timestamp), - severity: HealthAlertDisplay.formatSeverity(alert.type), - severityColor: HealthAlertDisplay.formatSeverityColor(alert.type), + formattedTimestamp: HealthAlertFormatter.formatTimestamp(alert.timestamp), + relativeTime: HealthAlertFormatter.formatRelativeTime(alert.timestamp), + severity: HealthAlertFormatter.formatSeverity(alert.type), + severityColor: HealthAlertFormatter.formatSeverityColor(alert.type), })); // Calculate derived fields @@ -77,7 +77,7 @@ export class HealthViewDataBuilder { hasDegradedComponents, hasErrorComponents, lastUpdated, - formattedLastUpdated: HealthStatusDisplay.formatTimestamp(lastUpdated), + formattedLastUpdated: HealthStatusFormatter.formatTimestamp(lastUpdated), }; } } diff --git a/apps/website/lib/builders/view-data/HomeViewDataBuilder.ts b/apps/website/lib/builders/view-data/HomeViewDataBuilder.ts index 352b63b2f..1a6dd6f41 100644 --- a/apps/website/lib/builders/view-data/HomeViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/HomeViewDataBuilder.ts @@ -1,9 +1,9 @@ 'use client'; +import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; +import { DashboardDateFormatter } from '@/lib/formatters/DashboardDateFormatter'; import type { DashboardOverviewDTO } from '@/lib/types/generated/DashboardOverviewDTO'; import type { HomeViewData } from '@/lib/view-data/HomeViewData'; -import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; -import { DashboardDateDisplay } from '@/lib/display-objects/DashboardDateDisplay'; export class HomeViewDataBuilder { /** @@ -19,7 +19,7 @@ export class HomeViewDataBuilder { id: race.id, track: race.track, car: race.car, - formattedDate: DashboardDateDisplay.format(new Date(race.scheduledAt)).date, + formattedDate: DashboardDateFormatter.format(new Date(race.scheduledAt)).date, })), topLeagues: (apiDto.leagueStandingsSummaries || []).map(league => ({ id: league.leagueId, diff --git a/apps/website/lib/builders/view-data/LeagueRosterAdminViewDataBuilder.ts b/apps/website/lib/builders/view-data/LeagueRosterAdminViewDataBuilder.ts index d06f4a7de..68e2ac63d 100644 --- a/apps/website/lib/builders/view-data/LeagueRosterAdminViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/LeagueRosterAdminViewDataBuilder.ts @@ -1,8 +1,8 @@ -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'; import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; +import type { LeagueRosterJoinRequestDTO } from '@/lib/types/generated/LeagueRosterJoinRequestDTO'; +import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO'; +import type { JoinRequestData, LeagueRosterAdminViewData, RosterMemberData } from '@/lib/view-data/LeagueRosterAdminViewData'; type LeagueRosterAdminInputDTO = { leagueId: string; @@ -23,7 +23,7 @@ export class LeagueRosterAdminViewDataBuilder { }, role: member.role, joinedAt: member.joinedAt, - formattedJoinedAt: DateDisplay.formatShort(member.joinedAt), + formattedJoinedAt: DateFormatter.formatShort(member.joinedAt), })); // Transform join requests @@ -34,7 +34,7 @@ export class LeagueRosterAdminViewDataBuilder { name: (req as { driver?: { name?: string } }).driver?.name || 'Unknown Driver', }, requestedAt: req.requestedAt, - formattedRequestedAt: DateDisplay.formatShort(req.requestedAt), + formattedRequestedAt: DateFormatter.formatShort(req.requestedAt), message: req.message ?? undefined, })); diff --git a/apps/website/lib/builders/view-data/LeagueSponsorshipsViewDataBuilder.ts b/apps/website/lib/builders/view-data/LeagueSponsorshipsViewDataBuilder.ts index 6fe16eaad..5d45e5c78 100644 --- a/apps/website/lib/builders/view-data/LeagueSponsorshipsViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/LeagueSponsorshipsViewDataBuilder.ts @@ -1,5 +1,5 @@ -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; -import { StatusDisplay } from '@/lib/display-objects/StatusDisplay'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; +import { StatusFormatter } from '@/lib/formatters/StatusFormatter'; import { LeagueSponsorshipsApiDto } from '@/lib/types/tbd/LeagueSponsorshipsApiDto'; import { LeagueSponsorshipsViewData } from '@/lib/view-data/LeagueSponsorshipsViewData'; @@ -19,8 +19,8 @@ export class LeagueSponsorshipsViewDataBuilder implements ViewDataBuilder ({ ...r, - formattedRequestedAt: DateDisplay.formatShort(r.requestedAt), - statusLabel: StatusDisplay.protestStatus(r.status), // Reusing protest status for now + formattedRequestedAt: DateFormatter.formatShort(r.requestedAt), + statusLabel: StatusFormatter.protestStatus(r.status), // Reusing protest status for now })), }; } diff --git a/apps/website/lib/builders/view-data/ProfileViewDataBuilder.ts b/apps/website/lib/builders/view-data/ProfileViewDataBuilder.ts index ebae52c94..eb23b3e48 100644 --- a/apps/website/lib/builders/view-data/ProfileViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/ProfileViewDataBuilder.ts @@ -1,12 +1,12 @@ +import { mediaConfig } from '@/lib/config/mediaConfig'; +import { CountryFlagFormatter } from '@/lib/formatters/CountryFlagFormatter'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; +import { FinishFormatter } from '@/lib/formatters/FinishFormatter'; +import { NumberFormatter } from '@/lib/formatters/NumberFormatter'; +import { PercentFormatter } from '@/lib/formatters/PercentFormatter'; +import { RatingFormatter } from '@/lib/formatters/RatingFormatter'; import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO'; 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'; import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder"; @@ -24,7 +24,7 @@ export class ProfileViewDataBuilder implements ViewDataBuilder { id: '', name: '', countryCode: '', - countryFlag: CountryFlagDisplay.fromCountryCode(null).toString(), + countryFlag: CountryFlagFormatter.fromCountryCode(null).toString(), avatarUrl: mediaConfig.avatars.defaultFallback, bio: null, iracingId: null, @@ -45,25 +45,25 @@ export class ProfileViewDataBuilder implements ViewDataBuilder { id: driver.id, name: driver.name, countryCode: driver.country, - countryFlag: CountryFlagDisplay.fromCountryCode(driver.country).toString(), + countryFlag: CountryFlagFormatter.fromCountryCode(driver.country).toString(), avatarUrl: driver.avatarUrl || mediaConfig.avatars.defaultFallback, bio: driver.bio || null, iracingId: driver.iracingId ? String(driver.iracingId) : null, - joinedAtLabel: DateDisplay.formatMonthYear(driver.joinedAt), + joinedAtLabel: DateFormatter.formatMonthYear(driver.joinedAt), }, stats: stats ? { - ratingLabel: RatingDisplay.format(stats.rating), + ratingLabel: RatingFormatter.format(stats.rating), globalRankLabel: driver.globalRank != null ? `#${driver.globalRank}` : '—', - 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), + totalRacesLabel: NumberFormatter.format(stats.totalRaces), + winsLabel: NumberFormatter.format(stats.wins), + podiumsLabel: NumberFormatter.format(stats.podiums), + dnfsLabel: NumberFormatter.format(stats.dnfs), + bestFinishLabel: FinishFormatter.format(stats.bestFinish), + worstFinishLabel: FinishFormatter.format(stats.worstFinish), + avgFinishLabel: FinishFormatter.formatAverage(stats.avgFinish), + consistencyLabel: PercentFormatter.formatWhole(stats.consistency), + percentileLabel: PercentFormatter.format(stats.percentile), } : null, teamMemberships: apiDto.teamMemberships.map((m) => ({ @@ -71,7 +71,7 @@ export class ProfileViewDataBuilder implements ViewDataBuilder { teamName: m.teamName, teamTag: m.teamTag || null, roleLabel: m.role, - joinedAtLabel: DateDisplay.formatMonthYear(m.joinedAt), + joinedAtLabel: DateFormatter.formatMonthYear(m.joinedAt), href: `/teams/${m.teamId}`, })), extendedProfile: extended @@ -92,18 +92,18 @@ export class ProfileViewDataBuilder implements ViewDataBuilder { id: a.id, title: a.title, description: a.description, - earnedAtLabel: DateDisplay.formatShort(a.earnedAt), + earnedAtLabel: DateFormatter.formatShort(a.earnedAt), icon: a.icon as any, rarityLabel: a.rarity, })), friends: socialSummary.friends.slice(0, 8).map((f) => ({ id: f.id, name: f.name, - countryFlag: CountryFlagDisplay.fromCountryCode(f.country).toString(), + countryFlag: CountryFlagFormatter.fromCountryCode(f.country).toString(), avatarUrl: f.avatarUrl || mediaConfig.avatars.defaultFallback, href: `/drivers/${f.id}`, })), - friendsCountLabel: NumberDisplay.format(socialSummary.friendsCount), + friendsCountLabel: NumberFormatter.format(socialSummary.friendsCount), } : null, }; diff --git a/apps/website/lib/builders/view-data/RacesViewDataBuilder.ts b/apps/website/lib/builders/view-data/RacesViewDataBuilder.ts index 12493554d..b0075ffa8 100644 --- a/apps/website/lib/builders/view-data/RacesViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/RacesViewDataBuilder.ts @@ -1,8 +1,8 @@ +import { DateFormatter } from '@/lib/formatters/DateFormatter'; +import { RaceStatusFormatter } from '@/lib/formatters/RaceStatusFormatter'; +import { RelativeTimeFormatter } from '@/lib/formatters/RelativeTimeFormatter'; import type { RacesPageDataDTO } from '@/lib/types/generated/RacesPageDataDTO'; import type { RacesViewData, RaceViewData } from '@/lib/view-data/RacesViewData'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; -import { RaceStatusDisplay } from '@/lib/display-objects/RaceStatusDisplay'; -import { RelativeTimeDisplay } from '@/lib/display-objects/RelativeTimeDisplay'; import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder"; @@ -19,13 +19,13 @@ export class RacesViewDataBuilder implements ViewDataBuilder { track: race.track, car: race.car, scheduledAt: race.scheduledAt, - scheduledAtLabel: DateDisplay.formatShort(race.scheduledAt), - timeLabel: DateDisplay.formatTime(race.scheduledAt), - relativeTimeLabel: RelativeTimeDisplay.format(race.scheduledAt, now), + scheduledAtLabel: DateFormatter.formatShort(race.scheduledAt), + timeLabel: DateFormatter.formatTime(race.scheduledAt), + relativeTimeLabel: RelativeTimeFormatter.format(race.scheduledAt, now), status: race.status as RaceViewData['status'], - statusLabel: RaceStatusDisplay.getLabel(race.status), - statusVariant: RaceStatusDisplay.getVariant(race.status), - statusIconName: RaceStatusDisplay.getIconName(race.status), + statusLabel: RaceStatusFormatter.getLabel(race.status), + statusVariant: RaceStatusFormatter.getVariant(race.status), + statusIconName: RaceStatusFormatter.getIconName(race.status), sessionType: 'Race', leagueId: race.leagueId, leagueName: race.leagueName, diff --git a/apps/website/lib/builders/view-data/TeamDetailViewDataBuilder.ts b/apps/website/lib/builders/view-data/TeamDetailViewDataBuilder.ts index 963905173..d4d8e886e 100644 --- a/apps/website/lib/builders/view-data/TeamDetailViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/TeamDetailViewDataBuilder.ts @@ -1,9 +1,9 @@ +import { DateFormatter } from '@/lib/formatters/DateFormatter'; +import { LeagueFormatter } from '@/lib/formatters/LeagueFormatter'; +import { MemberFormatter } from '@/lib/formatters/MemberFormatter'; +import { NumberFormatter } from '@/lib/formatters/NumberFormatter'; import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO'; -import type { TeamDetailViewData, TeamDetailData, TeamMemberData, SponsorMetric, TeamTab } from '@/lib/view-data/TeamDetailViewData'; -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'; +import type { SponsorMetric, TeamDetailData, TeamDetailViewData, TeamMemberData, TeamTab } from '@/lib/view-data/TeamDetailViewData'; import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder"; @@ -21,7 +21,7 @@ export class TeamDetailViewDataBuilder implements ViewDataBuilder { ownerId: apiDto.team.ownerId, leagues: (apiDto.team as any).leagues || [], createdAt: apiDto.team.createdAt, - foundedDateLabel: apiDto.team.createdAt ? DateDisplay.formatMonthYear(apiDto.team.createdAt) : 'Unknown', + foundedDateLabel: apiDto.team.createdAt ? DateFormatter.formatMonthYear(apiDto.team.createdAt) : 'Unknown', specialization: (apiDto.team as any).specialization || null, region: (apiDto.team as any).region || null, languages: (apiDto.team as any).languages || [], @@ -35,7 +35,7 @@ export class TeamDetailViewDataBuilder implements ViewDataBuilder { driverName: membership.driverName, role: membership.role, joinedAt: membership.joinedAt, - joinedAtLabel: DateDisplay.formatShort(membership.joinedAt), + joinedAtLabel: DateFormatter.formatShort(membership.joinedAt), isActive: membership.isActive, avatarUrl: membership.avatarUrl, })); @@ -51,19 +51,19 @@ export class TeamDetailViewDataBuilder implements ViewDataBuilder { { icon: 'users', label: 'Members', - value: NumberDisplay.format(memberships.length), + value: NumberFormatter.format(memberships.length), color: 'text-primary-blue', }, { icon: 'zap', label: 'Est. Reach', - value: NumberDisplay.format(memberships.length * 15), + value: NumberFormatter.format(memberships.length * 15), color: 'text-purple-400', }, { icon: 'calendar', label: 'Races', - value: NumberDisplay.format(leagueCount), + value: NumberFormatter.format(leagueCount), color: 'text-neon-aqua', }, { @@ -89,8 +89,8 @@ export class TeamDetailViewDataBuilder implements ViewDataBuilder { isAdmin, teamMetrics, tabs, - memberCountLabel: MemberDisplay.formatCount(memberships.length), - leagueCountLabel: LeagueDisplay.formatCount(leagueCount), + memberCountLabel: MemberFormatter.formatCount(memberships.length), + leagueCountLabel: LeagueFormatter.formatCount(leagueCount), }; } } diff --git a/apps/website/lib/builders/view-data/TeamsViewDataBuilder.ts b/apps/website/lib/builders/view-data/TeamsViewDataBuilder.ts index 16d2746fa..7af288b7c 100644 --- a/apps/website/lib/builders/view-data/TeamsViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/TeamsViewDataBuilder.ts @@ -1,8 +1,8 @@ +import { NumberFormatter } from '@/lib/formatters/NumberFormatter'; +import { RatingFormatter } from '@/lib/formatters/RatingFormatter'; import type { GetAllTeamsOutputDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO'; -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'; +import type { TeamSummaryData, TeamsViewData } from '@/lib/view-data/TeamsViewData'; import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder"; @@ -17,10 +17,10 @@ export class TeamsViewDataBuilder implements ViewDataBuilder { teamName: team.name, memberCount: team.memberCount, logoUrl: team.logoUrl || '', - ratingLabel: RatingDisplay.format(team.rating), + ratingLabel: RatingFormatter.format(team.rating), ratingValue: team.rating || 0, - winsLabel: NumberDisplay.format(team.totalWins || 0), - racesLabel: NumberDisplay.format(team.totalRaces || 0), + winsLabel: NumberFormatter.format(team.totalWins || 0), + racesLabel: NumberFormatter.format(team.totalRaces || 0), region: team.region || '', isRecruiting: team.isRecruiting, category: team.category || '', diff --git a/apps/website/lib/command-models/leagues/LeagueWizardCommandModel.ts b/apps/website/lib/command-models/leagues/LeagueWizardCommandModel.ts index 49b58bb9c..80faf14f9 100644 --- a/apps/website/lib/command-models/leagues/LeagueWizardCommandModel.ts +++ b/apps/website/lib/command-models/leagues/LeagueWizardCommandModel.ts @@ -1,5 +1,5 @@ +import { LeagueWizardValidationMessages } from '@/lib/formatters/LeagueWizardValidationMessages'; import type { CreateLeagueInputDTO } from '@/lib/types/generated/CreateLeagueInputDTO'; -import { LeagueWizardValidationMessages } from '@/lib/display-objects/LeagueWizardValidationMessages'; export type WizardStep = 1 | 2 | 3 | 4 | 5 | 6 | 7; diff --git a/apps/website/lib/contracts/formatters/DisplayObject.ts b/apps/website/lib/contracts/formatters/Formatter.ts similarity index 61% rename from apps/website/lib/contracts/formatters/DisplayObject.ts rename to apps/website/lib/contracts/formatters/Formatter.ts index 207e2bdfb..ab8011300 100644 --- a/apps/website/lib/contracts/formatters/DisplayObject.ts +++ b/apps/website/lib/contracts/formatters/Formatter.ts @@ -1,5 +1,5 @@ /** - * DisplayObject contract + * Formatter contract * * Deterministic, reusable, UI-only formatting/mapping logic. * @@ -12,18 +12,11 @@ * - No business rules */ -export interface DisplayObject { +export interface Formatter { /** * Format or map the display object * * @returns Primitive values only (strings, numbers, booleans) */ format(): unknown; - - /** - * Optional: Get multiple display variants - * - * Allows a single DisplayObject to expose multiple presentation formats - */ - variants?(): Record; } \ No newline at end of file diff --git a/apps/website/lib/display-objects/DashboardCountDisplay.test.ts b/apps/website/lib/display-objects/DashboardCountDisplay.test.ts deleted file mode 100644 index f6fcbb047..000000000 --- a/apps/website/lib/display-objects/DashboardCountDisplay.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { DashboardCountDisplay } from './DashboardCountDisplay'; - -describe('DashboardCountDisplay', () => { - describe('happy paths', () => { - it('should format positive numbers correctly', () => { - expect(DashboardCountDisplay.format(0)).toBe('0'); - expect(DashboardCountDisplay.format(1)).toBe('1'); - expect(DashboardCountDisplay.format(100)).toBe('100'); - expect(DashboardCountDisplay.format(1000)).toBe('1000'); - }); - - it('should handle null values', () => { - expect(DashboardCountDisplay.format(null)).toBe('0'); - }); - - it('should handle undefined values', () => { - expect(DashboardCountDisplay.format(undefined)).toBe('0'); - }); - }); - - describe('edge cases', () => { - it('should handle negative numbers', () => { - expect(DashboardCountDisplay.format(-1)).toBe('-1'); - expect(DashboardCountDisplay.format(-100)).toBe('-100'); - }); - - it('should handle large numbers', () => { - expect(DashboardCountDisplay.format(999999)).toBe('999999'); - expect(DashboardCountDisplay.format(1000000)).toBe('1000000'); - }); - - it('should handle decimal numbers', () => { - expect(DashboardCountDisplay.format(1.5)).toBe('1.5'); - expect(DashboardCountDisplay.format(100.99)).toBe('100.99'); - }); - }); -}); diff --git a/apps/website/lib/display-objects/DashboardViewDataConsistency.test.ts b/apps/website/lib/display-objects/DashboardViewDataConsistency.test.ts deleted file mode 100644 index 171cd2675..000000000 --- a/apps/website/lib/display-objects/DashboardViewDataConsistency.test.ts +++ /dev/null @@ -1,369 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { DashboardViewDataBuilder } from '../builders/view-data/DashboardViewDataBuilder'; -import { DashboardDateDisplay } from './DashboardDateDisplay'; -import { DashboardCountDisplay } from './DashboardCountDisplay'; -import { DashboardRankDisplay } from './DashboardRankDisplay'; -import { DashboardConsistencyDisplay } from './DashboardConsistencyDisplay'; -import { DashboardLeaguePositionDisplay } from './DashboardLeaguePositionDisplay'; -import { RatingDisplay } from './RatingDisplay'; -import type { DashboardOverviewDTO } from '@/lib/types/generated/DashboardOverviewDTO'; - -describe('Dashboard View Data - Cross-Component Consistency', () => { - describe('common patterns', () => { - it('should all use consistent formatting for numeric values', () => { - const dashboardDTO: DashboardOverviewDTO = { - currentDriver: { - id: 'driver-123', - name: 'John Doe', - country: 'USA', - rating: 1234.56, - globalRank: 42, - totalRaces: 150, - wins: 25, - podiums: 60, - consistency: 85, - }, - myUpcomingRaces: [], - otherUpcomingRaces: [], - upcomingRaces: [], - activeLeaguesCount: 3, - nextRace: null, - recentResults: [], - leagueStandingsSummaries: [ - { - leagueId: 'league-1', - leagueName: 'Test League', - position: 5, - totalDrivers: 50, - points: 1250, - }, - ], - feedSummary: { - notificationCount: 0, - items: [], - }, - friends: [ - { id: 'friend-1', name: 'Alice', country: 'UK' }, - { id: 'friend-2', name: 'Bob', country: 'Germany' }, - ], - }; - - const result = DashboardViewDataBuilder.build(dashboardDTO); - - // All numeric values should be formatted as strings - expect(typeof result.currentDriver.rating).toBe('string'); - expect(typeof result.currentDriver.rank).toBe('string'); - expect(typeof result.currentDriver.totalRaces).toBe('string'); - expect(typeof result.currentDriver.wins).toBe('string'); - expect(typeof result.currentDriver.podiums).toBe('string'); - expect(typeof result.currentDriver.consistency).toBe('string'); - expect(typeof result.activeLeaguesCount).toBe('string'); - expect(typeof result.friendCount).toBe('string'); - expect(typeof result.leagueStandings[0].position).toBe('string'); - expect(typeof result.leagueStandings[0].points).toBe('string'); - expect(typeof result.leagueStandings[0].totalDrivers).toBe('string'); - }); - - it('should all handle missing data gracefully', () => { - const dashboardDTO: DashboardOverviewDTO = { - myUpcomingRaces: [], - otherUpcomingRaces: [], - upcomingRaces: [], - activeLeaguesCount: 0, - nextRace: null, - recentResults: [], - leagueStandingsSummaries: [], - feedSummary: { - notificationCount: 0, - items: [], - }, - friends: [], - }; - - const result = DashboardViewDataBuilder.build(dashboardDTO); - - // All fields should have safe defaults - expect(result.currentDriver.name).toBe(''); - expect(result.currentDriver.avatarUrl).toBe(''); - expect(result.currentDriver.country).toBe(''); - expect(result.currentDriver.rating).toBe('0.0'); - expect(result.currentDriver.rank).toBe('0'); - expect(result.currentDriver.totalRaces).toBe('0'); - expect(result.currentDriver.wins).toBe('0'); - expect(result.currentDriver.podiums).toBe('0'); - expect(result.currentDriver.consistency).toBe('0%'); - expect(result.nextRace).toBeNull(); - expect(result.upcomingRaces).toEqual([]); - expect(result.leagueStandings).toEqual([]); - expect(result.feedItems).toEqual([]); - expect(result.friends).toEqual([]); - expect(result.activeLeaguesCount).toBe('0'); - expect(result.friendCount).toBe('0'); - }); - - it('should all preserve ISO timestamps for serialization', () => { - const now = new Date(); - const futureDate = new Date(now.getTime() + 24 * 60 * 60 * 1000); - const feedTimestamp = new Date(now.getTime() - 30 * 60 * 1000); - - const dashboardDTO: DashboardOverviewDTO = { - myUpcomingRaces: [], - otherUpcomingRaces: [], - upcomingRaces: [], - activeLeaguesCount: 1, - nextRace: { - id: 'race-1', - track: 'Spa', - car: 'Porsche', - scheduledAt: futureDate.toISOString(), - status: 'scheduled', - isMyLeague: true, - }, - recentResults: [], - leagueStandingsSummaries: [], - feedSummary: { - notificationCount: 1, - items: [ - { - id: 'feed-1', - type: 'notification', - headline: 'Test', - timestamp: feedTimestamp.toISOString(), - }, - ], - }, - friends: [], - }; - - const result = DashboardViewDataBuilder.build(dashboardDTO); - - // All timestamps should be preserved as ISO strings - expect(result.nextRace?.scheduledAt).toBe(futureDate.toISOString()); - expect(result.feedItems[0].timestamp).toBe(feedTimestamp.toISOString()); - }); - - it('should all handle boolean flags correctly', () => { - const dashboardDTO: DashboardOverviewDTO = { - myUpcomingRaces: [], - otherUpcomingRaces: [], - upcomingRaces: [ - { - id: 'race-1', - track: 'Spa', - car: 'Porsche', - scheduledAt: new Date().toISOString(), - status: 'scheduled', - isMyLeague: true, - }, - { - id: 'race-2', - track: 'Monza', - car: 'Ferrari', - scheduledAt: new Date().toISOString(), - status: 'scheduled', - isMyLeague: false, - }, - ], - activeLeaguesCount: 1, - nextRace: null, - recentResults: [], - leagueStandingsSummaries: [], - feedSummary: { - notificationCount: 0, - items: [], - }, - friends: [], - }; - - const result = DashboardViewDataBuilder.build(dashboardDTO); - - expect(result.upcomingRaces[0].isMyLeague).toBe(true); - expect(result.upcomingRaces[1].isMyLeague).toBe(false); - }); - }); - - describe('data integrity', () => { - it('should maintain data consistency across transformations', () => { - const dashboardDTO: DashboardOverviewDTO = { - currentDriver: { - id: 'driver-123', - name: 'John Doe', - country: 'USA', - rating: 1234.56, - globalRank: 42, - totalRaces: 150, - wins: 25, - podiums: 60, - consistency: 85, - }, - myUpcomingRaces: [], - otherUpcomingRaces: [], - upcomingRaces: [], - activeLeaguesCount: 3, - nextRace: null, - recentResults: [], - leagueStandingsSummaries: [], - feedSummary: { - notificationCount: 5, - items: [], - }, - friends: [ - { id: 'friend-1', name: 'Alice', country: 'UK' }, - { id: 'friend-2', name: 'Bob', country: 'Germany' }, - ], - }; - - const result = DashboardViewDataBuilder.build(dashboardDTO); - - // Verify derived fields match their source data - expect(result.friendCount).toBe(dashboardDTO.friends.length.toString()); - expect(result.activeLeaguesCount).toBe(dashboardDTO.activeLeaguesCount.toString()); - expect(result.hasFriends).toBe(dashboardDTO.friends.length > 0); - expect(result.hasUpcomingRaces).toBe(dashboardDTO.upcomingRaces.length > 0); - expect(result.hasLeagueStandings).toBe(dashboardDTO.leagueStandingsSummaries.length > 0); - expect(result.hasFeedItems).toBe(dashboardDTO.feedSummary.items.length > 0); - }); - - it('should handle complex real-world scenarios', () => { - const now = new Date(); - const race1Date = new Date(now.getTime() + 2 * 24 * 60 * 60 * 1000); - const race2Date = new Date(now.getTime() + 5 * 24 * 60 * 60 * 1000); - const feedTimestamp = new Date(now.getTime() - 60 * 60 * 1000); - - const dashboardDTO: DashboardOverviewDTO = { - currentDriver: { - id: 'driver-123', - name: 'John Doe', - country: 'USA', - avatarUrl: 'https://example.com/avatar.jpg', - rating: 2456.78, - globalRank: 15, - totalRaces: 250, - wins: 45, - podiums: 120, - consistency: 92.5, - }, - myUpcomingRaces: [], - otherUpcomingRaces: [], - upcomingRaces: [ - { - id: 'race-1', - leagueId: 'league-1', - leagueName: 'Pro League', - track: 'Spa', - car: 'Porsche 911 GT3', - scheduledAt: race1Date.toISOString(), - status: 'scheduled', - isMyLeague: true, - }, - { - id: 'race-2', - track: 'Monza', - car: 'Ferrari 488 GT3', - scheduledAt: race2Date.toISOString(), - status: 'scheduled', - isMyLeague: false, - }, - ], - activeLeaguesCount: 2, - nextRace: { - id: 'race-1', - leagueId: 'league-1', - leagueName: 'Pro League', - track: 'Spa', - car: 'Porsche 911 GT3', - scheduledAt: race1Date.toISOString(), - status: 'scheduled', - isMyLeague: true, - }, - recentResults: [], - leagueStandingsSummaries: [ - { - leagueId: 'league-1', - leagueName: 'Pro League', - position: 3, - totalDrivers: 100, - points: 2450, - }, - { - leagueId: 'league-2', - leagueName: 'Rookie League', - position: 1, - totalDrivers: 50, - points: 1800, - }, - ], - feedSummary: { - notificationCount: 3, - items: [ - { - id: 'feed-1', - type: 'race_result', - headline: 'Race completed', - body: 'You finished 3rd in the Pro League race', - timestamp: feedTimestamp.toISOString(), - ctaLabel: 'View Results', - ctaHref: '/races/123', - }, - { - id: 'feed-2', - type: 'league_update', - headline: 'League standings updated', - body: 'You moved up 2 positions', - timestamp: feedTimestamp.toISOString(), - }, - ], - }, - friends: [ - { id: 'friend-1', name: 'Alice', country: 'UK', avatarUrl: 'https://example.com/alice.jpg' }, - { id: 'friend-2', name: 'Bob', country: 'Germany' }, - { id: 'friend-3', name: 'Charlie', country: 'France', avatarUrl: 'https://example.com/charlie.jpg' }, - ], - }; - - const result = DashboardViewDataBuilder.build(dashboardDTO); - - // Verify all transformations - expect(result.currentDriver.name).toBe('John Doe'); - expect(result.currentDriver.rating).toBe('2,457'); - expect(result.currentDriver.rank).toBe('15'); - expect(result.currentDriver.totalRaces).toBe('250'); - expect(result.currentDriver.wins).toBe('45'); - expect(result.currentDriver.podiums).toBe('120'); - expect(result.currentDriver.consistency).toBe('92.5%'); - - expect(result.nextRace).not.toBeNull(); - expect(result.nextRace?.id).toBe('race-1'); - expect(result.nextRace?.track).toBe('Spa'); - expect(result.nextRace?.isMyLeague).toBe(true); - - expect(result.upcomingRaces).toHaveLength(2); - expect(result.upcomingRaces[0].isMyLeague).toBe(true); - expect(result.upcomingRaces[1].isMyLeague).toBe(false); - - expect(result.leagueStandings).toHaveLength(2); - expect(result.leagueStandings[0].position).toBe('#3'); - expect(result.leagueStandings[0].points).toBe('2450'); - expect(result.leagueStandings[1].position).toBe('#1'); - expect(result.leagueStandings[1].points).toBe('1800'); - - expect(result.feedItems).toHaveLength(2); - expect(result.feedItems[0].type).toBe('race_result'); - expect(result.feedItems[0].ctaLabel).toBe('View Results'); - expect(result.feedItems[1].type).toBe('league_update'); - expect(result.feedItems[1].ctaLabel).toBeUndefined(); - - expect(result.friends).toHaveLength(3); - expect(result.friends[0].avatarUrl).toBe('https://example.com/alice.jpg'); - expect(result.friends[1].avatarUrl).toBe(''); - expect(result.friends[2].avatarUrl).toBe('https://example.com/charlie.jpg'); - - expect(result.activeLeaguesCount).toBe('2'); - expect(result.friendCount).toBe('3'); - expect(result.hasUpcomingRaces).toBe(true); - expect(result.hasLeagueStandings).toBe(true); - expect(result.hasFeedItems).toBe(true); - expect(result.hasFriends).toBe(true); - }); - }); -}); diff --git a/apps/website/lib/display-objects/AchievementDisplay.ts b/apps/website/lib/formatters/AchievementFormatter.ts similarity index 96% rename from apps/website/lib/display-objects/AchievementDisplay.ts rename to apps/website/lib/formatters/AchievementFormatter.ts index 6d69b31a0..bcd9d4c64 100644 --- a/apps/website/lib/display-objects/AchievementDisplay.ts +++ b/apps/website/lib/formatters/AchievementFormatter.ts @@ -1,4 +1,4 @@ -export class AchievementDisplay { +export class AchievementFormatter { static getRarityVariant(rarity: string) { switch (rarity.toLowerCase()) { case 'common': diff --git a/apps/website/lib/display-objects/ActivityLevelDisplay.ts b/apps/website/lib/formatters/ActivityLevelFormatter.ts similarity index 94% rename from apps/website/lib/display-objects/ActivityLevelDisplay.ts rename to apps/website/lib/formatters/ActivityLevelFormatter.ts index cd0794390..adecaa584 100644 --- a/apps/website/lib/display-objects/ActivityLevelDisplay.ts +++ b/apps/website/lib/formatters/ActivityLevelFormatter.ts @@ -4,7 +4,7 @@ * Deterministic mapping of engagement rates to activity level labels. */ -export class ActivityLevelDisplay { +export class ActivityLevelFormatter { /** * Maps engagement rate to activity level label. */ diff --git a/apps/website/lib/display-objects/AvatarDisplay.ts b/apps/website/lib/formatters/AvatarFormatter.ts similarity index 96% rename from apps/website/lib/display-objects/AvatarDisplay.ts rename to apps/website/lib/formatters/AvatarFormatter.ts index 64ec4c7b8..4c2b515a8 100644 --- a/apps/website/lib/display-objects/AvatarDisplay.ts +++ b/apps/website/lib/formatters/AvatarFormatter.ts @@ -4,7 +4,7 @@ * Deterministic mapping of avatar-related data to display formats. */ -export class AvatarDisplay { +export class AvatarFormatter { /** * Converts binary buffer to base64 string for display. */ diff --git a/apps/website/lib/display-objects/CountryFlagDisplay.ts b/apps/website/lib/formatters/CountryFlagFormatter.ts similarity index 61% rename from apps/website/lib/display-objects/CountryFlagDisplay.ts rename to apps/website/lib/formatters/CountryFlagFormatter.ts index 7ba7a1bb8..51cd7d3cf 100644 --- a/apps/website/lib/display-objects/CountryFlagDisplay.ts +++ b/apps/website/lib/formatters/CountryFlagFormatter.ts @@ -1,18 +1,18 @@ -export class CountryFlagDisplay { +export class CountryFlagFormatter { private constructor(private readonly value: string) {} - static fromCountryCode(countryCode: string | null | undefined): CountryFlagDisplay { + static fromCountryCode(countryCode: string | null | undefined): CountryFlagFormatter { if (!countryCode) { - return new CountryFlagDisplay('🏁'); + return new CountryFlagFormatter('🏁'); } const code = countryCode.toUpperCase(); if (code.length !== 2) { - return new CountryFlagDisplay('🏁'); + return new CountryFlagFormatter('🏁'); } const codePoints = [...code].map((char) => 127397 + char.charCodeAt(0)); - return new CountryFlagDisplay(String.fromCodePoint(...codePoints)); + return new CountryFlagFormatter(String.fromCodePoint(...codePoints)); } toString(): string { diff --git a/apps/website/lib/display-objects/CurrencyDisplay.ts b/apps/website/lib/formatters/CurrencyFormatter.ts similarity index 97% rename from apps/website/lib/display-objects/CurrencyDisplay.ts rename to apps/website/lib/formatters/CurrencyFormatter.ts index 21fe6fc59..139091cea 100644 --- a/apps/website/lib/display-objects/CurrencyDisplay.ts +++ b/apps/website/lib/formatters/CurrencyFormatter.ts @@ -5,7 +5,7 @@ * Avoids Intl and toLocaleString to prevent SSR/hydration mismatches. */ -export class CurrencyDisplay { +export class CurrencyFormatter { /** * Formats an amount as currency (e.g., "$10.00" or "€1.000,00"). * Default currency is USD. diff --git a/apps/website/lib/display-objects/DashboardConsistencyDisplay.test.ts b/apps/website/lib/formatters/DashboardConsistencyFormatter.test.ts similarity index 92% rename from apps/website/lib/display-objects/DashboardConsistencyDisplay.test.ts rename to apps/website/lib/formatters/DashboardConsistencyFormatter.test.ts index a78b7133c..9b533ae5e 100644 --- a/apps/website/lib/display-objects/DashboardConsistencyDisplay.test.ts +++ b/apps/website/lib/formatters/DashboardConsistencyFormatter.test.ts @@ -1,5 +1,5 @@ -import { describe, it, expect } from 'vitest'; -import { DashboardConsistencyDisplay } from './DashboardConsistencyDisplay'; +import { describe, expect, it } from 'vitest'; +import { DashboardConsistencyDisplay } from './DashboardConsistencyFormatter'; describe('DashboardConsistencyDisplay', () => { describe('happy paths', () => { diff --git a/apps/website/lib/display-objects/DashboardConsistencyDisplay.ts b/apps/website/lib/formatters/DashboardConsistencyFormatter.ts similarity index 80% rename from apps/website/lib/display-objects/DashboardConsistencyDisplay.ts rename to apps/website/lib/formatters/DashboardConsistencyFormatter.ts index 08c9b0b0c..a0c40783e 100644 --- a/apps/website/lib/display-objects/DashboardConsistencyDisplay.ts +++ b/apps/website/lib/formatters/DashboardConsistencyFormatter.ts @@ -4,7 +4,7 @@ * Deterministic consistency formatting for dashboard display. */ -export class DashboardConsistencyDisplay { +export class DashboardConsistencyFormatter { static format(consistency: number): string { return `${consistency}%`; } diff --git a/apps/website/lib/formatters/DashboardCountFormatter.test.ts b/apps/website/lib/formatters/DashboardCountFormatter.test.ts new file mode 100644 index 000000000..95afdb86f --- /dev/null +++ b/apps/website/lib/formatters/DashboardCountFormatter.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from 'vitest'; +import { DashboardCountFormatter } from './DashboardCountFormatter'; + +describe('DashboardCountDisplay', () => { + describe('happy paths', () => { + it('should format positive numbers correctly', () => { + expect(DashboardCountFormatter.format(0)).toBe('0'); + expect(DashboardCountFormatter.format(1)).toBe('1'); + expect(DashboardCountFormatter.format(100)).toBe('100'); + expect(DashboardCountFormatter.format(1000)).toBe('1000'); + }); + + it('should handle null values', () => { + expect(DashboardCountFormatter.format(null)).toBe('0'); + }); + + it('should handle undefined values', () => { + expect(DashboardCountFormatter.format(undefined)).toBe('0'); + }); + }); + + describe('edge cases', () => { + it('should handle negative numbers', () => { + expect(DashboardCountFormatter.format(-1)).toBe('-1'); + expect(DashboardCountFormatter.format(-100)).toBe('-100'); + }); + + it('should handle large numbers', () => { + expect(DashboardCountFormatter.format(999999)).toBe('999999'); + expect(DashboardCountFormatter.format(1000000)).toBe('1000000'); + }); + + it('should handle decimal numbers', () => { + expect(DashboardCountFormatter.format(1.5)).toBe('1.5'); + expect(DashboardCountFormatter.format(100.99)).toBe('100.99'); + }); + }); +}); diff --git a/apps/website/lib/display-objects/DashboardCountDisplay.ts b/apps/website/lib/formatters/DashboardCountFormatter.ts similarity index 87% rename from apps/website/lib/display-objects/DashboardCountDisplay.ts rename to apps/website/lib/formatters/DashboardCountFormatter.ts index 97c5c43af..b18b6ae08 100644 --- a/apps/website/lib/display-objects/DashboardCountDisplay.ts +++ b/apps/website/lib/formatters/DashboardCountFormatter.ts @@ -4,7 +4,7 @@ * Deterministic count formatting for dashboard display. */ -export class DashboardCountDisplay { +export class DashboardCountFormatter { static format(count: number | null | undefined): string { if (count === null || count === undefined) { return '0'; diff --git a/apps/website/lib/display-objects/DashboardDateDisplay.test.ts b/apps/website/lib/formatters/DashboardDateFormatter.test.ts similarity index 100% rename from apps/website/lib/display-objects/DashboardDateDisplay.test.ts rename to apps/website/lib/formatters/DashboardDateFormatter.test.ts diff --git a/apps/website/lib/display-objects/DashboardDateDisplay.ts b/apps/website/lib/formatters/DashboardDateFormatter.ts similarity index 97% rename from apps/website/lib/display-objects/DashboardDateDisplay.ts rename to apps/website/lib/formatters/DashboardDateFormatter.ts index 6b9672a92..cf8f78146 100644 --- a/apps/website/lib/display-objects/DashboardDateDisplay.ts +++ b/apps/website/lib/formatters/DashboardDateFormatter.ts @@ -14,7 +14,7 @@ export interface DashboardDateDisplayData { /** * Format date for display (deterministic, no Intl) */ -export class DashboardDateDisplay { +export class DashboardDateFormatter { static format(date: Date): DashboardDateDisplayData { const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; diff --git a/apps/website/lib/display-objects/DashboardLeaguePositionDisplay.test.ts b/apps/website/lib/formatters/DashboardLeaguePositionFormatter.test.ts similarity index 93% rename from apps/website/lib/display-objects/DashboardLeaguePositionDisplay.test.ts rename to apps/website/lib/formatters/DashboardLeaguePositionFormatter.test.ts index 8011d12cf..6bab9ef9a 100644 --- a/apps/website/lib/display-objects/DashboardLeaguePositionDisplay.test.ts +++ b/apps/website/lib/formatters/DashboardLeaguePositionFormatter.test.ts @@ -1,5 +1,5 @@ -import { describe, it, expect } from 'vitest'; -import { DashboardLeaguePositionDisplay } from './DashboardLeaguePositionDisplay'; +import { describe, expect, it } from 'vitest'; +import { DashboardLeaguePositionDisplay } from './DashboardLeaguePositionFormatter'; describe('DashboardLeaguePositionDisplay', () => { describe('happy paths', () => { diff --git a/apps/website/lib/display-objects/DashboardLeaguePositionDisplay.ts b/apps/website/lib/formatters/DashboardLeaguePositionFormatter.ts similarity index 85% rename from apps/website/lib/display-objects/DashboardLeaguePositionDisplay.ts rename to apps/website/lib/formatters/DashboardLeaguePositionFormatter.ts index c2b0dde9f..66cb21a04 100644 --- a/apps/website/lib/display-objects/DashboardLeaguePositionDisplay.ts +++ b/apps/website/lib/formatters/DashboardLeaguePositionFormatter.ts @@ -4,7 +4,7 @@ * Deterministic league position formatting for dashboard display. */ -export class DashboardLeaguePositionDisplay { +export class DashboardLeaguePositionFormatter { static format(position: number | null | undefined): string { if (position === null || position === undefined) { return '-'; diff --git a/apps/website/lib/display-objects/DashboardRankDisplay.test.ts b/apps/website/lib/formatters/DashboardRankFormatter.test.ts similarity index 83% rename from apps/website/lib/display-objects/DashboardRankDisplay.test.ts rename to apps/website/lib/formatters/DashboardRankFormatter.test.ts index c048d8a7f..668ea0b19 100644 --- a/apps/website/lib/display-objects/DashboardRankDisplay.test.ts +++ b/apps/website/lib/formatters/DashboardRankFormatter.test.ts @@ -1,5 +1,5 @@ -import { describe, it, expect } from 'vitest'; -import { DashboardRankDisplay } from './DashboardRankDisplay'; +import { describe, expect, it } from 'vitest'; +import { DashboardRankDisplay } from './DashboardRankFormatter'; describe('DashboardRankDisplay', () => { describe('happy paths', () => { diff --git a/apps/website/lib/display-objects/DashboardRankDisplay.ts b/apps/website/lib/formatters/DashboardRankFormatter.ts similarity index 81% rename from apps/website/lib/display-objects/DashboardRankDisplay.ts rename to apps/website/lib/formatters/DashboardRankFormatter.ts index 2556d3e34..8165636df 100644 --- a/apps/website/lib/display-objects/DashboardRankDisplay.ts +++ b/apps/website/lib/formatters/DashboardRankFormatter.ts @@ -4,7 +4,7 @@ * Deterministic rank formatting for dashboard display. */ -export class DashboardRankDisplay { +export class DashboardRankFormatter { static format(rank: number): string { return rank.toString(); } diff --git a/apps/website/lib/display-objects/DateDisplay.ts b/apps/website/lib/formatters/DateFormatter.ts similarity index 98% rename from apps/website/lib/display-objects/DateDisplay.ts rename to apps/website/lib/formatters/DateFormatter.ts index c9b3c6fa4..1b41f5207 100644 --- a/apps/website/lib/display-objects/DateDisplay.ts +++ b/apps/website/lib/formatters/DateFormatter.ts @@ -1,4 +1,4 @@ -export class DateDisplay { +export class DateFormatter { /** * Formats a date as "Jan 18, 2026" using UTC. */ diff --git a/apps/website/lib/display-objects/DriverRegistrationStatusDisplay.tsx b/apps/website/lib/formatters/DriverRegistrationStatusFormatter.tsx similarity index 91% rename from apps/website/lib/display-objects/DriverRegistrationStatusDisplay.tsx rename to apps/website/lib/formatters/DriverRegistrationStatusFormatter.tsx index 956345217..272373d1d 100644 --- a/apps/website/lib/display-objects/DriverRegistrationStatusDisplay.tsx +++ b/apps/website/lib/formatters/DriverRegistrationStatusFormatter.tsx @@ -5,7 +5,7 @@ * to UI labels and variants. */ -export class DriverRegistrationStatusDisplay { +export class DriverRegistrationStatusFormatter { static statusMessage(isRegistered: boolean): string { return isRegistered ? "Registered for this race" : "Not registered"; } diff --git a/apps/website/lib/display-objects/DurationDisplay.ts b/apps/website/lib/formatters/DurationFormatter.ts similarity index 93% rename from apps/website/lib/display-objects/DurationDisplay.ts rename to apps/website/lib/formatters/DurationFormatter.ts index 0e97f4fef..103a56d7f 100644 --- a/apps/website/lib/display-objects/DurationDisplay.ts +++ b/apps/website/lib/formatters/DurationFormatter.ts @@ -4,7 +4,7 @@ * Deterministic formatting for time durations. */ -export class DurationDisplay { +export class DurationFormatter { /** * Formats milliseconds as "123.45ms". */ diff --git a/apps/website/lib/display-objects/FinishDisplay.ts b/apps/website/lib/formatters/FinishFormatter.ts similarity index 94% rename from apps/website/lib/display-objects/FinishDisplay.ts rename to apps/website/lib/formatters/FinishFormatter.ts index 1d8aafad5..6525e9100 100644 --- a/apps/website/lib/display-objects/FinishDisplay.ts +++ b/apps/website/lib/formatters/FinishFormatter.ts @@ -4,7 +4,7 @@ * Deterministic formatting for race finish positions. */ -export class FinishDisplay { +export class FinishFormatter { /** * Formats a finish position as "P1", "P2", etc. */ diff --git a/apps/website/lib/display-objects/HealthAlertDisplay.ts b/apps/website/lib/formatters/HealthAlertFormatter.ts similarity index 97% rename from apps/website/lib/display-objects/HealthAlertDisplay.ts rename to apps/website/lib/formatters/HealthAlertFormatter.ts index 9fd951325..75a79d695 100644 --- a/apps/website/lib/display-objects/HealthAlertDisplay.ts +++ b/apps/website/lib/formatters/HealthAlertFormatter.ts @@ -5,7 +5,7 @@ * This display object isolates UI-specific formatting from business logic. */ -export class HealthAlertDisplay { +export class HealthAlertFormatter { static formatSeverity(type: 'critical' | 'warning' | 'info'): string { const severities: Record = { critical: 'Critical', diff --git a/apps/website/lib/display-objects/HealthComponentDisplay.ts b/apps/website/lib/formatters/HealthComponentFormatter.ts similarity index 97% rename from apps/website/lib/display-objects/HealthComponentDisplay.ts rename to apps/website/lib/formatters/HealthComponentFormatter.ts index fb9162714..8bc0f9791 100644 --- a/apps/website/lib/display-objects/HealthComponentDisplay.ts +++ b/apps/website/lib/formatters/HealthComponentFormatter.ts @@ -5,7 +5,7 @@ * This display object isolates UI-specific formatting from business logic. */ -export class HealthComponentDisplay { +export class HealthComponentFormatter { static formatStatusLabel(status: 'ok' | 'degraded' | 'error' | 'unknown'): string { const labels: Record = { ok: 'Healthy', diff --git a/apps/website/lib/display-objects/HealthMetricDisplay.ts b/apps/website/lib/formatters/HealthMetricFormatter.ts similarity index 97% rename from apps/website/lib/display-objects/HealthMetricDisplay.ts rename to apps/website/lib/formatters/HealthMetricFormatter.ts index a8a04d7de..96b00a03a 100644 --- a/apps/website/lib/display-objects/HealthMetricDisplay.ts +++ b/apps/website/lib/formatters/HealthMetricFormatter.ts @@ -5,7 +5,7 @@ * This display object isolates UI-specific formatting from business logic. */ -export class HealthMetricDisplay { +export class HealthMetricFormatter { static formatUptime(uptime?: number): string { if (uptime === undefined || uptime === null) return 'N/A'; if (uptime < 0) return 'N/A'; diff --git a/apps/website/lib/display-objects/HealthStatusDisplay.ts b/apps/website/lib/formatters/HealthStatusFormatter.ts similarity index 98% rename from apps/website/lib/display-objects/HealthStatusDisplay.ts rename to apps/website/lib/formatters/HealthStatusFormatter.ts index fbd359af9..ef7b22458 100644 --- a/apps/website/lib/display-objects/HealthStatusDisplay.ts +++ b/apps/website/lib/formatters/HealthStatusFormatter.ts @@ -5,7 +5,7 @@ * This display object isolates UI-specific formatting from business logic. */ -export class HealthStatusDisplay { +export class HealthStatusFormatter { static formatStatusLabel(status: 'ok' | 'degraded' | 'error' | 'unknown'): string { const labels: Record = { ok: 'Healthy', diff --git a/apps/website/lib/display-objects/LeagueCreationStatusDisplay.ts b/apps/website/lib/formatters/LeagueCreationStatusFormatter.ts similarity index 87% rename from apps/website/lib/display-objects/LeagueCreationStatusDisplay.ts rename to apps/website/lib/formatters/LeagueCreationStatusFormatter.ts index c587f36d4..c43c62d76 100644 --- a/apps/website/lib/display-objects/LeagueCreationStatusDisplay.ts +++ b/apps/website/lib/formatters/LeagueCreationStatusFormatter.ts @@ -4,7 +4,7 @@ * Deterministic mapping of league creation status to display messages. */ -export class LeagueCreationStatusDisplay { +export class LeagueCreationStatusFormatter { /** * Maps league creation success status to display message. */ diff --git a/apps/website/lib/display-objects/LeagueDisplay.ts b/apps/website/lib/formatters/LeagueFormatter.ts similarity index 90% rename from apps/website/lib/display-objects/LeagueDisplay.ts rename to apps/website/lib/formatters/LeagueFormatter.ts index fbebfde88..25cf58f56 100644 --- a/apps/website/lib/display-objects/LeagueDisplay.ts +++ b/apps/website/lib/formatters/LeagueFormatter.ts @@ -4,7 +4,7 @@ * Deterministic display logic for leagues. */ -export class LeagueDisplay { +export class LeagueFormatter { /** * Formats a league count with pluralization. * Example: 1 -> "1 league", 2 -> "2 leagues" diff --git a/apps/website/lib/display-objects/LeagueRoleDisplay.ts b/apps/website/lib/formatters/LeagueRoleFormatter.ts similarity index 96% rename from apps/website/lib/display-objects/LeagueRoleDisplay.ts rename to apps/website/lib/formatters/LeagueRoleFormatter.ts index 951394b3e..5cb634b57 100644 --- a/apps/website/lib/display-objects/LeagueRoleDisplay.ts +++ b/apps/website/lib/formatters/LeagueRoleFormatter.ts @@ -25,7 +25,7 @@ export const leagueRoleDisplay: Record = { } as const; // For backward compatibility, also export the class with static method -export class LeagueRoleDisplay { +export class LeagueRoleFormatter { static getLeagueRoleDisplay(role: LeagueRole) { return leagueRoleDisplay[role]; } diff --git a/apps/website/lib/display-objects/LeagueTierDisplay.ts b/apps/website/lib/formatters/LeagueTierFormatter.ts similarity index 96% rename from apps/website/lib/display-objects/LeagueTierDisplay.ts rename to apps/website/lib/formatters/LeagueTierFormatter.ts index 0fa1ae8d2..281fd14b9 100644 --- a/apps/website/lib/display-objects/LeagueTierDisplay.ts +++ b/apps/website/lib/formatters/LeagueTierFormatter.ts @@ -11,7 +11,7 @@ export interface LeagueTierDisplayData { icon: string; } -export class LeagueTierDisplay { +export class LeagueTierFormatter { private static readonly CONFIG: Record = { premium: { color: 'text-yellow-400', diff --git a/apps/website/lib/display-objects/LeagueWizardValidationMessages.ts b/apps/website/lib/formatters/LeagueWizardValidationMessages.ts similarity index 96% rename from apps/website/lib/display-objects/LeagueWizardValidationMessages.ts rename to apps/website/lib/formatters/LeagueWizardValidationMessages.ts index b1a0e7ae0..48fd4983f 100644 --- a/apps/website/lib/display-objects/LeagueWizardValidationMessages.ts +++ b/apps/website/lib/formatters/LeagueWizardValidationMessages.ts @@ -1,3 +1,4 @@ +// TODO this file has no clear meaning export class LeagueWizardValidationMessages { static readonly LEAGUE_NAME_REQUIRED = 'League name is required'; static readonly LEAGUE_NAME_TOO_SHORT = 'League name must be at least 3 characters'; diff --git a/apps/website/lib/display-objects/MedalDisplay.ts b/apps/website/lib/formatters/MedalFormatter.ts similarity index 96% rename from apps/website/lib/display-objects/MedalDisplay.ts rename to apps/website/lib/formatters/MedalFormatter.ts index f61afcf90..50c4726c2 100644 --- a/apps/website/lib/display-objects/MedalDisplay.ts +++ b/apps/website/lib/formatters/MedalFormatter.ts @@ -1,4 +1,4 @@ -export class MedalDisplay { +export class MedalFormatter { static getVariant(position: number): 'warning' | 'low' | 'high' { switch (position) { case 1: return 'warning'; diff --git a/apps/website/lib/display-objects/MemberDisplay.ts b/apps/website/lib/formatters/MemberFormatter.ts similarity index 93% rename from apps/website/lib/display-objects/MemberDisplay.ts rename to apps/website/lib/formatters/MemberFormatter.ts index 8a4edc7fc..804bbafc8 100644 --- a/apps/website/lib/display-objects/MemberDisplay.ts +++ b/apps/website/lib/formatters/MemberFormatter.ts @@ -4,7 +4,7 @@ * Deterministic display logic for members. */ -export class MemberDisplay { +export class MemberFormatter { /** * Formats a member count with pluralization. * Example: 1 -> "1 member", 2 -> "2 members" diff --git a/apps/website/lib/display-objects/MembershipFeeTypeDisplay.ts b/apps/website/lib/formatters/MembershipFeeTypeFormatter.ts similarity index 84% rename from apps/website/lib/display-objects/MembershipFeeTypeDisplay.ts rename to apps/website/lib/formatters/MembershipFeeTypeFormatter.ts index 4d1f9e01e..35bed7b7a 100644 --- a/apps/website/lib/display-objects/MembershipFeeTypeDisplay.ts +++ b/apps/website/lib/formatters/MembershipFeeTypeFormatter.ts @@ -1,4 +1,4 @@ -export class MembershipFeeTypeDisplay { +export class MembershipFeeTypeFormatter { static format(type: string): string { switch (type) { case 'season': return 'Per Season'; diff --git a/apps/website/lib/display-objects/MemoryDisplay.ts b/apps/website/lib/formatters/MemoryFormatter.ts similarity index 92% rename from apps/website/lib/display-objects/MemoryDisplay.ts rename to apps/website/lib/formatters/MemoryFormatter.ts index dd2b5d388..6e9271324 100644 --- a/apps/website/lib/display-objects/MemoryDisplay.ts +++ b/apps/website/lib/formatters/MemoryFormatter.ts @@ -4,7 +4,7 @@ * Deterministic formatting for memory usage. */ -export class MemoryDisplay { +export class MemoryFormatter { /** * Formats bytes as "123.4MB". */ diff --git a/apps/website/lib/display-objects/NumberDisplay.ts b/apps/website/lib/formatters/NumberFormatter.ts similarity index 96% rename from apps/website/lib/display-objects/NumberDisplay.ts rename to apps/website/lib/formatters/NumberFormatter.ts index 377037213..2f1003d60 100644 --- a/apps/website/lib/display-objects/NumberDisplay.ts +++ b/apps/website/lib/formatters/NumberFormatter.ts @@ -5,7 +5,7 @@ * Avoids Intl and toLocaleString to prevent SSR/hydration mismatches. */ -export class NumberDisplay { +export class NumberFormatter { /** * Formats a number with thousands separators (commas). * Example: 1234567 -> "1,234,567" diff --git a/apps/website/lib/display-objects/OnboardingStatusDisplay.ts b/apps/website/lib/formatters/OnboardingStatusFormatter.ts similarity index 95% rename from apps/website/lib/display-objects/OnboardingStatusDisplay.ts rename to apps/website/lib/formatters/OnboardingStatusFormatter.ts index 164a5fafd..0caaf921b 100644 --- a/apps/website/lib/display-objects/OnboardingStatusDisplay.ts +++ b/apps/website/lib/formatters/OnboardingStatusFormatter.ts @@ -4,7 +4,7 @@ * Deterministic mapping of onboarding status to display labels and variants. */ -export class OnboardingStatusDisplay { +export class OnboardingStatusFormatter { /** * Maps onboarding success status to display label. */ diff --git a/apps/website/lib/display-objects/PayerTypeDisplay.ts b/apps/website/lib/formatters/PayerTypeFormatter.ts similarity index 75% rename from apps/website/lib/display-objects/PayerTypeDisplay.ts rename to apps/website/lib/formatters/PayerTypeFormatter.ts index bc6168b99..2ab510d34 100644 --- a/apps/website/lib/display-objects/PayerTypeDisplay.ts +++ b/apps/website/lib/formatters/PayerTypeFormatter.ts @@ -1,4 +1,4 @@ -export class PayerTypeDisplay { +export class PayerTypeFormatter { static format(type: string): string { return type.charAt(0).toUpperCase() + type.slice(1); } diff --git a/apps/website/lib/display-objects/PaymentTypeDisplay.ts b/apps/website/lib/formatters/PaymentTypeFormatter.ts similarity index 76% rename from apps/website/lib/display-objects/PaymentTypeDisplay.ts rename to apps/website/lib/formatters/PaymentTypeFormatter.ts index b0db97cec..e19227f47 100644 --- a/apps/website/lib/display-objects/PaymentTypeDisplay.ts +++ b/apps/website/lib/formatters/PaymentTypeFormatter.ts @@ -1,4 +1,4 @@ -export class PaymentTypeDisplay { +export class PaymentTypeFormatter { static format(type: string): string { return type.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase()); } diff --git a/apps/website/lib/display-objects/PercentDisplay.ts b/apps/website/lib/formatters/PercentFormatter.ts similarity index 94% rename from apps/website/lib/display-objects/PercentDisplay.ts rename to apps/website/lib/formatters/PercentFormatter.ts index ef216d78c..7346d9535 100644 --- a/apps/website/lib/display-objects/PercentDisplay.ts +++ b/apps/website/lib/formatters/PercentFormatter.ts @@ -4,7 +4,7 @@ * Deterministic formatting for percentages. */ -export class PercentDisplay { +export class PercentFormatter { /** * Formats a decimal value as a percentage string. * Example: 0.1234 -> "12.3%" diff --git a/apps/website/lib/display-objects/PrizeTypeDisplay.ts b/apps/website/lib/formatters/PrizeTypeFormatter.ts similarity index 86% rename from apps/website/lib/display-objects/PrizeTypeDisplay.ts rename to apps/website/lib/formatters/PrizeTypeFormatter.ts index 1b0229dc8..aa09838c0 100644 --- a/apps/website/lib/display-objects/PrizeTypeDisplay.ts +++ b/apps/website/lib/formatters/PrizeTypeFormatter.ts @@ -1,4 +1,4 @@ -export class PrizeTypeDisplay { +export class PrizeTypeFormatter { static format(type: string): string { switch (type) { case 'cash': return 'Cash Prize'; diff --git a/apps/website/lib/display-objects/ProfileDisplay.ts b/apps/website/lib/formatters/ProfileFormatter.ts similarity index 90% rename from apps/website/lib/display-objects/ProfileDisplay.ts rename to apps/website/lib/formatters/ProfileFormatter.ts index 4a52c4fbe..958a2bdd9 100644 --- a/apps/website/lib/display-objects/ProfileDisplay.ts +++ b/apps/website/lib/formatters/ProfileFormatter.ts @@ -30,7 +30,7 @@ export interface TeamRoleDisplayData { badgeClasses: string; } -export class ProfileDisplay { +export class ProfileFormatter { private static readonly countryFlagDisplay: Record = { US: { flag: '🇺🇸', label: 'United States' }, GB: { flag: '🇬🇧', label: 'United Kingdom' }, @@ -47,7 +47,7 @@ export class ProfileDisplay { static getCountryFlag(countryCode: string): CountryFlagDisplayData { const code = countryCode.toUpperCase(); - return ProfileDisplay.countryFlagDisplay[code] || ProfileDisplay.countryFlagDisplay.DEFAULT; + return ProfileFormatter.countryFlagDisplay[code] || ProfileFormatter.countryFlagDisplay.DEFAULT; } private static readonly achievementRarityDisplay: Record = { @@ -74,7 +74,7 @@ export class ProfileDisplay { }; static getAchievementRarity(rarity: string): AchievementRarityDisplayData { - return ProfileDisplay.achievementRarityDisplay[rarity] || ProfileDisplay.achievementRarityDisplay.common; + return ProfileFormatter.achievementRarityDisplay[rarity] || ProfileFormatter.achievementRarityDisplay.common; } private static readonly achievementIconDisplay: Record = { @@ -87,7 +87,7 @@ export class ProfileDisplay { }; static getAchievementIcon(icon: string): AchievementIconDisplayData { - return ProfileDisplay.achievementIconDisplay[icon] || ProfileDisplay.achievementIconDisplay.trophy; + return ProfileFormatter.achievementIconDisplay[icon] || ProfileFormatter.achievementIconDisplay.trophy; } private static readonly socialPlatformDisplay: Record = { @@ -110,7 +110,7 @@ export class ProfileDisplay { }; static getSocialPlatform(platform: string): SocialPlatformDisplayData { - return ProfileDisplay.socialPlatformDisplay[platform] || ProfileDisplay.socialPlatformDisplay.discord; + return ProfileFormatter.socialPlatformDisplay[platform] || ProfileFormatter.socialPlatformDisplay.discord; } static formatMonthYear(dateString: string): string { @@ -184,6 +184,6 @@ export class ProfileDisplay { }; static getTeamRole(role: string): TeamRoleDisplayData { - return ProfileDisplay.teamRoleDisplay[role] || ProfileDisplay.teamRoleDisplay.member; + return ProfileFormatter.teamRoleDisplay[role] || ProfileFormatter.teamRoleDisplay.member; } } diff --git a/apps/website/lib/display-objects/RaceStatusDisplay.ts b/apps/website/lib/formatters/RaceStatusFormatter.ts similarity index 96% rename from apps/website/lib/display-objects/RaceStatusDisplay.ts rename to apps/website/lib/formatters/RaceStatusFormatter.ts index c175274e4..6534b7e6d 100644 --- a/apps/website/lib/display-objects/RaceStatusDisplay.ts +++ b/apps/website/lib/formatters/RaceStatusFormatter.ts @@ -6,7 +6,7 @@ export type RaceStatusVariant = 'info' | 'success' | 'neutral' | 'warning' | 'primary' | 'default'; -export class RaceStatusDisplay { +export class RaceStatusFormatter { private static readonly CONFIG: Record = { scheduled: { variant: 'primary', diff --git a/apps/website/lib/display-objects/RatingDisplay.test.ts b/apps/website/lib/formatters/RatingFormatter.test.ts similarity index 100% rename from apps/website/lib/display-objects/RatingDisplay.test.ts rename to apps/website/lib/formatters/RatingFormatter.test.ts diff --git a/apps/website/lib/display-objects/RatingDisplay.ts b/apps/website/lib/formatters/RatingFormatter.ts similarity index 63% rename from apps/website/lib/display-objects/RatingDisplay.ts rename to apps/website/lib/formatters/RatingFormatter.ts index 14c5da623..19d5c141f 100644 --- a/apps/website/lib/display-objects/RatingDisplay.ts +++ b/apps/website/lib/formatters/RatingFormatter.ts @@ -1,12 +1,12 @@ -import { NumberDisplay } from './NumberDisplay'; +import { NumberFormatter } from './NumberFormatter'; -export class RatingDisplay { +export class RatingFormatter { /** * Formats a rating as a rounded number with thousands separators. * Example: 1234.56 -> "1,235" */ static format(rating: number | null | undefined): string { if (rating === null || rating === undefined) return '—'; - return NumberDisplay.format(Math.round(rating)); + return NumberFormatter.format(Math.round(rating)); } } diff --git a/apps/website/lib/display-objects/RatingTrendDisplay.ts b/apps/website/lib/formatters/RatingTrendFormatter.ts similarity index 93% rename from apps/website/lib/display-objects/RatingTrendDisplay.ts rename to apps/website/lib/formatters/RatingTrendFormatter.ts index 54b0614a2..2f97f5bbb 100644 --- a/apps/website/lib/display-objects/RatingTrendDisplay.ts +++ b/apps/website/lib/formatters/RatingTrendFormatter.ts @@ -1,4 +1,4 @@ -export class RatingTrendDisplay { +export class RatingTrendFormatter { static getTrend(currentRating: number, previousRating: number | undefined): 'up' | 'down' | 'same' { if (!previousRating) return 'same'; if (currentRating > previousRating) return 'up'; diff --git a/apps/website/lib/display-objects/RelativeTimeDisplay.ts b/apps/website/lib/formatters/RelativeTimeFormatter.ts similarity index 97% rename from apps/website/lib/display-objects/RelativeTimeDisplay.ts rename to apps/website/lib/formatters/RelativeTimeFormatter.ts index 27c7b270e..38cea8760 100644 --- a/apps/website/lib/display-objects/RelativeTimeDisplay.ts +++ b/apps/website/lib/formatters/RelativeTimeFormatter.ts @@ -4,7 +4,7 @@ * Deterministic relative time formatting. */ -export class RelativeTimeDisplay { +export class RelativeTimeFormatter { /** * Formats a date relative to "now". * "now" must be passed as an argument for determinism. diff --git a/apps/website/lib/display-objects/SeasonStatusDisplay.ts b/apps/website/lib/formatters/SeasonStatusFormatter.ts similarity index 95% rename from apps/website/lib/display-objects/SeasonStatusDisplay.ts rename to apps/website/lib/formatters/SeasonStatusFormatter.ts index 24db070fe..2696a9b74 100644 --- a/apps/website/lib/display-objects/SeasonStatusDisplay.ts +++ b/apps/website/lib/formatters/SeasonStatusFormatter.ts @@ -10,7 +10,7 @@ export interface SeasonStatusDisplayData { label: string; } -export class SeasonStatusDisplay { +export class SeasonStatusFormatter { private static readonly CONFIG: Record = { active: { color: 'text-performance-green', diff --git a/apps/website/lib/display-objects/SkillLevelDisplay.ts b/apps/website/lib/formatters/SkillLevelFormatter.ts similarity index 97% rename from apps/website/lib/display-objects/SkillLevelDisplay.ts rename to apps/website/lib/formatters/SkillLevelFormatter.ts index a28fb9eec..51cc52ce3 100644 --- a/apps/website/lib/display-objects/SkillLevelDisplay.ts +++ b/apps/website/lib/formatters/SkillLevelFormatter.ts @@ -1,4 +1,4 @@ -export class SkillLevelDisplay { +export class SkillLevelFormatter { static getLabel(skillLevel: string): string { const levels: Record = { pro: 'Pro', diff --git a/apps/website/lib/display-objects/SkillLevelIconDisplay.ts b/apps/website/lib/formatters/SkillLevelIconFormatter.ts similarity index 86% rename from apps/website/lib/display-objects/SkillLevelIconDisplay.ts rename to apps/website/lib/formatters/SkillLevelIconFormatter.ts index 7311b213b..86a2a5570 100644 --- a/apps/website/lib/display-objects/SkillLevelIconDisplay.ts +++ b/apps/website/lib/formatters/SkillLevelIconFormatter.ts @@ -1,4 +1,4 @@ -export class SkillLevelIconDisplay { +export class SkillLevelIconFormatter { static getIcon(skillLevel: string): string { const icons: Record = { beginner: '🥉', diff --git a/apps/website/lib/display-objects/StatusDisplay.ts b/apps/website/lib/formatters/StatusFormatter.ts similarity index 96% rename from apps/website/lib/display-objects/StatusDisplay.ts rename to apps/website/lib/formatters/StatusFormatter.ts index 86fa95786..8bbd01952 100644 --- a/apps/website/lib/display-objects/StatusDisplay.ts +++ b/apps/website/lib/formatters/StatusFormatter.ts @@ -4,7 +4,7 @@ * Deterministic mapping of status codes to human-readable labels. */ -export class StatusDisplay { +export class StatusFormatter { /** * Maps transaction status to label. */ diff --git a/apps/website/lib/display-objects/TeamCreationStatusDisplay.ts b/apps/website/lib/formatters/TeamCreationStatusFormatter.ts similarity index 88% rename from apps/website/lib/display-objects/TeamCreationStatusDisplay.ts rename to apps/website/lib/formatters/TeamCreationStatusFormatter.ts index 13638e188..e4320cc1d 100644 --- a/apps/website/lib/display-objects/TeamCreationStatusDisplay.ts +++ b/apps/website/lib/formatters/TeamCreationStatusFormatter.ts @@ -4,7 +4,7 @@ * Deterministic mapping of team creation status to display messages. */ -export class TeamCreationStatusDisplay { +export class TeamCreationStatusFormatter { /** * Maps team creation success status to display message. */ diff --git a/apps/website/lib/display-objects/TimeDisplay.ts b/apps/website/lib/formatters/TimeFormatter.ts similarity index 94% rename from apps/website/lib/display-objects/TimeDisplay.ts rename to apps/website/lib/formatters/TimeFormatter.ts index 172b122c5..e61011989 100644 --- a/apps/website/lib/display-objects/TimeDisplay.ts +++ b/apps/website/lib/formatters/TimeFormatter.ts @@ -1,4 +1,4 @@ -export class TimeDisplay { +export class TimeFormatter { static timeAgo(timestamp: Date | string): string { const date = typeof timestamp === 'string' ? new Date(timestamp) : timestamp; const diffMs = Date.now() - date.getTime(); diff --git a/apps/website/lib/display-objects/TransactionTypeDisplay.ts b/apps/website/lib/formatters/TransactionTypeFormatter.ts similarity index 72% rename from apps/website/lib/display-objects/TransactionTypeDisplay.ts rename to apps/website/lib/formatters/TransactionTypeFormatter.ts index 3a4474059..b146b3b7b 100644 --- a/apps/website/lib/display-objects/TransactionTypeDisplay.ts +++ b/apps/website/lib/formatters/TransactionTypeFormatter.ts @@ -1,4 +1,4 @@ -export class TransactionTypeDisplay { +export class TransactionTypeFormatter { static format(type: string): string { return type.charAt(0).toUpperCase() + type.slice(1); } diff --git a/apps/website/lib/display-objects/UserRoleDisplay.ts b/apps/website/lib/formatters/UserRoleFormatter.ts similarity index 91% rename from apps/website/lib/display-objects/UserRoleDisplay.ts rename to apps/website/lib/formatters/UserRoleFormatter.ts index 5b9dfd0ba..c12443adc 100644 --- a/apps/website/lib/display-objects/UserRoleDisplay.ts +++ b/apps/website/lib/formatters/UserRoleFormatter.ts @@ -4,7 +4,7 @@ * Deterministic mapping of user role codes to display labels. */ -export class UserRoleDisplay { +export class UserRoleFormatter { /** * Maps user role to display label. */ diff --git a/apps/website/lib/display-objects/UserStatusDisplay.ts b/apps/website/lib/formatters/UserStatusFormatter.ts similarity index 96% rename from apps/website/lib/display-objects/UserStatusDisplay.ts rename to apps/website/lib/formatters/UserStatusFormatter.ts index 15c5d2a29..079bfd542 100644 --- a/apps/website/lib/display-objects/UserStatusDisplay.ts +++ b/apps/website/lib/formatters/UserStatusFormatter.ts @@ -4,7 +4,7 @@ * Deterministic mapping of user status codes to display labels and variants. */ -export class UserStatusDisplay { +export class UserStatusFormatter { /** * Maps user status to display label. */ diff --git a/apps/website/lib/display-objects/WinRateDisplay.ts b/apps/website/lib/formatters/WinRateFormatter.ts similarity index 91% rename from apps/website/lib/display-objects/WinRateDisplay.ts rename to apps/website/lib/formatters/WinRateFormatter.ts index d64f83874..b53321da7 100644 --- a/apps/website/lib/display-objects/WinRateDisplay.ts +++ b/apps/website/lib/formatters/WinRateFormatter.ts @@ -1,4 +1,4 @@ -export class WinRateDisplay { +export class WinRateFormatter { static calculate(racesCompleted: number, wins: number): string { if (racesCompleted === 0) return '0.0'; const rate = (wins / racesCompleted) * 100; diff --git a/apps/website/lib/services/home/HomeService.ts b/apps/website/lib/services/home/HomeService.ts index 1228e9920..271046184 100644 --- a/apps/website/lib/services/home/HomeService.ts +++ b/apps/website/lib/services/home/HomeService.ts @@ -1,24 +1,24 @@ import { FeatureFlagService } from '@/lib/feature/FeatureFlagService'; // API Clients -import { RacesApiClient } from '@/lib/api/races/RacesApiClient'; import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient'; +import { RacesApiClient } from '@/lib/api/races/RacesApiClient'; import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient'; // Services import { SessionService } from '@/lib/services/auth/SessionService'; // Infrastructure -import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger'; -import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter'; import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl'; +import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter'; +import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger'; // DTO types import { Result } from '@/lib/contracts/Result'; import type { Service } from '@/lib/contracts/services/Service'; import type { HomeDataDTO } from '@/lib/types/dtos/HomeDataDTO'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; /** * HomeService @@ -56,7 +56,7 @@ export class HomeService implements Service { id: r.id, track: r.track, car: r.car, - formattedDate: DateDisplay.formatShort(r.scheduledAt), + formattedDate: DateFormatter.formatShort(r.scheduledAt), })), topLeagues: leaguesDto.leagues.slice(0, 4).map(l => ({ id: l.id, diff --git a/apps/website/lib/view-models/AdminUserViewModel.ts b/apps/website/lib/view-models/AdminUserViewModel.ts index 97ee42845..9bad19bb2 100644 --- a/apps/website/lib/view-models/AdminUserViewModel.ts +++ b/apps/website/lib/view-models/AdminUserViewModel.ts @@ -1,8 +1,8 @@ +import { DateFormatter } from "@/lib/formatters/DateFormatter"; +import { UserRoleFormatter } from "@/lib/formatters/UserRoleFormatter"; +import { UserStatusFormatter } from "@/lib/formatters/UserStatusFormatter"; import type { AdminUserViewData } from '@/lib/view-data/AdminUserViewData'; import { ViewModel } from "../contracts/view-models/ViewModel"; -import { UserStatusDisplay } from "@/lib/display-objects/UserStatusDisplay"; -import { UserRoleDisplay } from "@/lib/display-objects/UserRoleDisplay"; -import { DateDisplay } from "@/lib/display-objects/DateDisplay"; /** * AdminUserViewModel @@ -31,28 +31,28 @@ export class AdminUserViewModel extends ViewModel { /** UI-specific: Role badges using Display Object */ get roleBadges(): string[] { - return this.roles.map(role => UserRoleDisplay.roleLabel(role)); + return this.roles.map(role => UserRoleFormatter.roleLabel(role)); } /** UI-specific: Status badge label using Display Object */ get statusBadgeLabel(): string { - return UserStatusDisplay.statusLabel(this.status); + return UserStatusFormatter.statusLabel(this.status); } /** UI-specific: Status badge variant using Display Object */ get statusBadgeVariant(): string { - return UserStatusDisplay.statusVariant(this.status); + return UserStatusFormatter.statusVariant(this.status); } /** UI-specific: Formatted last login date */ get lastLoginFormatted(): string { return this.lastLoginAt - ? DateDisplay.formatShort(this.lastLoginAt) + ? DateFormatter.formatShort(this.lastLoginAt) : 'Never'; } /** UI-specific: Formatted creation date */ get createdAtFormatted(): string { - return DateDisplay.formatShort(this.createdAt); + return DateFormatter.formatShort(this.createdAt); } } diff --git a/apps/website/lib/view-models/AnalyticsMetricsViewModel.ts b/apps/website/lib/view-models/AnalyticsMetricsViewModel.ts index 8612b807b..be2ffff43 100644 --- a/apps/website/lib/view-models/AnalyticsMetricsViewModel.ts +++ b/apps/website/lib/view-models/AnalyticsMetricsViewModel.ts @@ -4,11 +4,11 @@ * * Accepts AnalyticsMetricsViewData as input and produces UI-ready data. */ -import { AnalyticsMetricsViewData } from "../view-data/AnalyticsMetricsViewData"; +import { DurationFormatter } from "@/lib/formatters/DurationFormatter"; +import { NumberFormatter } from "@/lib/formatters/NumberFormatter"; +import { PercentFormatter } from "@/lib/formatters/PercentFormatter"; import { ViewModel } from "../contracts/view-models/ViewModel"; -import { NumberDisplay } from "@/lib/display-objects/NumberDisplay"; -import { DurationDisplay } from "@/lib/display-objects/DurationDisplay"; -import { PercentDisplay } from "@/lib/display-objects/PercentDisplay"; +import { AnalyticsMetricsViewData } from "../view-data/AnalyticsMetricsViewData"; export class AnalyticsMetricsViewModel extends ViewModel { private readonly data: AnalyticsMetricsViewData; @@ -25,21 +25,21 @@ export class AnalyticsMetricsViewModel extends ViewModel { /** UI-specific: Formatted page views */ get formattedPageViews(): string { - return NumberDisplay.format(this.pageViews); + return NumberFormatter.format(this.pageViews); } /** UI-specific: Formatted unique visitors */ get formattedUniqueVisitors(): string { - return NumberDisplay.format(this.uniqueVisitors); + return NumberFormatter.format(this.uniqueVisitors); } /** UI-specific: Formatted session duration */ get formattedSessionDuration(): string { - return DurationDisplay.formatSeconds(this.averageSessionDuration); + return DurationFormatter.formatSeconds(this.averageSessionDuration); } /** UI-specific: Formatted bounce rate */ get formattedBounceRate(): string { - return PercentDisplay.format(this.bounceRate); + return PercentFormatter.format(this.bounceRate); } } \ No newline at end of file diff --git a/apps/website/lib/view-models/AvailableLeaguesViewModel.ts b/apps/website/lib/view-models/AvailableLeaguesViewModel.ts index 47d9fb010..6a856cfd6 100644 --- a/apps/website/lib/view-models/AvailableLeaguesViewModel.ts +++ b/apps/website/lib/view-models/AvailableLeaguesViewModel.ts @@ -6,19 +6,17 @@ * Accepts AvailableLeaguesViewData as input and produces UI-ready data. */ import { ViewModel } from "../contracts/view-models/ViewModel"; +import { CurrencyFormatter } from "../formatters/CurrencyFormatter"; +import { LeagueTierFormatter } from "../formatters/LeagueTierFormatter"; +import { NumberFormatter } from "../formatters/NumberFormatter"; +import { SeasonStatusFormatter } from "../formatters/SeasonStatusFormatter"; import { AvailableLeaguesViewData, AvailableLeagueViewData } from "../view-data/AvailableLeaguesViewData"; -import { NumberDisplay } from "../display-objects/NumberDisplay"; -import { CurrencyDisplay } from "../display-objects/CurrencyDisplay"; -import { LeagueTierDisplay } from "../display-objects/LeagueTierDisplay"; -import { SeasonStatusDisplay } from "../display-objects/SeasonStatusDisplay"; export class AvailableLeaguesViewModel extends ViewModel { - private readonly data: AvailableLeaguesViewData; readonly leagues: AvailableLeagueViewModel[]; constructor(data: AvailableLeaguesViewData) { super(); - this.data = data; this.leagues = data.leagues.map(league => new AvailableLeagueViewModel(league)); } } @@ -46,7 +44,7 @@ export class AvailableLeagueViewModel extends ViewModel { /** UI-specific: Formatted average views */ get formattedAvgViews(): string { - return NumberDisplay.formatCompact(this.avgViewsPerRace); + return NumberFormatter.formatCompact(this.avgViewsPerRace); } /** UI-specific: CPM calculation */ @@ -56,7 +54,7 @@ export class AvailableLeagueViewModel extends ViewModel { /** UI-specific: Formatted CPM */ get formattedCpm(): string { - return CurrencyDisplay.formatCompact(this.cpm); + return CurrencyFormatter.formatCompact(this.cpm); } /** UI-specific: Check if any sponsor slots are available */ @@ -66,12 +64,12 @@ export class AvailableLeagueViewModel extends ViewModel { /** UI-specific: Tier configuration for badge styling */ get tierConfig() { - return LeagueTierDisplay.getDisplay(this.tier); + return LeagueTierFormatter.getDisplay(this.tier); } /** UI-specific: Status configuration for season state */ get statusConfig() { - return SeasonStatusDisplay.getDisplay(this.seasonStatus); + return SeasonStatusFormatter.getDisplay(this.seasonStatus); } } \ No newline at end of file diff --git a/apps/website/lib/view-models/AvatarViewModel.ts b/apps/website/lib/view-models/AvatarViewModel.ts index 7f9bca66b..e34aa18ad 100644 --- a/apps/website/lib/view-models/AvatarViewModel.ts +++ b/apps/website/lib/view-models/AvatarViewModel.ts @@ -1,6 +1,6 @@ -import { ViewModel } from "../contracts/view-models/ViewModel"; -import { AvatarDisplay } from "../display-objects/AvatarDisplay"; import { AvatarViewData } from "@/lib/view-data/AvatarViewData"; +import { ViewModel } from "../contracts/view-models/ViewModel"; +import { AvatarFormatter } from "../formatters/AvatarFormatter"; /** * Avatar View Model @@ -23,11 +23,11 @@ export class AvatarViewModel extends ViewModel { /** UI-specific: Derive content type label using Display Object */ get contentTypeLabel(): string { - return AvatarDisplay.formatContentType(this.data.contentType); + return AvatarFormatter.formatContentType(this.data.contentType); } /** UI-specific: Derive validity check using Display Object */ get hasValidData(): boolean { - return AvatarDisplay.hasValidData(this.data.buffer, this.data.contentType); + return AvatarFormatter.hasValidData(this.data.buffer, this.data.contentType); } } \ No newline at end of file diff --git a/apps/website/lib/view-models/CompleteOnboardingViewModel.ts b/apps/website/lib/view-models/CompleteOnboardingViewModel.ts index 08f03fc13..ae174cfbe 100644 --- a/apps/website/lib/view-models/CompleteOnboardingViewModel.ts +++ b/apps/website/lib/view-models/CompleteOnboardingViewModel.ts @@ -1,5 +1,5 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { OnboardingStatusDisplay } from '../display-objects/OnboardingStatusDisplay'; +import { OnboardingStatusFormatter } from '../formatters/OnboardingStatusFormatter'; import type { CompleteOnboardingViewData } from '../view-data/CompleteOnboardingViewData'; /** @@ -22,22 +22,22 @@ export class CompleteOnboardingViewModel extends ViewModel { /** UI-specific: Status label using Display Object */ get statusLabel(): string { - return OnboardingStatusDisplay.statusLabel(this.success); + return OnboardingStatusFormatter.statusLabel(this.success); } /** UI-specific: Status variant using Display Object */ get statusVariant(): string { - return OnboardingStatusDisplay.statusVariant(this.success); + return OnboardingStatusFormatter.statusVariant(this.success); } /** UI-specific: Status icon using Display Object */ get statusIcon(): string { - return OnboardingStatusDisplay.statusIcon(this.success); + return OnboardingStatusFormatter.statusIcon(this.success); } /** UI-specific: Status message using Display Object */ get statusMessage(): string { - return OnboardingStatusDisplay.statusMessage(this.success, this.errorMessage); + return OnboardingStatusFormatter.statusMessage(this.success, this.errorMessage); } /** UI-specific: Whether onboarding was successful */ diff --git a/apps/website/lib/view-models/CreateLeagueViewModel.ts b/apps/website/lib/view-models/CreateLeagueViewModel.ts index 760627a60..1659ef39b 100644 --- a/apps/website/lib/view-models/CreateLeagueViewModel.ts +++ b/apps/website/lib/view-models/CreateLeagueViewModel.ts @@ -1,6 +1,6 @@ -import type { CreateLeagueViewData } from '../view-data/CreateLeagueViewData'; import { ViewModel } from "../contracts/view-models/ViewModel"; -import { LeagueCreationStatusDisplay } from '../display-objects/LeagueCreationStatusDisplay'; +import { LeagueCreationStatusFormatter } from '../formatters/LeagueCreationStatusFormatter'; +import type { CreateLeagueViewData } from '../view-data/CreateLeagueViewData'; /** * View Model for Create League Result @@ -21,7 +21,7 @@ export class CreateLeagueViewModel extends ViewModel { /** UI-specific: Success message using Display Object */ get successMessage(): string { - return LeagueCreationStatusDisplay.statusMessage(this.success); + return LeagueCreationStatusFormatter.statusMessage(this.success); } /** UI-specific: Whether league creation was successful */ diff --git a/apps/website/lib/view-models/CreateTeamViewModel.ts b/apps/website/lib/view-models/CreateTeamViewModel.ts index cd0200e77..d78ded747 100644 --- a/apps/website/lib/view-models/CreateTeamViewModel.ts +++ b/apps/website/lib/view-models/CreateTeamViewModel.ts @@ -1,6 +1,6 @@ -import type { CreateTeamViewData } from '../view-data/CreateTeamViewData'; import { ViewModel } from "../contracts/view-models/ViewModel"; -import { TeamCreationStatusDisplay } from '../display-objects/TeamCreationStatusDisplay'; +import { TeamCreationStatusFormatter } from '../formatters/TeamCreationStatusFormatter'; +import type { CreateTeamViewData } from '../view-data/CreateTeamViewData'; /** * View Model for Create Team Result @@ -21,7 +21,7 @@ export class CreateTeamViewModel extends ViewModel { /** UI-specific: Success message using Display Object */ get successMessage(): string { - return TeamCreationStatusDisplay.statusMessage(this.success); + return TeamCreationStatusFormatter.statusMessage(this.success); } /** UI-specific: Whether team creation was successful */ diff --git a/apps/website/lib/view-models/DashboardStatsViewModel.ts b/apps/website/lib/view-models/DashboardStatsViewModel.ts index 0c484d122..7fa6c194b 100644 --- a/apps/website/lib/view-models/DashboardStatsViewModel.ts +++ b/apps/website/lib/view-models/DashboardStatsViewModel.ts @@ -1,6 +1,6 @@ -import { ViewModel } from "../contracts/view-models/ViewModel"; -import { ActivityLevelDisplay } from "@/lib/display-objects/ActivityLevelDisplay"; +import { ActivityLevelFormatter } from "@/lib/formatters/ActivityLevelFormatter"; import type { DashboardStatsViewData } from '@/lib/view-data/DashboardStatsViewData'; +import { ViewModel } from "../contracts/view-models/ViewModel"; /** * DashboardStatsViewModel @@ -68,7 +68,7 @@ export class DashboardStatsViewModel extends ViewModel { // Derive activity level using Display Object const engagementRate = this.totalUsers > 0 ? (this.recentLogins / this.totalUsers) * 100 : 0; - this.activityLevelLabel = ActivityLevelDisplay.levelLabel(engagementRate); - this.activityLevelValue = ActivityLevelDisplay.levelValue(engagementRate); + this.activityLevelLabel = ActivityLevelFormatter.levelLabel(engagementRate); + this.activityLevelValue = ActivityLevelFormatter.levelValue(engagementRate); } } diff --git a/apps/website/lib/view-models/DriverLeaderboardItemViewModel.ts b/apps/website/lib/view-models/DriverLeaderboardItemViewModel.ts index 8b7480b63..f50ebfecf 100644 --- a/apps/website/lib/view-models/DriverLeaderboardItemViewModel.ts +++ b/apps/website/lib/view-models/DriverLeaderboardItemViewModel.ts @@ -1,9 +1,9 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; +import { RatingTrendFormatter } from "../formatters/RatingTrendFormatter"; +import { SkillLevelFormatter } from "../formatters/SkillLevelFormatter"; +import { SkillLevelIconFormatter } from "../formatters/SkillLevelIconFormatter"; +import { WinRateFormatter } from "../formatters/WinRateFormatter"; import type { LeaderboardDriverItem } from '../view-data/LeaderboardDriverItem'; -import { SkillLevelDisplay } from "../display-objects/SkillLevelDisplay"; -import { SkillLevelIconDisplay } from "../display-objects/SkillLevelIconDisplay"; -import { WinRateDisplay } from "../display-objects/WinRateDisplay"; -import { RatingTrendDisplay } from "../display-objects/RatingTrendDisplay"; export class DriverLeaderboardItemViewModel extends ViewModel { private readonly data: LeaderboardDriverItem; @@ -29,12 +29,12 @@ export class DriverLeaderboardItemViewModel extends ViewModel { /** UI-specific: Skill level color */ get skillLevelColor(): string { - return SkillLevelDisplay.getColor(this.skillLevel); + return SkillLevelFormatter.getColor(this.skillLevel); } /** UI-specific: Skill level icon */ get skillLevelIcon(): string { - return SkillLevelIconDisplay.getIcon(this.skillLevel); + return SkillLevelIconFormatter.getIcon(this.skillLevel); } /** UI-specific: Win rate */ @@ -44,17 +44,17 @@ export class DriverLeaderboardItemViewModel extends ViewModel { /** UI-specific: Formatted win rate */ get winRateFormatted(): string { - return WinRateDisplay.format(this.winRate); + return WinRateFormatter.format(this.winRate); } /** UI-specific: Rating trend */ get ratingTrend(): 'up' | 'down' | 'same' { - return RatingTrendDisplay.getTrend(this.rating, this.previousRating); + return RatingTrendFormatter.getTrend(this.rating, this.previousRating); } /** UI-specific: Rating change indicator */ get ratingChangeIndicator(): string { - return RatingTrendDisplay.getChangeIndicator(this.rating, this.previousRating); + return RatingTrendFormatter.getChangeIndicator(this.rating, this.previousRating); } /** UI-specific: Position badge */ diff --git a/apps/website/lib/view-models/DriverProfileDriverSummaryViewModel.ts b/apps/website/lib/view-models/DriverProfileDriverSummaryViewModel.ts index de5336366..c469b4649 100644 --- a/apps/website/lib/view-models/DriverProfileDriverSummaryViewModel.ts +++ b/apps/website/lib/view-models/DriverProfileDriverSummaryViewModel.ts @@ -1,8 +1,8 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; +import { DashboardConsistencyFormatter } from "../formatters/DashboardConsistencyFormatter"; +import { NumberFormatter } from "../formatters/NumberFormatter"; +import { RatingFormatter } from "../formatters/RatingFormatter"; import { ProfileViewData } from "../view-data/ProfileViewData"; -import { RatingDisplay } from "../display-objects/RatingDisplay"; -import { DashboardConsistencyDisplay } from "../display-objects/DashboardConsistencyDisplay"; -import { NumberDisplay } from "../display-objects/NumberDisplay"; /** * Driver Profile Driver Summary View Model @@ -44,7 +44,7 @@ export class DriverProfileDriverSummaryViewModel extends ViewModel { } get ratingLabel(): string { - return RatingDisplay.format(this.rating); + return RatingFormatter.format(this.rating); } get globalRank(): number | null { @@ -52,7 +52,7 @@ export class DriverProfileDriverSummaryViewModel extends ViewModel { } get globalRankLabel(): string { - return this.globalRank ? NumberDisplay.format(this.globalRank) : '—'; + return this.globalRank ? NumberFormatter.format(this.globalRank) : '—'; } get consistency(): number | null { @@ -60,7 +60,7 @@ export class DriverProfileDriverSummaryViewModel extends ViewModel { } get consistencyLabel(): string { - return this.consistency ? DashboardConsistencyDisplay.format(this.consistency) : '—'; + return this.consistency ? DashboardConsistencyFormatter.format(this.consistency) : '—'; } get bio(): string | null { @@ -72,6 +72,6 @@ export class DriverProfileDriverSummaryViewModel extends ViewModel { } get totalDriversLabel(): string { - return this.totalDrivers ? NumberDisplay.format(this.totalDrivers) : '—'; + return this.totalDrivers ? NumberFormatter.format(this.totalDrivers) : '—'; } } \ No newline at end of file diff --git a/apps/website/lib/view-models/DriverRegistrationStatusViewModel.ts b/apps/website/lib/view-models/DriverRegistrationStatusViewModel.ts index 1ba904679..af2f8f457 100644 --- a/apps/website/lib/view-models/DriverRegistrationStatusViewModel.ts +++ b/apps/website/lib/view-models/DriverRegistrationStatusViewModel.ts @@ -1,6 +1,6 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; +import { DriverRegistrationStatusFormatter } from "../formatters/DriverRegistrationStatusFormatter"; import type { DriverRegistrationStatusViewData } from "../view-data/DriverRegistrationStatusViewData"; -import { DriverRegistrationStatusDisplay } from "../display-objects/DriverRegistrationStatusDisplay"; export class DriverRegistrationStatusViewModel extends ViewModel { constructor(private readonly viewData: DriverRegistrationStatusViewData) { @@ -24,14 +24,14 @@ export class DriverRegistrationStatusViewModel extends ViewModel { } get statusMessage(): string { - return DriverRegistrationStatusDisplay.statusMessage(this.isRegistered); + return DriverRegistrationStatusFormatter.statusMessage(this.isRegistered); } get statusBadgeVariant(): string { - return DriverRegistrationStatusDisplay.statusBadgeVariant(this.isRegistered); + return DriverRegistrationStatusFormatter.statusBadgeVariant(this.isRegistered); } get registrationButtonText(): string { - return DriverRegistrationStatusDisplay.registrationButtonText(this.isRegistered); + return DriverRegistrationStatusFormatter.registrationButtonText(this.isRegistered); } } \ No newline at end of file diff --git a/apps/website/lib/view-models/DriverSummaryViewModel.ts b/apps/website/lib/view-models/DriverSummaryViewModel.ts index af8dc4677..8b657490e 100644 --- a/apps/website/lib/view-models/DriverSummaryViewModel.ts +++ b/apps/website/lib/view-models/DriverSummaryViewModel.ts @@ -1,7 +1,7 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; +import { NumberFormatter } from "../formatters/NumberFormatter"; +import { RatingFormatter } from "../formatters/RatingFormatter"; import type { DriverSummaryData } from "../view-data/DriverSummaryData"; -import { NumberDisplay } from "../display-objects/NumberDisplay"; -import { RatingDisplay } from "../display-objects/RatingDisplay"; /** * View Model for driver summary with rating and rank @@ -30,7 +30,7 @@ export class DriverSummaryViewModel extends ViewModel { } get ratingLabel(): string { - return RatingDisplay.format(this.rating); + return RatingFormatter.format(this.rating); } get rank(): number | null { @@ -38,7 +38,7 @@ export class DriverSummaryViewModel extends ViewModel { } get rankLabel(): string { - return this.rank === null ? '—' : NumberDisplay.format(this.rank); + return this.rank === null ? '—' : NumberFormatter.format(this.rank); } get roleBadgeText(): string { diff --git a/apps/website/lib/view-models/DriverTeamViewModel.ts b/apps/website/lib/view-models/DriverTeamViewModel.ts index 02367c4ab..f197c2fd9 100644 --- a/apps/website/lib/view-models/DriverTeamViewModel.ts +++ b/apps/website/lib/view-models/DriverTeamViewModel.ts @@ -4,8 +4,8 @@ * Client-only UI helper built from ViewData. */ -import { ProfileDisplay } from "../display-objects/ProfileDisplay"; import { ViewModel } from "../contracts/view-models/ViewModel"; +import { ProfileFormatter } from "../formatters/ProfileFormatter"; import type { TeamDetailData } from "../view-data/TeamDetailViewData"; export class DriverTeamViewModel extends ViewModel { @@ -39,6 +39,6 @@ export class DriverTeamViewModel extends ViewModel { /** UI-specific: Display role */ get displayRole(): string { - return ProfileDisplay.getTeamRole(this.role).text; + return ProfileFormatter.getTeamRole(this.role).text; } } \ No newline at end of file diff --git a/apps/website/lib/view-models/DriverViewModel.ts b/apps/website/lib/view-models/DriverViewModel.ts index 6230cdb74..133694939 100644 --- a/apps/website/lib/view-models/DriverViewModel.ts +++ b/apps/website/lib/view-models/DriverViewModel.ts @@ -5,7 +5,7 @@ * Note: client-only ViewModel created from ViewData (never DTO). */ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { RatingDisplay } from "../display-objects/RatingDisplay"; +import { RatingFormatter } from "../formatters/RatingFormatter"; import type { DriverViewData } from "../view-data/DriverViewData"; export class DriverViewModel extends ViewModel { @@ -32,6 +32,6 @@ export class DriverViewModel extends ViewModel { /** UI-specific: Formatted rating */ get formattedRating(): string { - return this.rating !== undefined ? RatingDisplay.format(this.rating) : "Unrated"; + return this.rating !== undefined ? RatingFormatter.format(this.rating) : "Unrated"; } } \ No newline at end of file diff --git a/apps/website/lib/view-models/LeagueMemberViewModel.ts b/apps/website/lib/view-models/LeagueMemberViewModel.ts index 192ec0216..09931d4ee 100644 --- a/apps/website/lib/view-models/LeagueMemberViewModel.ts +++ b/apps/website/lib/view-models/LeagueMemberViewModel.ts @@ -1,5 +1,5 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { LeagueRoleDisplay, LeagueRole } from "../display-objects/LeagueRoleDisplay"; +import { LeagueRole, LeagueRoleFormatter } from "../formatters/LeagueRoleFormatter"; import type { LeagueMemberViewData } from "../view-data/LeagueMemberViewData"; export class LeagueMemberViewModel extends ViewModel { @@ -23,7 +23,7 @@ export class LeagueMemberViewModel extends ViewModel { /** UI-specific: Badge classes for role */ get roleBadgeClasses(): string { - return LeagueRoleDisplay.getLeagueRoleDisplay(this.role as LeagueRole)?.badgeClasses || ''; + return LeagueRoleFormatter.getLeagueRoleDisplay(this.role as LeagueRole)?.badgeClasses || ''; } /** UI-specific: Whether this member is the owner */ diff --git a/apps/website/lib/view-models/LeagueViewModel.ts b/apps/website/lib/view-models/LeagueViewModel.ts index 209d72c51..bc1c8566c 100644 --- a/apps/website/lib/view-models/LeagueViewModel.ts +++ b/apps/website/lib/view-models/LeagueViewModel.ts @@ -1,6 +1,6 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { CurrencyDisplay } from "../display-objects/CurrencyDisplay"; -import { LeagueTierDisplay } from "../display-objects/LeagueTierDisplay"; +import { CurrencyFormatter } from "../formatters/CurrencyFormatter"; +import { LeagueTierFormatter } from "../formatters/LeagueTierFormatter"; import type { LeagueViewData } from "../view-data/LeagueDetailViewData"; export class LeagueViewModel extends ViewModel { @@ -50,7 +50,7 @@ export class LeagueViewModel extends ViewModel { } get formattedMainSponsorCpm(): string { - return CurrencyDisplay.format(this.mainSponsorCpm); + return CurrencyFormatter.format(this.mainSponsorCpm); } get racesLeft(): number { @@ -58,6 +58,6 @@ export class LeagueViewModel extends ViewModel { } get tierConfig() { - return LeagueTierDisplay.getDisplay(this.tier); + return LeagueTierFormatter.getDisplay(this.tier); } } diff --git a/apps/website/lib/view-models/LeagueWalletViewModel.ts b/apps/website/lib/view-models/LeagueWalletViewModel.ts index 203625486..37a9fdb3f 100644 --- a/apps/website/lib/view-models/LeagueWalletViewModel.ts +++ b/apps/website/lib/view-models/LeagueWalletViewModel.ts @@ -1,7 +1,7 @@ -import { WalletTransactionViewModel } from './WalletTransactionViewModel'; import { ViewModel } from "../contracts/view-models/ViewModel"; -import { CurrencyDisplay } from "../display-objects/CurrencyDisplay"; +import { CurrencyFormatter } from "../formatters/CurrencyFormatter"; import type { LeagueWalletViewData } from "../view-data/LeagueWalletViewData"; +import { WalletTransactionViewModel } from './WalletTransactionViewModel'; export class LeagueWalletViewModel extends ViewModel { private readonly data: LeagueWalletViewData; @@ -24,22 +24,22 @@ export class LeagueWalletViewModel extends ViewModel { /** UI-specific: Formatted balance */ get formattedBalance(): string { - return CurrencyDisplay.format(this.balance, this.currency); + return CurrencyFormatter.format(this.balance, this.currency); } /** UI-specific: Formatted total revenue */ get formattedTotalRevenue(): string { - return CurrencyDisplay.format(this.totalRevenue, this.currency); + return CurrencyFormatter.format(this.totalRevenue, this.currency); } /** UI-specific: Formatted total fees */ get formattedTotalFees(): string { - return CurrencyDisplay.format(this.totalFees, this.currency); + return CurrencyFormatter.format(this.totalFees, this.currency); } /** UI-specific: Formatted pending payouts */ get formattedPendingPayouts(): string { - return CurrencyDisplay.format(this.pendingPayouts, this.currency); + return CurrencyFormatter.format(this.pendingPayouts, this.currency); } /** UI-specific: Filtered transactions by type */ diff --git a/apps/website/lib/view-models/MembershipFeeViewModel.ts b/apps/website/lib/view-models/MembershipFeeViewModel.ts index 498f29e55..c2899cd4a 100644 --- a/apps/website/lib/view-models/MembershipFeeViewModel.ts +++ b/apps/website/lib/view-models/MembershipFeeViewModel.ts @@ -1,7 +1,7 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { CurrencyDisplay } from "../display-objects/CurrencyDisplay"; -import { DateDisplay } from "../display-objects/DateDisplay"; -import { MembershipFeeTypeDisplay } from "../display-objects/MembershipFeeTypeDisplay"; +import { CurrencyFormatter } from "../formatters/CurrencyFormatter"; +import { DateFormatter } from "../formatters/DateFormatter"; +import { MembershipFeeTypeFormatter } from "../formatters/MembershipFeeTypeFormatter"; import type { MembershipFeeViewData } from "../view-data/MembershipFeeViewData"; export class MembershipFeeViewModel extends ViewModel { @@ -23,12 +23,12 @@ export class MembershipFeeViewModel extends ViewModel { /** UI-specific: Formatted amount */ get formattedAmount(): string { - return CurrencyDisplay.format(this.amount, 'EUR'); + return CurrencyFormatter.format(this.amount, 'EUR'); } /** UI-specific: Type display */ get typeDisplay(): string { - return MembershipFeeTypeDisplay.format(this.type); + return MembershipFeeTypeFormatter.format(this.type); } /** UI-specific: Status display */ @@ -43,11 +43,11 @@ export class MembershipFeeViewModel extends ViewModel { /** UI-specific: Formatted created date */ get formattedCreatedAt(): string { - return DateDisplay.formatShort(this.createdAt); + return DateFormatter.formatShort(this.createdAt); } /** UI-specific: Formatted updated date */ get formattedUpdatedAt(): string { - return DateDisplay.formatShort(this.updatedAt); + return DateFormatter.formatShort(this.updatedAt); } } diff --git a/apps/website/lib/view-models/PaymentViewModel.ts b/apps/website/lib/view-models/PaymentViewModel.ts index 7bd2a7222..3ce190c3a 100644 --- a/apps/website/lib/view-models/PaymentViewModel.ts +++ b/apps/website/lib/view-models/PaymentViewModel.ts @@ -1,9 +1,9 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { CurrencyDisplay } from "../display-objects/CurrencyDisplay"; -import { DateDisplay } from "../display-objects/DateDisplay"; -import { PaymentTypeDisplay } from "../display-objects/PaymentTypeDisplay"; -import { PayerTypeDisplay } from "../display-objects/PayerTypeDisplay"; -import { StatusDisplay } from "../display-objects/StatusDisplay"; +import { CurrencyFormatter } from "../formatters/CurrencyFormatter"; +import { DateFormatter } from "../formatters/DateFormatter"; +import { PayerTypeFormatter } from "../formatters/PayerTypeFormatter"; +import { PaymentTypeFormatter } from "../formatters/PaymentTypeFormatter"; +import { StatusFormatter } from "../formatters/StatusFormatter"; import type { PaymentViewData } from "../view-data/PaymentViewData"; export class PaymentViewModel extends ViewModel { @@ -29,12 +29,12 @@ export class PaymentViewModel extends ViewModel { /** UI-specific: Formatted amount */ get formattedAmount(): string { - return CurrencyDisplay.format(this.amount, 'EUR'); + return CurrencyFormatter.format(this.amount, 'EUR'); } /** UI-specific: Formatted net amount */ get formattedNetAmount(): string { - return CurrencyDisplay.format(this.netAmount, 'EUR'); + return CurrencyFormatter.format(this.netAmount, 'EUR'); } /** UI-specific: Status color */ @@ -50,26 +50,26 @@ export class PaymentViewModel extends ViewModel { /** UI-specific: Formatted created date */ get formattedCreatedAt(): string { - return DateDisplay.formatShort(this.createdAt); + return DateFormatter.formatShort(this.createdAt); } /** UI-specific: Formatted completed date */ get formattedCompletedAt(): string { - return this.completedAt ? DateDisplay.formatShort(this.completedAt) : 'Not completed'; + return this.completedAt ? DateFormatter.formatShort(this.completedAt) : 'Not completed'; } /** UI-specific: Status display */ get statusDisplay(): string { - return StatusDisplay.transactionStatus(this.status); + return StatusFormatter.transactionStatus(this.status); } /** UI-specific: Type display */ get typeDisplay(): string { - return PaymentTypeDisplay.format(this.type); + return PaymentTypeFormatter.format(this.type); } /** UI-specific: Payer type display */ get payerTypeDisplay(): string { - return PayerTypeDisplay.format(this.payerType); + return PayerTypeFormatter.format(this.payerType); } } diff --git a/apps/website/lib/view-models/PrizeViewModel.ts b/apps/website/lib/view-models/PrizeViewModel.ts index 73dfed8a7..790b98e47 100644 --- a/apps/website/lib/view-models/PrizeViewModel.ts +++ b/apps/website/lib/view-models/PrizeViewModel.ts @@ -1,8 +1,8 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { CurrencyDisplay } from "../display-objects/CurrencyDisplay"; -import { FinishDisplay } from "../display-objects/FinishDisplay"; -import { DateDisplay } from "../display-objects/DateDisplay"; -import { PrizeTypeDisplay } from "../display-objects/PrizeTypeDisplay"; +import { CurrencyFormatter } from "../formatters/CurrencyFormatter"; +import { DateFormatter } from "../formatters/DateFormatter"; +import { FinishFormatter } from "../formatters/FinishFormatter"; +import { PrizeTypeFormatter } from "../formatters/PrizeTypeFormatter"; import type { PrizeViewData } from "../view-data/PrizeViewData"; export class PrizeViewModel extends ViewModel { @@ -28,17 +28,17 @@ export class PrizeViewModel extends ViewModel { /** UI-specific: Formatted amount */ get formattedAmount(): string { - return CurrencyDisplay.format(this.amount, 'EUR'); + return CurrencyFormatter.format(this.amount, 'EUR'); } /** UI-specific: Position display */ get positionDisplay(): string { - return FinishDisplay.format(this.position); + return FinishFormatter.format(this.position); } /** UI-specific: Type display */ get typeDisplay(): string { - return PrizeTypeDisplay.format(this.type); + return PrizeTypeFormatter.format(this.type); } /** UI-specific: Status display */ @@ -58,11 +58,11 @@ export class PrizeViewModel extends ViewModel { /** UI-specific: Formatted awarded date */ get formattedAwardedAt(): string { - return this.awardedAt ? DateDisplay.formatShort(this.awardedAt) : 'Not awarded'; + return this.awardedAt ? DateFormatter.formatShort(this.awardedAt) : 'Not awarded'; } /** UI-specific: Formatted created date */ get formattedCreatedAt(): string { - return DateDisplay.formatShort(this.createdAt); + return DateFormatter.formatShort(this.createdAt); } } diff --git a/apps/website/lib/view-models/ProtestViewModel.ts b/apps/website/lib/view-models/ProtestViewModel.ts index 3bcd13680..879d4b5b6 100644 --- a/apps/website/lib/view-models/ProtestViewModel.ts +++ b/apps/website/lib/view-models/ProtestViewModel.ts @@ -1,7 +1,7 @@ -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; -import { StatusDisplay } from '@/lib/display-objects/StatusDisplay'; -import { ViewModel } from "../contracts/view-models/ViewModel"; +import { DateFormatter } from '@/lib/formatters/DateFormatter'; +import { StatusFormatter } from '@/lib/formatters/StatusFormatter'; import type { ProtestViewData } from "@/lib/view-data/ProtestViewData"; +import { ViewModel } from "../contracts/view-models/ViewModel"; export class ProtestViewModel extends ViewModel { private readonly data: ProtestViewData; @@ -27,11 +27,11 @@ export class ProtestViewModel extends ViewModel { /** UI-specific: Formatted submitted date */ get formattedSubmittedAt(): string { - return DateDisplay.formatShort(this.submittedAt); + return DateFormatter.formatShort(this.submittedAt); } /** UI-specific: Status display */ get statusDisplay(): string { - return StatusDisplay.protestStatus(this.status); + return StatusFormatter.protestStatus(this.status); } } diff --git a/apps/website/lib/view-models/RaceDetailUserResultViewModel.ts b/apps/website/lib/view-models/RaceDetailUserResultViewModel.ts index 2a9cd730d..da9225baf 100644 --- a/apps/website/lib/view-models/RaceDetailUserResultViewModel.ts +++ b/apps/website/lib/view-models/RaceDetailUserResultViewModel.ts @@ -1,5 +1,5 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { DurationDisplay } from "../display-objects/DurationDisplay"; +import { DurationFormatter } from "../formatters/DurationFormatter"; import type { RaceDetailUserResultViewData } from "../view-data/RaceDetailUserResultViewData"; export class RaceDetailUserResultViewModel extends ViewModel { @@ -54,6 +54,6 @@ export class RaceDetailUserResultViewModel extends ViewModel { /** UI-specific: Formatted lap time */ get lapTimeFormatted(): string { if (this.fastestLap <= 0) return '--:--.---'; - return DurationDisplay.formatSeconds(this.fastestLap); + return DurationFormatter.formatSeconds(this.fastestLap); } } diff --git a/apps/website/lib/view-models/RaceListItemViewModel.ts b/apps/website/lib/view-models/RaceListItemViewModel.ts index 9f9396870..030e84321 100644 --- a/apps/website/lib/view-models/RaceListItemViewModel.ts +++ b/apps/website/lib/view-models/RaceListItemViewModel.ts @@ -1,6 +1,6 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { RaceStatusDisplay } from "../display-objects/RaceStatusDisplay"; -import { DateDisplay } from "../display-objects/DateDisplay"; +import { DateFormatter } from "../formatters/DateFormatter"; +import { RaceStatusFormatter } from "../formatters/RaceStatusFormatter"; import type { RaceListItemViewData } from "../view-data/RaceListItemViewData"; export class RaceListItemViewModel extends ViewModel { @@ -29,12 +29,12 @@ export class RaceListItemViewModel extends ViewModel { /** UI-specific: Formatted scheduled time */ get formattedScheduledTime(): string { - return DateDisplay.formatDateTime(this.scheduledAt); + return DateFormatter.formatDateTime(this.scheduledAt); } /** UI-specific: Badge variant for status */ get statusBadgeVariant(): string { - return RaceStatusDisplay.getVariant(this.status); + return RaceStatusFormatter.getVariant(this.status); } /** UI-specific: Time until start in minutes */ diff --git a/apps/website/lib/view-models/RaceResultViewModel.ts b/apps/website/lib/view-models/RaceResultViewModel.ts index b5fa09957..a54ce7a6b 100644 --- a/apps/website/lib/view-models/RaceResultViewModel.ts +++ b/apps/website/lib/view-models/RaceResultViewModel.ts @@ -1,6 +1,6 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { FinishDisplay } from '../display-objects/FinishDisplay'; -import { DurationDisplay } from '../display-objects/DurationDisplay'; +import { DurationFormatter } from '../formatters/DurationFormatter'; +import { FinishFormatter } from '../formatters/FinishFormatter'; import type { RaceResultViewData } from "../view-data/RaceResultViewData"; export class RaceResultViewModel extends ViewModel { @@ -50,7 +50,7 @@ export class RaceResultViewModel extends ViewModel { /** UI-specific: Badge for position */ get positionBadge(): string { - return FinishDisplay.format(this.position); + return FinishFormatter.format(this.position); } /** UI-specific: Color for incidents badge */ @@ -63,7 +63,7 @@ export class RaceResultViewModel extends ViewModel { /** UI-specific: Formatted lap time */ get lapTimeFormatted(): string { if (this.fastestLap <= 0) return '--:--.---'; - return DurationDisplay.formatSeconds(this.fastestLap); + return DurationFormatter.formatSeconds(this.fastestLap); } /** Required by ResultsTable */ @@ -72,11 +72,11 @@ export class RaceResultViewModel extends ViewModel { } get formattedPosition(): string { - return FinishDisplay.format(this.position); + return FinishFormatter.format(this.position); } get formattedStartPosition(): string { - return FinishDisplay.format(this.startPosition); + return FinishFormatter.format(this.startPosition); } get formattedIncidents(): string { diff --git a/apps/website/lib/view-models/RenewalAlertViewModel.ts b/apps/website/lib/view-models/RenewalAlertViewModel.ts index fa80bbe4e..92ef92b72 100644 --- a/apps/website/lib/view-models/RenewalAlertViewModel.ts +++ b/apps/website/lib/view-models/RenewalAlertViewModel.ts @@ -1,6 +1,6 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { CurrencyDisplay } from "../display-objects/CurrencyDisplay"; -import { DateDisplay } from "../display-objects/DateDisplay"; +import { CurrencyFormatter } from "../formatters/CurrencyFormatter"; +import { DateFormatter } from "../formatters/DateFormatter"; import type { RenewalAlertViewData } from "../view-data/RenewalAlertViewData"; export class RenewalAlertViewModel extends ViewModel { @@ -20,11 +20,11 @@ export class RenewalAlertViewModel extends ViewModel { } get formattedPrice(): string { - return CurrencyDisplay.format(this.price); + return CurrencyFormatter.format(this.price); } get formattedRenewDate(): string { - return DateDisplay.formatShort(this.renewDate); + return DateFormatter.formatShort(this.renewDate); } get typeIcon() { diff --git a/apps/website/lib/view-models/SponsorshipDetailViewModel.ts b/apps/website/lib/view-models/SponsorshipDetailViewModel.ts index fa17da981..1fab64a8a 100644 --- a/apps/website/lib/view-models/SponsorshipDetailViewModel.ts +++ b/apps/website/lib/view-models/SponsorshipDetailViewModel.ts @@ -1,5 +1,5 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { CurrencyDisplay } from "../display-objects/CurrencyDisplay"; +import { CurrencyFormatter } from "../formatters/CurrencyFormatter"; import type { SponsorshipDetailViewData } from "../view-data/SponsorshipDetailViewData"; export class SponsorshipDetailViewModel extends ViewModel { @@ -36,7 +36,7 @@ export class SponsorshipDetailViewModel extends ViewModel { /** UI-specific: Formatted amount */ get formattedAmount(): string { - return CurrencyDisplay.format(this.amount, this.currency); + return CurrencyFormatter.format(this.amount, this.currency); } /** UI-specific: Tier badge variant */ diff --git a/apps/website/lib/view-models/SponsorshipPricingViewModel.ts b/apps/website/lib/view-models/SponsorshipPricingViewModel.ts index d0e109688..4f211ef18 100644 --- a/apps/website/lib/view-models/SponsorshipPricingViewModel.ts +++ b/apps/website/lib/view-models/SponsorshipPricingViewModel.ts @@ -1,5 +1,5 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { CurrencyDisplay } from "../display-objects/CurrencyDisplay"; +import { CurrencyFormatter } from "../formatters/CurrencyFormatter"; import type { SponsorshipPricingViewData } from "../view-data/SponsorshipPricingViewData"; export class SponsorshipPricingViewModel extends ViewModel { @@ -16,12 +16,12 @@ export class SponsorshipPricingViewModel extends ViewModel { /** UI-specific: Formatted main slot price */ get formattedMainSlotPrice(): string { - return CurrencyDisplay.format(this.mainSlotPrice, this.currency); + return CurrencyFormatter.format(this.mainSlotPrice, this.currency); } /** UI-specific: Formatted secondary slot price */ get formattedSecondarySlotPrice(): string { - return CurrencyDisplay.format(this.secondarySlotPrice, this.currency); + return CurrencyFormatter.format(this.secondarySlotPrice, this.currency); } /** UI-specific: Price difference */ @@ -31,7 +31,7 @@ export class SponsorshipPricingViewModel extends ViewModel { /** UI-specific: Formatted price difference */ get formattedPriceDifference(): string { - return CurrencyDisplay.format(this.priceDifference, this.currency); + return CurrencyFormatter.format(this.priceDifference, this.currency); } /** UI-specific: Discount percentage for secondary slot */ diff --git a/apps/website/lib/view-models/SponsorshipRequestViewModel.ts b/apps/website/lib/view-models/SponsorshipRequestViewModel.ts index 6b3ca03b9..e91edec38 100644 --- a/apps/website/lib/view-models/SponsorshipRequestViewModel.ts +++ b/apps/website/lib/view-models/SponsorshipRequestViewModel.ts @@ -1,6 +1,6 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { CurrencyDisplay } from "../display-objects/CurrencyDisplay"; -import { DateDisplay } from "../display-objects/DateDisplay"; +import { CurrencyFormatter } from "../formatters/CurrencyFormatter"; +import { DateFormatter } from "../formatters/DateFormatter"; import type { SponsorshipRequestViewData } from "../view-data/SponsorshipRequestViewData"; export class SponsorshipRequestViewModel extends ViewModel { @@ -35,12 +35,12 @@ export class SponsorshipRequestViewModel extends ViewModel { /** UI-specific: Formatted date */ get formattedDate(): string { - return DateDisplay.formatMonthDay(this.createdAt); + return DateFormatter.formatMonthDay(this.createdAt); } /** UI-specific: Net amount in dollars */ get netAmountDollars(): string { - return CurrencyDisplay.format(this.netAmount / 100, 'USD'); + return CurrencyFormatter.format(this.netAmount / 100, 'USD'); } /** UI-specific: Tier display */ diff --git a/apps/website/lib/view-models/SponsorshipViewModel.ts b/apps/website/lib/view-models/SponsorshipViewModel.ts index 9e3e938a5..b86ab7544 100644 --- a/apps/website/lib/view-models/SponsorshipViewModel.ts +++ b/apps/website/lib/view-models/SponsorshipViewModel.ts @@ -1,7 +1,7 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { CurrencyDisplay } from '../display-objects/CurrencyDisplay'; -import { DateDisplay } from '../display-objects/DateDisplay'; -import { NumberDisplay } from '../display-objects/NumberDisplay'; +import { CurrencyFormatter } from '../formatters/CurrencyFormatter'; +import { DateFormatter } from '../formatters/DateFormatter'; +import { NumberFormatter } from '../formatters/NumberFormatter'; import type { SponsorshipViewData } from "../view-data/SponsorshipViewData"; /** @@ -52,11 +52,11 @@ export class SponsorshipViewModel extends ViewModel { } get formattedImpressions(): string { - return NumberDisplay.format(this.impressions); + return NumberFormatter.format(this.impressions); } get formattedPrice(): string { - return CurrencyDisplay.format(this.price); + return CurrencyFormatter.format(this.price); } get daysRemaining(): number { @@ -92,8 +92,8 @@ export class SponsorshipViewModel extends ViewModel { } get periodDisplay(): string { - const start = DateDisplay.formatMonthYear(this.startDate); - const end = DateDisplay.formatMonthYear(this.endDate); + const start = DateFormatter.formatMonthYear(this.startDate); + const end = DateFormatter.formatMonthYear(this.endDate); return `${start} - ${end}`; } } diff --git a/apps/website/lib/view-models/StandingEntryViewModel.ts b/apps/website/lib/view-models/StandingEntryViewModel.ts index c160088b7..b1204a254 100644 --- a/apps/website/lib/view-models/StandingEntryViewModel.ts +++ b/apps/website/lib/view-models/StandingEntryViewModel.ts @@ -1,5 +1,5 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { FinishDisplay } from "../display-objects/FinishDisplay"; +import { FinishFormatter } from "../formatters/FinishFormatter"; import type { StandingEntryViewData } from "../view-data/StandingEntryViewData"; export class StandingEntryViewModel extends ViewModel { @@ -20,7 +20,7 @@ export class StandingEntryViewModel extends ViewModel { /** UI-specific: Badge for position display */ get positionBadge(): string { - return FinishDisplay.format(this.position); + return FinishFormatter.format(this.position); } /** UI-specific: Points difference to leader */ diff --git a/apps/website/lib/view-models/TeamJoinRequestViewModel.ts b/apps/website/lib/view-models/TeamJoinRequestViewModel.ts index 56dc6761c..1c34be257 100644 --- a/apps/website/lib/view-models/TeamJoinRequestViewModel.ts +++ b/apps/website/lib/view-models/TeamJoinRequestViewModel.ts @@ -1,5 +1,5 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { DateDisplay } from "../display-objects/DateDisplay"; +import { DateFormatter } from "../formatters/DateFormatter"; import type { TeamJoinRequestViewData } from "../view-data/TeamJoinRequestViewData"; export class TeamJoinRequestViewModel extends ViewModel { @@ -35,7 +35,7 @@ export class TeamJoinRequestViewModel extends ViewModel { /** UI-specific: Formatted requested date */ get formattedRequestedAt(): string { - return DateDisplay.formatDateTime(this.requestedAt); + return DateFormatter.formatDateTime(this.requestedAt); } /** UI-specific: Status color */ diff --git a/apps/website/lib/view-models/TeamMemberViewModel.ts b/apps/website/lib/view-models/TeamMemberViewModel.ts index ea4f305a6..b41c904d5 100644 --- a/apps/website/lib/view-models/TeamMemberViewModel.ts +++ b/apps/website/lib/view-models/TeamMemberViewModel.ts @@ -1,6 +1,6 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { DateDisplay } from "../display-objects/DateDisplay"; -import type { TeamMemberViewData, TeamMemberRole } from "../view-data/TeamMemberViewData"; +import { DateFormatter } from "../formatters/DateFormatter"; +import type { TeamMemberRole, TeamMemberViewData } from "../view-data/TeamMemberViewData"; function normalizeTeamRole(role: string): TeamMemberRole { if (role === 'owner' || role === 'manager' || role === 'member') return role; @@ -53,6 +53,6 @@ export class TeamMemberViewModel extends ViewModel { /** UI-specific: Formatted joined date */ get formattedJoinedAt(): string { - return DateDisplay.formatShort(this.joinedAt); + return DateFormatter.formatShort(this.joinedAt); } } diff --git a/apps/website/lib/view-models/UpcomingRaceCardViewModel.ts b/apps/website/lib/view-models/UpcomingRaceCardViewModel.ts index 80be3bf8b..58144859e 100644 --- a/apps/website/lib/view-models/UpcomingRaceCardViewModel.ts +++ b/apps/website/lib/view-models/UpcomingRaceCardViewModel.ts @@ -1,5 +1,5 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { DateDisplay } from "../display-objects/DateDisplay"; +import { DateFormatter } from "../formatters/DateFormatter"; import type { UpcomingRaceCardViewData } from "../view-data/UpcomingRaceCardViewData"; /** @@ -22,6 +22,6 @@ export class UpcomingRaceCardViewModel extends ViewModel { /** UI-specific: formatted date label */ get formattedDate(): string { - return DateDisplay.formatMonthDay(this.scheduledAt); + return DateFormatter.formatMonthDay(this.scheduledAt); } } diff --git a/apps/website/lib/view-models/WalletTransactionViewModel.ts b/apps/website/lib/view-models/WalletTransactionViewModel.ts index 4f9b65e4c..ad7031799 100644 --- a/apps/website/lib/view-models/WalletTransactionViewModel.ts +++ b/apps/website/lib/view-models/WalletTransactionViewModel.ts @@ -1,7 +1,7 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; -import { CurrencyDisplay } from "../display-objects/CurrencyDisplay"; -import { DateDisplay } from "../display-objects/DateDisplay"; -import { TransactionTypeDisplay } from "../display-objects/TransactionTypeDisplay"; +import { CurrencyFormatter } from "../formatters/CurrencyFormatter"; +import { DateFormatter } from "../formatters/DateFormatter"; +import { TransactionTypeFormatter } from "../formatters/TransactionTypeFormatter"; import type { WalletTransactionViewData } from "../view-data/WalletTransactionViewData"; export class WalletTransactionViewModel extends ViewModel { @@ -25,7 +25,7 @@ export class WalletTransactionViewModel extends ViewModel { /** UI-specific: Formatted amount with sign */ get formattedAmount(): string { const sign = this.amount > 0 ? '+' : ''; - return `${sign}${CurrencyDisplay.format(Math.abs(this.amount))}`; + return `${sign}${CurrencyFormatter.format(Math.abs(this.amount))}`; } /** UI-specific: Amount color */ @@ -35,12 +35,12 @@ export class WalletTransactionViewModel extends ViewModel { /** UI-specific: Type display */ get typeDisplay(): string { - return TransactionTypeDisplay.format(this.type); + return TransactionTypeFormatter.format(this.type); } /** UI-specific: Formatted date */ get formattedDate(): string { - return DateDisplay.formatShort(this.date); + return DateFormatter.formatShort(this.date); } /** UI-specific: Is incoming */ diff --git a/apps/website/lib/view-models/WalletViewModel.ts b/apps/website/lib/view-models/WalletViewModel.ts index d05c3672c..cea2ee8ea 100644 --- a/apps/website/lib/view-models/WalletViewModel.ts +++ b/apps/website/lib/view-models/WalletViewModel.ts @@ -1,7 +1,7 @@ -import { WalletTransactionViewModel } from './WalletTransactionViewModel'; import { ViewModel } from "../contracts/view-models/ViewModel"; -import { CurrencyDisplay } from "../display-objects/CurrencyDisplay"; +import { CurrencyFormatter } from "../formatters/CurrencyFormatter"; import type { WalletViewData } from "../view-data/WalletViewData"; +import { WalletTransactionViewModel } from './WalletTransactionViewModel'; export class WalletViewModel extends ViewModel { private readonly data: WalletViewData; @@ -24,7 +24,7 @@ export class WalletViewModel extends ViewModel { /** UI-specific: Formatted balance */ get formattedBalance(): string { - return CurrencyDisplay.format(this.balance, this.currency); + return CurrencyFormatter.format(this.balance, this.currency); } /** UI-specific: Balance color */ diff --git a/apps/website/templates/LeagueScheduleTemplate.tsx b/apps/website/templates/LeagueScheduleTemplate.tsx index b1d714acb..3749f73b8 100644 --- a/apps/website/templates/LeagueScheduleTemplate.tsx +++ b/apps/website/templates/LeagueScheduleTemplate.tsx @@ -1,23 +1,22 @@ 'use client'; -import { useState } from 'react'; +import { + navigateToEditRaceAction, + navigateToRaceResultsAction, + navigateToRescheduleRaceAction, + registerForRaceAction, + withdrawFromRaceAction +} from '@/app/actions/leagueScheduleActions'; import { EnhancedLeagueSchedulePanel } from '@/components/leagues/EnhancedLeagueSchedulePanel'; import { RaceDetailModal } from '@/components/leagues/RaceDetailModal'; -import type { LeagueScheduleViewData } from '@/lib/view-data/leagues/LeagueScheduleViewData'; +import { LeagueScheduleViewData } from '@/lib/view-data/leagues/LeagueScheduleViewData'; import { Box } from '@/ui/Box'; -import { Text } from '@/ui/Text'; import { Button } from '@/ui/Button'; -import { Icon } from '@/ui/Icon'; import { Group } from '@/ui/Group'; -import { Calendar, Plus } from 'lucide-react'; -import { DateDisplay } from '@/lib/display-objects/DateDisplay'; -import { - registerForRaceAction, - withdrawFromRaceAction, - navigateToEditRaceAction, - navigateToRescheduleRaceAction, - navigateToRaceResultsAction -} from '@/app/actions/leagueScheduleActions'; +import { Icon } from '@/ui/Icon'; +import { Text } from '@/ui/Text'; +import { Plus } from 'lucide-react'; +import { useState } from 'react'; interface LeagueScheduleTemplateProps { viewData: LeagueScheduleViewData;