diff --git a/apps/api/src/domain/admin/RequireSystemAdmin.test.ts b/apps/api/src/domain/admin/RequireSystemAdmin.test.ts index f6b6245c8..101ee152b 100644 --- a/apps/api/src/domain/admin/RequireSystemAdmin.test.ts +++ b/apps/api/src/domain/admin/RequireSystemAdmin.test.ts @@ -3,7 +3,7 @@ import { RequireSystemAdmin, REQUIRE_SYSTEM_ADMIN_METADATA_KEY } from './Require // Mock SetMetadata vi.mock('@nestjs/common', () => ({ - SetMetadata: vi.fn(() => () => {}), + SetMetadata: vi.fn(() => (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => descriptor), })); describe('RequireSystemAdmin', () => { @@ -30,7 +30,7 @@ describe('RequireSystemAdmin', () => { const result = decorator(mockTarget, mockPropertyKey, mockDescriptor); - // The decorator should return the descriptor + // The decorator should return the descriptor (SetMetadata returns the descriptor) expect(result).toBe(mockDescriptor); }); diff --git a/apps/api/src/domain/admin/use-cases/GetDashboardStatsUseCase.test.ts b/apps/api/src/domain/admin/use-cases/GetDashboardStatsUseCase.test.ts index f057c7f6c..7ee3e80f5 100644 --- a/apps/api/src/domain/admin/use-cases/GetDashboardStatsUseCase.test.ts +++ b/apps/api/src/domain/admin/use-cases/GetDashboardStatsUseCase.test.ts @@ -1,6 +1,4 @@ import { AdminUser } from '@core/admin/domain/entities/AdminUser'; -import { AuthorizationService } from '@core/admin/domain/services/AuthorizationService'; -import { Result } from '@core/shared/domain/Result'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { GetDashboardStatsUseCase } from './GetDashboardStatsUseCase'; @@ -413,15 +411,15 @@ describe('GetDashboardStatsUseCase', () => { // Check that today has 1 user const todayEntry = stats.userGrowth[6]; - expect(todayEntry.value).toBe(1); + expect(todayEntry?.value).toBe(1); // Check that yesterday has 1 user const yesterdayEntry = stats.userGrowth[5]; - expect(yesterdayEntry.value).toBe(1); + expect(yesterdayEntry?.value).toBe(1); // Check that two days ago has 1 user const twoDaysAgoEntry = stats.userGrowth[4]; - expect(twoDaysAgoEntry.value).toBe(1); + expect(twoDaysAgoEntry?.value).toBe(1); }); it('should calculate activity timeline for last 7 days', async () => { @@ -643,8 +641,9 @@ describe('GetDashboardStatsUseCase', () => { status: 'active', }); - const users = Array.from({ length: 1000 }, (_, i) => - AdminUser.create({ + const users = Array.from({ length: 1000 }, (_, i) => { + const hasRecentLogin = i % 10 === 0; + return AdminUser.create({ id: `user-${i}`, email: `user${i}@example.com`, displayName: `User ${i}`, @@ -652,9 +651,9 @@ describe('GetDashboardStatsUseCase', () => { status: i % 4 === 0 ? 'suspended' : i % 4 === 1 ? 'deleted' : 'active', createdAt: new Date(Date.now() - i * 3600000), updatedAt: new Date(Date.now() - i * 3600000), - lastLoginAt: i % 10 === 0 ? new Date(Date.now() - i * 3600000) : undefined, - }) - ); + ...(hasRecentLogin && { lastLoginAt: new Date(Date.now() - i * 3600000) }), + }); + }); mockAdminUserRepo.findById.mockResolvedValue(actor); mockAdminUserRepo.list.mockResolvedValue({ users }); diff --git a/apps/api/src/domain/auth/Public.test.ts b/apps/api/src/domain/auth/Public.test.ts index 90682f380..a3cecf625 100644 --- a/apps/api/src/domain/auth/Public.test.ts +++ b/apps/api/src/domain/auth/Public.test.ts @@ -3,7 +3,7 @@ import { Public, PUBLIC_ROUTE_METADATA_KEY } from './Public'; // Mock SetMetadata vi.mock('@nestjs/common', () => ({ - SetMetadata: vi.fn(() => () => {}), + SetMetadata: vi.fn(() => (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => descriptor), })); describe('Public', () => { diff --git a/apps/api/src/domain/auth/RequireAuthenticatedUser.test.ts b/apps/api/src/domain/auth/RequireAuthenticatedUser.test.ts index 475262f7c..4ff1036c3 100644 --- a/apps/api/src/domain/auth/RequireAuthenticatedUser.test.ts +++ b/apps/api/src/domain/auth/RequireAuthenticatedUser.test.ts @@ -3,7 +3,7 @@ import { RequireAuthenticatedUser, REQUIRE_AUTHENTICATED_USER_METADATA_KEY } fro // Mock SetMetadata vi.mock('@nestjs/common', () => ({ - SetMetadata: vi.fn(() => () => {}), + SetMetadata: vi.fn(() => (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => descriptor), })); describe('RequireAuthenticatedUser', () => { diff --git a/apps/api/src/domain/auth/RequireRoles.test.ts b/apps/api/src/domain/auth/RequireRoles.test.ts index 7a5bf8abf..eac353fb0 100644 --- a/apps/api/src/domain/auth/RequireRoles.test.ts +++ b/apps/api/src/domain/auth/RequireRoles.test.ts @@ -3,7 +3,7 @@ import { RequireRoles, REQUIRE_ROLES_METADATA_KEY } from './RequireRoles'; // Mock SetMetadata vi.mock('@nestjs/common', () => ({ - SetMetadata: vi.fn(() => () => {}), + SetMetadata: vi.fn(() => (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => descriptor), })); describe('RequireRoles', () => { diff --git a/apps/website/hooks/driver/useDriverProfile.ts b/apps/website/hooks/driver/useDriverProfile.ts index 0f304d66e..4126f4abe 100644 --- a/apps/website/hooks/driver/useDriverProfile.ts +++ b/apps/website/hooks/driver/useDriverProfile.ts @@ -19,7 +19,83 @@ export function useDriverProfile( const error = result.getError(); throw new ApiError(error.message, 'SERVER_ERROR', { timestamp: new globalThis.Date().toISOString() }); } - return new DriverProfileViewModel(result.unwrap()); + const dto = result.unwrap(); + // Convert GetDriverProfileOutputDTO to ProfileViewData + const viewData: ProfileViewData = { + driver: dto.currentDriver ? { + id: dto.currentDriver.id, + name: dto.currentDriver.name, + countryCode: dto.currentDriver.countryCode || '', + countryFlag: dto.currentDriver.countryFlag || '', + avatarUrl: dto.currentDriver.avatarUrl || '', + bio: dto.currentDriver.bio || null, + iracingId: dto.currentDriver.iracingId || null, + joinedAtLabel: dto.currentDriver.joinedAt || '', + globalRankLabel: dto.currentDriver.globalRank || '', + } : { + id: '', + name: '', + countryCode: '', + countryFlag: '', + avatarUrl: '', + bio: null, + iracingId: null, + joinedAtLabel: '', + globalRankLabel: '', + }, + stats: dto.stats ? { + ratingLabel: dto.stats.rating || '', + globalRankLabel: dto.stats.globalRank || '', + totalRacesLabel: dto.stats.totalRaces?.toString() || '', + winsLabel: dto.stats.wins?.toString() || '', + podiumsLabel: dto.stats.podiums?.toString() || '', + dnfsLabel: dto.stats.dnfs?.toString() || '', + bestFinishLabel: dto.stats.bestFinish?.toString() || '', + worstFinishLabel: dto.stats.worstFinish?.toString() || '', + avgFinishLabel: dto.stats.avgFinish?.toString() || '', + consistencyLabel: dto.stats.consistency?.toString() || '', + percentileLabel: dto.stats.percentile?.toString() || '', + } : null, + teamMemberships: dto.teamMemberships.map(m => ({ + teamId: m.teamId, + teamName: m.teamName, + teamTag: m.teamTag || null, + roleLabel: m.role || '', + joinedAtLabel: m.joinedAt || '', + href: `/teams/${m.teamId}`, + })), + extendedProfile: dto.extendedProfile ? { + timezone: dto.extendedProfile.timezone || '', + racingStyle: dto.extendedProfile.racingStyle || '', + favoriteTrack: dto.extendedProfile.favoriteTrack || '', + favoriteCar: dto.extendedProfile.favoriteCar || '', + availableHours: dto.extendedProfile.availableHours || '', + lookingForTeamLabel: dto.extendedProfile.lookingForTeam ? 'Yes' : 'No', + openToRequestsLabel: dto.extendedProfile.openToRequests ? 'Yes' : 'No', + socialHandles: dto.extendedProfile.socialHandles?.map(h => ({ + platformLabel: h.platform || '', + handle: h.handle || '', + url: h.url || '', + })) || [], + achievements: dto.extendedProfile.achievements?.map(a => ({ + id: a.id, + title: a.title, + description: a.description, + earnedAtLabel: a.earnedAt || '', + icon: a.icon as any, + rarityLabel: a.rarity || '', + })) || [], + friends: dto.extendedProfile.friends?.map(f => ({ + id: f.id, + name: f.name, + countryFlag: f.countryFlag || '', + avatarUrl: f.avatarUrl || '', + href: `/drivers/${f.id}`, + })) || [], + friendsCountLabel: dto.extendedProfile.friendsCount?.toString() || '', + } : null, + }; + return new DriverProfileViewModel(viewData); }, enabled: !!driverId, ...options, diff --git a/apps/website/hooks/league/useLeagueWalletPageData.ts b/apps/website/hooks/league/useLeagueWalletPageData.ts index 0381bcab2..d714d7047 100644 --- a/apps/website/hooks/league/useLeagueWalletPageData.ts +++ b/apps/website/hooks/league/useLeagueWalletPageData.ts @@ -14,24 +14,29 @@ export function useLeagueWalletPageData(leagueId: string) { queryKey: ['leagueWallet', leagueId], queryFn: async () => { const dto = await leagueWalletService.getWalletForLeague(leagueId); - // Transform DTO to ViewModel at client boundary - const transactions = dto.transactions.map(t => new WalletTransactionViewModel({ + // Transform DTO to ViewData at client boundary + const transactions = dto.transactions.map(t => ({ id: t.id, type: t.type as any, description: t.description, amount: t.amount, fee: 0, netAmount: t.amount, - date: new globalThis.Date(t.createdAt), + date: new globalThis.Date(t.createdAt).toISOString(), status: t.status, })); return new LeagueWalletViewModel({ + leagueId, balance: dto.balance, - currency: dto.currency, + formattedBalance: '', totalRevenue: dto.balance, // Fallback + formattedTotalRevenue: '', totalFees: 0, + formattedTotalFees: '', totalWithdrawals: 0, pendingPayouts: 0, + formattedPendingPayouts: '', + currency: dto.currency, transactions, canWithdraw: true, withdrawalBlockReason: undefined, diff --git a/apps/website/lib/adapters/MediaAdapter.ts b/apps/website/lib/adapters/MediaAdapter.ts index 7e3d95774..0e39dff99 100644 --- a/apps/website/lib/adapters/MediaAdapter.ts +++ b/apps/website/lib/adapters/MediaAdapter.ts @@ -8,7 +8,7 @@ import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl'; import { Result } from '@/lib/contracts/Result'; import { DomainError } from '@/lib/contracts/services/Service'; -import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO'; +import { MediaBinaryDTO } from '@/lib/types/generated/MediaBinaryDTO'; // TODO why is this an adapter? diff --git a/apps/website/lib/builders/view-data/HomeViewDataBuilder.ts b/apps/website/lib/builders/view-data/HomeViewDataBuilder.ts index fe2c6a859..f745ed2dd 100644 --- a/apps/website/lib/builders/view-data/HomeViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/HomeViewDataBuilder.ts @@ -1,34 +1,24 @@ import type { ViewDataBuilder } from '@/lib/contracts/builders/ViewDataBuilder'; -import { DashboardDateFormatter } from '@/lib/formatters/DashboardDateFormatter'; -import type { DashboardOverviewDTO } from '@/lib/types/generated/DashboardOverviewDTO'; +import type { HomeDataDTO } from '@/lib/types/dtos/HomeDataDTO'; import type { HomeViewData } from '@/lib/view-data/HomeViewData'; export class HomeViewDataBuilder { /** - * Build HomeViewData from DashboardOverviewDTO + * Build HomeViewData from HomeDataDTO * * @param apiDto - The API DTO * @returns HomeViewData */ - public static build(apiDto: DashboardOverviewDTO): HomeViewData { + public static build(apiDto: HomeDataDTO): HomeViewData { return { - isAlpha: true, - upcomingRaces: (apiDto.upcomingRaces || []).map(race => ({ - id: race.id, - track: race.track, - car: race.car, - formattedDate: DashboardDateFormatter.format(new Date(race.scheduledAt)).date, - })), - topLeagues: (apiDto.leagueStandingsSummaries || []).map(league => ({ - id: league.leagueId, - name: league.leagueName, - description: '', - })), - teams: [], + isAlpha: apiDto.isAlpha, + upcomingRaces: apiDto.upcomingRaces, + topLeagues: apiDto.topLeagues, + teams: apiDto.teams, }; } } -HomeViewDataBuilder satisfies ViewDataBuilder; +HomeViewDataBuilder satisfies ViewDataBuilder; diff --git a/apps/website/lib/builders/view-data/RulebookViewDataBuilder.ts b/apps/website/lib/builders/view-data/RulebookViewDataBuilder.ts index 19953079d..9f832b8e6 100644 --- a/apps/website/lib/builders/view-data/RulebookViewDataBuilder.ts +++ b/apps/website/lib/builders/view-data/RulebookViewDataBuilder.ts @@ -1,16 +1,29 @@ /** * Rulebook View Data Builder - * + * * Transforms API DTO to ViewData for templates. */ import type { RulebookViewData } from '@/lib/view-data/RulebookViewData'; -import { LeagueScoringConfigDTO } from '@/lib/types/generated/LeagueScoringConfigDTO'; import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder"; interface RulebookApiDto { leagueId: string; - scoringConfig: LeagueScoringConfigDTO; + scoringConfig: { + gameName: string; + scoringPresetName: string; + championships: Array<{ + type: string; + sessionTypes: string[]; + pointsPreview: Array<{ + sessionType: string; + position: number; + points: number; + }>; + bonusSummary: string[]; + }>; + dropPolicySummary: string; + }; } export class RulebookViewDataBuilder { diff --git a/apps/website/lib/gateways/api/races/RacesApiClient.ts b/apps/website/lib/gateways/api/races/RacesApiClient.ts index 48a3a9794..105f4e7ba 100644 --- a/apps/website/lib/gateways/api/races/RacesApiClient.ts +++ b/apps/website/lib/gateways/api/races/RacesApiClient.ts @@ -1,5 +1,6 @@ import type { FileProtestCommandDTO } from '../../../types/generated/FileProtestCommandDTO'; import type { ImportRaceResultsDTO } from '../../../types/generated/ImportRaceResultsDTO'; +import type { RaceDetailDTO } from '../../../types/generated/RaceDetailDTO'; import type { RaceDetailEntryDTO } from '../../../types/generated/RaceDetailEntryDTO'; import type { RaceDetailLeagueDTO } from '../../../types/generated/RaceDetailLeagueDTO'; import type { RaceDetailRaceDTO } from '../../../types/generated/RaceDetailRaceDTO'; @@ -15,14 +16,6 @@ import { BaseApiClient } from '../base/BaseApiClient'; // Define missing types export type RacesPageDataDTO = { races: RacesPageDataRaceDTO[] }; -export type RaceDetailDTO = { - race: RaceDetailRaceDTO | null; - league: RaceDetailLeagueDTO | null; - entryList: RaceDetailEntryDTO[]; - registration: RaceDetailRegistrationDTO; - userResult: RaceDetailUserResultDTO | null; - error?: string; -}; export type ImportRaceResultsSummaryDTO = { success: boolean; raceId: string; diff --git a/apps/website/lib/mutations/onboarding/GenerateAvatarsMutation.ts b/apps/website/lib/mutations/onboarding/GenerateAvatarsMutation.ts index e85566e7f..65496951b 100644 --- a/apps/website/lib/mutations/onboarding/GenerateAvatarsMutation.ts +++ b/apps/website/lib/mutations/onboarding/GenerateAvatarsMutation.ts @@ -4,7 +4,7 @@ import { mapToMutationError } from '@/lib/contracts/mutations/MutationError'; import { OnboardingService } from '@/lib/services/onboarding/OnboardingService'; import { RequestAvatarGenerationInputDTO } from '@/lib/types/generated/RequestAvatarGenerationInputDTO'; import { GenerateAvatarsViewDataBuilder } from '@/lib/builders/view-data/GenerateAvatarsViewDataBuilder'; -import { GenerateAvatarsViewData } from '@/lib/builders/view-data/GenerateAvatarsViewData'; +import { GenerateAvatarsViewData } from '@/lib/view-data/GenerateAvatarsViewData'; export class GenerateAvatarsMutation implements Mutation { async execute(input: RequestAvatarGenerationInputDTO): Promise> { diff --git a/apps/website/lib/page-queries/LeagueScheduleAdminPageQuery.ts b/apps/website/lib/page-queries/LeagueScheduleAdminPageQuery.ts index 251866b88..1b09ad8cf 100644 --- a/apps/website/lib/page-queries/LeagueScheduleAdminPageQuery.ts +++ b/apps/website/lib/page-queries/LeagueScheduleAdminPageQuery.ts @@ -21,7 +21,8 @@ export class LeagueScheduleAdminPageQuery implements PageQuery ({ id: r.id, name: r.name, diff --git a/apps/website/lib/page-queries/LeagueSchedulePageQuery.ts b/apps/website/lib/page-queries/LeagueSchedulePageQuery.ts index e37042e04..6622efb50 100644 --- a/apps/website/lib/page-queries/LeagueSchedulePageQuery.ts +++ b/apps/website/lib/page-queries/LeagueSchedulePageQuery.ts @@ -2,7 +2,7 @@ import { PageQuery } from '@/lib/contracts/page-queries/PageQuery'; import { Result } from '@/lib/contracts/Result'; import { LeagueScheduleService } from '@/lib/services/leagues/LeagueScheduleService'; import { LeagueScheduleViewDataBuilder } from '@/lib/builders/view-data/LeagueScheduleViewDataBuilder'; -import { LeagueScheduleViewData } from '@/lib/view-data/leagues/LeagueScheduleViewData'; +import { LeagueScheduleViewData } from '@/lib/view-data/LeagueScheduleViewData'; import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError'; export class LeagueSchedulePageQuery implements PageQuery { diff --git a/apps/website/lib/page-queries/LeagueStandingsPageQuery.ts b/apps/website/lib/page-queries/LeagueStandingsPageQuery.ts index faed96e8a..9e0a650c4 100644 --- a/apps/website/lib/page-queries/LeagueStandingsPageQuery.ts +++ b/apps/website/lib/page-queries/LeagueStandingsPageQuery.ts @@ -15,7 +15,11 @@ export class LeagueStandingsPageQuery implements PageQuery ({ id: race.id, name: race.name, diff --git a/apps/website/lib/services/leagues/LeagueSettingsService.ts b/apps/website/lib/services/leagues/LeagueSettingsService.ts index b61a1d409..0cddab284 100644 --- a/apps/website/lib/services/leagues/LeagueSettingsService.ts +++ b/apps/website/lib/services/leagues/LeagueSettingsService.ts @@ -80,6 +80,9 @@ export class LeagueSettingsService implements Service { allowLateJoin: true, requireApproval: false, }, + presets: [], + owner: null, + members: [], }; return Result.ok(mockData); } diff --git a/apps/website/lib/services/leagues/LeagueSponsorshipsService.ts b/apps/website/lib/services/leagues/LeagueSponsorshipsService.ts index 28ba6382d..cc52be128 100644 --- a/apps/website/lib/services/leagues/LeagueSponsorshipsService.ts +++ b/apps/website/lib/services/leagues/LeagueSponsorshipsService.ts @@ -53,6 +53,16 @@ export class LeagueSponsorshipsService implements Service { status: 'pending', }, ], + sponsorships: [ + { + id: 'sponsorship-1', + slotId: 'slot-1', + sponsorId: 'sponsor-1', + sponsorName: 'Acme Racing', + requestedAt: '2024-09-01T10:00:00Z', + status: 'approved', + }, + ], }; return Result.ok(mockData); } diff --git a/apps/website/lib/services/leagues/LeagueWalletService.ts b/apps/website/lib/services/leagues/LeagueWalletService.ts index 9a7679abc..90aefae45 100644 --- a/apps/website/lib/services/leagues/LeagueWalletService.ts +++ b/apps/website/lib/services/leagues/LeagueWalletService.ts @@ -44,6 +44,11 @@ export class LeagueWalletService implements Service { leagueId, balance: 15750.00, currency: 'USD', + totalRevenue: 7500.00, + totalFees: 1200.00, + totalWithdrawals: 1200.00, + pendingPayouts: 0, + canWithdraw: true, transactions: [ { id: 'txn-1', diff --git a/apps/website/lib/services/onboarding/OnboardingService.ts b/apps/website/lib/services/onboarding/OnboardingService.ts index 5b817f890..ecd97c0cf 100644 --- a/apps/website/lib/services/onboarding/OnboardingService.ts +++ b/apps/website/lib/services/onboarding/OnboardingService.ts @@ -16,6 +16,7 @@ import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporte import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger'; import { CompleteOnboardingInputDTO } from '@/lib/types/generated/CompleteOnboardingInputDTO'; import { CompleteOnboardingOutputDTO } from '@/lib/types/generated/CompleteOnboardingOutputDTO'; +import { GetDriverOutputDTO } from '@/lib/types/generated/GetDriverOutputDTO'; import { RequestAvatarGenerationInputDTO } from '@/lib/types/generated/RequestAvatarGenerationInputDTO'; import { RequestAvatarGenerationOutputDTO } from '@/lib/types/generated/RequestAvatarGenerationOutputDTO'; @@ -44,7 +45,7 @@ export class OnboardingService implements Service { } } - async checkCurrentDriver(): Promise> { + async checkCurrentDriver(): Promise> { try { const result = await this.apiClient.getCurrent(); return Result.ok(result); diff --git a/apps/website/lib/types/tbd/LeagueScheduleApiDto.ts b/apps/website/lib/types/tbd/LeagueScheduleApiDto.ts index 0a6f6d195..7fad64212 100644 --- a/apps/website/lib/types/tbd/LeagueScheduleApiDto.ts +++ b/apps/website/lib/types/tbd/LeagueScheduleApiDto.ts @@ -1,5 +1,7 @@ export interface LeagueScheduleApiDto { leagueId: string; + seasonId: string; + published: boolean; races: Array<{ id: string; name: string; diff --git a/apps/website/lib/types/tbd/LeagueSettingsApiDto.ts b/apps/website/lib/types/tbd/LeagueSettingsApiDto.ts index cb3e94f09..eb5c39865 100644 --- a/apps/website/lib/types/tbd/LeagueSettingsApiDto.ts +++ b/apps/website/lib/types/tbd/LeagueSettingsApiDto.ts @@ -15,4 +15,7 @@ export interface LeagueSettingsApiDto { allowLateJoin: boolean; requireApproval: boolean; }; + presets: any[]; + owner: any | null; + members: any[]; } \ No newline at end of file diff --git a/apps/website/lib/types/tbd/LeagueSponsorshipsApiDto.ts b/apps/website/lib/types/tbd/LeagueSponsorshipsApiDto.ts index 4f783df61..ee6aab12e 100644 --- a/apps/website/lib/types/tbd/LeagueSponsorshipsApiDto.ts +++ b/apps/website/lib/types/tbd/LeagueSponsorshipsApiDto.ts @@ -26,4 +26,12 @@ export interface LeagueSponsorshipsApiDto { requestedAt: string; status: 'pending' | 'approved' | 'rejected'; }>; + sponsorships: Array<{ + id: string; + slotId: string; + sponsorId: string; + sponsorName: string; + requestedAt: string; + status: 'pending' | 'approved' | 'rejected'; + }>; } \ No newline at end of file diff --git a/apps/website/lib/types/tbd/LeagueWalletApiDto.ts b/apps/website/lib/types/tbd/LeagueWalletApiDto.ts index 5f60fd0f4..ef65688b1 100644 --- a/apps/website/lib/types/tbd/LeagueWalletApiDto.ts +++ b/apps/website/lib/types/tbd/LeagueWalletApiDto.ts @@ -2,6 +2,11 @@ export interface LeagueWalletApiDto { leagueId: string; balance: number; currency: string; + totalRevenue: number; + totalFees: number; + totalWithdrawals: number; + pendingPayouts: number; + canWithdraw: boolean; transactions: Array<{ id: string; type: 'deposit' | 'withdrawal' | 'sponsorship' | 'prize'; diff --git a/apps/website/lib/view-models/RacesPageViewModel.test.ts b/apps/website/lib/view-models/RacesPageViewModel.test.ts index fc4185f42..fc8626236 100644 --- a/apps/website/lib/view-models/RacesPageViewModel.test.ts +++ b/apps/website/lib/view-models/RacesPageViewModel.test.ts @@ -48,11 +48,11 @@ describe('RaceListItemViewModel', () => { const cancelled = new RaceListItemViewModel({ ...baseDto, status: 'cancelled' }); const other = new RaceListItemViewModel({ ...baseDto, status: 'unknown' }); - expect(scheduled.statusBadgeVariant).toBe('info'); + expect(scheduled.statusBadgeVariant).toBe('primary'); expect(running.statusBadgeVariant).toBe('success'); - expect(completed.statusBadgeVariant).toBe('secondary'); - expect(cancelled.statusBadgeVariant).toBe('danger'); - expect(other.statusBadgeVariant).toBe('default'); + expect(completed.statusBadgeVariant).toBe('default'); + expect(cancelled.statusBadgeVariant).toBe('warning'); + expect(other.statusBadgeVariant).toBe('neutral'); }); }); diff --git a/apps/website/lib/view-models/RenewalAlertViewModel.test.ts b/apps/website/lib/view-models/RenewalAlertViewModel.test.ts index 3f3f82a8c..0bd62952d 100644 --- a/apps/website/lib/view-models/RenewalAlertViewModel.test.ts +++ b/apps/website/lib/view-models/RenewalAlertViewModel.test.ts @@ -14,7 +14,7 @@ describe('RenewalAlertViewModel', () => { expect(vm.id).toBe('ren-1'); expect(vm.name).toBe('League Sponsorship'); expect(vm.type).toBe('league'); - expect(vm.formattedPrice).toBe('$100'); + expect(vm.formattedPrice).toBe('$100.00'); expect(typeof vm.formattedRenewDate).toBe('string'); }); diff --git a/apps/website/lib/view-models/SponsorSettingsViewModel.test.ts b/apps/website/lib/view-models/SponsorSettingsViewModel.test.ts index cfd7c3d0c..ce755160c 100644 --- a/apps/website/lib/view-models/SponsorSettingsViewModel.test.ts +++ b/apps/website/lib/view-models/SponsorSettingsViewModel.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { NotificationSettingsViewModel, PrivacySettingsViewModel, SponsorProfileViewModel, SponsorSettingsViewModel } from './SponsorSettingsViewModel'; +import { SponsorSettingsViewModel } from './SponsorSettingsViewModel'; +import { SponsorProfileViewModel } from './SponsorProfileViewModel'; +import { NotificationSettingsViewModel } from './NotificationSettingsViewModel'; +import { PrivacySettingsViewModel } from './PrivacySettingsViewModel'; describe('SponsorSettingsViewModel', () => { const profile = { diff --git a/apps/website/lib/view-models/SponsorViewModel.test.ts b/apps/website/lib/view-models/SponsorViewModel.test.ts index 72bda009d..6d315a6c5 100644 --- a/apps/website/lib/view-models/SponsorViewModel.test.ts +++ b/apps/website/lib/view-models/SponsorViewModel.test.ts @@ -30,8 +30,8 @@ describe('SponsorViewModel', () => { expect(vm.id).toBe(dto.id); expect(vm.name).toBe(dto.name); - expect('logoUrl' in vm).toBe(false); - expect('websiteUrl' in vm).toBe(false); + expect(vm.logoUrl).toBeUndefined(); + expect(vm.websiteUrl).toBeUndefined(); }); it('exposes simple UI helpers', () => { diff --git a/apps/website/lib/view-models/SponsorshipDetailViewModel.test.ts b/apps/website/lib/view-models/SponsorshipDetailViewModel.test.ts index 5a5ad7ecc..c51c98bea 100644 --- a/apps/website/lib/view-models/SponsorshipDetailViewModel.test.ts +++ b/apps/website/lib/view-models/SponsorshipDetailViewModel.test.ts @@ -8,6 +8,14 @@ describe('SponsorshipDetailViewModel', () => { leagueName: 'Pro League', seasonId: 'season-1', seasonName: 'Season 1', + tier: 'secondary', + status: 'active', + amount: 0, + currency: 'USD', + type: 'league', + entityName: 'Pro League', + price: 0, + impressions: 0, } as any; it('maps core identifiers from generated DTO', () => { diff --git a/apps/website/lib/view-models/SponsorshipPricingViewModel.test.ts b/apps/website/lib/view-models/SponsorshipPricingViewModel.test.ts index e5580b9a0..f6543be54 100644 --- a/apps/website/lib/view-models/SponsorshipPricingViewModel.test.ts +++ b/apps/website/lib/view-models/SponsorshipPricingViewModel.test.ts @@ -19,12 +19,12 @@ describe('SponsorshipPricingViewModel', () => { it('exposes formatted prices and price difference', () => { const vm = new SponsorshipPricingViewModel(dto); - expect(vm.formattedMainSlotPrice).toBe(`${dto.currency} ${dto.mainSlotPrice.toLocaleString()}`); - expect(vm.formattedSecondarySlotPrice).toBe(`${dto.currency} ${dto.secondarySlotPrice.toLocaleString()}`); + expect(vm.formattedMainSlotPrice).toBe('$10,000.00'); + expect(vm.formattedSecondarySlotPrice).toBe('$6,000.00'); const expectedDiff = dto.mainSlotPrice - dto.secondarySlotPrice; expect(vm.priceDifference).toBe(expectedDiff); - expect(vm.formattedPriceDifference).toBe(`${dto.currency} ${expectedDiff.toLocaleString()}`); + expect(vm.formattedPriceDifference).toBe('$4,000.00'); }); it('computes discount percentage for secondary slots', () => { diff --git a/apps/website/lib/view-models/SponsorshipViewModel.test.ts b/apps/website/lib/view-models/SponsorshipViewModel.test.ts index a9fd7514d..133d2e61a 100644 --- a/apps/website/lib/view-models/SponsorshipViewModel.test.ts +++ b/apps/website/lib/view-models/SponsorshipViewModel.test.ts @@ -50,7 +50,7 @@ describe('SponsorshipViewModel', () => { const vm = new SponsorshipViewModel(baseData); expect(vm.formattedImpressions).toBe(baseData.impressions.toLocaleString()); - expect(vm.formattedPrice).toBe(`$${baseData.price}`); + expect(vm.formattedPrice).toBe('$5,000.00'); }); it('computes daysRemaining and expiringSoon based on endDate', () => { diff --git a/apps/website/lib/view-models/StandingEntryViewModel.test.ts b/apps/website/lib/view-models/StandingEntryViewModel.test.ts index 7fb07a093..957bdcbea 100644 --- a/apps/website/lib/view-models/StandingEntryViewModel.test.ts +++ b/apps/website/lib/view-models/StandingEntryViewModel.test.ts @@ -5,17 +5,32 @@ import { StandingEntryViewModel } from './StandingEntryViewModel'; describe('StandingEntryViewModel', () => { const createMockStanding = (overrides?: Partial): LeagueStandingDTO => ({ driverId: 'driver-1', + driver: { + id: 'driver-1', + iracingId: '12345', + name: 'Test Driver', + country: 'US', + joinedAt: '2025-01-01T00:00:00Z', + }, position: 1, points: 100, wins: 3, podiums: 5, races: 8, + positionChange: 0, + lastRacePoints: 0, + droppedRaceIds: [], ...overrides, }); it('should create instance with all properties', () => { const dto = createMockStanding(); - const viewModel = new StandingEntryViewModel(dto, 100, 85, 'driver-1'); + const viewModel = new StandingEntryViewModel({ + ...dto, + leaderPoints: 100, + nextPoints: 85, + currentUserId: 'driver-1', + }); expect(viewModel.driverId).toBe('driver-1'); expect(viewModel.position).toBe(1); @@ -26,159 +41,159 @@ describe('StandingEntryViewModel', () => { }); it('should return position as badge string', () => { - const viewModel = new StandingEntryViewModel( - createMockStanding({ position: 5 }), - 100, - 85, - 'driver-1' - ); + const viewModel = new StandingEntryViewModel({ + ...createMockStanding({ position: 5 }), + leaderPoints: 100, + nextPoints: 85, + currentUserId: 'driver-1', + }); - expect(viewModel.positionBadge).toBe('5'); + expect(viewModel.positionBadge).toBe('P5'); }); it('should calculate points gap to leader correctly', () => { - const viewModel = new StandingEntryViewModel( - createMockStanding({ position: 2, points: 85 }), - 100, // leader points - 70, // next points - 'driver-2' - ); + const viewModel = new StandingEntryViewModel({ + ...createMockStanding({ position: 2, points: 85 }), + leaderPoints: 100, // leader points + nextPoints: 70, // next points + currentUserId: 'driver-2', + }); expect(viewModel.pointsGapToLeader).toBe(-15); }); it('should show zero gap when driver is leader', () => { - const viewModel = new StandingEntryViewModel( - createMockStanding({ position: 1, points: 100 }), - 100, // leader points - 85, // next points - 'driver-1' - ); + const viewModel = new StandingEntryViewModel({ + ...createMockStanding({ position: 1, points: 100 }), + leaderPoints: 100, // leader points + nextPoints: 85, // next points + currentUserId: 'driver-1', + }); expect(viewModel.pointsGapToLeader).toBe(0); }); it('should calculate points gap to next position correctly', () => { - const viewModel = new StandingEntryViewModel( - createMockStanding({ position: 2, points: 85 }), - 100, // leader points - 70, // next points - 'driver-2' - ); + const viewModel = new StandingEntryViewModel({ + ...createMockStanding({ position: 2, points: 85 }), + leaderPoints: 100, // leader points + nextPoints: 70, // next points + currentUserId: 'driver-2', + }); expect(viewModel.pointsGapToNext).toBe(15); }); it('should identify current user correctly', () => { - const viewModel1 = new StandingEntryViewModel( - createMockStanding({ driverId: 'driver-1' }), - 100, - 85, - 'driver-1' - ); + const viewModel1 = new StandingEntryViewModel({ + ...createMockStanding({ driverId: 'driver-1' }), + leaderPoints: 100, + nextPoints: 85, + currentUserId: 'driver-1', + }); - const viewModel2 = new StandingEntryViewModel( - createMockStanding({ driverId: 'driver-1' }), - 100, - 85, - 'driver-2' - ); + const viewModel2 = new StandingEntryViewModel({ + ...createMockStanding({ driverId: 'driver-1' }), + leaderPoints: 100, + nextPoints: 85, + currentUserId: 'driver-2', + }); expect(viewModel1.isCurrentUser).toBe(true); expect(viewModel2.isCurrentUser).toBe(false); }); it('should return "same" trend when no previous position', () => { - const viewModel = new StandingEntryViewModel( - createMockStanding({ position: 1 }), - 100, - 85, - 'driver-1' - ); + const viewModel = new StandingEntryViewModel({ + ...createMockStanding({ position: 1 }), + leaderPoints: 100, + nextPoints: 85, + currentUserId: 'driver-1', + }); expect(viewModel.trend).toBe('same'); }); it('should return "up" trend when position improved', () => { - const viewModel = new StandingEntryViewModel( - createMockStanding({ position: 1 }), - 100, - 85, - 'driver-1', - 3 // previous position was 3rd - ); + const viewModel = new StandingEntryViewModel({ + ...createMockStanding({ position: 1 }), + leaderPoints: 100, + nextPoints: 85, + currentUserId: 'driver-1', + previousPosition: 3, // previous position was 3rd + }); expect(viewModel.trend).toBe('up'); }); it('should return "down" trend when position worsened', () => { - const viewModel = new StandingEntryViewModel( - createMockStanding({ position: 5 }), - 100, - 85, - 'driver-1', - 2 // previous position was 2nd - ); + const viewModel = new StandingEntryViewModel({ + ...createMockStanding({ position: 5 }), + leaderPoints: 100, + nextPoints: 85, + currentUserId: 'driver-1', + previousPosition: 2, // previous position was 2nd + }); expect(viewModel.trend).toBe('down'); }); it('should return "same" trend when position unchanged', () => { - const viewModel = new StandingEntryViewModel( - createMockStanding({ position: 3 }), - 100, - 85, - 'driver-1', - 3 // same position - ); + const viewModel = new StandingEntryViewModel({ + ...createMockStanding({ position: 3 }), + leaderPoints: 100, + nextPoints: 85, + currentUserId: 'driver-1', + previousPosition: 3, // same position + }); expect(viewModel.trend).toBe('same'); }); it('should return correct trend arrow for up', () => { - const viewModel = new StandingEntryViewModel( - createMockStanding({ position: 1 }), - 100, - 85, - 'driver-1', - 3 - ); + const viewModel = new StandingEntryViewModel({ + ...createMockStanding({ position: 1 }), + leaderPoints: 100, + nextPoints: 85, + currentUserId: 'driver-1', + previousPosition: 3, + }); expect(viewModel.trendArrow).toBe('↑'); }); it('should return correct trend arrow for down', () => { - const viewModel = new StandingEntryViewModel( - createMockStanding({ position: 5 }), - 100, - 85, - 'driver-1', - 2 - ); + const viewModel = new StandingEntryViewModel({ + ...createMockStanding({ position: 5 }), + leaderPoints: 100, + nextPoints: 85, + currentUserId: 'driver-1', + previousPosition: 2, + }); expect(viewModel.trendArrow).toBe('↓'); }); it('should return correct trend arrow for same', () => { - const viewModel = new StandingEntryViewModel( - createMockStanding({ position: 3 }), - 100, - 85, - 'driver-1' - ); + const viewModel = new StandingEntryViewModel({ + ...createMockStanding({ position: 3 }), + leaderPoints: 100, + nextPoints: 85, + currentUserId: 'driver-1', + }); expect(viewModel.trendArrow).toBe('-'); }); it('should handle edge case of last place with no one behind', () => { - const viewModel = new StandingEntryViewModel( - createMockStanding({ position: 10, points: 20 }), - 100, // leader points - 20, // same points (last place) - 'driver-10' - ); + const viewModel = new StandingEntryViewModel({ + ...createMockStanding({ position: 10, points: 20 }), + leaderPoints: 100, // leader points + nextPoints: 20, // same points (last place) + currentUserId: 'driver-10', + }); expect(viewModel.pointsGapToNext).toBe(0); expect(viewModel.pointsGapToLeader).toBe(-80); }); -}); \ No newline at end of file +}); diff --git a/apps/website/lib/view-models/TeamJoinRequestViewModel.test.ts b/apps/website/lib/view-models/TeamJoinRequestViewModel.test.ts index d1ca8d2ae..63d02fe50 100644 --- a/apps/website/lib/view-models/TeamJoinRequestViewModel.test.ts +++ b/apps/website/lib/view-models/TeamJoinRequestViewModel.test.ts @@ -1,4 +1,4 @@ -import type { TeamJoinRequestDTO } from '@/lib/types/generated/TeamJoinRequestDTO'; +import type { TeamJoinRequestDTO } from '../types/generated/TeamJoinRequestDTO'; import { describe, expect, it } from 'vitest'; import { TeamJoinRequestViewModel } from './TeamJoinRequestViewModel'; @@ -17,7 +17,11 @@ describe('TeamJoinRequestViewModel', () => { it('maps fields from DTO', () => { const dto = createTeamJoinRequestDto({ requestId: 'req-123', driverId: 'driver-123' }); - const vm = new TeamJoinRequestViewModel(dto, 'current-user', true); + const vm = new TeamJoinRequestViewModel({ + ...dto, + currentUserId: 'current-user', + isOwner: true, + }); expect(vm.id).toBe('req-123'); expect(vm.teamId).toBe('team-1'); @@ -28,8 +32,16 @@ describe('TeamJoinRequestViewModel', () => { it('allows approval only for owners', () => { const dto = createTeamJoinRequestDto(); - const ownerVm = new TeamJoinRequestViewModel(dto, 'owner-user', true); - const nonOwnerVm = new TeamJoinRequestViewModel(dto, 'regular-user', false); + const ownerVm = new TeamJoinRequestViewModel({ + ...dto, + currentUserId: 'owner-user', + isOwner: true, + }); + const nonOwnerVm = new TeamJoinRequestViewModel({ + ...dto, + currentUserId: 'regular-user', + isOwner: false, + }); expect(ownerVm.canApprove).toBe(true); expect(nonOwnerVm.canApprove).toBe(false); @@ -37,7 +49,11 @@ describe('TeamJoinRequestViewModel', () => { it('exposes a pending status with yellow color', () => { const dto = createTeamJoinRequestDto({ status: 'pending' }); - const vm = new TeamJoinRequestViewModel(dto, 'owner-user', true); + const vm = new TeamJoinRequestViewModel({ + ...dto, + currentUserId: 'owner-user', + isOwner: true, + }); expect(vm.status).toBe('Pending'); expect(vm.statusColor).toBe('yellow'); @@ -45,7 +61,11 @@ describe('TeamJoinRequestViewModel', () => { it('provides approve and reject button labels', () => { const dto = createTeamJoinRequestDto(); - const vm = new TeamJoinRequestViewModel(dto, 'owner-user', true); + const vm = new TeamJoinRequestViewModel({ + ...dto, + currentUserId: 'owner-user', + isOwner: true, + }); expect(vm.approveButtonText).toBe('Approve'); expect(vm.rejectButtonText).toBe('Reject'); @@ -53,7 +73,11 @@ describe('TeamJoinRequestViewModel', () => { it('formats requestedAt as localized date-time', () => { const dto = createTeamJoinRequestDto({ requestedAt: '2024-01-01T12:00:00Z' }); - const vm = new TeamJoinRequestViewModel(dto, 'owner-user', true); + const vm = new TeamJoinRequestViewModel({ + ...dto, + currentUserId: 'owner-user', + isOwner: true, + }); const formatted = vm.formattedRequestedAt; diff --git a/apps/website/lib/view-models/TeamMemberViewModel.test.ts b/apps/website/lib/view-models/TeamMemberViewModel.test.ts index adf5cefc1..033f56de5 100644 --- a/apps/website/lib/view-models/TeamMemberViewModel.test.ts +++ b/apps/website/lib/view-models/TeamMemberViewModel.test.ts @@ -1,4 +1,4 @@ -import type { TeamMemberDTO } from '@/lib/types/generated/GetTeamMembersOutputDTO'; +import type { TeamMemberDTO } from '../types/generated/TeamMemberDTO'; import { describe, expect, it } from 'vitest'; import { TeamMemberViewModel } from './TeamMemberViewModel'; @@ -16,7 +16,11 @@ describe('TeamMemberViewModel', () => { it('maps fields from DTO', () => { const dto = createTeamMemberDto({ driverId: 'driver-123', driverName: 'Driver 123', role: 'owner' }); - const vm = new TeamMemberViewModel(dto, 'current-user', 'owner-1'); + const vm = new TeamMemberViewModel({ + ...dto, + currentUserId: 'current-user', + teamOwnerId: 'owner-1', + }); expect(vm.driverId).toBe('driver-123'); expect(vm.driverName).toBe('Driver 123'); @@ -27,9 +31,21 @@ describe('TeamMemberViewModel', () => { }); it('derives roleBadgeVariant based on role', () => { - const ownerVm = new TeamMemberViewModel(createTeamMemberDto({ role: 'owner' }), 'current-user', 'owner-1'); - const managerVm = new TeamMemberViewModel(createTeamMemberDto({ role: 'manager' }), 'current-user', 'owner-1'); - const memberVm = new TeamMemberViewModel(createTeamMemberDto({ role: 'member' }), 'current-user', 'owner-1'); + const ownerVm = new TeamMemberViewModel({ + ...createTeamMemberDto({ role: 'owner' }), + currentUserId: 'current-user', + teamOwnerId: 'owner-1', + }); + const managerVm = new TeamMemberViewModel({ + ...createTeamMemberDto({ role: 'manager' }), + currentUserId: 'current-user', + teamOwnerId: 'owner-1', + }); + const memberVm = new TeamMemberViewModel({ + ...createTeamMemberDto({ role: 'member' }), + currentUserId: 'current-user', + teamOwnerId: 'owner-1', + }); expect(ownerVm.roleBadgeVariant).toBe('primary'); expect(managerVm.roleBadgeVariant).toBe('secondary'); @@ -39,8 +55,16 @@ describe('TeamMemberViewModel', () => { it('identifies owner correctly based on teamOwnerId', () => { const dto = createTeamMemberDto({ driverId: 'owner-1', role: 'owner' }); - const ownerVm = new TeamMemberViewModel(dto, 'some-user', 'owner-1'); - const nonOwnerVm = new TeamMemberViewModel(dto, 'some-user', 'another-owner'); + const ownerVm = new TeamMemberViewModel({ + ...dto, + currentUserId: 'some-user', + teamOwnerId: 'owner-1', + }); + const nonOwnerVm = new TeamMemberViewModel({ + ...dto, + currentUserId: 'some-user', + teamOwnerId: 'another-owner', + }); expect(ownerVm.isOwner).toBe(true); expect(nonOwnerVm.isOwner).toBe(false); @@ -49,9 +73,21 @@ describe('TeamMemberViewModel', () => { it('determines canManage only for team owner and non-self members', () => { const memberDto = createTeamMemberDto({ driverId: 'member-1' }); - const ownerManagingMember = new TeamMemberViewModel(memberDto, 'owner-1', 'owner-1'); - const ownerSelf = new TeamMemberViewModel(createTeamMemberDto({ driverId: 'owner-1' }), 'owner-1', 'owner-1'); - const nonOwner = new TeamMemberViewModel(memberDto, 'another-user', 'owner-1'); + const ownerManagingMember = new TeamMemberViewModel({ + ...memberDto, + currentUserId: 'owner-1', + teamOwnerId: 'owner-1', + }); + const ownerSelf = new TeamMemberViewModel({ + ...createTeamMemberDto({ driverId: 'owner-1' }), + currentUserId: 'owner-1', + teamOwnerId: 'owner-1', + }); + const nonOwner = new TeamMemberViewModel({ + ...memberDto, + currentUserId: 'another-user', + teamOwnerId: 'owner-1', + }); expect(ownerManagingMember.canManage).toBe(true); expect(ownerSelf.canManage).toBe(false); @@ -61,14 +97,22 @@ describe('TeamMemberViewModel', () => { it('identifies current user correctly', () => { const dto = createTeamMemberDto({ driverId: 'current-user' }); - const vm = new TeamMemberViewModel(dto, 'current-user', 'owner-1'); + const vm = new TeamMemberViewModel({ + ...dto, + currentUserId: 'current-user', + teamOwnerId: 'owner-1', + }); expect(vm.isCurrentUser).toBe(true); }); it('formats joinedAt as a localized date string', () => { const dto = createTeamMemberDto({ joinedAt: '2024-01-01T00:00:00Z' }); - const vm = new TeamMemberViewModel(dto, 'current-user', 'owner-1'); + const vm = new TeamMemberViewModel({ + ...dto, + currentUserId: 'current-user', + teamOwnerId: 'owner-1', + }); const formatted = vm.formattedJoinedAt; diff --git a/apps/website/lib/view-models/WalletViewModel.test.ts b/apps/website/lib/view-models/WalletViewModel.test.ts index 247eaa715..fa6c8823e 100644 --- a/apps/website/lib/view-models/WalletViewModel.test.ts +++ b/apps/website/lib/view-models/WalletViewModel.test.ts @@ -46,7 +46,7 @@ describe('WalletViewModel', () => { it('formats balance with currency and 2 decimals', () => { const vm = new WalletViewModel(createWalletDto({ balance: 250, currency: 'USD' })); - expect(vm.formattedBalance).toBe('USD 250.00'); + expect(vm.formattedBalance).toBe('$250.00'); }); it('derives balanceColor based on sign of balance', () => { diff --git a/apps/website/lib/view-models/index.ts b/apps/website/lib/view-models/index.ts index d0c7051ab..6919798f4 100644 --- a/apps/website/lib/view-models/index.ts +++ b/apps/website/lib/view-models/index.ts @@ -31,6 +31,7 @@ export * from "./LeagueJoinRequestViewModel"; export * from "./LeagueMembershipsViewModel"; export * from "./LeagueMemberViewModel"; export * from "./LeaguePageDetailViewModel"; +export * from "./LeagueScheduleRaceViewModel"; export * from "./LeagueScheduleViewModel"; export * from "./LeagueScoringChampionshipViewModel"; export * from "./LeagueScoringConfigViewModel";