website cleanup
This commit is contained in:
@@ -14,6 +14,7 @@ import { PenaltiesApiClient } from '../api/penalties/PenaltiesApiClient';
|
||||
import { PenaltyService } from './penalties/PenaltyService';
|
||||
import { ConsoleErrorReporter } from '../infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '../infrastructure/logging/ConsoleLogger';
|
||||
import { LandingService } from './landing/LandingService';
|
||||
|
||||
// Services
|
||||
import { RaceService } from './races/RaceService';
|
||||
@@ -88,6 +89,23 @@ export class ServiceFactory {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy compatibility: older pages/components were written against a static ServiceFactory.
|
||||
* Prefer `useServices()` + react-query hooks.
|
||||
*/
|
||||
private static defaultInstance: ServiceFactory | null = null;
|
||||
|
||||
private static getDefaultInstance(): ServiceFactory {
|
||||
if (!this.defaultInstance) {
|
||||
this.defaultInstance = new ServiceFactory(process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001');
|
||||
}
|
||||
return this.defaultInstance;
|
||||
}
|
||||
|
||||
static getSponsorService(): SponsorService {
|
||||
return this.getDefaultInstance().createSponsorService();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create RaceService instance
|
||||
*/
|
||||
@@ -151,8 +169,8 @@ export class ServiceFactory {
|
||||
/**
|
||||
* Create LeagueMembershipService instance
|
||||
*/
|
||||
createLeagueMembershipService(): LeagueMembershipService {
|
||||
return new LeagueMembershipService(this.apiClients.leagues);
|
||||
createLeagueMembershipService(): LeagueMembershipService {
|
||||
return new LeagueMembershipService();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,4 +297,11 @@ export class ServiceFactory {
|
||||
createPenaltyService(): PenaltyService {
|
||||
return new PenaltyService(this.apiClients.penalties);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create LandingService instance (used by server components)
|
||||
*/
|
||||
createLandingService(): LandingService {
|
||||
return new LandingService(this.apiClients.races, this.apiClients.leagues, this.apiClients.teams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
'use client';
|
||||
|
||||
import React, { createContext, useContext, useMemo, ReactNode } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { createContext, ReactNode, useContext, useMemo } from 'react';
|
||||
import { ServiceFactory } from './ServiceFactory';
|
||||
|
||||
// Import all service types
|
||||
import { RaceService } from './races/RaceService';
|
||||
import { RaceResultsService } from './races/RaceResultsService';
|
||||
import { RaceStewardingService } from './races/RaceStewardingService';
|
||||
import { DriverService } from './drivers/DriverService';
|
||||
import { AnalyticsService } from './analytics/AnalyticsService';
|
||||
import { AuthService } from './auth/AuthService';
|
||||
import { SessionService } from './auth/SessionService';
|
||||
import { DashboardService } from './dashboard/DashboardService';
|
||||
import { DriverRegistrationService } from './drivers/DriverRegistrationService';
|
||||
import { TeamService } from './teams/TeamService';
|
||||
import { TeamJoinService } from './teams/TeamJoinService';
|
||||
import { LeagueService } from './leagues/LeagueService';
|
||||
import { DriverService } from './drivers/DriverService';
|
||||
import { LeagueMembershipService } from './leagues/LeagueMembershipService';
|
||||
import { LeagueService } from './leagues/LeagueService';
|
||||
import { LeagueSettingsService } from './leagues/LeagueSettingsService';
|
||||
import { LeagueStewardingService } from './leagues/LeagueStewardingService';
|
||||
import { LeagueWalletService } from './leagues/LeagueWalletService';
|
||||
import { AvatarService } from './media/AvatarService';
|
||||
import { MediaService } from './media/MediaService';
|
||||
import { MembershipFeeService } from './payments/MembershipFeeService';
|
||||
import { PaymentService } from './payments/PaymentService';
|
||||
import { WalletService } from './payments/WalletService';
|
||||
import { PenaltyService } from './penalties/PenaltyService';
|
||||
import { ProtestService } from './protests/ProtestService';
|
||||
import { RaceResultsService } from './races/RaceResultsService';
|
||||
import { RaceService } from './races/RaceService';
|
||||
import { RaceStewardingService } from './races/RaceStewardingService';
|
||||
import { SponsorService } from './sponsors/SponsorService';
|
||||
import { SponsorshipService } from './sponsors/SponsorshipService';
|
||||
import { PaymentService } from './payments/PaymentService';
|
||||
import { AnalyticsService } from './analytics/AnalyticsService';
|
||||
import { DashboardService } from './dashboard/DashboardService';
|
||||
import { MediaService } from './media/MediaService';
|
||||
import { AvatarService } from './media/AvatarService';
|
||||
import { WalletService } from './payments/WalletService';
|
||||
import { MembershipFeeService } from './payments/MembershipFeeService';
|
||||
import { AuthService } from './auth/AuthService';
|
||||
import { SessionService } from './auth/SessionService';
|
||||
import { ProtestService } from './protests/ProtestService';
|
||||
import { PenaltyService } from './penalties/PenaltyService';
|
||||
import { TeamJoinService } from './teams/TeamJoinService';
|
||||
import { TeamService } from './teams/TeamService';
|
||||
|
||||
export interface Services {
|
||||
raceService: RaceService;
|
||||
@@ -115,7 +115,7 @@ export function ServiceProvider({ children }: ServiceProviderProps) {
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
// Before using this check for enhanced hooks that use react-query
|
||||
export function useServices(): Services {
|
||||
const services = useContext(ServicesContext);
|
||||
if (!services) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { AuthApiClient } from '../../api/auth/AuthApiClient';
|
||||
import { SessionViewModel } from '../../view-models/SessionViewModel';
|
||||
import type { LoginParams } from '../../types/generated/LoginParams';
|
||||
import type { SignupParams } from '../../types/generated/SignupParams';
|
||||
import type { LoginWithIracingCallbackParams } from '../../types/generated/LoginWithIracingCallbackParams';
|
||||
import type { LoginParamsDTO } from '../../types/generated/LoginParamsDTO';
|
||||
import type { SignupParamsDTO } from '../../types/generated/SignupParamsDTO';
|
||||
import type { LoginWithIracingCallbackParamsDTO } from '../../types/generated/LoginWithIracingCallbackParamsDTO';
|
||||
|
||||
/**
|
||||
* Auth Service
|
||||
@@ -18,7 +18,7 @@ export class AuthService {
|
||||
/**
|
||||
* Sign up a new user
|
||||
*/
|
||||
async signup(params: SignupParams): Promise<SessionViewModel> {
|
||||
async signup(params: SignupParamsDTO): Promise<SessionViewModel> {
|
||||
try {
|
||||
const dto = await this.apiClient.signup(params);
|
||||
return new SessionViewModel(dto.user);
|
||||
@@ -30,7 +30,7 @@ export class AuthService {
|
||||
/**
|
||||
* Log in an existing user
|
||||
*/
|
||||
async login(params: LoginParams): Promise<SessionViewModel> {
|
||||
async login(params: LoginParamsDTO): Promise<SessionViewModel> {
|
||||
try {
|
||||
const dto = await this.apiClient.login(params);
|
||||
return new SessionViewModel(dto.user);
|
||||
@@ -60,7 +60,7 @@ export class AuthService {
|
||||
/**
|
||||
* Login with iRacing callback
|
||||
*/
|
||||
async loginWithIracingCallback(params: LoginWithIracingCallbackParams): Promise<SessionViewModel> {
|
||||
async loginWithIracingCallback(params: LoginWithIracingCallbackParamsDTO): Promise<SessionViewModel> {
|
||||
try {
|
||||
const dto = await this.apiClient.loginWithIracingCallback(params);
|
||||
return new SessionViewModel(dto.user);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { DriversApiClient } from "@/lib/api/drivers/DriversApiClient";
|
||||
import { CompleteOnboardingInputDTO } from "@/lib/types/generated/CompleteOnboardingInputDTO";
|
||||
import { DriverProfileDTO } from "@/lib/types/generated/DriverProfileDTO";
|
||||
import type { DriverDTO } from "@/lib/types/generated/DriverDTO";
|
||||
import { DriversApiClient } from '@/lib/api/drivers/DriversApiClient';
|
||||
import type { CompleteOnboardingInputDTO } from '@/lib/types/generated/CompleteOnboardingInputDTO';
|
||||
import type { GetDriverOutputDTO } from '@/lib/types/generated/GetDriverOutputDTO';
|
||||
import { CompleteOnboardingViewModel } from "@/lib/view-models/CompleteOnboardingViewModel";
|
||||
import { DriverLeaderboardViewModel } from "@/lib/view-models/DriverLeaderboardViewModel";
|
||||
import { DriverViewModel } from "@/lib/view-models/DriverViewModel";
|
||||
@@ -50,7 +49,92 @@ export class DriverService {
|
||||
*/
|
||||
async getDriverProfile(driverId: string): Promise<DriverProfileViewModel> {
|
||||
const dto = await this.apiClient.getDriverProfile(driverId);
|
||||
return new DriverProfileViewModel(dto);
|
||||
return new DriverProfileViewModel({
|
||||
currentDriver: dto.currentDriver
|
||||
? {
|
||||
id: dto.currentDriver.id,
|
||||
name: dto.currentDriver.name,
|
||||
country: dto.currentDriver.country,
|
||||
avatarUrl: dto.currentDriver.avatarUrl,
|
||||
iracingId: dto.currentDriver.iracingId ?? null,
|
||||
joinedAt: dto.currentDriver.joinedAt,
|
||||
rating: dto.currentDriver.rating ?? null,
|
||||
globalRank: dto.currentDriver.globalRank ?? null,
|
||||
consistency: dto.currentDriver.consistency ?? null,
|
||||
bio: dto.currentDriver.bio ?? null,
|
||||
totalDrivers: dto.currentDriver.totalDrivers ?? null,
|
||||
}
|
||||
: null,
|
||||
stats: dto.stats
|
||||
? {
|
||||
totalRaces: dto.stats.totalRaces,
|
||||
wins: dto.stats.wins,
|
||||
podiums: dto.stats.podiums,
|
||||
dnfs: dto.stats.dnfs,
|
||||
avgFinish: dto.stats.avgFinish ?? null,
|
||||
bestFinish: dto.stats.bestFinish ?? null,
|
||||
worstFinish: dto.stats.worstFinish ?? null,
|
||||
finishRate: dto.stats.finishRate ?? null,
|
||||
winRate: dto.stats.winRate ?? null,
|
||||
podiumRate: dto.stats.podiumRate ?? null,
|
||||
percentile: dto.stats.percentile ?? null,
|
||||
rating: dto.stats.rating ?? null,
|
||||
consistency: dto.stats.consistency ?? null,
|
||||
overallRank: dto.stats.overallRank ?? null,
|
||||
}
|
||||
: null,
|
||||
finishDistribution: dto.finishDistribution
|
||||
? {
|
||||
totalRaces: dto.finishDistribution.totalRaces,
|
||||
wins: dto.finishDistribution.wins,
|
||||
podiums: dto.finishDistribution.podiums,
|
||||
topTen: dto.finishDistribution.topTen,
|
||||
dnfs: dto.finishDistribution.dnfs,
|
||||
other: dto.finishDistribution.other,
|
||||
}
|
||||
: null,
|
||||
teamMemberships: dto.teamMemberships.map((m) => ({
|
||||
teamId: m.teamId,
|
||||
teamName: m.teamName,
|
||||
teamTag: m.teamTag ?? null,
|
||||
role: m.role,
|
||||
joinedAt: m.joinedAt,
|
||||
isCurrent: m.isCurrent,
|
||||
})),
|
||||
socialSummary: {
|
||||
friendsCount: dto.socialSummary.friendsCount,
|
||||
friends: dto.socialSummary.friends.map((f) => ({
|
||||
id: f.id,
|
||||
name: f.name,
|
||||
country: f.country,
|
||||
avatarUrl: f.avatarUrl,
|
||||
})),
|
||||
},
|
||||
extendedProfile: dto.extendedProfile
|
||||
? {
|
||||
socialHandles: dto.extendedProfile.socialHandles.map((h) => ({
|
||||
platform: h.platform as any,
|
||||
handle: h.handle,
|
||||
url: h.url,
|
||||
})),
|
||||
achievements: dto.extendedProfile.achievements.map((a) => ({
|
||||
id: a.id,
|
||||
title: a.title,
|
||||
description: a.description,
|
||||
icon: a.icon as any,
|
||||
rarity: a.rarity as any,
|
||||
earnedAt: a.earnedAt,
|
||||
})),
|
||||
racingStyle: dto.extendedProfile.racingStyle,
|
||||
favoriteTrack: dto.extendedProfile.favoriteTrack,
|
||||
favoriteCar: dto.extendedProfile.favoriteCar,
|
||||
timezone: dto.extendedProfile.timezone,
|
||||
availableHours: dto.extendedProfile.availableHours,
|
||||
lookingForTeam: dto.extendedProfile.lookingForTeam,
|
||||
openToRequests: dto.extendedProfile.openToRequests,
|
||||
}
|
||||
: null,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,15 +149,15 @@ export class DriverService {
|
||||
/**
|
||||
* Find driver by ID
|
||||
*/
|
||||
async findById(id: string): Promise<DriverDTO | null> {
|
||||
async findById(id: string): Promise<GetDriverOutputDTO | null> {
|
||||
return this.apiClient.getDriver(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find multiple drivers by IDs
|
||||
*/
|
||||
async findByIds(ids: string[]): Promise<DriverDTO[]> {
|
||||
async findByIds(ids: string[]): Promise<GetDriverOutputDTO[]> {
|
||||
const drivers = await Promise.all(ids.map(id => this.apiClient.getDriver(id)));
|
||||
return drivers.filter((d): d is DriverDTO => d !== null);
|
||||
return drivers.filter((d): d is GetDriverOutputDTO => d !== null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,17 @@
|
||||
import { RacesApiClient } from '@/lib/api/races/RacesApiClient';
|
||||
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
|
||||
import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient';
|
||||
import type { AllLeaguesWithCapacityDTO } from '@/lib/types/generated/AllLeaguesWithCapacityDTO';
|
||||
import type { GetAllTeamsOutputDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO';
|
||||
import type { RacesPageDataDTO } from '@/lib/types/generated/RacesPageDataDTO';
|
||||
import type { LeagueSummaryDTO } from '@/lib/types/generated/LeagueSummaryDTO';
|
||||
import type { GetAllTeamsOutputDTO, TeamListItemDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO';
|
||||
import type { TeamListItemDTO } from '@/lib/types/generated/TeamListItemDTO';
|
||||
import { RacesPageViewModel } from '@/lib/view-models/RacesPageViewModel';
|
||||
import { HomeDiscoveryViewModel } from '@/lib/view-models/HomeDiscoveryViewModel';
|
||||
import { LeagueCardViewModel } from '@/lib/view-models/LeagueCardViewModel';
|
||||
import { TeamCardViewModel } from '@/lib/view-models/TeamCardViewModel';
|
||||
import { UpcomingRaceCardViewModel } from '@/lib/view-models/UpcomingRaceCardViewModel';
|
||||
|
||||
// DTO matching backend RacesPageDataDTO for discovery usage
|
||||
interface RacesPageDataDTO {
|
||||
races: {
|
||||
id: string;
|
||||
track: string;
|
||||
car: string;
|
||||
scheduledAt: string;
|
||||
status: string;
|
||||
leagueId: string;
|
||||
leagueName: string;
|
||||
strengthOfField: number | null;
|
||||
isUpcoming: boolean;
|
||||
isLive: boolean;
|
||||
isPast: boolean;
|
||||
}[];
|
||||
}
|
||||
|
||||
export class LandingService {
|
||||
constructor(
|
||||
private readonly racesApi: RacesApiClient,
|
||||
@@ -36,21 +22,21 @@ export class LandingService {
|
||||
async getHomeDiscovery(): Promise<HomeDiscoveryViewModel> {
|
||||
const [racesDto, leaguesDto, teamsDto] = await Promise.all([
|
||||
this.racesApi.getPageData() as Promise<RacesPageDataDTO>,
|
||||
this.leaguesApi.getAllWithCapacity() as Promise<{ leagues: LeagueSummaryDTO[] }>,
|
||||
this.teamsApi.getAll(),
|
||||
this.leaguesApi.getAllWithCapacity() as Promise<AllLeaguesWithCapacityDTO>,
|
||||
this.teamsApi.getAll() as Promise<GetAllTeamsOutputDTO>,
|
||||
]);
|
||||
|
||||
const racesVm = new RacesPageViewModel(racesDto);
|
||||
|
||||
const topLeagues = leaguesDto.leagues.slice(0, 4).map(
|
||||
league => new LeagueCardViewModel({
|
||||
(league: LeagueSummaryDTO) => new LeagueCardViewModel({
|
||||
id: league.id,
|
||||
name: league.name,
|
||||
description: 'Competitive iRacing league',
|
||||
}),
|
||||
);
|
||||
|
||||
const teams = (teamsDto as GetAllTeamsOutputDTO).teams.slice(0, 4).map(
|
||||
const teams = teamsDto.teams.slice(0, 4).map(
|
||||
(team: TeamListItemDTO) =>
|
||||
new TeamCardViewModel({
|
||||
id: team.id,
|
||||
|
||||
@@ -47,6 +47,26 @@ export class LeagueMembershipService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Join a league.
|
||||
*
|
||||
* NOTE: The API currently exposes membership mutations via league member management endpoints.
|
||||
* For now we keep the website decoupled by consuming only the API through this service.
|
||||
*/
|
||||
async joinLeague(leagueId: string, driverId: string): Promise<void> {
|
||||
// Temporary: no join endpoint exposed yet in API.
|
||||
// Keep behavior predictable for UI.
|
||||
throw new Error('Joining leagues is not available in this build.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Leave a league.
|
||||
*/
|
||||
async leaveLeague(leagueId: string, driverId: string): Promise<void> {
|
||||
// Temporary: no leave endpoint exposed yet in API.
|
||||
throw new Error('Leaving leagues is not available in this build.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set memberships in cache (for use after API calls).
|
||||
*/
|
||||
@@ -118,4 +138,4 @@ export class LeagueMembershipService {
|
||||
clearLeagueMemberships(leagueId: string): void {
|
||||
LeagueMembershipService.clearLeagueMemberships(leagueId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import { SubmitBlocker, ThrottleBlocker } from "@/lib/blockers";
|
||||
import { RaceDTO } from "@/lib/types/generated/RaceDTO";
|
||||
import { LeagueStatsDTO } from "@/lib/types/generated/LeagueStatsDTO";
|
||||
import { LeagueScoringConfigDTO } from "@/lib/types/LeagueScoringConfigDTO";
|
||||
import type { LeagueMembership } from "@/lib/types/LeagueMembership";
|
||||
import type { LeagueSeasonSummaryDTO } from '@/lib/types/generated/LeagueSeasonSummaryDTO';
|
||||
|
||||
|
||||
/**
|
||||
@@ -48,9 +50,9 @@ export class LeagueService {
|
||||
name: league.name,
|
||||
description: league.description ?? '',
|
||||
ownerId: league.ownerId,
|
||||
createdAt: '', // Not provided by API
|
||||
maxDrivers: league.maxMembers,
|
||||
usedDriverSlots: league.memberCount,
|
||||
createdAt: league.createdAt,
|
||||
maxDrivers: league.settings.maxDrivers ?? 0,
|
||||
usedDriverSlots: league.usedSlots,
|
||||
structureSummary: 'TBD',
|
||||
timingSummary: 'TBD'
|
||||
}));
|
||||
@@ -66,16 +68,20 @@ export class LeagueService {
|
||||
// League memberships (roles, statuses)
|
||||
const membershipsDto = await this.apiClient.getMemberships(leagueId);
|
||||
|
||||
const memberships: LeagueMembership[] = membershipsDto.members.map((m) => ({
|
||||
driverId: m.driverId,
|
||||
leagueId,
|
||||
role: (m.role as LeagueMembership['role']) ?? 'member',
|
||||
joinedAt: m.joinedAt,
|
||||
status: 'active',
|
||||
}));
|
||||
|
||||
// Resolve unique drivers that appear in standings
|
||||
const driverIds: string[] = Array.from(new Set(dto.standings.map((entry: any) => entry.driverId)));
|
||||
const driverDtos = await Promise.all(driverIds.map((id: string) => this.driversApiClient.getDriver(id)));
|
||||
const drivers = driverDtos.filter((d): d is NonNullable<typeof d> => d !== null);
|
||||
|
||||
const dtoWithExtras = {
|
||||
standings: dto.standings,
|
||||
drivers,
|
||||
memberships: membershipsDto.members,
|
||||
};
|
||||
const dtoWithExtras = { standings: dto.standings, drivers, memberships };
|
||||
|
||||
return new LeagueStandingsViewModel(dtoWithExtras, currentUserId);
|
||||
}
|
||||
@@ -96,6 +102,13 @@ export class LeagueService {
|
||||
return new LeagueScheduleViewModel(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get seasons for a league
|
||||
*/
|
||||
async getLeagueSeasons(leagueId: string): Promise<LeagueSeasonSummaryDTO[]> {
|
||||
return this.apiClient.getSeasons(leagueId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get league memberships
|
||||
*/
|
||||
@@ -162,18 +175,19 @@ export class LeagueService {
|
||||
// Get membership
|
||||
const membershipsDto = await this.apiClient.getMemberships(leagueId);
|
||||
const membership = membershipsDto.members.find((m: any) => m.driverId === currentDriverId);
|
||||
const isAdmin = membership ? ['admin', 'owner'].includes(membership.role) : false;
|
||||
const isAdmin = membership ? ['admin', 'owner'].includes((membership as any).role) : false;
|
||||
|
||||
// Get main sponsor
|
||||
let mainSponsor = null;
|
||||
try {
|
||||
const seasonsDto = await this.apiClient.getSeasons(leagueId);
|
||||
const activeSeason = seasonsDto.seasons.find((s: any) => s.status === 'active') ?? seasonsDto.seasons[0];
|
||||
const seasons = await this.apiClient.getSeasons(leagueId);
|
||||
const activeSeason = seasons.find((s) => s.status === 'active') ?? seasons[0];
|
||||
if (activeSeason) {
|
||||
const sponsorshipsDto = await this.apiClient.getSeasonSponsorships(activeSeason.id);
|
||||
const sponsorshipsDto = await this.apiClient.getSeasonSponsorships(activeSeason.seasonId);
|
||||
const mainSponsorship = sponsorshipsDto.sponsorships.find((s: any) => s.tier === 'main' && s.status === 'active');
|
||||
if (mainSponsorship) {
|
||||
const sponsor = await this.sponsorsApiClient.getSponsor(mainSponsorship.sponsorId);
|
||||
const sponsorResult = await this.sponsorsApiClient.getSponsor((mainSponsorship as any).sponsorId ?? (mainSponsorship as any).sponsor?.id);
|
||||
const sponsor = (sponsorResult as any)?.sponsor ?? null;
|
||||
if (sponsor) {
|
||||
mainSponsor = {
|
||||
name: sponsor.name,
|
||||
@@ -241,7 +255,11 @@ export class LeagueService {
|
||||
const allRaces = leagueRaces.races.map(r => new RaceViewModel(r as RaceDTO));
|
||||
|
||||
// League stats endpoint currently returns global league statistics rather than per-league values
|
||||
const leagueStats = await this.apiClient.getTotal();
|
||||
const leagueStats: LeagueStatsDTO = {
|
||||
totalMembers: league.usedSlots,
|
||||
totalRaces: allRaces.length,
|
||||
averageRating: 0,
|
||||
};
|
||||
|
||||
// Get sponsors
|
||||
const sponsors = await this.getLeagueSponsors(leagueId);
|
||||
@@ -268,16 +286,17 @@ export class LeagueService {
|
||||
private async getLeagueSponsors(leagueId: string): Promise<SponsorInfo[]> {
|
||||
try {
|
||||
const seasons = await this.apiClient.getSeasons(leagueId);
|
||||
const activeSeason = seasons.seasons.find((s: any) => s.status === 'active') ?? seasons.seasons[0];
|
||||
const activeSeason = seasons.find((s) => s.status === 'active') ?? seasons[0];
|
||||
|
||||
if (!activeSeason) return [];
|
||||
|
||||
const sponsorships = await this.apiClient.getSeasonSponsorships(activeSeason.id);
|
||||
const sponsorships = await this.apiClient.getSeasonSponsorships(activeSeason.seasonId);
|
||||
const activeSponsorships = sponsorships.sponsorships.filter((s: any) => s.status === 'active');
|
||||
|
||||
const sponsorInfos: SponsorInfo[] = [];
|
||||
for (const sponsorship of activeSponsorships) {
|
||||
const sponsor = await this.sponsorsApiClient.getSponsor(sponsorship.sponsorId);
|
||||
const sponsorResult = await this.sponsorsApiClient.getSponsor((sponsorship as any).sponsorId ?? (sponsorship as any).sponsor?.id);
|
||||
const sponsor = (sponsorResult as any)?.sponsor ?? null;
|
||||
if (sponsor) {
|
||||
// Tagline is not supplied by the sponsor API in this build; callers may derive one from marketing content if needed
|
||||
sponsorInfos.push({
|
||||
@@ -285,7 +304,7 @@ export class LeagueService {
|
||||
name: sponsor.name,
|
||||
logoUrl: sponsor.logoUrl ?? '',
|
||||
websiteUrl: sponsor.websiteUrl ?? '',
|
||||
tier: sponsorship.tier,
|
||||
tier: ((sponsorship as any).tier as 'main' | 'secondary') ?? 'secondary',
|
||||
tagline: '',
|
||||
});
|
||||
}
|
||||
@@ -312,4 +331,4 @@ export class LeagueService {
|
||||
const result = await this.apiClient.getScoringPresets();
|
||||
return result.presets;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { LeaguesApiClient } from "@/lib/api/leagues/LeaguesApiClient";
|
||||
import { DriversApiClient } from "@/lib/api/drivers/DriversApiClient";
|
||||
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 type { LeagueConfigFormModel } from "@/lib/types/LeagueConfigFormModel";
|
||||
import type { LeagueScoringPresetDTO } from "@/lib/types/generated/LeagueScoringPresetDTO";
|
||||
import { LeagueSettingsViewModel } from "@/lib/view-models/LeagueSettingsViewModel";
|
||||
import { DriverSummaryViewModel } from "@/lib/view-models/DriverSummaryViewModel";
|
||||
|
||||
@@ -36,7 +35,7 @@ export class LeagueSettingsService {
|
||||
|
||||
// Get config
|
||||
const configDto = await this.leaguesApiClient.getLeagueConfig(leagueId);
|
||||
const config: LeagueConfigFormModel = configDto.config;
|
||||
const config: LeagueConfigFormModel = (configDto.form ?? undefined) as unknown as LeagueConfigFormModel;
|
||||
|
||||
// Get presets
|
||||
const presetsDto = await this.leaguesApiClient.getScoringPresets();
|
||||
@@ -102,4 +101,4 @@ export class LeagueSettingsService {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { RacesApiClient } from '../../api/races/RacesApiClient';
|
||||
import { RaceDetailViewModel } from '../../view-models/RaceDetailViewModel';
|
||||
import { RacesPageViewModel } from '../../view-models/RacesPageViewModel';
|
||||
import { RaceStatsViewModel } from '../../view-models/RaceStatsViewModel';
|
||||
import type { FileProtestCommandDTO } from '../../types/generated/FileProtestCommandDTO';
|
||||
import type { RaceStatsDTO } from '../../types/generated/RaceStatsDTO';
|
||||
/**
|
||||
* Race Service
|
||||
@@ -33,6 +34,14 @@ export class RaceService {
|
||||
return new RacesPageViewModel(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get races page data filtered by league
|
||||
*/
|
||||
async getLeagueRacesPageData(leagueId: string): Promise<RacesPageViewModel> {
|
||||
const dto = await this.apiClient.getPageData(leagueId);
|
||||
return new RacesPageViewModel(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all races page data with view model transformation
|
||||
* Currently same as getRacesPageData, but can be extended for different filtering
|
||||
@@ -54,14 +63,14 @@ export class RaceService {
|
||||
* Register for a race
|
||||
*/
|
||||
async registerForRace(raceId: string, leagueId: string, driverId: string): Promise<void> {
|
||||
await this.apiClient.register(raceId, { leagueId, driverId });
|
||||
await this.apiClient.register(raceId, { raceId, leagueId, driverId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Withdraw from a race
|
||||
*/
|
||||
async withdrawFromRace(raceId: string, driverId: string): Promise<void> {
|
||||
await this.apiClient.withdraw(raceId, { driverId });
|
||||
await this.apiClient.withdraw(raceId, { raceId, driverId });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,6 +94,13 @@ export class RaceService {
|
||||
await this.apiClient.reopen(raceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* File a protest
|
||||
*/
|
||||
async fileProtest(input: FileProtestCommandDTO): Promise<void> {
|
||||
await this.apiClient.fileProtest(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find races by league ID
|
||||
*
|
||||
@@ -92,7 +108,8 @@ export class RaceService {
|
||||
* so this method deliberately signals that the operation is unavailable instead of making
|
||||
* assumptions about URL structure.
|
||||
*/
|
||||
async findByLeagueId(_leagueId: string): Promise<never> {
|
||||
throw new Error('Finding races by league ID is not supported in this build');
|
||||
async findByLeagueId(leagueId: string): Promise<RacesPageViewModel['races']> {
|
||||
const page = await this.getLeagueRacesPageData(leagueId);
|
||||
return page.races;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { SponsorsApiClient } from '../../api/sponsors/SponsorsApiClient';
|
||||
import type { GetEntitySponsorshipPricingResultDto } from '../../api/sponsors/SponsorsApiClient';
|
||||
import { SponsorshipPricingViewModel } from '../../view-models/SponsorshipPricingViewModel';
|
||||
import { SponsorSponsorshipsViewModel } from '../../view-models/SponsorSponsorshipsViewModel';
|
||||
import { SponsorshipRequestViewModel } from '../../view-models/SponsorshipRequestViewModel';
|
||||
import type { SponsorSponsorshipsDTO } from '../../types/generated';
|
||||
import type { GetPendingSponsorshipRequestsOutputDTO } from '../../types/generated/GetPendingSponsorshipRequestsOutputDTO';
|
||||
import type { SponsorshipRequestDTO } from '../../types/generated/SponsorshipRequestDTO';
|
||||
|
||||
/**
|
||||
* Sponsorship Service
|
||||
@@ -20,8 +20,16 @@ export class SponsorshipService {
|
||||
* Get sponsorship pricing with view model transformation
|
||||
*/
|
||||
async getSponsorshipPricing(): Promise<SponsorshipPricingViewModel> {
|
||||
// Pricing shape isn't finalized in the API yet.
|
||||
// Keep a predictable, UI-friendly structure until a dedicated DTO is introduced.
|
||||
const dto = await this.apiClient.getPricing();
|
||||
return new SponsorshipPricingViewModel(dto);
|
||||
const main = dto.pricing.find((p) => p.entityType === 'main')?.price ?? 0;
|
||||
const secondary = dto.pricing.find((p) => p.entityType === 'secondary')?.price ?? 0;
|
||||
return new SponsorshipPricingViewModel({
|
||||
mainSlotPrice: main,
|
||||
secondarySlotPrice: secondary,
|
||||
currency: 'USD',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,21 +47,22 @@ export class SponsorshipService {
|
||||
* Get pending sponsorship requests for an entity
|
||||
*/
|
||||
async getPendingSponsorshipRequests(params: { entityType: string; entityId: string }): Promise<SponsorshipRequestViewModel[]> {
|
||||
const dto = await this.apiClient.getPendingSponsorshipRequests(params);
|
||||
return dto.requests.map(dto => new SponsorshipRequestViewModel(dto));
|
||||
const dto = (await this.apiClient.getPendingSponsorshipRequests(params)) as unknown as GetPendingSponsorshipRequestsOutputDTO;
|
||||
const requests = (dto as any).requests as SponsorshipRequestDTO[];
|
||||
return (requests ?? []).map((r: SponsorshipRequestDTO) => new SponsorshipRequestViewModel(r));
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept a sponsorship request
|
||||
*/
|
||||
async acceptSponsorshipRequest(requestId: string, respondedBy: string): Promise<void> {
|
||||
await this.apiClient.acceptSponsorshipRequest(requestId, respondedBy);
|
||||
await this.apiClient.acceptSponsorshipRequest(requestId, { respondedBy });
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject a sponsorship request
|
||||
*/
|
||||
async rejectSponsorshipRequest(requestId: string, respondedBy: string, reason?: string): Promise<void> {
|
||||
await this.apiClient.rejectSponsorshipRequest(requestId, respondedBy, reason);
|
||||
await this.apiClient.rejectSponsorshipRequest(requestId, { respondedBy, ...(reason ? { reason } : {}) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ import { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
|
||||
import { CreateTeamViewModel } from '@/lib/view-models/CreateTeamViewModel';
|
||||
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';
|
||||
import type { GetAllTeamsOutputDTO, TeamListItemDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO';
|
||||
import type { GetAllTeamsOutputDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO';
|
||||
import type { TeamListItemDTO } from '@/lib/types/generated/TeamListItemDTO';
|
||||
import type { GetTeamDetailsOutputDTO } from '@/lib/types/generated/GetTeamDetailsOutputDTO';
|
||||
import type { GetTeamMembersOutputDTO, TeamMemberDTO } from '@/lib/types/generated/GetTeamMembersOutputDTO';
|
||||
import type { GetTeamMembersOutputDTO } from '@/lib/types/generated/GetTeamMembersOutputDTO';
|
||||
import type { TeamMemberDTO } from '@/lib/types/generated/TeamMemberDTO';
|
||||
import type { CreateTeamInputDTO } from '@/lib/types/generated/CreateTeamInputDTO';
|
||||
import type { CreateTeamOutputDTO } from '@/lib/types/generated/CreateTeamOutputDTO';
|
||||
import type { UpdateTeamInputDTO } from '@/lib/types/generated/UpdateTeamInputDTO';
|
||||
@@ -71,19 +72,19 @@ export class TeamService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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<GetTeamMembershipOutputDTO | 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
|
||||
@@ -91,11 +92,11 @@ export class TeamService {
|
||||
* The backend does not yet expose a dedicated endpoint for removing team memberships,
|
||||
* so this method fails explicitly to avoid silently ignoring removal requests.
|
||||
*/
|
||||
async removeMembership(teamId: string, driverId: string): Promise<void> {
|
||||
void teamId;
|
||||
void driverId;
|
||||
throw new Error('Team membership removal is not supported in this build');
|
||||
}
|
||||
async removeMembership(teamId: string, driverId: string): Promise<void> {
|
||||
void teamId;
|
||||
void driverId;
|
||||
throw new Error('Team membership removal is not supported in this build');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update team membership role
|
||||
@@ -103,10 +104,10 @@ export class TeamService {
|
||||
* Role updates for team memberships are not supported by the current API surface;
|
||||
* callers must treat this as an unavailable operation.
|
||||
*/
|
||||
async updateMembership(teamId: string, driverId: string, role: string): Promise<void> {
|
||||
void teamId;
|
||||
void driverId;
|
||||
void role;
|
||||
throw new Error('Team membership role updates are not supported in this build');
|
||||
}
|
||||
}
|
||||
async updateMembership(teamId: string, driverId: string, role: string): Promise<void> {
|
||||
void teamId;
|
||||
void driverId;
|
||||
void role;
|
||||
throw new Error('Team membership role updates are not supported in this build');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user