resolve manual DTOs

This commit is contained in:
2025-12-18 22:19:40 +01:00
parent 4a3087ae35
commit d617654928
179 changed files with 3716 additions and 1257 deletions

View File

@@ -173,8 +173,7 @@ interface TeamLeaderboardPreviewProps {
function TeamLeaderboardPreview({ teams, onTeamClick }: TeamLeaderboardPreviewProps) {
const router = useRouter();
const top5 = [...teams]
.filter((t) => t.rating !== null)
.sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0))
.sort((a, b) => b.memberCount - a.memberCount)
.slice(0, 5);
const getMedalColor = (position: number) => {
@@ -257,8 +256,8 @@ function TeamLeaderboardPreview({ teams, onTeamClick }: TeamLeaderboardPreviewPr
{/* Stats */}
<div className="flex items-center gap-4 text-sm">
<div className="text-center">
<p className="text-purple-400 font-mono font-semibold">{team.rating?.toLocaleString()}</p>
<p className="text-[10px] text-gray-500">Rating</p>
<p className="text-purple-400 font-mono font-semibold">{team.memberCount}</p>
<p className="text-[10px] text-gray-500">Members</p>
</div>
<div className="text-center">
<p className="text-performance-green font-mono font-semibold">{team.totalWins}</p>

View File

@@ -7,7 +7,7 @@ import Card from '@/components/ui/Card';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
import { isLeagueAdminOrHigherRole } from '@/lib/leagueRoles';
import { useServices } from '@/lib/services/ServiceProvider';
import type { LeagueConfigFormModel } from '@/lib/types/LeagueConfigFormModel';
import type { LeagueConfigFormModel } from '@core/racing/application';
import { LeagueSettingsViewModel } from '@/lib/view-models/LeagueSettingsViewModel';
import { AlertTriangle, Settings, UserCog } from 'lucide-react';
import { useParams, useRouter } from 'next/navigation';

View File

@@ -4,7 +4,7 @@ import Button from '@/components/ui/Button';
import Card from '@/components/ui/Card';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
import { useServices } from '@/lib/services/ServiceProvider';
import type { LeagueMembership } from '@/lib/types/LeagueMembership';
import type { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
import type { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel';
import Link from 'next/link';
import { useEffect, useState } from 'react';

View File

@@ -1,24 +1,10 @@
import { BaseApiClient } from '../base/BaseApiClient';
import { RecordPageViewOutputDTO } from '../../types/generated/RecordPageViewOutputDTO';
import { RecordEngagementOutputDTO } from '../../types/generated/RecordEngagementOutputDTO';
// TODO: Move these types to apps/website/lib/types/generated when available
type RecordPageViewInputDto = { path: string; userId?: string };
type RecordEngagementInputDto = { eventType: string; userId?: string; metadata?: Record<string, unknown> };
// TODO: Move these types to apps/website/lib/types/generated when available
type AnalyticsDashboardDto = {
totalUsers: number;
activeUsers: number;
totalRaces: number;
totalLeagues: number;
};
type AnalyticsMetricsDto = {
pageViews: number;
uniqueVisitors: number;
averageSessionDuration: number;
bounceRate: number;
};
import { GetDashboardDataOutputDTO } from '../../types/generated/GetDashboardDataOutputDTO';
import { GetAnalyticsMetricsOutputDTO } from '../../types/generated/GetAnalyticsMetricsOutputDTO';
import { RecordPageViewInputDTO } from '../../types/generated/RecordPageViewInputDTO';
import { RecordEngagementInputDTO } from '../../types/generated/RecordEngagementInputDTO';
/**
* Analytics API Client
@@ -27,22 +13,22 @@ type AnalyticsMetricsDto = {
*/
export class AnalyticsApiClient extends BaseApiClient {
/** Record a page view */
recordPageView(input: RecordPageViewInputDto): Promise<RecordPageViewOutputDTO> {
recordPageView(input: RecordPageViewInputDTO): Promise<RecordPageViewOutputDTO> {
return this.post<RecordPageViewOutputDTO>('/analytics/page-view', input);
}
/** Record an engagement event */
recordEngagement(input: RecordEngagementInputDto): Promise<RecordEngagementOutputDTO> {
recordEngagement(input: RecordEngagementInputDTO): Promise<RecordEngagementOutputDTO> {
return this.post<RecordEngagementOutputDTO>('/analytics/engagement', input);
}
/** Get analytics dashboard data */
getDashboardData(): Promise<AnalyticsDashboardDto> {
return this.get<AnalyticsDashboardDto>('/analytics/dashboard');
getDashboardData(): Promise<GetDashboardDataOutputDTO> {
return this.get<GetDashboardDataOutputDTO>('/analytics/dashboard');
}
/** Get analytics metrics */
getAnalyticsMetrics(): Promise<AnalyticsMetricsDto> {
return this.get<AnalyticsMetricsDto>('/analytics/metrics');
getAnalyticsMetrics(): Promise<GetAnalyticsMetricsOutputDTO> {
return this.get<GetAnalyticsMetricsOutputDTO>('/analytics/metrics');
}
}

View File

@@ -1,9 +1,9 @@
import { BaseApiClient } from '../base/BaseApiClient';
import { AuthSessionDTO } from '../../types/generated/AuthSessionDTO';
// TODO: Create DTOs for login/signup params in apps/website/lib/types/generated
type LoginParamsDto = { email: string; password: string };
type SignupParamsDto = { email: string; password: string; displayName: string };
import { LoginParams } from '../../types/generated/LoginParams';
import { SignupParams } from '../../types/generated/SignupParams';
import { LoginWithIracingCallbackParams } from '../../types/generated/LoginWithIracingCallbackParams';
import { IracingAuthRedirectResult } from '../../types/generated/IracingAuthRedirectResult';
/**
* Auth API Client
@@ -12,12 +12,12 @@ type SignupParamsDto = { email: string; password: string; displayName: string };
*/
export class AuthApiClient extends BaseApiClient {
/** Sign up with email */
signup(params: SignupParamsDto): Promise<AuthSessionDTO> {
signup(params: SignupParams): Promise<AuthSessionDTO> {
return this.post<AuthSessionDTO>('/auth/signup', params);
}
/** Login with email */
login(params: LoginParamsDto): Promise<AuthSessionDTO> {
login(params: LoginParams): Promise<AuthSessionDTO> {
return this.post<AuthSessionDTO>('/auth/login', params);
}
@@ -32,9 +32,19 @@ export class AuthApiClient extends BaseApiClient {
}
/** Start iRacing auth redirect */
getIracingAuthUrl(returnTo?: string): string {
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
const params = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : '';
return `${baseUrl}/auth/iracing/start${params}`;
startIracingAuthRedirect(returnTo?: string): Promise<IracingAuthRedirectResult> {
const query = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : '';
return this.get<IracingAuthRedirectResult>(`/auth/iracing/start${query}`);
}
/** Login with iRacing callback */
loginWithIracingCallback(params: LoginWithIracingCallbackParams): Promise<AuthSessionDTO> {
const query = new URLSearchParams();
query.append('code', params.code);
query.append('state', params.state);
if (params.returnTo) {
query.append('returnTo', params.returnTo);
}
return this.get<AuthSessionDTO>(`/auth/iracing/callback?${query.toString()}`);
}
}

View File

@@ -1,61 +1,27 @@
import { BaseApiClient } from '../base/BaseApiClient';
import {
DashboardDriverSummaryDTO,
DashboardRaceSummaryDTO,
DashboardLeagueStandingSummaryDTO,
DashboardFeedItemSummaryDTO,
DashboardFriendSummaryDTO,
DashboardRecentResultDTO,
} from '../../types/generated';
// DTOs
export type DriverDto = {
id: string;
name: string;
avatarUrl: string;
country: string;
totalRaces: number;
wins: number;
podiums: number;
rating: number;
globalRank: number;
consistency: number;
};
export type RaceDto = {
id: string;
track: string;
car: string;
scheduledAt: string; // ISO date string
isMyLeague: boolean;
leagueName?: string;
};
export type LeagueStandingDto = {
leagueId: string;
leagueName: string;
position: number;
points: number;
totalDrivers: number;
};
export type FeedItemDto = {
id: string;
type: string;
headline: string;
body: string | null;
timestamp: string; // ISO date string
ctaHref?: string;
ctaLabel?: string;
};
export type FriendDto = {
id: string;
name: string;
avatarUrl: string;
country: string;
};
// Define DashboardOverviewDTO using generated types
export type DashboardOverviewDto = {
currentDriver: DriverDto;
nextRace: RaceDto | null;
upcomingRaces: RaceDto[];
leagueStandings: LeagueStandingDto[];
feedItems: FeedItemDto[];
friends: FriendDto[];
currentDriver: DashboardDriverSummaryDTO | null;
myUpcomingRaces: DashboardRaceSummaryDTO[];
otherUpcomingRaces: DashboardRaceSummaryDTO[];
upcomingRaces: DashboardRaceSummaryDTO[];
activeLeaguesCount: number;
nextRace: DashboardRaceSummaryDTO | null;
recentResults: DashboardRecentResultDTO[];
leagueStandingsSummaries: DashboardLeagueStandingSummaryDTO[];
feedSummary: {
feedItems: DashboardFeedItemSummaryDTO[];
};
friends: DashboardFriendSummaryDTO[];
};
/**

View File

@@ -1,15 +1,6 @@
import { BaseApiClient } from '../base/BaseApiClient';
// Import generated types
import type { CompleteOnboardingInputDTO, CompleteOnboardingOutputDTO, DriverRegistrationStatusDTO, DriverLeaderboardItemDTO, DriverProfileDTO } from '../../types/generated';
// TODO: Create proper DriverDTO in generated types
type DriverDTO = {
id: string;
name: string;
avatarUrl?: string;
iracingId?: string;
rating?: number;
};
import type { CompleteOnboardingInputDTO, CompleteOnboardingOutputDTO, DriverRegistrationStatusDTO, DriverLeaderboardItemDTO, DriverProfileDTO, GetDriverOutputDTO } from '../../types/generated';
type DriversLeaderboardDto = {
drivers: DriverLeaderboardItemDTO[];
@@ -32,8 +23,8 @@ export class DriversApiClient extends BaseApiClient {
}
/** Get current driver (based on session) */
getCurrent(): Promise<DriverDTO | null> {
return this.get<DriverDTO | null>('/drivers/current');
getCurrent(): Promise<GetDriverOutputDTO | null> {
return this.get<GetDriverOutputDTO | null>('/drivers/current');
}
/** Get driver registration status for a specific race */
@@ -42,8 +33,8 @@ export class DriversApiClient extends BaseApiClient {
}
/** Get driver by ID */
getDriver(driverId: string): Promise<DriverDTO | null> {
return this.get<DriverDTO | null>(`/drivers/${driverId}`);
getDriver(driverId: string): Promise<GetDriverOutputDTO | null> {
return this.get<GetDriverOutputDTO | null>(`/drivers/${driverId}`);
}
/** Get driver profile with full details */
@@ -52,7 +43,7 @@ export class DriversApiClient extends BaseApiClient {
}
/** Update current driver profile */
updateProfile(updates: { bio?: string; country?: string }): Promise<DriverDTO> {
return this.put<DriverDTO>('/drivers/profile', updates);
updateProfile(updates: { bio?: string; country?: string }): Promise<GetDriverOutputDTO> {
return this.put<GetDriverOutputDTO>('/drivers/profile', updates);
}
}

View File

@@ -8,6 +8,8 @@ import { AnalyticsApiClient } from './analytics/AnalyticsApiClient';
import { AuthApiClient } from './auth/AuthApiClient';
import { PaymentsApiClient } from './payments/PaymentsApiClient';
import { DashboardApiClient } from './dashboard/DashboardApiClient';
import { PenaltiesApiClient } from './penalties/PenaltiesApiClient';
import { ProtestsApiClient } from './protests/ProtestsApiClient';
/**
* Main API Client
@@ -25,6 +27,8 @@ export class ApiClient {
public readonly auth: AuthApiClient;
public readonly payments: PaymentsApiClient;
public readonly dashboard: DashboardApiClient;
public readonly penalties: PenaltiesApiClient;
public readonly protests: ProtestsApiClient;
constructor(baseUrl: string) {
this.leagues = new LeaguesApiClient(baseUrl);
@@ -37,6 +41,8 @@ export class ApiClient {
this.auth = new AuthApiClient(baseUrl);
this.payments = new PaymentsApiClient(baseUrl);
this.dashboard = new DashboardApiClient(baseUrl);
this.penalties = new PenaltiesApiClient(baseUrl);
this.protests = new ProtestsApiClient(baseUrl);
}
}

View File

@@ -7,6 +7,8 @@ import type {
LeagueMembershipsDto,
CreateLeagueInputDto,
CreateLeagueOutputDto,
SponsorshipDetailDTO,
RaceDTO,
} from '../../dtos';
/**
@@ -61,8 +63,8 @@ export class LeaguesApiClient extends BaseApiClient {
}
/** Get season sponsorships */
getSeasonSponsorships(seasonId: string): Promise<{ sponsorships: Array<{ sponsorId: string; tier: string; status: string }> }> {
return this.get<{ sponsorships: Array<{ sponsorId: string; tier: string; status: string }> }>(`/seasons/${seasonId}/sponsorships`);
getSeasonSponsorships(seasonId: string): Promise<{ sponsorships: SponsorshipDetailDTO[] }> {
return this.get<{ sponsorships: SponsorshipDetailDTO[] }>(`/leagues/seasons/${seasonId}/sponsorships`);
}
/** Get league config */
@@ -84,7 +86,7 @@ export class LeaguesApiClient extends BaseApiClient {
}
/** Get races for a league */
getRaces(leagueId: string): Promise<{ races: any[] }> {
return this.get<{ races: any[] }>(`/leagues/${leagueId}/races`);
getRaces(leagueId: string): Promise<{ races: RaceDTO[] }> {
return this.get<{ races: RaceDTO[] }>(`/leagues/${leagueId}/races`);
}
}

View File

@@ -1,14 +1,13 @@
import type {
DeleteMediaOutputDto,
GetMediaOutputDto,
RequestAvatarGenerationInputDto,
RequestAvatarGenerationOutputDto,
UpdateAvatarInputDto,
UpdateAvatarOutputDto,
UploadMediaInputDto,
UploadMediaOutputDto,
} from '../../dtos';
import type { GetAvatarOutputDto } from '../../types/GetAvatarOutputDto';
DeleteMediaOutputDTO,
GetMediaOutputDTO,
RequestAvatarGenerationInputDTO,
RequestAvatarGenerationOutputDTO,
UpdateAvatarInputDTO,
UpdateAvatarOutputDTO,
UploadMediaOutputDTO,
} from '../generated';
import type { GetAvatarOutputDTO } from '../generated';
import { BaseApiClient } from '../base/BaseApiClient';
/**
@@ -18,38 +17,38 @@ import { BaseApiClient } from '../base/BaseApiClient';
*/
export class MediaApiClient extends BaseApiClient {
/** Upload media file */
uploadMedia(input: UploadMediaInputDto): Promise<UploadMediaOutputDto> {
uploadMedia(input: { file: File; type: string; category?: string }): Promise<UploadMediaOutputDTO> {
const formData = new FormData();
formData.append('file', input.file);
formData.append('type', input.type);
if (input.category) {
formData.append('category', input.category);
}
return this.post<UploadMediaOutputDto>('/media/upload', formData);
return this.post<UploadMediaOutputDTO>('/media/upload', formData);
}
/** Get media by ID */
getMedia(mediaId: string): Promise<GetMediaOutputDto> {
return this.get<GetMediaOutputDto>(`/media/${mediaId}`);
getMedia(mediaId: string): Promise<GetMediaOutputDTO> {
return this.get<GetMediaOutputDTO>(`/media/${mediaId}`);
}
/** Delete media by ID */
deleteMedia(mediaId: string): Promise<DeleteMediaOutputDto> {
return this.delete<DeleteMediaOutputDto>(`/media/${mediaId}`);
deleteMedia(mediaId: string): Promise<DeleteMediaOutputDTO> {
return this.delete<DeleteMediaOutputDTO>(`/media/${mediaId}`);
}
/** Request avatar generation */
requestAvatarGeneration(input: RequestAvatarGenerationInputDto): Promise<RequestAvatarGenerationOutputDto> {
return this.post<RequestAvatarGenerationOutputDto>('/media/avatar/generate', input);
requestAvatarGeneration(input: RequestAvatarGenerationInputDTO): Promise<RequestAvatarGenerationOutputDTO> {
return this.post<RequestAvatarGenerationOutputDTO>('/media/avatar/generate', input);
}
/** Get avatar for driver */
getAvatar(driverId: string): Promise<GetAvatarOutputDto> {
return this.get<GetAvatarOutputDto>(`/media/avatar/${driverId}`);
getAvatar(driverId: string): Promise<GetAvatarOutputDTO> {
return this.get<GetAvatarOutputDTO>(`/media/avatar/${driverId}`);
}
/** Update avatar for driver */
updateAvatar(input: UpdateAvatarInputDto): Promise<UpdateAvatarOutputDto> {
return this.put<UpdateAvatarOutputDto>(`/media/avatar/${input.driverId}`, { avatarUrl: input.avatarUrl });
updateAvatar(input: UpdateAvatarInputDTO): Promise<UpdateAvatarOutputDTO> {
return this.put<UpdateAvatarOutputDTO>(`/media/avatar/${input.driverId}`, { avatarUrl: input.avatarUrl });
}
}

View File

@@ -1,7 +1,8 @@
import { BaseApiClient } from '../base/BaseApiClient';
import type { PaymentDto, MembershipFeeDto, MemberPaymentDto, PrizeDto, WalletDto, TransactionDto, UpdatePaymentStatusInputDTO } from '../types/generated';
// TODO: Import these types from apps/website/lib/types/generated when available
type GetPaymentsOutputDto = { payments: import('../types/generated').PaymentDto[] };
// Define missing types that are not fully generated
type GetPaymentsOutputDto = { payments: PaymentDto[] };
type CreatePaymentInputDto = {
type: 'sponsorship' | 'membership_fee';
amount: number;
@@ -10,15 +11,15 @@ type CreatePaymentInputDto = {
leagueId: string;
seasonId?: string;
};
type CreatePaymentOutputDto = { payment: import('../types/generated').PaymentDto };
type CreatePaymentOutputDto = { payment: PaymentDto };
type GetMembershipFeesOutputDto = {
fee: import('../types/generated').MembershipFeeDto | null;
payments: import('../types/generated').MemberPaymentDto[]
fee: MembershipFeeDto | null;
payments: MemberPaymentDto[]
};
type GetPrizesOutputDto = { prizes: import('../types/generated').PrizeDto[] };
type GetPrizesOutputDto = { prizes: PrizeDto[] };
type GetWalletOutputDto = {
wallet: import('../types/generated').WalletDto;
transactions: import('../types/generated').TransactionDto[]
wallet: WalletDto;
transactions: TransactionDto[]
};
type ProcessWalletTransactionInputDto = {
leagueId: string;
@@ -29,8 +30,8 @@ type ProcessWalletTransactionInputDto = {
referenceType?: 'sponsorship' | 'membership_fee' | 'prize';
};
type ProcessWalletTransactionOutputDto = {
wallet: import('../types/generated').WalletDto;
transaction: import('../types/generated').TransactionDto
wallet: WalletDto;
transaction: TransactionDto
};
type UpdateMemberPaymentInputDto = {
feeId: string;
@@ -38,8 +39,33 @@ type UpdateMemberPaymentInputDto = {
status?: 'pending' | 'paid' | 'overdue';
paidAt?: Date | string;
};
type UpdateMemberPaymentOutputDto = { payment: import('../types/generated').MemberPaymentDto };
type GetWalletTransactionsOutputDto = { transactions: import('../types/generated').TransactionDto[] };
type UpdateMemberPaymentOutputDto = { payment: MemberPaymentDto };
type GetWalletTransactionsOutputDto = { transactions: TransactionDto[] };
type UpdatePaymentStatusOutputDto = { payment: PaymentDto };
type UpsertMembershipFeeInputDto = {
leagueId: string;
seasonId?: string;
type: 'season' | 'monthly' | 'per_race';
amount: number;
};
type UpsertMembershipFeeOutputDto = { fee: MembershipFeeDto };
type CreatePrizeInputDto = {
leagueId: string;
seasonId: string;
position: number;
name: string;
amount: number;
type: 'cash' | 'merchandise' | 'other';
description?: string;
};
type CreatePrizeOutputDto = { prize: PrizeDto };
type AwardPrizeInputDto = {
prizeId: string;
driverId: string;
};
type AwardPrizeOutputDto = { prize: PrizeDto };
type DeletePrizeInputDto = { prizeId: string };
type DeletePrizeOutputDto = { success: boolean };
/**
* Payments API Client
@@ -48,12 +74,14 @@ type GetWalletTransactionsOutputDto = { transactions: import('../types/generated
*/
export class PaymentsApiClient extends BaseApiClient {
/** Get payments */
getPayments(leagueId?: string, driverId?: string): Promise<GetPaymentsOutputDto> {
getPayments(query?: { leagueId?: string; payerId?: string; type?: 'sponsorship' | 'membership_fee'; status?: 'pending' | 'completed' | 'failed' | 'refunded' }): Promise<GetPaymentsOutputDto> {
const params = new URLSearchParams();
if (leagueId) params.append('leagueId', leagueId);
if (driverId) params.append('driverId', driverId);
const query = params.toString();
return this.get<GetPaymentsOutputDto>(`/payments${query ? `?${query}` : ''}`);
if (query?.leagueId) params.append('leagueId', query.leagueId);
if (query?.payerId) params.append('payerId', query.payerId);
if (query?.type) params.append('type', query.type);
if (query?.status) params.append('status', query.status);
const queryString = params.toString();
return this.get<GetPaymentsOutputDto>(`/payments${queryString ? `?${queryString}` : ''}`);
}
/** Create a payment */
@@ -62,21 +90,63 @@ export class PaymentsApiClient extends BaseApiClient {
}
/** Get membership fees */
getMembershipFees(leagueId: string): Promise<GetMembershipFeesOutputDto> {
return this.get<GetMembershipFeesOutputDto>(`/payments/membership-fees?leagueId=${leagueId}`);
getMembershipFees(query: { leagueId: string; driverId?: string }): Promise<GetMembershipFeesOutputDto> {
const params = new URLSearchParams();
params.append('leagueId', query.leagueId);
if (query.driverId) params.append('driverId', query.driverId);
const queryString = params.toString();
return this.get<GetMembershipFeesOutputDto>(`/payments/membership-fees?${queryString}`);
}
/** Get prizes */
getPrizes(leagueId?: string, seasonId?: string): Promise<GetPrizesOutputDto> {
getPrizes(query?: { leagueId?: string; seasonId?: string }): Promise<GetPrizesOutputDto> {
const params = new URLSearchParams();
if (leagueId) params.append('leagueId', leagueId);
if (seasonId) params.append('seasonId', seasonId);
const query = params.toString();
return this.get<GetPrizesOutputDto>(`/payments/prizes${query ? `?${query}` : ''}`);
if (query?.leagueId) params.append('leagueId', query.leagueId);
if (query?.seasonId) params.append('seasonId', query.seasonId);
const queryString = params.toString();
return this.get<GetPrizesOutputDto>(`/payments/prizes${queryString ? `?${queryString}` : ''}`);
}
/** Get wallet */
getWallet(driverId: string): Promise<GetWalletOutputDto> {
return this.get<GetWalletOutputDto>(`/payments/wallets?driverId=${driverId}`);
getWallet(query?: { leagueId?: string }): Promise<GetWalletOutputDto> {
const params = new URLSearchParams();
if (query?.leagueId) params.append('leagueId', query.leagueId);
const queryString = params.toString();
return this.get<GetWalletOutputDto>(`/payments/wallets${queryString ? `?${queryString}` : ''}`);
}
/** Update payment status */
updatePaymentStatus(input: UpdatePaymentStatusInputDTO): Promise<UpdatePaymentStatusOutputDto> {
return this.patch<UpdatePaymentStatusOutputDto>('/payments/status', input);
}
/** Upsert membership fee */
upsertMembershipFee(input: UpsertMembershipFeeInputDto): Promise<UpsertMembershipFeeOutputDto> {
return this.post<UpsertMembershipFeeOutputDto>('/payments/membership-fees', input);
}
/** Update member payment */
updateMemberPayment(input: UpdateMemberPaymentInputDto): Promise<UpdateMemberPaymentOutputDto> {
return this.patch<UpdateMemberPaymentOutputDto>('/payments/membership-fees/member-payment', input);
}
/** Create prize */
createPrize(input: CreatePrizeInputDto): Promise<CreatePrizeOutputDto> {
return this.post<CreatePrizeOutputDto>('/payments/prizes', input);
}
/** Award prize */
awardPrize(input: AwardPrizeInputDto): Promise<AwardPrizeOutputDto> {
return this.patch<AwardPrizeOutputDto>('/payments/prizes/award', input);
}
/** Delete prize */
deletePrize(prizeId: string): Promise<DeletePrizeOutputDto> {
return this.delete<DeletePrizeOutputDto>(`/payments/prizes?prizeId=${prizeId}`);
}
/** Process wallet transaction */
processWalletTransaction(input: ProcessWalletTransactionInputDto): Promise<ProcessWalletTransactionOutputDto> {
return this.post<ProcessWalletTransactionOutputDto>('/payments/wallets/transactions', input);
}
}

View File

@@ -1,4 +1,6 @@
import { BaseApiClient } from '../base/BaseApiClient';
import { RacePenaltiesDTO } from '../../types/generated/RacePenaltiesDTO';
import { ApplyPenaltyCommandDTO } from '../../types/generated/ApplyPenaltyCommandDTO';
/**
* Penalties API Client
@@ -7,12 +9,12 @@ import { BaseApiClient } from '../base/BaseApiClient';
*/
export class PenaltiesApiClient extends BaseApiClient {
/** Get penalties for a race */
getRacePenalties(raceId: string): Promise<{ penalties: any[] }> {
return this.get<{ penalties: any[] }>(`/races/${raceId}/penalties`);
getRacePenalties(raceId: string): Promise<RacePenaltiesDTO> {
return this.get<RacePenaltiesDTO>(`/races/${raceId}/penalties`);
}
/** Apply a penalty */
applyPenalty(input: any): Promise<void> {
applyPenalty(input: ApplyPenaltyCommandDTO): Promise<void> {
return this.post<void>('/races/penalties/apply', input);
}
}

View File

@@ -3,7 +3,9 @@ import type {
LeagueAdminProtestsDTO,
ApplyPenaltyCommandDTO,
RequestProtestDefenseCommandDTO,
} from '../../types';
ReviewProtestCommandDTO,
} from '../../types/generated';
import type { RaceProtestsDTO } from '../../types';
/**
* Protests API Client
@@ -32,12 +34,12 @@ export class ProtestsApiClient extends BaseApiClient {
}
/** Review protest */
reviewProtest(input: { protestId: string; stewardId: string; decision: string; decisionNotes: string }): Promise<void> {
reviewProtest(input: ReviewProtestCommandDTO): Promise<void> {
return this.post<void>(`/protests/${input.protestId}/review`, input);
}
/** Get protests for a race */
getRaceProtests(raceId: string): Promise<{ protests: any[] }> {
return this.get<{ protests: any[] }>(`/races/${raceId}/protests`);
getRaceProtests(raceId: string): Promise<RaceProtestsDTO> {
return this.get<RaceProtestsDTO>(`/races/${raceId}/protests`);
}
}

View File

@@ -1,15 +1,34 @@
import { BaseApiClient } from '../base/BaseApiClient';
import type {
RaceStatsDto,
RacesPageDataDto,
RaceDetailDto,
RaceResultsDetailDto,
RaceWithSOFDto,
RegisterForRaceInputDto,
ImportRaceResultsInputDto,
ImportRaceResultsSummaryDto,
WithdrawFromRaceInputDto,
} from '../../dtos';
import type { RaceStatsDTO } from '../../types/generated/RaceStatsDTO';
import type { RacesPageDataRaceDTO } from '../../types/generated/RacesPageDataRaceDTO';
import type { RaceResultsDetailDTO } from '../../types/generated/RaceResultsDetailDTO';
import type { RaceWithSOFDTO } from '../../types/generated/RaceWithSOFDTO';
import type { RegisterForRaceParamsDTO } from '../../types/generated/RegisterForRaceParamsDTO';
import type { ImportRaceResultsDTO } from '../../types/generated/ImportRaceResultsDTO';
import type { WithdrawFromRaceParamsDTO } from '../../types/generated/WithdrawFromRaceParamsDTO';
import type { RaceDetailRaceDTO } from '../../types/generated/RaceDetailRaceDTO';
import type { RaceDetailLeagueDTO } from '../../types/generated/RaceDetailLeagueDTO';
import type { RaceDetailEntryDTO } from '../../types/generated/RaceDetailEntryDTO';
import type { RaceDetailRegistrationDTO } from '../../types/generated/RaceDetailRegistrationDTO';
import type { RaceDetailUserResultDTO } from '../../types/generated/RaceDetailUserResultDTO';
// Define missing types
type RacesPageDataDTO = { races: RacesPageDataRaceDTO[] };
type RaceDetailDTO = {
race: RaceDetailRaceDTO | null;
league: RaceDetailLeagueDTO | null;
entryList: RaceDetailEntryDTO[];
registration: RaceDetailRegistrationDTO;
userResult: RaceDetailUserResultDTO | null;
error?: string;
};
type ImportRaceResultsSummaryDTO = {
success: boolean;
raceId: string;
driversProcessed: number;
resultsRecorded: number;
errors?: string[];
};
/**
* Races API Client
@@ -18,42 +37,42 @@ import type {
*/
export class RacesApiClient extends BaseApiClient {
/** Get total number of races */
getTotal(): Promise<RaceStatsDto> {
return this.get<RaceStatsDto>('/races/total-races');
getTotal(): Promise<RaceStatsDTO> {
return this.get<RaceStatsDTO>('/races/total-races');
}
/** Get races page data */
getPageData(): Promise<RacesPageDataDto> {
return this.get<RacesPageDataDto>('/races/page-data');
getPageData(): Promise<RacesPageDataDTO> {
return this.get<RacesPageDataDTO>('/races/page-data');
}
/** Get race detail */
getDetail(raceId: string, driverId: string): Promise<RaceDetailDto> {
return this.get<RaceDetailDto>(`/races/${raceId}?driverId=${driverId}`);
getDetail(raceId: string, driverId: string): Promise<RaceDetailDTO> {
return this.get<RaceDetailDTO>(`/races/${raceId}?driverId=${driverId}`);
}
/** Get race results detail */
getResultsDetail(raceId: string): Promise<RaceResultsDetailDto> {
return this.get<RaceResultsDetailDto>(`/races/${raceId}/results`);
getResultsDetail(raceId: string): Promise<RaceResultsDetailDTO> {
return this.get<RaceResultsDetailDTO>(`/races/${raceId}/results`);
}
/** Get race with strength of field */
getWithSOF(raceId: string): Promise<RaceWithSOFDto> {
return this.get<RaceWithSOFDto>(`/races/${raceId}/sof`);
getWithSOF(raceId: string): Promise<RaceWithSOFDTO> {
return this.get<RaceWithSOFDTO>(`/races/${raceId}/sof`);
}
/** Register for race */
register(raceId: string, input: RegisterForRaceInputDto): Promise<void> {
register(raceId: string, input: RegisterForRaceParamsDTO): Promise<void> {
return this.post<void>(`/races/${raceId}/register`, input);
}
/** Import race results */
importResults(raceId: string, input: ImportRaceResultsInputDto): Promise<ImportRaceResultsSummaryDto> {
return this.post<ImportRaceResultsSummaryDto>(`/races/${raceId}/import-results`, input);
importResults(raceId: string, input: ImportRaceResultsDTO): Promise<ImportRaceResultsSummaryDTO> {
return this.post<ImportRaceResultsSummaryDTO>(`/races/${raceId}/import-results`, input);
}
/** Withdraw from race */
withdraw(raceId: string, input: WithdrawFromRaceInputDto): Promise<void> {
withdraw(raceId: string, input: WithdrawFromRaceParamsDTO): Promise<void> {
return this.post<void>(`/races/${raceId}/withdraw`, input);
}

View File

@@ -2,11 +2,15 @@ import { BaseApiClient } from '../base/BaseApiClient';
import type { CreateSponsorInputDTO } from '../../types/generated/CreateSponsorInputDTO';
import type { SponsorDashboardDTO } from '../../types/generated/SponsorDashboardDTO';
import type { SponsorSponsorshipsDTO } from '../../types/generated/SponsorSponsorshipsDTO';
import type { GetPendingSponsorshipRequestsOutputDTO } from '../../types/generated/GetPendingSponsorshipRequestsOutputDTO';
import type { AcceptSponsorshipRequestInputDTO } from '../../types/generated/AcceptSponsorshipRequestInputDTO';
import type { RejectSponsorshipRequestInputDTO } from '../../types/generated/RejectSponsorshipRequestInputDTO';
import type { GetSponsorOutputDTO } from '../../types/generated/GetSponsorOutputDTO';
import type { SponsorDTO } from '../../types/generated/SponsorDTO';
// TODO: Move these types to apps/website/lib/types/generated when available
// Types that are not yet generated
export type CreateSponsorOutputDto = { id: string; name: string };
export type GetEntitySponsorshipPricingResultDto = { pricing: Array<{ entityType: string; price: number }> };
export type SponsorDTO = { id: string; name: string; logoUrl?: string; websiteUrl?: string };
export type GetSponsorsOutputDto = { sponsors: SponsorDTO[] };
/**
@@ -41,22 +45,22 @@ export class SponsorsApiClient extends BaseApiClient {
}
/** Get sponsor by ID */
getSponsor(sponsorId: string): Promise<SponsorDTO | null> {
return this.get<SponsorDTO | null>(`/sponsors/${sponsorId}`);
getSponsor(sponsorId: string): Promise<GetSponsorOutputDTO | null> {
return this.get<GetSponsorOutputDTO | null>(`/sponsors/${sponsorId}`);
}
/** Get pending sponsorship requests for an entity */
getPendingSponsorshipRequests(params: { entityType: string; entityId: string }): Promise<{ requests: any[] }> {
return this.get<{ requests: any[] }>(`/sponsors/requests?entityType=${params.entityType}&entityId=${params.entityId}`);
getPendingSponsorshipRequests(params: { entityType: string; entityId: string }): Promise<GetPendingSponsorshipRequestsOutputDTO> {
return this.get<GetPendingSponsorshipRequestsOutputDTO>(`/sponsors/requests?entityType=${params.entityType}&entityId=${params.entityId}`);
}
/** Accept a sponsorship request */
acceptSponsorshipRequest(requestId: string, respondedBy: string): Promise<void> {
return this.post(`/sponsors/requests/${requestId}/accept`, { respondedBy });
acceptSponsorshipRequest(requestId: string, input: AcceptSponsorshipRequestInputDTO): Promise<void> {
return this.post(`/sponsors/requests/${requestId}/accept`, input);
}
/** Reject a sponsorship request */
rejectSponsorshipRequest(requestId: string, respondedBy: string, reason?: string): Promise<void> {
return this.post(`/sponsors/requests/${requestId}/reject`, { respondedBy, reason });
rejectSponsorshipRequest(requestId: string, input: RejectSponsorshipRequestInputDTO): Promise<void> {
return this.post(`/sponsors/requests/${requestId}/reject`, input);
}
}

View File

@@ -1,15 +1,14 @@
import { LeagueMemberDTO } from '@/lib/types/generated/LeagueMemberDTO';
import type {
AllTeamsDto,
CreateTeamInputDto,
CreateTeamOutputDto,
DriverTeamDto,
TeamDetailsDto,
TeamJoinRequestsDto,
TeamMembersDto,
UpdateTeamInputDto,
UpdateTeamOutputDto,
} from '../../dtos';
import type { GetAllTeamsOutputDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO';
import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO';
import type { GetTeamMembersOutputDTO } from '@/lib/types/generated/GetTeamMembersOutputDTO';
import type { GetTeamJoinRequestsOutputDTO } from '@/lib/types/generated/GetTeamJoinRequestsOutputDTO';
import type { CreateTeamInputDTO } from '@/lib/types/generated/CreateTeamInputDTO';
import type { CreateTeamOutputDTO } from '@/lib/types/generated/CreateTeamOutputDTO';
import type { UpdateTeamInputDTO } from '@/lib/types/generated/UpdateTeamInputDTO';
import type { UpdateTeamOutputDTO } from '@/lib/types/generated/UpdateTeamOutputDTO';
import type { GetDriverTeamOutputDTO } from '@/lib/types/generated/GetDriverTeamOutputDTO';
import type { GetTeamMembershipOutputDTO } from '@/lib/types/generated/GetTeamMembershipOutputDTO';
import { BaseApiClient } from '../base/BaseApiClient';
/**
@@ -19,42 +18,42 @@ import { BaseApiClient } from '../base/BaseApiClient';
*/
export class TeamsApiClient extends BaseApiClient {
/** Get all teams */
getAll(): Promise<AllTeamsDto> {
return this.get<AllTeamsDto>('/teams/all');
getAll(): Promise<GetAllTeamsOutputDTO> {
return this.get<GetAllTeamsOutputDTO>('/teams/all');
}
/** Get team details */
getDetails(teamId: string): Promise<TeamDetailsDto | null> {
return this.get<TeamDetailsDto | null>(`/teams/${teamId}`);
getDetails(teamId: string): Promise<GetTeamDetailsOutputDTO | null> {
return this.get<GetTeamDetailsOutputDTO | null>(`/teams/${teamId}`);
}
/** Get team members */
getMembers(teamId: string): Promise<TeamMembersDto> {
return this.get<TeamMembersDto>(`/teams/${teamId}/members`);
getMembers(teamId: string): Promise<GetTeamMembersOutputDTO> {
return this.get<GetTeamMembersOutputDTO>(`/teams/${teamId}/members`);
}
/** Get team join requests */
getJoinRequests(teamId: string): Promise<TeamJoinRequestsDto> {
return this.get<TeamJoinRequestsDto>(`/teams/${teamId}/join-requests`);
getJoinRequests(teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
return this.get<GetTeamJoinRequestsOutputDTO>(`/teams/${teamId}/join-requests`);
}
/** Create a new team */
create(input: CreateTeamInputDto): Promise<CreateTeamOutputDto> {
return this.post<CreateTeamOutputDto>('/teams', input);
create(input: CreateTeamInputDTO): Promise<CreateTeamOutputDTO> {
return this.post<CreateTeamOutputDTO>('/teams', input);
}
/** Update team */
update(teamId: string, input: UpdateTeamInputDto): Promise<UpdateTeamOutputDto> {
return this.patch<UpdateTeamOutputDto>(`/teams/${teamId}`, input);
update(teamId: string, input: UpdateTeamInputDTO): Promise<UpdateTeamOutputDTO> {
return this.patch<UpdateTeamOutputDTO>(`/teams/${teamId}`, input);
}
/** Get driver's team */
getDriverTeam(driverId: string): Promise<DriverTeamDto | null> {
return this.get<DriverTeamDto | null>(`/teams/driver/${driverId}`);
getDriverTeam(driverId: string): Promise<GetDriverTeamOutputDTO | null> {
return this.get<GetDriverTeamOutputDTO | null>(`/teams/driver/${driverId}`);
}
/** Get membership for a driver in a team */
getMembership(teamId: string, driverId: string): Promise<LeagueMemberDTO | null> {
return this.get<LeagueMemberDTO | null>(`/teams/${teamId}/members/${driverId}`);
getMembership(teamId: string, driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
return this.get<GetTeamMembershipOutputDTO | null>(`/teams/${teamId}/members/${driverId}`);
}
}

View File

@@ -1,50 +0,0 @@
import { api } from '../apiClient';
import type {
AuthenticatedUserDTO,
AuthSessionDTO,
SignupParams,
LoginParams,
IracingAuthRedirectResult,
LoginWithIracingCallbackParams,
} from '../../../apps/api/src/modules/auth/dto/AuthDto'; // Using generated API DTOs
export class AuthApiClient {
async getCurrentSession(): Promise<AuthSessionDTO | null> {
try {
return await api.get<AuthSessionDTO>('/auth/session');
} catch (error) {
// Handle error, e.g., if session is not found or API is down
console.error('Error fetching current session:', error);
return null;
}
}
async signupWithEmail(params: SignupParams): Promise<AuthSessionDTO> {
return api.post<AuthSessionDTO>('/auth/signup', params);
}
async loginWithEmail(params: LoginParams): Promise<AuthSessionDTO> {
return api.post<AuthSessionDTO>('/auth/login', params);
}
async startIracingAuthRedirect(returnTo?: string): Promise<IracingAuthRedirectResult> {
const query = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : '';
return api.get<IracingAuthRedirectResult>(`/auth/iracing/start${query}`);
}
async loginWithIracingCallback(params: LoginWithIracingCallbackParams): Promise<AuthSessionDTO> {
const query = new URLSearchParams();
query.append('code', params.code);
query.append('state', params.state);
if (params.returnTo) {
query.append('returnTo', params.returnTo);
}
return await api.get<AuthSessionDTO>(`/auth/iracing/callback?${query.toString()}`);
}
async logout(): Promise<void> {
return api.post<void>('/auth/logout', {});
}
}
export const authApiClient = new AuthApiClient();

View File

@@ -1,9 +1,31 @@
import { WizardStep } from '@/lib/types/WizardStep';
import { WizardErrors } from '@/lib/types/WizardErrors';
import { CreateLeagueInputDTO } from '@/lib/types/CreateLeagueInputDTO';
import { LeagueWizardValidationMessages } from '@/lib/display-objects/LeagueWizardValidationMessages';
import { ScoringPresetApplier } from '@/lib/utilities/ScoringPresetApplier';
export type WizardStep = 1 | 2 | 3 | 4 | 5 | 6 | 7;
export interface WizardErrors {
basics?: {
name?: string;
description?: string;
visibility?: string;
};
structure?: {
maxDrivers?: string;
maxTeams?: string;
driversPerTeam?: string;
};
timings?: {
qualifyingMinutes?: string;
mainRaceMinutes?: string;
roundsPlanned?: string;
};
scoring?: {
patternId?: string;
};
submit?: string;
}
type LeagueWizardFormData = {
leagueId: string | undefined;
basics: {

View File

@@ -1,4 +1,5 @@
import { LeagueRole } from '@/lib/types/LeagueRole';
import type { MembershipRole } from '@core/racing/domain/entities/MembershipRole';
type LeagueRole = MembershipRole;
export interface LeagueRoleDisplayData {
text: string;

View File

@@ -1,18 +1,8 @@
import { AnalyticsApiClient } from '../../api/analytics/AnalyticsApiClient';
import { RecordPageViewOutputViewModel } from '../../view-models/RecordPageViewOutputViewModel';
import { RecordEngagementOutputViewModel } from '../../view-models/RecordEngagementOutputViewModel';
// TODO: Create proper DTOs in generated types
interface RecordPageViewInputDTO {
path: string;
userId?: string;
}
interface RecordEngagementInputDTO {
eventType: string;
userId?: string;
metadata?: Record<string, unknown>;
}
import { RecordPageViewInputDTO } from '../../types/generated/RecordPageViewInputDTO';
import { RecordEngagementInputDTO } from '../../types/generated/RecordEngagementInputDTO';
/**
* Analytics Service

View File

@@ -1,7 +1,7 @@
import { apiClient } from '@/lib/apiClient';
import { LeagueMembership } from '@/lib/types/LeagueMembership';
import { MembershipRole } from '@/lib/types/MembershipRole';
import { MembershipStatus } from '@/lib/types/MembershipStatus';
import type { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
import type { MembershipRole } from '@core/racing/domain/entities/MembershipRole';
import type { MembershipStatus } from '@core/racing/domain/entities/MembershipStatus';
export class LeagueMembershipService {
// In-memory cache for memberships (populated via API calls)

View File

@@ -15,7 +15,6 @@ import { LeagueDetailViewModel } from "@/lib/view-models/LeagueDetailViewModel";
import { LeagueDetailPageViewModel, SponsorInfo } from "@/lib/view-models/LeagueDetailPageViewModel";
import { RaceViewModel } from "@/lib/view-models/RaceViewModel";
import { SubmitBlocker, ThrottleBlocker } from "@/lib/blockers";
import { DriverDTO } from "@/lib/types/DriverDTO";
import { RaceDTO } from "@/lib/types/generated/RaceDTO";
import { LeagueScoringConfigDTO } from "@/lib/types/LeagueScoringConfigDTO";
import { LeagueStatsDTO } from "@/lib/types/generated/LeagueStatsDTO";

View File

@@ -1,8 +1,8 @@
import { LeaguesApiClient } from "@/lib/api/leagues/LeaguesApiClient";
import { DriversApiClient } from "@/lib/api/drivers/DriversApiClient";
import type { LeagueConfigFormModel } from "@/lib/types/LeagueConfigFormModel";
import type { LeagueScoringPresetDTO } from "@/lib/types/LeagueScoringPresetDTO";
import type { DriverDTO } from "@/lib/types/DriverDTO";
import type { LeagueConfigFormModel } from "@core/racing/application";
import type { LeagueScoringPresetDTO } from "@core/racing/application/ports/LeagueScoringPresetProvider";
import type { GetDriverOutputDTO } from "@/lib/types/generated/GetDriverOutputDTO";
import { LeagueSettingsViewModel } from "@/lib/view-models/LeagueSettingsViewModel";
import { DriverSummaryViewModel } from "@/lib/view-models/DriverSummaryViewModel";
@@ -50,7 +50,7 @@ export class LeagueSettingsService {
// TODO: get rating and rank from API
owner = new DriverSummaryViewModel({
driver: ownerDriver,
rating: ownerDriver.rating ?? null,
rating: null, // TODO: get from API
rank: null, // TODO: get from API
});
}
@@ -64,7 +64,7 @@ export class LeagueSettingsService {
if (driver) {
members.push(new DriverSummaryViewModel({
driver,
rating: driver.rating ?? null,
rating: null, // TODO: get from API
rank: null, // TODO: get from API
}));
}

View File

@@ -1,26 +1,23 @@
import { apiClient } from '@/lib/apiClient';
import { LeagueWizardCommandModel } from '@/lib/command-models/leagues/LeagueWizardCommandModel';
import { CreateLeagueResult } from '@/lib/types/CreateLeagueResult';
import { CreateLeagueOutputDTO } from '@/lib/types/generated/CreateLeagueOutputDTO';
export class LeagueWizardService {
static async createLeague(
form: LeagueWizardCommandModel,
ownerId: string,
): Promise<CreateLeagueResult> {
): Promise<CreateLeagueOutputDTO> {
const command = form.toCreateLeagueCommand(ownerId);
const result = await apiClient.leagues.create(command);
return {
leagueId: result.leagueId,
success: result.success,
};
return result;
}
// Static method for backward compatibility
static async createLeagueFromConfig(
form: LeagueWizardCommandModel,
ownerId: string,
): Promise<CreateLeagueResult> {
): Promise<CreateLeagueOutputDTO> {
return this.createLeague(form, ownerId);
}
}

View File

@@ -83,9 +83,9 @@ describe('MediaService', () => {
const expectedOutput = {
id: 'media-123',
url: 'https://example.com/image.jpg',
type: 'image' as const,
category: 'avatar' as const,
uploadedAt: new Date('2023-01-15'),
type: 'image',
category: 'avatar',
uploadedAt: '2023-01-15T00:00:00.000Z',
size: 2048000,
};
mockApiClient.getMedia.mockResolvedValue(expectedOutput);
@@ -98,7 +98,7 @@ describe('MediaService', () => {
expect(result.url).toBe('https://example.com/image.jpg');
expect(result.type).toBe('image');
expect(result.category).toBe('avatar');
expect(result.uploadedAt).toEqual(new Date('2023-01-15'));
expect(result.uploadedAt).toEqual(new Date('2023-01-15T00:00:00.000Z'));
expect(result.size).toBe(2048000);
expect(result.formattedSize).toBe('2000.00 KB');
});
@@ -109,8 +109,8 @@ describe('MediaService', () => {
const expectedOutput = {
id: 'media-456',
url: 'https://example.com/video.mp4',
type: 'video' as const,
uploadedAt: new Date('2023-02-20'),
type: 'video',
uploadedAt: '2023-02-20T00:00:00.000Z',
};
mockApiClient.getMedia.mockResolvedValue(expectedOutput);

View File

@@ -1,5 +1,5 @@
import { describe, it, expect, vi, Mocked } from 'vitest';
import { MembershipFeeService, GetMembershipFeesOutputDto } from './MembershipFeeService';
import { MembershipFeeService } from './MembershipFeeService';
import { PaymentsApiClient } from '../../api/payments/PaymentsApiClient';
import { MembershipFeeViewModel } from '../../view-models';
import type { MembershipFeeDto } from '../../types/generated';
@@ -17,36 +17,30 @@ describe('MembershipFeeService', () => {
});
describe('getMembershipFees', () => {
it('should call apiClient.getMembershipFees with correct leagueId and return mapped view models', async () => {
it('should call apiClient.getMembershipFees with correct leagueId and return fee and payments', async () => {
const leagueId = 'league-123';
const mockFees: MembershipFeeDto[] = [
{ id: 'fee-1', leagueId: 'league-123' },
{ id: 'fee-2', leagueId: 'league-123' },
];
const mockOutput: GetMembershipFeesOutputDto = { fees: mockFees };
const mockFee: MembershipFeeDto = { id: 'fee-1', leagueId: 'league-123', seasonId: undefined, type: 'season', amount: 100, enabled: true, createdAt: new Date(), updatedAt: new Date() };
const mockPayments: any[] = [];
const mockOutput = { fee: mockFee, payments: mockPayments };
mockApiClient.getMembershipFees.mockResolvedValue(mockOutput);
const result = await service.getMembershipFees(leagueId);
expect(mockApiClient.getMembershipFees).toHaveBeenCalledWith(leagueId);
expect(result).toHaveLength(2);
expect(result[0]).toBeInstanceOf(MembershipFeeViewModel);
expect(result[0].id).toEqual('fee-1');
expect(result[0].leagueId).toEqual('league-123');
expect(result[1]).toBeInstanceOf(MembershipFeeViewModel);
expect(result[1].id).toEqual('fee-2');
expect(result[1].leagueId).toEqual('league-123');
expect(mockApiClient.getMembershipFees).toHaveBeenCalledWith({ leagueId });
expect(result.fee).toBeInstanceOf(MembershipFeeViewModel);
expect(result.fee!.id).toEqual('fee-1');
expect(result.payments).toEqual([]);
});
it('should return empty array when no fees are returned', async () => {
it('should return null fee when no fee is returned', async () => {
const leagueId = 'league-456';
const mockOutput: GetMembershipFeesOutputDto = { fees: [] };
const mockOutput = { fee: null, payments: [] };
mockApiClient.getMembershipFees.mockResolvedValue(mockOutput);
const result = await service.getMembershipFees(leagueId);
expect(mockApiClient.getMembershipFees).toHaveBeenCalledWith(leagueId);
expect(result).toEqual([]);
expect(mockApiClient.getMembershipFees).toHaveBeenCalledWith({ leagueId });
expect(result.fee).toBeNull();
});
});
});

View File

@@ -4,7 +4,8 @@ import { PaymentsApiClient } from '../../api/payments/PaymentsApiClient';
// TODO: This DTO should be generated from OpenAPI spec when the endpoint is added
export interface GetMembershipFeesOutputDto {
fees: MembershipFeeDto[];
fee: MembershipFeeDto | null;
payments: import('./MemberPaymentDto').MemberPaymentDto[];
}
/**
@@ -21,8 +22,11 @@ export class MembershipFeeService {
/**
* Get membership fees by league ID with view model transformation
*/
async getMembershipFees(leagueId: string): Promise<MembershipFeeViewModel[]> {
const dto = await this.apiClient.getMembershipFees(leagueId);
return dto.fees.map((fee: MembershipFeeDto) => new MembershipFeeViewModel(fee));
async getMembershipFees(leagueId: string): Promise<{ fee: MembershipFeeViewModel | null; payments: any[] }> {
const dto = await this.apiClient.getMembershipFees({ leagueId });
return {
fee: dto.fee ? new MembershipFeeViewModel(dto.fee) : null,
payments: dto.payments // TODO: map to view models if needed
};
}
}

View File

@@ -42,7 +42,7 @@ describe('PaymentService', () => {
const result = await service.getPayments();
expect(mockApiClient.getPayments).toHaveBeenCalledWith(undefined, undefined);
expect(mockApiClient.getPayments).toHaveBeenCalledWith(undefined);
expect(result).toHaveLength(1);
expect(result[0]).toBeInstanceOf(PaymentViewModel);
expect(result[0].id).toBe('payment-1');
@@ -54,7 +54,7 @@ describe('PaymentService', () => {
await service.getPayments('league-1', 'user-1');
expect(mockApiClient.getPayments).toHaveBeenCalledWith('league-1', 'user-1');
expect(mockApiClient.getPayments).toHaveBeenCalledWith({ leagueId: 'league-1', payerId: 'user-1' });
});
});
@@ -189,7 +189,7 @@ describe('PaymentService', () => {
const result = await service.getPrizes();
expect(mockApiClient.getPrizes).toHaveBeenCalledWith(undefined, undefined);
expect(mockApiClient.getPrizes).toHaveBeenCalledWith(undefined);
expect(result).toHaveLength(1);
expect(result[0]).toBeInstanceOf(PrizeViewModel);
expect(result[0].id).toBe('prize-1');
@@ -201,7 +201,7 @@ describe('PaymentService', () => {
await service.getPrizes('league-1', 'season-1');
expect(mockApiClient.getPrizes).toHaveBeenCalledWith('league-1', 'season-1');
expect(mockApiClient.getPrizes).toHaveBeenCalledWith({ leagueId: 'league-1', seasonId: 'season-1' });
});
});
@@ -232,9 +232,9 @@ describe('PaymentService', () => {
mockApiClient.getWallet.mockResolvedValue(mockResponse);
const result = await service.getWallet('user-1');
const result = await service.getWallet('league-1');
expect(mockApiClient.getWallet).toHaveBeenCalledWith('user-1');
expect(mockApiClient.getWallet).toHaveBeenCalledWith({ leagueId: 'league-1' });
expect(result).toBeInstanceOf(WalletViewModel);
expect(result.id).toBe('wallet-1');
expect(result.balance).toBe(1000);

View File

@@ -31,8 +31,9 @@ export class PaymentService {
/**
* Get all payments with optional filters
*/
async getPayments(leagueId?: string, driverId?: string): Promise<PaymentViewModel[]> {
const dto = await this.apiClient.getPayments(leagueId, driverId);
async getPayments(leagueId?: string, payerId?: string): Promise<PaymentViewModel[]> {
const query = leagueId || payerId ? { leagueId, payerId } : undefined;
const dto = await this.apiClient.getPayments(query);
return dto.payments.map((payment: PaymentDTO) => new PaymentViewModel(payment));
}
@@ -60,8 +61,8 @@ export class PaymentService {
/**
* Get membership fees for a league
*/
async getMembershipFees(leagueId: string): Promise<MembershipFeeViewModel | null> {
const dto = await this.apiClient.getMembershipFees(leagueId);
async getMembershipFees(leagueId: string, driverId?: string): Promise<MembershipFeeViewModel | null> {
const dto = await this.apiClient.getMembershipFees({ leagueId, driverId });
return dto.fee ? new MembershipFeeViewModel(dto.fee) : null;
}
@@ -69,22 +70,23 @@ export class PaymentService {
* Get prizes with optional filters
*/
async getPrizes(leagueId?: string, seasonId?: string): Promise<PrizeViewModel[]> {
const dto = await this.apiClient.getPrizes(leagueId, seasonId);
const query = leagueId || seasonId ? { leagueId, seasonId } : undefined;
const dto = await this.apiClient.getPrizes(query);
return dto.prizes.map((prize: PrizeDto) => new PrizeViewModel(prize));
}
/**
* Get wallet for a driver
* Get wallet for a league
*/
async getWallet(driverId: string): Promise<WalletViewModel> {
const dto = await this.apiClient.getWallet(driverId);
async getWallet(leagueId: string): Promise<WalletViewModel> {
const dto = await this.apiClient.getWallet({ leagueId });
return new WalletViewModel({ ...dto.wallet, transactions: dto.transactions });
}
/**
* Get payment history for a user (driver)
*/
async getPaymentHistory(driverId: string): Promise<PaymentViewModel[]> {
return await this.getPayments(undefined, driverId);
async getPaymentHistory(payerId: string): Promise<PaymentViewModel[]> {
return await this.getPayments(undefined, payerId);
}
}

View File

@@ -42,9 +42,9 @@ describe('WalletService', () => {
mockApiClient.getWallet.mockResolvedValue(mockResponse);
const result = await service.getWallet('user-1');
const result = await service.getWallet('league-1');
expect(mockApiClient.getWallet).toHaveBeenCalledWith('user-1');
expect(mockApiClient.getWallet).toHaveBeenCalledWith({ leagueId: 'league-1' });
expect(result).toBeInstanceOf(WalletViewModel);
expect(result.id).toBe('wallet-1');
expect(result.balance).toBe(1000);
@@ -69,7 +69,7 @@ describe('WalletService', () => {
mockApiClient.getWallet.mockResolvedValue(mockResponse);
const result = await service.getWallet('user-1');
const result = await service.getWallet('league-1');
expect(result.transactions).toHaveLength(0);
});

View File

@@ -102,8 +102,10 @@ describe('RaceResultsService', () => {
const input = { raceId, results: [{ position: 1 }] };
const mockDto = {
success: true,
raceId,
importedCount: 10,
driversProcessed: 10,
resultsRecorded: 10,
errors: ['Error 1'],
};
@@ -114,7 +116,8 @@ describe('RaceResultsService', () => {
expect(mockApiClient.importResults).toHaveBeenCalledWith(raceId, input);
expect(result).toBeInstanceOf(ImportRaceResultsSummaryViewModel);
expect(result.raceId).toBe(raceId);
expect(result.importedCount).toBe(10);
expect(result.driversProcessed).toBe(10);
expect(result.resultsRecorded).toBe(10);
expect(result.errors).toEqual(['Error 1']);
});
@@ -123,8 +126,10 @@ describe('RaceResultsService', () => {
const input = { raceId, results: [] };
const mockDto = {
success: true,
raceId,
importedCount: 5,
driversProcessed: 5,
resultsRecorded: 5,
errors: [],
};
@@ -132,7 +137,8 @@ describe('RaceResultsService', () => {
const result = await service.importResults(raceId, input);
expect(result.importedCount).toBe(5);
expect(result.driversProcessed).toBe(5);
expect(result.resultsRecorded).toBe(5);
expect(result.errors).toEqual([]);
});

View File

@@ -2,15 +2,16 @@ import { RacesApiClient } from '../../api/races/RacesApiClient';
import { RaceResultsDetailViewModel } from '../../view-models/RaceResultsDetailViewModel';
import { RaceWithSOFViewModel } from '../../view-models/RaceWithSOFViewModel';
import { ImportRaceResultsSummaryViewModel } from '../../view-models/ImportRaceResultsSummaryViewModel';
import type { ImportRaceResultsDTO } from '../../types/generated/ImportRaceResultsDTO';
// TODO: Move this type to apps/website/lib/types/generated when available
type ImportRaceResultsInputDto = { raceId: string; results: Array<unknown> };
// TODO: Move this type to apps/website/lib/types/generated when available
// Define types
type ImportRaceResultsInputDto = ImportRaceResultsDTO;
type ImportRaceResultsSummaryDto = {
success: boolean;
raceId: string;
importedCount: number;
errors: string[];
driversProcessed: number;
resultsRecorded: number;
errors?: string[];
};
/**
@@ -41,10 +42,10 @@ export class RaceResultsService {
}
/**
* Import race results and get summary
*/
async importResults(raceId: string, input: ImportRaceResultsInputDto): Promise<ImportRaceResultsSummaryViewModel> {
const dto = await this.apiClient.importResults(raceId, input) as ImportRaceResultsSummaryDto;
return new ImportRaceResultsSummaryViewModel(dto);
}
* Import race results and get summary
*/
async importResults(raceId: string, input: ImportRaceResultsInputDto): Promise<ImportRaceResultsSummaryViewModel> {
const dto = await this.apiClient.importResults(raceId, input);
return new ImportRaceResultsSummaryViewModel(dto);
}
}

View File

@@ -16,6 +16,10 @@ describe('SponsorService', () => {
getSponsorships: vi.fn(),
create: vi.fn(),
getPricing: vi.fn(),
getSponsor: vi.fn(),
getPendingSponsorshipRequests: vi.fn(),
acceptSponsorshipRequest: vi.fn(),
rejectSponsorshipRequest: vi.fn(),
} as Mocked<SponsorsApiClient>;
service = new SponsorService(mockApiClient);

View File

@@ -29,11 +29,16 @@ describe('TeamService', () => {
{
id: 'team-1',
name: 'Test Team',
logoUrl: 'https://example.com/logo.png',
tag: 'TT',
description: 'A test team',
memberCount: 5,
rating: 1500,
leagues: ['league-1'],
specialization: 'endurance' as const,
region: 'EU',
languages: ['en'],
},
],
totalCount: 1,
};
mockApiClient.getAll.mockResolvedValue(mockDto);
@@ -45,6 +50,7 @@ describe('TeamService', () => {
expect(result[0]).toBeInstanceOf(TeamSummaryViewModel);
expect(result[0].id).toBe('team-1');
expect(result[0].name).toBe('Test Team');
expect(result[0].tag).toBe('TT');
});
it('should throw error when apiClient.getAll fails', async () => {
@@ -58,13 +64,24 @@ describe('TeamService', () => {
describe('getTeamDetails', () => {
it('should call apiClient.getDetails and return TeamDetailsViewModel when data exists', async () => {
const mockDto = {
id: 'team-1',
name: 'Test Team',
description: 'A test team',
logoUrl: 'https://example.com/logo.png',
memberCount: 5,
ownerId: 'owner-1',
members: [],
team: {
id: 'team-1',
name: 'Test Team',
tag: 'TT',
description: 'A test team',
ownerId: 'owner-1',
leagues: ['league-1'],
createdAt: '2023-01-01T00:00:00Z',
specialization: 'endurance',
region: 'EU',
languages: ['en'],
},
membership: {
role: 'member',
joinedAt: '2023-01-01T00:00:00Z',
isActive: true,
},
canManage: false,
};
mockApiClient.getDetails.mockResolvedValue(mockDto);
@@ -75,6 +92,7 @@ describe('TeamService', () => {
expect(result).toBeInstanceOf(TeamDetailsViewModel);
expect(result?.id).toBe('team-1');
expect(result?.name).toBe('Test Team');
expect(result?.tag).toBe('TT');
});
it('should return null when apiClient.getDetails returns null', async () => {
@@ -100,11 +118,17 @@ describe('TeamService', () => {
members: [
{
driverId: 'driver-1',
driver: { id: 'driver-1', name: 'Driver One', avatarUrl: 'avatar.png', iracingId: '123', rating: 1400 },
role: 'member',
driverName: 'Driver One',
role: 'member' as const,
joinedAt: '2023-01-01T00:00:00Z',
isActive: true,
avatarUrl: 'avatar.png',
},
],
totalCount: 1,
ownerCount: 0,
managerCount: 0,
memberCount: 1,
};
mockApiClient.getMembers.mockResolvedValue(mockDto);
@@ -176,14 +200,35 @@ describe('TeamService', () => {
describe('getDriverTeam', () => {
it('should call apiClient.getDriverTeam and return the result', async () => {
const mockOutput = { teamId: 'team-1', teamName: 'Test Team', role: 'member' };
const mockOutput = {
team: {
id: 'team-1',
name: 'Test Team',
tag: 'TT',
description: 'A test team',
ownerId: 'owner-1',
leagues: [],
specialization: 'endurance' as const,
region: 'EU',
languages: ['en'],
},
membership: {
role: 'member',
joinedAt: '2023-01-01T00:00:00Z',
isActive: true,
},
isOwner: false,
canManage: false,
};
mockApiClient.getDriverTeam.mockResolvedValue(mockOutput);
const result = await service.getDriverTeam('driver-1');
expect(mockApiClient.getDriverTeam).toHaveBeenCalledWith('driver-1');
expect(result).toEqual(mockOutput);
expect(result?.teamId).toBe('team-1');
expect(result?.teamName).toBe('Test Team');
expect(result?.role).toBe('member');
});
it('should return null when apiClient.getDriverTeam returns null', async () => {

View File

@@ -6,16 +6,15 @@ import { UpdateTeamViewModel } from '@/lib/view-models/UpdateTeamViewModel';
import { DriverTeamViewModel } from '@/lib/view-models/DriverTeamViewModel';
import { LeagueMemberDTO } from '@/lib/types/generated/LeagueMemberDTO';
import type { TeamsApiClient } from '../../api/teams/TeamsApiClient';
// TODO: Move these types to apps/website/lib/types/generated when available
type DriverDTO = { id: string; name: string; avatarUrl?: string; iracingId?: string; rating?: number };
type CreateTeamInputDto = { name: string; tag: string; description?: string };
type CreateTeamOutputDto = { id: string; success: boolean };
type UpdateTeamInputDto = { name?: string; tag?: string; description?: string };
type UpdateTeamOutputDto = { success: boolean };
type DriverTeamDto = { teamId: string; teamName: string; role: string };
type TeamSummaryDTO = { id: string; name: string; logoUrl?: string; memberCount: number; rating: number };
type TeamMemberDTO = { driverId: string; driver?: DriverDTO; role: string; joinedAt: string };
import type { GetAllTeamsOutputDTO, TeamListItemDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO';
import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO';
import type { GetTeamMembersOutputDTO, TeamMemberDTO } from '@/lib/types/generated/GetTeamMembersOutputDTO';
import type { CreateTeamInputDTO } from '@/lib/types/generated/CreateTeamInputDTO';
import type { CreateTeamOutputDTO } from '@/lib/types/generated/CreateTeamOutputDTO';
import type { UpdateTeamInputDTO } from '@/lib/types/generated/UpdateTeamInputDTO';
import type { UpdateTeamOutputDTO } from '@/lib/types/generated/UpdateTeamOutputDTO';
import type { GetDriverTeamOutputDTO } from '@/lib/types/generated/GetDriverTeamOutputDTO';
import type { GetTeamMembershipOutputDTO } from '@/lib/types/generated/GetTeamMembershipOutputDTO';
/**
* Team Service
@@ -32,15 +31,15 @@ export class TeamService {
* Get all teams with view model transformation
*/
async getAllTeams(): Promise<TeamSummaryViewModel[]> {
const dto = await this.apiClient.getAll();
return dto.teams.map((team: TeamSummaryDTO) => new TeamSummaryViewModel(team));
const dto: GetAllTeamsOutputDTO = await this.apiClient.getAll();
return dto.teams.map((team: TeamListItemDTO) => new TeamSummaryViewModel(team));
}
/**
* Get team details with view model transformation
*/
async getTeamDetails(teamId: string, currentUserId: string): Promise<TeamDetailsViewModel | null> {
const dto = await this.apiClient.getDetails(teamId);
const dto: GetTeamDetailsOutputDTO | null = await this.apiClient.getDetails(teamId);
if (!dto) {
return null;
}
@@ -51,40 +50,40 @@ export class TeamService {
* Get team members with view model transformation
*/
async getTeamMembers(teamId: string, currentUserId: string, teamOwnerId: string): Promise<TeamMemberViewModel[]> {
const dto = await this.apiClient.getMembers(teamId);
const dto: GetTeamMembersOutputDTO = await this.apiClient.getMembers(teamId);
return dto.members.map((member: TeamMemberDTO) => new TeamMemberViewModel(member, currentUserId, teamOwnerId));
}
/**
* Create a new team with view model transformation
*/
async createTeam(input: CreateTeamInputDto): Promise<CreateTeamViewModel> {
const dto = await this.apiClient.create(input);
async createTeam(input: CreateTeamInputDTO): Promise<CreateTeamViewModel> {
const dto: CreateTeamOutputDTO = await this.apiClient.create(input);
return new CreateTeamViewModel(dto);
}
/**
* Update team with view model transformation
*/
async updateTeam(teamId: string, input: UpdateTeamInputDto): Promise<UpdateTeamViewModel> {
const dto = await this.apiClient.update(teamId, input);
async updateTeam(teamId: string, input: UpdateTeamInputDTO): Promise<UpdateTeamViewModel> {
const dto: UpdateTeamOutputDTO = await this.apiClient.update(teamId, input);
return new UpdateTeamViewModel(dto);
}
/**
* Get driver's team with view model transformation
*/
async getDriverTeam(driverId: string): Promise<DriverTeamViewModel | null> {
const dto = await this.apiClient.getDriverTeam(driverId);
return dto ? new DriverTeamViewModel(dto) : null;
}
* Get driver's team with view model transformation
*/
async getDriverTeam(driverId: string): Promise<DriverTeamViewModel | null> {
const dto: GetDriverTeamOutputDTO | null = await this.apiClient.getDriverTeam(driverId);
return dto ? new DriverTeamViewModel(dto) : null;
}
/**
* Get team membership for a driver
*/
async getMembership(teamId: string, driverId: string): Promise<LeagueMemberDTO | null> {
return this.apiClient.getMembership(teamId, driverId);
}
/**
* Get team membership for a driver
*/
async getMembership(teamId: string, driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
return this.apiClient.getMembership(teamId, driverId);
}
/**
* Remove a driver from the team

View File

@@ -1,7 +0,0 @@
export interface CreateLeagueInputDTO {
name: string;
description: string;
isPublic: boolean;
maxMembers: number;
ownerId: string;
}

View File

@@ -1,5 +0,0 @@
export interface CreateLeagueResult {
leagueId: string;
seasonId?: string;
success: boolean;
}

View File

@@ -1,50 +0,0 @@
export interface LeagueConfigFormModel {
leagueId?: string;
basics: {
name: string;
description?: string;
visibility: 'public' | 'private' | 'unlisted';
gameId: string;
};
structure: {
mode: 'solo' | 'fixedTeams';
maxDrivers?: number;
maxTeams?: number;
driversPerTeam?: number;
};
championships: {
enableDriverChampionship: boolean;
enableTeamChampionship: boolean;
enableNationsChampionship: boolean;
enableTrophyChampionship: boolean;
};
scoring: {
patternId?: string;
customScoringEnabled?: boolean;
};
dropPolicy: {
strategy: 'none' | 'bestNResults' | 'dropWorstN';
n?: number;
};
timings: {
practiceMinutes?: number;
qualifyingMinutes?: number;
sprintRaceMinutes?: number;
mainRaceMinutes?: number;
sessionCount?: number;
roundsPlanned?: number;
raceDayOfWeek?: number;
raceTimeUtc?: string;
};
stewarding: {
decisionMode: 'owner_only' | 'admin_vote' | 'steward_panel';
requiredVotes?: number;
requireDefense: boolean;
defenseTimeLimit: number;
voteTimeLimit: number;
protestDeadlineHours: number;
stewardingClosesHours: number;
notifyAccusedOnProtest: boolean;
notifyOnVoteRequired: boolean;
};
}

View File

@@ -1,14 +0,0 @@
import { MembershipRole } from './MembershipRole';
import { MembershipStatus } from './MembershipStatus';
/**
* Lightweight league membership model for UI.
*/
export interface LeagueMembership {
id: string;
leagueId: string;
driverId: string;
role: MembershipRole;
status: MembershipStatus;
joinedAt: string;
}

View File

@@ -1,3 +0,0 @@
import { MembershipRole } from './MembershipRole';
export type LeagueRole = MembershipRole;

View File

@@ -1,15 +0,0 @@
export type LeagueScoringPresetPrimaryChampionshipType =
| 'driver'
| 'team'
| 'nations'
| 'trophy';
export interface LeagueScoringPresetDTO {
id: string;
name: string;
description: string;
primaryChampionshipType: LeagueScoringPresetPrimaryChampionshipType;
sessionSummary: string;
bonusSummary: string;
dropPolicySummary: string;
}

View File

@@ -1 +0,0 @@
export type MembershipRole = 'owner' | 'admin' | 'steward' | 'member';

View File

@@ -1 +0,0 @@
export type MembershipStatus = 'active' | 'inactive' | 'pending';

View File

@@ -1,21 +0,0 @@
export interface WizardErrors {
basics?: {
name?: string;
description?: string;
visibility?: string;
};
structure?: {
maxDrivers?: string;
maxTeams?: string;
driversPerTeam?: string;
};
timings?: {
qualifyingMinutes?: string;
mainRaceMinutes?: string;
roundsPlanned?: string;
};
scoring?: {
patternId?: string;
};
submit?: string;
}

View File

@@ -1 +0,0 @@
export type WizardStep = 1 | 2 | 3 | 4 | 5 | 6 | 7;

View File

@@ -0,0 +1,9 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface AcceptSponsorshipRequestInputDTO {
respondedBy: string;
}

View File

@@ -8,9 +8,5 @@ export interface ApplyPenaltyCommandDTO {
raceId: string;
driverId: string;
stewardId: string;
type: string;
value?: number;
reason: string;
protestId?: string;
notes?: string;
enum: string;
}

View File

@@ -0,0 +1,10 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface CreateTeamInputDTO {
name: string;
tag: string;
}

View File

@@ -0,0 +1,10 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface CreateTeamOutputDTO {
id: string;
success: boolean;
}

View File

@@ -0,0 +1,9 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface DeleteMediaOutputDTO {
success: boolean;
}

View File

@@ -6,8 +6,7 @@
export interface DriverDTO {
id: string;
iracingId: string;
name: string;
avatarUrl?: string;
iracingId?: string;
rating?: number;
}
country: string;
}

View File

@@ -0,0 +1,11 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface DriverProfileAchievementDTO {
id: string;
title: string;
description: string;
}

View File

@@ -4,11 +4,9 @@
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface RaceDetailEntryDTO {
export interface DriverProfileDriverSummaryDTO {
id: string;
name: string;
country: string;
avatarUrl: string;
rating: number | null;
isCurrentUser: boolean;
}

View File

@@ -0,0 +1,14 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface DriverProfileFinishDistributionDTO {
totalRaces: number;
wins: number;
podiums: number;
topTen: number;
dnfs: number;
other: number;
}

View File

@@ -0,0 +1,12 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface DriverProfileSocialFriendSummaryDTO {
id: string;
name: string;
country: string;
avatarUrl: string;
}

View File

@@ -0,0 +1,9 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface DriverProfileSocialSummaryDTO {
friendsCount: number;
}

View File

@@ -0,0 +1,12 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface DriverProfileStatsDTO {
totalRaces: number;
wins: number;
podiums: number;
dnfs: number;
}

View File

@@ -0,0 +1,10 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface DriverProfileTeamMembershipDTO {
teamId: string;
teamName: string;
}

View File

@@ -0,0 +1,22 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface TeamListItemDTO {
id: string;
name: string;
tag: string;
description: string;
memberCount: number;
leagues: string[];
specialization?: 'endurance' | 'sprint' | 'mixed';
region?: string;
languages?: string[];
}
export interface GetAllTeamsOutputDTO {
teams: TeamListItemDTO[];
totalCount: number;
}

View File

@@ -0,0 +1,12 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface GetAnalyticsMetricsOutputDTO {
pageViews: number;
uniqueVisitors: number;
averageSessionDuration: number;
bounceRate: number;
}

View File

@@ -4,6 +4,6 @@
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface GetAvatarOutputDto {
export interface GetAvatarOutputDTO {
avatarUrl: string;
}
}

View File

@@ -0,0 +1,12 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface GetDashboardDataOutputDTO {
totalUsers: number;
activeUsers: number;
totalRaces: number;
totalLeagues: number;
}

View File

@@ -0,0 +1,14 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface GetDriverOutputDTO {
id: string;
iracingId: string;
name: string;
country: string;
bio?: string;
joinedAt: string;
}

View File

@@ -0,0 +1,31 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface TeamDTO {
id: string;
name: string;
tag: string;
description: string;
ownerId: string;
leagues: string[];
createdAt?: string;
specialization?: 'endurance' | 'sprint' | 'mixed';
region?: string;
languages?: string[];
}
export interface MembershipDTO {
role: 'owner' | 'manager' | 'member';
joinedAt: string;
isActive: boolean;
}
export interface GetDriverTeamOutputDTO {
team: TeamDTO;
membership: MembershipDTO;
isOwner: boolean;
canManage: boolean;
}

View File

@@ -0,0 +1,15 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface GetMediaOutputDTO {
id: string;
url: string;
type: string;
category?: string;
/** Format: date-time */
uploadedAt: string;
size?: number;
}

View File

@@ -0,0 +1,10 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface GetPendingSponsorshipRequestsOutputDTO {
entityType: string;
entityId: string;
}

View File

@@ -0,0 +1,8 @@
export interface GetSponsorOutputDTO {
sponsor: {
id: string;
name: string;
logoUrl?: string;
websiteUrl?: string;
};
}

View File

@@ -0,0 +1,30 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface TeamDTO {
id: string;
name: string;
tag: string;
description: string;
ownerId: string;
leagues: string[];
createdAt?: string;
specialization?: 'endurance' | 'sprint' | 'mixed';
region?: string;
languages?: string[];
}
export interface MembershipDTO {
role: 'owner' | 'manager' | 'member';
joinedAt: string;
isActive: boolean;
}
export interface GetTeamDetailsOutputDTO {
team: TeamDTO;
membership: MembershipDTO | null;
canManage: boolean;
}

View File

@@ -0,0 +1,21 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface TeamJoinRequestDTO {
requestId: string;
driverId: string;
driverName: string;
teamId: string;
status: 'pending' | 'approved' | 'rejected';
requestedAt: string;
avatarUrl: string;
}
export interface GetTeamJoinRequestsOutputDTO {
requests: TeamJoinRequestDTO[];
pendingCount: number;
totalCount: number;
}

View File

@@ -0,0 +1,22 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface TeamMemberDTO {
driverId: string;
driverName: string;
role: 'owner' | 'manager' | 'member';
joinedAt: string;
isActive: boolean;
avatarUrl: string;
}
export interface GetTeamMembersOutputDTO {
members: TeamMemberDTO[];
totalCount: number;
ownerCount: number;
managerCount: number;
memberCount: number;
}

View File

@@ -0,0 +1,11 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface GetTeamMembershipOutputDTO {
role: string;
joinedAt: string;
isActive: boolean;
}

View File

@@ -0,0 +1,10 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface IracingAuthRedirectResult {
redirectUrl: string;
state: string;
}

View File

@@ -4,6 +4,20 @@
* Do not edit manually - regenerate using: npm run api:sync-types
*/
import type { LeagueConfigFormModelBasicsDTO } from './LeagueConfigFormModelBasicsDTO';
import type { LeagueConfigFormModelStructureDTO } from './LeagueConfigFormModelStructureDTO';
import type { LeagueConfigFormModelScoringDTO } from './LeagueConfigFormModelScoringDTO';
import type { LeagueConfigFormModelDropPolicyDTO } from './LeagueConfigFormModelDropPolicyDTO';
import type { LeagueConfigFormModelTimingsDTO } from './LeagueConfigFormModelTimingsDTO';
import type { LeagueConfigFormModelStewardingDTO } from './LeagueConfigFormModelStewardingDTO';
export interface LeagueConfigFormModelDTO {
leagueId: string;
basics: LeagueConfigFormModelBasicsDTO;
structure: LeagueConfigFormModelStructureDTO;
championships: any[];
scoring: LeagueConfigFormModelScoringDTO;
dropPolicy: LeagueConfigFormModelDropPolicyDTO;
timings: LeagueConfigFormModelTimingsDTO;
stewarding: LeagueConfigFormModelStewardingDTO;
}

View File

@@ -0,0 +1,10 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface LoginParams {
email: string;
password: string;
}

View File

@@ -0,0 +1,11 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface LoginWithIracingCallbackParams {
code: string;
state: string;
returnTo?: string;
}

View File

@@ -0,0 +1,12 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
import { RacePenaltyDTO } from './RacePenaltyDTO';
export interface RacePenaltiesDTO {
penalties: RacePenaltyDTO[];
driverMap: Record<string, string>;
}

View File

@@ -0,0 +1,5 @@
export interface RecordEngagementInputDTO {
eventType: string;
userId?: string;
metadata?: Record<string, unknown>;
}

View File

@@ -0,0 +1,4 @@
export interface RecordPageViewInputDTO {
path: string;
userId?: string;
}

View File

@@ -0,0 +1,9 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface RejectSponsorshipRequestInputDTO {
respondedBy: string;
}

View File

@@ -0,0 +1,11 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface ReviewProtestCommandDTO {
protestId: string;
stewardId: string;
enum: string;
}

View File

@@ -0,0 +1,14 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface SignupParams {
email: string;
password: string;
displayName: string;
iracingCustomerId?: string;
primaryDriverId?: string;
avatarUrl?: string;
}

View File

@@ -0,0 +1,10 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface SponsorDTO {
id: string;
name: string;
}

View File

@@ -0,0 +1,11 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface SponsorshipRequestDTO {
id: string;
sponsorId: string;
sponsorName: string;
}

View File

@@ -0,0 +1,10 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface UpdateAvatarInputDTO {
driverId: string;
avatarUrl: string;
}

View File

@@ -0,0 +1,9 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface UpdateAvatarOutputDTO {
success: boolean;
}

View File

@@ -0,0 +1,11 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface UpdateTeamInputDTO {
name?: string;
tag?: string;
description?: string;
}

View File

@@ -0,0 +1,9 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface UpdateTeamOutputDTO {
success: boolean;
}

View File

@@ -0,0 +1,9 @@
/**
* Auto-generated DTO from OpenAPI spec
* This file is generated by scripts/generate-api-types.ts
* Do not edit manually - regenerate using: npm run api:sync-types
*/
export interface UploadMediaOutputDTO {
success: boolean;
}

View File

@@ -1,5 +1,7 @@
import { LeagueMembershipService } from '@/lib/services/leagues/LeagueMembershipService';
import { LeagueRoleUtility } from '@/lib/utilities/LeagueRoleUtility';
import type { MembershipRole } from '@core/racing/domain/entities/MembershipRole';
type LeagueRole = MembershipRole;
export class LeagueMembershipUtility {
/**

View File

@@ -1,4 +1,5 @@
import { LeagueRole } from '@/lib/types/LeagueRole';
import type { MembershipRole } from '@core/racing/domain/entities/MembershipRole';
type LeagueRole = MembershipRole;
export class LeagueRoleUtility {
static isLeagueOwnerRole(role: LeagueRole): boolean {

View File

@@ -1,16 +1,16 @@
import type { DriverDTO } from '../types/DriverDTO';
import type { GetDriverOutputDTO } from '../types/generated/GetDriverOutputDTO';
/**
* View Model for driver summary with rating and rank
* Transform from DTO to ViewModel with UI fields
*/
export class DriverSummaryViewModel {
driver: DriverDTO;
driver: GetDriverOutputDTO;
rating: number | null;
rank: number | null;
constructor(dto: {
driver: DriverDTO;
driver: GetDriverOutputDTO;
rating?: number | null;
rank?: number | null;
}) {

View File

@@ -1,3 +1,5 @@
import type { GetDriverTeamOutputDTO } from '@/lib/types/generated/GetDriverTeamOutputDTO';
/**
* View Model for Driver's Team
*
@@ -6,21 +8,22 @@
export class DriverTeamViewModel {
teamId: string;
teamName: string;
tag: string;
role: string;
isOwner: boolean;
canManage: boolean;
constructor(dto: { teamId: string; teamName: string; role: string }) {
this.teamId = dto.teamId;
this.teamName = dto.teamName;
this.role = dto.role;
constructor(dto: GetDriverTeamOutputDTO) {
this.teamId = dto.team.id;
this.teamName = dto.team.name;
this.tag = dto.team.tag;
this.role = dto.membership.role;
this.isOwner = dto.isOwner;
this.canManage = dto.canManage;
}
/** UI-specific: Display role */
get displayRole(): string {
return this.role.charAt(0).toUpperCase() + this.role.slice(1);
}
/** UI-specific: Is owner */
get isOwner(): boolean {
return this.role === 'owner';
}
}

View File

@@ -1,20 +1,23 @@
// TODO: Create ImportRaceResultsSummaryDTO in apps/website/lib/types/generated when available
interface ImportRaceResultsSummaryDTO {
success: boolean;
raceId: string;
importedCount: number;
errors: string[];
driversProcessed: number;
resultsRecorded: number;
errors?: string[];
}
export class ImportRaceResultsSummaryViewModel {
success: boolean;
raceId: string;
importedCount: number;
driversProcessed: number;
resultsRecorded: number;
errors: string[];
constructor(dto: ImportRaceResultsSummaryDTO) {
this.success = dto.success;
this.raceId = dto.raceId;
this.importedCount = dto.importedCount;
this.errors = dto.errors;
this.driversProcessed = dto.driversProcessed;
this.resultsRecorded = dto.resultsRecorded;
this.errors = dto.errors || [];
}
// TODO: Add additional UI-specific fields when DTO is available
}

View File

@@ -3,7 +3,7 @@ import { LeagueStatsDTO } from '../types/generated/LeagueStatsDTO';
import { LeagueMembershipsDTO } from '../types/generated/LeagueMembershipsDTO';
import { LeagueScheduleDTO } from '../types/generated/LeagueScheduleDTO';
import { LeagueStandingsDTO } from '../types/generated/LeagueStandingsDTO';
import { DriverDTO } from '../types/DriverDTO';
import { GetDriverOutputDTO } from '../types/generated/GetDriverOutputDTO';
import { RaceDTO } from '../types/generated/RaceDTO';
import { LeagueScoringConfigDTO } from '../types/LeagueScoringConfigDTO';
import { RaceViewModel } from './RaceViewModel';
@@ -20,7 +20,7 @@ export interface SponsorInfo {
// Driver summary for management section
export interface DriverSummary {
driver: DriverDTO;
driver: GetDriverOutputDTO;
rating: number | null;
rank: number | null;
}
@@ -50,13 +50,13 @@ export class LeagueDetailPageViewModel {
};
// Owner info
owner: DriverDTO | null;
owner: GetDriverOutputDTO | null;
// Scoring configuration
scoringConfig: LeagueScoringConfigDTO | null;
// Drivers and memberships
drivers: DriverDTO[];
drivers: GetDriverOutputDTO[];
memberships: LeagueMembershipWithRole[];
// Races
@@ -93,9 +93,9 @@ export class LeagueDetailPageViewModel {
constructor(
league: LeagueWithCapacityDTO,
owner: DriverDTO | null,
owner: GetDriverOutputDTO | null,
scoringConfig: LeagueScoringConfigDTO | null,
drivers: DriverDTO[],
drivers: GetDriverOutputDTO[],
memberships: LeagueMembershipsDTO,
allRaces: RaceViewModel[],
leagueStats: LeagueStatsDTO,

View File

@@ -1,4 +1,4 @@
import type { LeagueScoringPresetDTO } from '../types/LeagueScoringPresetDTO';
import type { LeagueScoringPresetDTO } from '@core/racing/application/ports/LeagueScoringPresetProvider';
/**
* View Model for league scoring presets

View File

@@ -1,5 +1,5 @@
import type { LeagueConfigFormModel } from '../types/LeagueConfigFormModel';
import type { LeagueScoringPresetDTO } from '../types/LeagueScoringPresetDTO';
import type { LeagueConfigFormModel } from '@core/racing/application';
import type { LeagueScoringPresetDTO } from '@core/racing/application/ports/LeagueScoringPresetProvider';
import { LeagueScoringPresetsViewModel } from './LeagueScoringPresetsViewModel';
import { DriverSummaryViewModel } from './DriverSummaryViewModel';

View File

@@ -1,14 +1,14 @@
import { LeagueStandingDTO } from '../types/generated/LeagueStandingDTO';
import { StandingEntryViewModel } from './StandingEntryViewModel';
import { DriverDTO } from '../types/DriverDTO';
import { GetDriverOutputDTO } from '../types/generated/GetDriverOutputDTO';
import { LeagueMembership } from '../types/LeagueMembership';
export class LeagueStandingsViewModel {
standings: StandingEntryViewModel[];
drivers: DriverDTO[];
drivers: GetDriverOutputDTO[];
memberships: LeagueMembership[];
constructor(dto: { standings: LeagueStandingDTO[]; drivers: DriverDTO[]; memberships: LeagueMembership[] }, currentUserId: string, previousStandings?: LeagueStandingDTO[]) {
constructor(dto: { standings: LeagueStandingDTO[]; drivers: GetDriverOutputDTO[]; memberships: LeagueMembership[] }, currentUserId: string, previousStandings?: LeagueStandingDTO[]) {
const leaderPoints = dto.standings[0]?.points || 0;
this.standings = dto.standings.map((entry, index) => {
const nextPoints = dto.standings[index + 1]?.points || entry.points;

View File

@@ -6,9 +6,9 @@ describe('MediaViewModel', () => {
const dto = {
id: 'media-123',
url: 'https://example.com/image.jpg',
type: 'image' as const,
category: 'avatar' as const,
uploadedAt: new Date('2023-01-15'),
type: 'image',
category: 'avatar',
uploadedAt: '2023-01-15T00:00:00.000Z',
size: 2048000,
};
@@ -26,8 +26,8 @@ describe('MediaViewModel', () => {
const dto = {
id: 'media-123',
url: 'https://example.com/image.jpg',
type: 'image' as const,
uploadedAt: new Date('2023-01-15'),
type: 'image',
uploadedAt: '2023-01-15T00:00:00.000Z',
};
const viewModel = new MediaViewModel(dto);
@@ -41,7 +41,7 @@ describe('MediaViewModel', () => {
id: 'media-123',
url: 'https://example.com/image.jpg',
type: 'image',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
});
expect(viewModel.formattedSize).toBe('Unknown');
@@ -52,7 +52,7 @@ describe('MediaViewModel', () => {
id: 'media-123',
url: 'https://example.com/image.jpg',
type: 'image',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
size: 512000, // 500 KB
});
@@ -64,7 +64,7 @@ describe('MediaViewModel', () => {
id: 'media-123',
url: 'https://example.com/image.jpg',
type: 'image',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
size: 2048000, // 2 MB
});
@@ -76,7 +76,7 @@ describe('MediaViewModel', () => {
id: 'media-123',
url: 'https://example.com/image.jpg',
type: 'image',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
size: 1024, // 1 KB
});
@@ -88,7 +88,7 @@ describe('MediaViewModel', () => {
id: 'media-123',
url: 'https://example.com/video.mp4',
type: 'video',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
size: 104857600, // 100 MB
});
@@ -100,19 +100,19 @@ describe('MediaViewModel', () => {
id: '1',
url: 'image.jpg',
type: 'image',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
});
const videoVm = new MediaViewModel({
id: '2',
url: 'video.mp4',
type: 'video',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
});
const docVm = new MediaViewModel({
id: '3',
url: 'doc.pdf',
type: 'document',
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
});
expect(imageVm.type).toBe('image');
@@ -129,7 +129,7 @@ describe('MediaViewModel', () => {
url: 'https://example.com/image.jpg',
type: 'image',
category,
uploadedAt: new Date(),
uploadedAt: new Date().toISOString(),
});
expect(viewModel.category).toBe(category);

View File

@@ -1,12 +1,4 @@
// Note: No generated DTO available for Media yet
interface MediaDTO {
id: string;
url: string;
type: 'image' | 'video' | 'document';
category?: 'avatar' | 'team-logo' | 'league-cover' | 'race-result';
uploadedAt: Date;
size?: number;
}
import type { GetMediaOutputDTO } from '../types/generated';
/**
* Media View Model
@@ -21,12 +13,12 @@ export class MediaViewModel {
uploadedAt: Date;
size?: number;
constructor(dto: MediaDTO) {
constructor(dto: GetMediaOutputDTO) {
this.id = dto.id;
this.url = dto.url;
this.type = dto.type;
this.uploadedAt = dto.uploadedAt;
if (dto.category !== undefined) this.category = dto.category;
this.type = dto.type as 'image' | 'video' | 'document';
this.uploadedAt = new Date(dto.uploadedAt);
if (dto.category !== undefined) this.category = dto.category as 'avatar' | 'team-logo' | 'league-cover' | 'race-result';
if (dto.size !== undefined) this.size = dto.size;
}

View File

@@ -1,70 +1,53 @@
import { TeamMemberViewModel } from './TeamMemberViewModel';
// Note: No generated DTO available for TeamDetails yet
interface DriverDTO {
id: string;
name: string;
avatarUrl?: string;
iracingId?: string;
rating?: number;
}
interface TeamMemberDTO {
driverId: string;
driver?: DriverDTO;
role: string;
joinedAt: string;
}
interface TeamDetailsDTO {
id: string;
name: string;
description?: string;
logoUrl?: string;
memberCount: number;
ownerId: string;
members: TeamMemberDTO[];
}
import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO';
export class TeamDetailsViewModel {
id: string;
name: string;
tag: string;
description?: string;
logoUrl?: string;
memberCount: number;
ownerId: string;
members: TeamMemberViewModel[];
leagues: string[];
createdAt?: string;
specialization?: string;
region?: string;
languages?: string[];
membership: { role: string; joinedAt: string; isActive: boolean } | null;
canManage: boolean;
private currentUserId: string;
constructor(dto: TeamDetailsDTO, currentUserId: string) {
this.id = dto.id;
this.name = dto.name;
this.description = dto.description;
this.logoUrl = dto.logoUrl;
this.memberCount = dto.memberCount;
this.ownerId = dto.ownerId;
this.members = dto.members.map(m => new TeamMemberViewModel(m, currentUserId, dto.ownerId));
constructor(dto: GetTeamDetailsOutputDTO, currentUserId: string) {
this.id = dto.team.id;
this.name = dto.team.name;
this.tag = dto.team.tag;
this.description = dto.team.description;
this.ownerId = dto.team.ownerId;
this.leagues = dto.team.leagues;
this.createdAt = dto.team.createdAt;
this.specialization = dto.team.specialization;
this.region = dto.team.region;
this.languages = dto.team.languages;
this.membership = dto.membership;
this.canManage = dto.canManage;
this.currentUserId = currentUserId;
}
/** UI-specific: Whether current user is owner */
get isOwner(): boolean {
return this.currentUserId === this.ownerId;
return this.membership?.role === 'owner';
}
/** UI-specific: Whether can add members */
get canAddMembers(): boolean {
return this.isOwner && this.memberCount < 10; // Assuming max 10
/** UI-specific: Whether can manage team */
get canManage(): boolean {
return this.canManage;
}
/** UI-specific: Member management actions available */
get memberActionsAvailable(): boolean {
return this.isOwner;
/** UI-specific: Whether current user is member */
get isMember(): boolean {
return this.membership !== null;
}
/** UI-specific: Team status */
get teamStatus(): string {
if (this.memberCount < 5) return 'Recruiting';
if (this.memberCount < 10) return 'Active';
return 'Full';
/** UI-specific: Current user's role */
get userRole(): string {
return this.membership?.role || 'none';
}
}

Some files were not shown because too many files have changed in this diff Show More