code quality

This commit is contained in:
2026-01-26 17:47:37 +01:00
parent 9ac74f5046
commit 3a4f460a7d
21 changed files with 121 additions and 58 deletions

View File

@@ -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

View File

@@ -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<ErrorStats | null>(null);
const [isExpanded, setIsExpanded] = useState(true);

View File

@@ -54,13 +54,13 @@ export function LeagueOwnershipTransfer({
{ownerSummary ? (
<DriverSummaryPill
driver={new DriverViewModel({
id: ownerSummary.driver.id,
name: ownerSummary.driver.name,
avatarUrl: ownerSummary.driver.avatarUrl ?? null,
iracingId: ownerSummary.driver.iracingId,
country: ownerSummary.driver.country,
bio: ownerSummary.driver.bio,
joinedAt: ownerSummary.driver.joinedAt,
id: ownerSummary.id,
name: ownerSummary.name,
avatarUrl: ownerSummary.avatarUrl ?? null,
iracingId: undefined, // Missing in summary
country: '—', // Missing in summary
bio: undefined, // Missing in summary
joinedAt: '—', // Missing in summary
})}
rating={ownerSummary.rating}
rank={ownerSummary.rank}
@@ -98,8 +98,8 @@ export function LeagueOwnershipTransfer({
options={[
{ value: '', label: 'Select new owner...' },
...settings.members.map((member) => ({
value: member.driver.id,
label: member.driver.name,
value: member.id,
label: member.name,
})),
]}
/>

View File

@@ -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 (
<Stack key={league.id} flexShrink={0} w="320px">
<Link href={routes.league.detail(league.id)} block>

View File

@@ -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);

View File

@@ -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<LeagueWithCapacityAndScoringDTO, ApiError>;
}
interface UseLeagueMembershipsOptions {
leagueId: string;
queryOptions?: UseQueryOptions<LeagueMembershipsDTO, ApiError>;

View File

@@ -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;

View File

@@ -41,7 +41,17 @@ export class ProtestReviewMutation implements Mutation<ApplyPenaltyCommand | Req
async applyPenalty(input: ApplyPenaltyCommand): Promise<Result<void, DomainError>> {
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());
}

View File

@@ -60,6 +60,14 @@ export class PaymentService implements Service {
async getWallet(leagueId: string): Promise<WalletViewModel> {
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 });
}
}

View File

@@ -23,6 +23,14 @@ export class WalletService implements Service {
async getWallet(leagueId: string): Promise<WalletViewModel> {
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 });
}
}

View File

@@ -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<any> {
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<Result<unknown, DomainError>> {
async getRaceResultsDetail(raceId: string): Promise<Result<any, DomainError>> {
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<any> {
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;
}

View File

@@ -38,6 +38,17 @@ export class SponsorshipService implements Service {
async getSponsorSponsorships(sponsorId: string): Promise<SponsorSponsorshipsViewModel | null> {
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);
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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[];

View File

@@ -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 {

View File

@@ -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; }