From 3a4f460a7d0aa5f73aa8210473547a70c249a79e Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Mon, 26 Jan 2026 17:47:37 +0100 Subject: [PATCH] code quality --- apps/website/components/dev/DevToolbar.tsx | 2 +- .../errors/ErrorAnalyticsDashboard.tsx | 9 +++++++-- .../leagues/LeagueOwnershipTransfer.tsx | 18 ++++++++--------- .../components/leagues/LeagueSlider.tsx | 6 +++--- apps/website/hooks/driver/useDriverProfile.ts | 19 ++++++------------ apps/website/hooks/league/useLeagueDetail.ts | 5 +++++ .../lib/gateways/api/races/RacesApiClient.ts | 1 + .../leagues/ProtestReviewMutation.ts | 12 ++++++++++- .../lib/services/payments/PaymentService.ts | 10 +++++++++- .../lib/services/payments/WalletService.ts | 10 +++++++++- .../lib/services/races/RaceResultsService.ts | 20 ++++++++++++++++--- .../services/sponsors/SponsorshipService.ts | 13 +++++++++++- apps/website/lib/view-data/LeaguesViewData.ts | 2 +- apps/website/lib/view-data/ProfileViewData.ts | 4 ++-- .../lib/view-data/TeamDetailViewData.ts | 6 +++++- .../lib/view-data/TeamSummaryViewData.ts | 20 +++++++++---------- .../lib/view-models/DriverProfileViewModel.ts | 2 +- .../lib/view-models/DriverSummaryViewModel.ts | 6 +++--- .../view-models/LeagueScheduleViewModel.ts | 2 ++ .../ScoringConfigurationViewModel.ts | 2 ++ .../lib/view-models/TeamSummaryViewModel.ts | 10 +++++----- 21 files changed, 121 insertions(+), 58 deletions(-) diff --git a/apps/website/components/dev/DevToolbar.tsx b/apps/website/components/dev/DevToolbar.tsx index 77c351b5a..19b555442 100644 --- a/apps/website/components/dev/DevToolbar.tsx +++ b/apps/website/components/dev/DevToolbar.tsx @@ -6,7 +6,7 @@ import { useEffectiveDriverId } from "@/hooks/useEffectiveDriverId"; import { ApiConnectionMonitor } from '@/lib/gateways/api/base/ApiConnectionMonitor'; import { CircuitBreakerRegistry } from '@/lib/gateways/api/base/RetryHandler'; import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler'; -import { ChevronUp, Wrench, X } from 'lucide-react'; +import { ChevronDown, ChevronUp, Wrench, X } from 'lucide-react'; import { useEffect, useState } from 'react'; // Import our new components diff --git a/apps/website/components/errors/ErrorAnalyticsDashboard.tsx b/apps/website/components/errors/ErrorAnalyticsDashboard.tsx index 2cf7307b9..321da8349 100644 --- a/apps/website/components/errors/ErrorAnalyticsDashboard.tsx +++ b/apps/website/components/errors/ErrorAnalyticsDashboard.tsx @@ -64,13 +64,18 @@ interface NavigatorWithConnection extends Navigator { }; } +interface ErrorAnalyticsDashboardProps { + refreshInterval?: number; + showInProduction?: boolean; +} + /** * Comprehensive Error Analytics Dashboard * Shows real-time error statistics, API metrics, and environment details */ -export function ErrorAnalyticsDashboard({ +export function ErrorAnalyticsDashboard({ refreshInterval = 5000, - showInProduction = false + showInProduction = false }: ErrorAnalyticsDashboardProps) { const [stats, setStats] = useState(null); const [isExpanded, setIsExpanded] = useState(true); diff --git a/apps/website/components/leagues/LeagueOwnershipTransfer.tsx b/apps/website/components/leagues/LeagueOwnershipTransfer.tsx index f2fba5f57..196acf463 100644 --- a/apps/website/components/leagues/LeagueOwnershipTransfer.tsx +++ b/apps/website/components/leagues/LeagueOwnershipTransfer.tsx @@ -54,13 +54,13 @@ export function LeagueOwnershipTransfer({ {ownerSummary ? ( ({ - value: member.driver.id, - label: member.driver.name, + value: member.id, + label: member.name, })), ]} /> diff --git a/apps/website/components/leagues/LeagueSlider.tsx b/apps/website/components/leagues/LeagueSlider.tsx index 1d7385927..2b3a22781 100644 --- a/apps/website/components/leagues/LeagueSlider.tsx +++ b/apps/website/components/leagues/LeagueSlider.tsx @@ -1,7 +1,7 @@ 'use client'; import { LeagueCard } from '@/components/leagues/LeagueCardWrapper'; -import { LeagueSummaryViewModelBuilder } from '@/lib/builders/view-models/LeagueSummaryViewModelBuilder'; +import { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel'; import { routes } from '@/lib/routing/RouteConfig'; import type { LeaguesViewData } from '@/lib/view-data/LeaguesViewData'; import { Button } from '@/ui/Button'; @@ -129,8 +129,8 @@ export function LeagueSlider({ hideScrollbar > {leagues.map((league) => { - const viewModel = LeagueSummaryViewModelBuilder.build(league); - + const viewModel = new LeagueSummaryViewModel(league); + return ( diff --git a/apps/website/hooks/driver/useDriverProfile.ts b/apps/website/hooks/driver/useDriverProfile.ts index 4126f4abe..f612a9428 100644 --- a/apps/website/hooks/driver/useDriverProfile.ts +++ b/apps/website/hooks/driver/useDriverProfile.ts @@ -2,6 +2,7 @@ import { useInject } from '@/lib/di/hooks/useInject'; import { enhanceQueryResult } from '@/lib/di/hooks/useReactQueryWithApiError'; import { DRIVER_SERVICE_TOKEN } from '@/lib/di/tokens'; import { ApiError } from '@/lib/gateways/api/base/ApiError'; +import type { ProfileViewData } from '@/lib/view-data/ProfileViewData'; import { DriverProfileViewModel, type DriverProfileViewModelData } from '@/lib/view-models/DriverProfileViewModel'; import { useQuery, UseQueryOptions } from '@tanstack/react-query'; @@ -25,13 +26,13 @@ export function useDriverProfile( driver: dto.currentDriver ? { id: dto.currentDriver.id, name: dto.currentDriver.name, - countryCode: dto.currentDriver.countryCode || '', - countryFlag: dto.currentDriver.countryFlag || '', + countryCode: dto.currentDriver.country || '', + countryFlag: '', avatarUrl: dto.currentDriver.avatarUrl || '', bio: dto.currentDriver.bio || null, iracingId: dto.currentDriver.iracingId || null, joinedAtLabel: dto.currentDriver.joinedAt || '', - globalRankLabel: dto.currentDriver.globalRank || '', + globalRankLabel: dto.currentDriver.globalRank?.toString() || '', } : { id: '', name: '', @@ -44,8 +45,8 @@ export function useDriverProfile( globalRankLabel: '', }, stats: dto.stats ? { - ratingLabel: dto.stats.rating || '', - globalRankLabel: dto.stats.globalRank || '', + ratingLabel: dto.stats.rating?.toString() || '', + globalRankLabel: dto.stats.overallRank?.toString() || '', totalRacesLabel: dto.stats.totalRaces?.toString() || '', winsLabel: dto.stats.wins?.toString() || '', podiumsLabel: dto.stats.podiums?.toString() || '', @@ -85,14 +86,6 @@ export function useDriverProfile( 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); diff --git a/apps/website/hooks/league/useLeagueDetail.ts b/apps/website/hooks/league/useLeagueDetail.ts index 88f773f36..e0792751a 100644 --- a/apps/website/hooks/league/useLeagueDetail.ts +++ b/apps/website/hooks/league/useLeagueDetail.ts @@ -6,6 +6,11 @@ import type { LeagueWithCapacityAndScoringDTO } from '@/lib/types/generated/Leag import { useQuery, UseQueryOptions } from '@tanstack/react-query'; +interface UseLeagueDetailOptions { + leagueId: string; + queryOptions?: UseQueryOptions; +} + interface UseLeagueMembershipsOptions { leagueId: string; queryOptions?: UseQueryOptions; diff --git a/apps/website/lib/gateways/api/races/RacesApiClient.ts b/apps/website/lib/gateways/api/races/RacesApiClient.ts index 105f4e7ba..7182368e1 100644 --- a/apps/website/lib/gateways/api/races/RacesApiClient.ts +++ b/apps/website/lib/gateways/api/races/RacesApiClient.ts @@ -15,6 +15,7 @@ import type { WithdrawFromRaceParamsDTO } from '../../../types/generated/Withdra import { BaseApiClient } from '../base/BaseApiClient'; // Define missing types +export type { RaceDetailDTO }; export type RacesPageDataDTO = { races: RacesPageDataRaceDTO[] }; export type ImportRaceResultsSummaryDTO = { success: boolean; diff --git a/apps/website/lib/mutations/leagues/ProtestReviewMutation.ts b/apps/website/lib/mutations/leagues/ProtestReviewMutation.ts index 1612ce6c9..f8455b35a 100644 --- a/apps/website/lib/mutations/leagues/ProtestReviewMutation.ts +++ b/apps/website/lib/mutations/leagues/ProtestReviewMutation.ts @@ -41,7 +41,17 @@ export class ProtestReviewMutation implements Mutation> { try { - const result = await this.service.applyPenalty(input); + const dto = { + raceId: input.raceId, + driverId: input.accusedDriverId, + stewardId: 'system', // Missing in command + type: input.penaltyType, + value: input.penaltyValue, + reason: input.reason, + protestId: input.protestId, + notes: input.stewardNotes + }; + const result = await this.service.applyPenalty(dto); if (result.isErr()) { return Result.err(result.getError()); } diff --git a/apps/website/lib/services/payments/PaymentService.ts b/apps/website/lib/services/payments/PaymentService.ts index 9303305e3..047662ac8 100644 --- a/apps/website/lib/services/payments/PaymentService.ts +++ b/apps/website/lib/services/payments/PaymentService.ts @@ -60,6 +60,14 @@ export class PaymentService implements Service { async getWallet(leagueId: string): Promise { const data = await this.apiClient.getWallet({ leagueId }); - return new WalletViewModel({ ...data.wallet, transactions: data.transactions }); + const transactions = data.transactions.map(t => ({ + ...t, + type: t.type as any, + fee: 0, + netAmount: t.amount, + date: t.createdAt, + status: 'completed' as const + })); + return new WalletViewModel({ ...data.wallet, transactions }); } } diff --git a/apps/website/lib/services/payments/WalletService.ts b/apps/website/lib/services/payments/WalletService.ts index abc2a9f78..31da31e6f 100644 --- a/apps/website/lib/services/payments/WalletService.ts +++ b/apps/website/lib/services/payments/WalletService.ts @@ -23,6 +23,14 @@ export class WalletService implements Service { async getWallet(leagueId: string): Promise { const data = await this.apiClient.getWallet({ leagueId }); - return new WalletViewModel({ ...data.wallet, transactions: data.transactions }); + const transactions = data.transactions.map(t => ({ + ...t, + type: t.type as any, + fee: 0, // DTO missing fee + netAmount: t.amount, // DTO missing netAmount + date: t.createdAt, // Map createdAt to date + status: 'completed' as const // DTO missing status + })); + return new WalletViewModel({ ...data.wallet, transactions }); } } diff --git a/apps/website/lib/services/races/RaceResultsService.ts b/apps/website/lib/services/races/RaceResultsService.ts index f04cdd59a..9e585c7ef 100644 --- a/apps/website/lib/services/races/RaceResultsService.ts +++ b/apps/website/lib/services/races/RaceResultsService.ts @@ -5,8 +5,10 @@ import { ApiError } from '@/lib/gateways/api/base/ApiError'; import { RacesApiClient } from '@/lib/gateways/api/races/RacesApiClient'; import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter'; import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger'; +import type { ImportRaceResultsSummaryViewData } from '@/lib/view-data/ImportRaceResultsSummaryViewData'; import { ImportRaceResultsSummaryViewModel } from '@/lib/view-models/ImportRaceResultsSummaryViewModel'; import { RaceResultsDetailViewModel } from '@/lib/view-models/RaceResultsDetailViewModel'; +import type { RaceWithSOFViewData } from '@/lib/view-data/RaceWithSOFViewData'; import { RaceWithSOFViewModel } from '@/lib/view-models/RaceWithSOFViewModel'; import { injectable, unmanaged } from 'inversify'; @@ -46,14 +48,21 @@ export class RaceResultsService implements Service { async importResults(raceId: string, input: any): Promise { const res = await this.apiClient.importResults(raceId, input); - return new ImportRaceResultsSummaryViewModel(res); + const viewData: ImportRaceResultsSummaryViewData = { + success: res.success, + raceId: res.raceId, + driversProcessed: res.driversProcessed, + resultsRecorded: res.resultsRecorded, + errors: res.errors || [], + }; + return new ImportRaceResultsSummaryViewModel(viewData); } /** * Get race results detail * Returns results for a specific race */ - async getRaceResultsDetail(raceId: string): Promise> { + async getRaceResultsDetail(raceId: string): Promise> { try { const data = await this.apiClient.getResultsDetail(raceId); return Result.ok(data); @@ -78,7 +87,12 @@ export class RaceResultsService implements Service { async getWithSOF(raceId: string): Promise { try { const data = await this.apiClient.getWithSOF(raceId); - return new RaceWithSOFViewModel(data); + const viewData: RaceWithSOFViewData = { + id: data.id, + track: data.track, + strengthOfField: data.strengthOfField ?? null, + }; + return new RaceWithSOFViewModel(viewData); } catch (error: unknown) { throw error; } diff --git a/apps/website/lib/services/sponsors/SponsorshipService.ts b/apps/website/lib/services/sponsors/SponsorshipService.ts index 0aea40427..27b02dae2 100644 --- a/apps/website/lib/services/sponsors/SponsorshipService.ts +++ b/apps/website/lib/services/sponsors/SponsorshipService.ts @@ -38,6 +38,17 @@ export class SponsorshipService implements Service { async getSponsorSponsorships(sponsorId: string): Promise { const data = await this.apiClient.getSponsorships(sponsorId); if (!data) return null; - return new SponsorSponsorshipsViewModel(data); + + const mappedData = { + ...data, + sponsorships: data.sponsorships.map(s => ({ + ...s, + type: 'league', // DTO missing type + entityName: s.leagueName, // DTO missing entityName + price: s.amount // DTO missing price + })) + }; + + return new SponsorSponsorshipsViewModel(mappedData as any); } } diff --git a/apps/website/lib/view-data/LeaguesViewData.ts b/apps/website/lib/view-data/LeaguesViewData.ts index 0c14b6ced..0590394b7 100644 --- a/apps/website/lib/view-data/LeaguesViewData.ts +++ b/apps/website/lib/view-data/LeaguesViewData.ts @@ -29,7 +29,7 @@ export interface LeaguesViewData extends ViewData { scoring: { gameId: string; gameName: string; - primaryChampionshipType: string; + primaryChampionshipType: 'driver' | 'team' | 'nations' | 'trophy'; scoringPresetId: string; scoringPresetName: string; dropPolicySummary: string; diff --git a/apps/website/lib/view-data/ProfileViewData.ts b/apps/website/lib/view-data/ProfileViewData.ts index 28c12c638..0b28fbb4e 100644 --- a/apps/website/lib/view-data/ProfileViewData.ts +++ b/apps/website/lib/view-data/ProfileViewData.ts @@ -55,13 +55,13 @@ export interface ProfileViewData extends ViewData { icon: 'trophy' | 'medal' | 'star' | 'crown' | 'target' | 'zap'; rarityLabel: string; }>; - friends: Array<{ + friends?: Array<{ id: string; name: string; countryFlag: string; avatarUrl: string; href: string; }>; - friendsCountLabel: string; + friendsCountLabel?: string; } | null; } diff --git a/apps/website/lib/view-data/TeamDetailViewData.ts b/apps/website/lib/view-data/TeamDetailViewData.ts index 62bfabe89..9151cd834 100644 --- a/apps/website/lib/view-data/TeamDetailViewData.ts +++ b/apps/website/lib/view-data/TeamDetailViewData.ts @@ -24,7 +24,11 @@ export interface TeamDetailData { region?: string; languages?: string[] | null; category?: string; - membership?: string | null; + membership?: { + role: string; + joinedAt: string; + isActive: boolean; + } | null; canManage: boolean; } diff --git a/apps/website/lib/view-data/TeamSummaryViewData.ts b/apps/website/lib/view-data/TeamSummaryViewData.ts index ad2837272..6468ffde9 100644 --- a/apps/website/lib/view-data/TeamSummaryViewData.ts +++ b/apps/website/lib/view-data/TeamSummaryViewData.ts @@ -4,15 +4,15 @@ export interface TeamSummaryViewData { tag: string; memberCount: number; description?: string; - totalWins: number; - totalRaces: number; - performanceLevel: 'beginner' | 'intermediate' | 'advanced' | 'pro'; + totalWins?: number; + totalRaces?: number; + performanceLevel?: string; isRecruiting: boolean; - specialization: 'endurance' | 'sprint' | 'mixed' | undefined; - region: string | undefined; - languages: string[]; - leagues: string[]; - logoUrl: string | undefined; - rating: number | undefined; - category: string | undefined; + specialization?: string; + region?: string; + languages?: string[]; + leagues?: string[]; + logoUrl?: string; + rating?: number; + category?: string; } diff --git a/apps/website/lib/view-models/DriverProfileViewModel.ts b/apps/website/lib/view-models/DriverProfileViewModel.ts index e8ef9abb4..78666056c 100644 --- a/apps/website/lib/view-models/DriverProfileViewModel.ts +++ b/apps/website/lib/view-models/DriverProfileViewModel.ts @@ -2,7 +2,7 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; import { ProfileViewData } from "../view-data/ProfileViewData"; import { DriverProfileDriverSummaryViewModel } from "./DriverProfileDriverSummaryViewModel"; -export { DriverProfileDriverSummaryViewModel as DriverProfileSocialSummaryViewModel }; +export { DriverProfileDriverSummaryViewModel }; export interface DriverProfileStatsViewModel extends ViewModel { totalRaces: number; diff --git a/apps/website/lib/view-models/DriverSummaryViewModel.ts b/apps/website/lib/view-models/DriverSummaryViewModel.ts index 8b657490e..26a79d8a3 100644 --- a/apps/website/lib/view-models/DriverSummaryViewModel.ts +++ b/apps/website/lib/view-models/DriverSummaryViewModel.ts @@ -9,16 +9,16 @@ import type { DriverSummaryData } from "../view-data/DriverSummaryData"; * Client-only UI helper built from ViewData. */ export class DriverSummaryViewModel extends ViewModel { - constructor(private readonly viewData: DriverSummaryData) { + constructor(private readonly viewData: any) { super(); } get id(): string { - return this.viewData.driverId; + return this.viewData.driverId || this.viewData.id; } get name(): string { - return this.viewData.driverName; + return this.viewData.driverName || this.viewData.name; } get avatarUrl(): string | null { diff --git a/apps/website/lib/view-models/LeagueScheduleViewModel.ts b/apps/website/lib/view-models/LeagueScheduleViewModel.ts index 406739e7d..3b83ad481 100644 --- a/apps/website/lib/view-models/LeagueScheduleViewModel.ts +++ b/apps/website/lib/view-models/LeagueScheduleViewModel.ts @@ -2,6 +2,8 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; import type { LeagueScheduleViewData } from "../view-data/LeagueScheduleViewData"; import { LeagueScheduleRaceViewModel } from "./LeagueScheduleRaceViewModel"; +export { LeagueScheduleRaceViewModel }; + export class LeagueScheduleViewModel extends ViewModel { readonly races: LeagueScheduleRaceViewModel[]; diff --git a/apps/website/lib/view-models/ScoringConfigurationViewModel.ts b/apps/website/lib/view-models/ScoringConfigurationViewModel.ts index 6110aa60f..b5bbcb761 100644 --- a/apps/website/lib/view-models/ScoringConfigurationViewModel.ts +++ b/apps/website/lib/view-models/ScoringConfigurationViewModel.ts @@ -1,5 +1,7 @@ import { ViewModel } from "../contracts/view-models/ViewModel"; import type { CustomPointsConfig, ScoringConfigurationViewData } from "../view-data/ScoringConfigurationViewData"; + +export type { CustomPointsConfig }; import { LeagueScoringPresetViewModel } from './LeagueScoringPresetViewModel'; export class ScoringConfigurationViewModel extends ViewModel { diff --git a/apps/website/lib/view-models/TeamSummaryViewModel.ts b/apps/website/lib/view-models/TeamSummaryViewModel.ts index c9962dc6c..6fec19f23 100644 --- a/apps/website/lib/view-models/TeamSummaryViewModel.ts +++ b/apps/website/lib/view-models/TeamSummaryViewModel.ts @@ -15,14 +15,14 @@ export class TeamSummaryViewModel extends ViewModel { get tag(): string { return this.data.tag; } get memberCount(): number { return this.data.memberCount; } get description(): string | undefined { return this.data.description; } - get totalWins(): number { return this.data.totalWins; } - get totalRaces(): number { return this.data.totalRaces; } - get performanceLevel(): string { return this.data.performanceLevel; } + get totalWins(): number { return this.data.totalWins || 0; } + get totalRaces(): number { return this.data.totalRaces || 0; } + get performanceLevel(): string { return this.data.performanceLevel || 'beginner'; } get isRecruiting(): boolean { return this.data.isRecruiting; } get specialization(): string | undefined { return this.data.specialization; } get region(): string | undefined { return this.data.region; } - get languages(): string[] { return this.data.languages; } - get leagues(): string[] { return this.data.leagues; } + get languages(): string[] { return this.data.languages || []; } + get leagues(): string[] { return this.data.leagues || []; } get logoUrl(): string | undefined { return this.data.logoUrl; } get rating(): number | undefined { return this.data.rating; } get category(): string | undefined { return this.data.category; }