/** * Domain-specific API Client for GridPilot Website * * This module provides a strongly-typed HTTP client for all API operations. * The website should use these methods instead of directly importing core use cases. */ // ============================================================================ // Types - These mirror the API DTOs // ============================================================================ // Common Types export interface DriverDTO { id: string; name: string; avatarUrl?: string; iracingId?: string; rating?: number; } export interface ProtestViewModel { id: string; raceId: string; complainantId: string; defendantId: string; description: string; status: string; createdAt: string; } export interface LeagueMemberViewModel { driverId: string; driver?: DriverDTO; role: string; joinedAt: string; } export interface StandingEntryViewModel { driverId: string; driver?: DriverDTO; position: number; points: number; wins: number; podiums: number; races: number; } export interface ScheduledRaceViewModel { id: string; name: string; scheduledTime: string; status: string; trackName?: string; } // League Types export interface LeagueSummaryViewModel { id: string; name: string; description?: string; logoUrl?: string; coverImage?: string; memberCount: number; maxMembers: number; isPublic: boolean; ownerId: string; ownerName?: string; scoringType?: string; status?: string; } export interface AllLeaguesWithCapacityViewModel { leagues: LeagueSummaryViewModel[]; } export interface LeagueStatsDto { totalLeagues: number; } export interface LeagueJoinRequestViewModel { id: string; leagueId: string; driverId: string; requestedAt: Date; message?: string; } export interface LeagueAdminPermissionsViewModel { canManageMembers: boolean; canManageRaces: boolean; canManageSettings: boolean; canManageProtests: boolean; isOwner: boolean; isAdmin: boolean; } export interface LeagueOwnerSummaryViewModel { leagueId: string; leagueName: string; memberCount: number; pendingRequests: number; } export interface LeagueConfigFormModelDto { id: string; name: string; description?: string; isPublic: boolean; maxMembers: number; // Add other config fields as needed } export interface LeagueAdminProtestsViewModel { protests: ProtestViewModel[]; } export interface LeagueSeasonSummaryViewModel { id: string; name: string; startDate?: string; endDate?: string; status: string; } export interface LeagueMembershipsViewModel { members: LeagueMemberViewModel[]; } export interface LeagueStandingsViewModel { standings: StandingEntryViewModel[]; } export interface LeagueScheduleViewModel { races: ScheduledRaceViewModel[]; } export interface LeagueStatsViewModel { leagueId: string; totalRaces: number; completedRaces: number; scheduledRaces: number; averageSOF?: number; highestSOF?: number; lowestSOF?: number; } export interface LeagueAdminViewModel { config: LeagueConfigFormModelDto; members: LeagueMemberViewModel[]; joinRequests: LeagueJoinRequestViewModel[]; } export interface CreateLeagueInput { name: string; description?: string; isPublic: boolean; maxMembers: number; ownerId: string; } export interface CreateLeagueOutput { leagueId: string; success: boolean; } // Driver Types export interface DriverLeaderboardItemViewModel { id: string; name: string; avatarUrl?: string; rating: number; wins: number; races: number; skillLevel: string; } export interface DriversLeaderboardViewModel { drivers: DriverLeaderboardItemViewModel[]; } export interface DriverStatsDto { totalDrivers: number; } export interface CompleteOnboardingInput { iracingId: string; displayName: string; } export interface CompleteOnboardingOutput { driverId: string; success: boolean; } export interface DriverRegistrationStatusViewModel { isRegistered: boolean; raceId: string; driverId: string; } // Team Types export interface TeamSummaryViewModel { id: string; name: string; logoUrl?: string; memberCount: number; rating: number; } export interface AllTeamsViewModel { teams: TeamSummaryViewModel[]; } export interface TeamMemberViewModel { driverId: string; driver?: DriverDTO; role: string; joinedAt: string; } export interface TeamJoinRequestItemViewModel { id: string; teamId: string; driverId: string; requestedAt: string; message?: string; } export interface TeamDetailsViewModel { id: string; name: string; description?: string; logoUrl?: string; memberCount: number; ownerId: string; members: TeamMemberViewModel[]; } export interface TeamMembersViewModel { members: TeamMemberViewModel[]; } export interface TeamJoinRequestsViewModel { requests: TeamJoinRequestItemViewModel[]; } export interface DriverTeamViewModel { teamId: string; teamName: string; role: string; joinedAt: Date; } export interface CreateTeamInput { name: string; description?: string; ownerId: string; } export interface CreateTeamOutput { teamId: string; success: boolean; } export interface UpdateTeamInput { name?: string; description?: string; logoUrl?: string; } export interface UpdateTeamOutput { success: boolean; } // Race Types export interface RaceListItemViewModel { id: string; name: string; leagueId: string; leagueName: string; scheduledTime: string; status: string; trackName?: string; } export interface AllRacesPageViewModel { races: RaceListItemViewModel[]; } export interface RaceStatsDto { totalRaces: number; } // Sponsor Types export interface GetEntitySponsorshipPricingResultDto { mainSlotPrice: number; secondarySlotPrice: number; currency: string; } export interface SponsorViewModel { id: string; name: string; logoUrl?: string; websiteUrl?: string; } export interface GetSponsorsOutput { sponsors: SponsorViewModel[]; } export interface CreateSponsorInput { name: string; logoUrl?: string; websiteUrl?: string; userId: string; } export interface CreateSponsorOutput { sponsorId: string; success: boolean; } export interface SponsorDashboardDTO { sponsorId: string; sponsorName: string; totalSponsorships: number; activeSponsorships: number; totalInvestment: number; } export interface SponsorshipDetailViewModel { id: string; leagueId: string; leagueName: string; seasonId: string; tier: 'main' | 'secondary'; status: string; amount: number; currency: string; } export interface SponsorSponsorshipsDTO { sponsorId: string; sponsorName: string; sponsorships: SponsorshipDetailViewModel[]; } // Media Types export interface RequestAvatarGenerationInput { driverId: string; style?: string; } export interface RequestAvatarGenerationOutput { success: boolean; avatarUrl?: string; error?: string; } // Analytics Types export interface RecordPageViewInput { path: string; userId?: string; sessionId?: string; } export interface RecordPageViewOutput { success: boolean; } export interface RecordEngagementInput { eventType: string; eventData?: Record; userId?: string; sessionId?: string; } export interface RecordEngagementOutput { success: boolean; } // Auth Types export interface LoginParams { email: string; password: string; } export interface SignupParams { email: string; password: string; displayName: string; } export interface SessionData { userId: string; email: string; displayName?: string; driverId?: string; isAuthenticated: boolean; } // Payments Types export interface PaymentViewModel { id: string; amount: number; currency: string; status: string; createdAt: string; } export interface GetPaymentsOutput { payments: PaymentViewModel[]; } export interface CreatePaymentInput { amount: number; currency: string; leagueId: string; driverId: string; description?: string; } export interface CreatePaymentOutput { paymentId: string; success: boolean; } export interface MembershipFeeViewModel { leagueId: string; amount: number; currency: string; period: string; } export interface MemberPaymentViewModel { driverId: string; amount: number; paidAt: string; status: string; } export interface GetMembershipFeesOutput { fees: MembershipFeeViewModel[]; memberPayments: MemberPaymentViewModel[]; } export interface PrizeViewModel { id: string; name: string; amount: number; currency: string; position?: number; } export interface GetPrizesOutput { prizes: PrizeViewModel[]; } export interface WalletTransactionViewModel { id: string; type: 'deposit' | 'withdrawal'; amount: number; description?: string; createdAt: string; } export interface WalletViewModel { driverId: string; balance: number; currency: string; transactions: WalletTransactionViewModel[]; } export interface GetWalletOutput { wallet: WalletViewModel; } // ============================================================================ // Base API Client // ============================================================================ class BaseApiClient { private baseUrl: string; constructor(baseUrl: string) { this.baseUrl = baseUrl; } protected async request(method: string, path: string, data?: object): Promise { const headers: HeadersInit = { 'Content-Type': 'application/json', }; const config: RequestInit = { method, headers, credentials: 'include', // Include cookies for auth }; if (data) { config.body = JSON.stringify(data); } const response = await fetch(`${this.baseUrl}${path}`, config); if (!response.ok) { let errorData: { message?: string } = { message: response.statusText }; try { errorData = await response.json(); } catch { // Keep default error message } throw new Error(errorData.message || `API request failed with status ${response.status}`); } const text = await response.text(); if (!text) { return null as T; } return JSON.parse(text) as T; } protected get(path: string): Promise { return this.request('GET', path); } protected post(path: string, data: object): Promise { return this.request('POST', path, data); } protected put(path: string, data: object): Promise { return this.request('PUT', path, data); } protected delete(path: string): Promise { return this.request('DELETE', path); } protected patch(path: string, data: object): Promise { return this.request('PATCH', path, data); } } // ============================================================================ // Domain-Specific API Clients // ============================================================================ class LeaguesApiClient extends BaseApiClient { constructor(baseUrl: string) { super(baseUrl); } /** Get all leagues with capacity information */ getAllWithCapacity(): Promise { return this.get('/leagues/all-with-capacity'); } /** Get total number of leagues */ getTotal(): Promise { return this.get('/leagues/total-leagues'); } /** Get league standings */ getStandings(leagueId: string): Promise { return this.get(`/leagues/${leagueId}/standings`); } /** Get league schedule */ getSchedule(leagueId: string): Promise { return this.get(`/leagues/${leagueId}/schedule`); } /** Get league stats */ getStats(leagueId: string): Promise { return this.get(`/leagues/${leagueId}/stats`); } /** Get league memberships */ getMemberships(leagueId: string): Promise { return this.get(`/leagues/${leagueId}/memberships`); } /** Get league join requests */ getJoinRequests(leagueId: string): Promise { return this.get(`/leagues/${leagueId}/join-requests`); } /** Approve a join request */ approveJoinRequest(leagueId: string, requestId: string): Promise<{ success: boolean }> { return this.post<{ success: boolean }>(`/leagues/${leagueId}/join-requests/approve`, { requestId }); } /** Reject a join request */ rejectJoinRequest(leagueId: string, requestId: string, reason?: string): Promise<{ success: boolean }> { return this.post<{ success: boolean }>(`/leagues/${leagueId}/join-requests/reject`, { requestId, reason }); } /** Get league admin permissions */ getAdminPermissions(leagueId: string, performerDriverId: string): Promise { return this.get(`/leagues/${leagueId}/permissions/${performerDriverId}`); } /** Get league owner summary */ getOwnerSummary(leagueId: string, ownerId: string): Promise { return this.get(`/leagues/${leagueId}/owner-summary/${ownerId}`); } /** Get league full config */ getConfig(leagueId: string): Promise { return this.get(`/leagues/${leagueId}/config`); } /** Get league protests */ getProtests(leagueId: string): Promise { return this.get(`/leagues/${leagueId}/protests`); } /** Get league seasons */ getSeasons(leagueId: string): Promise { return this.get(`/leagues/${leagueId}/seasons`); } /** Get league admin data */ getAdmin(leagueId: string): Promise { return this.get(`/leagues/${leagueId}/admin`); } /** Create a new league */ create(input: CreateLeagueInput): Promise { return this.post('/leagues', input); } /** Remove a member from league */ removeMember(leagueId: string, performerDriverId: string, targetDriverId: string): Promise<{ success: boolean }> { return this.patch<{ success: boolean }>(`/leagues/${leagueId}/members/${targetDriverId}/remove`, { performerDriverId }); } /** Update member role */ updateMemberRole(leagueId: string, performerDriverId: string, targetDriverId: string, newRole: string): Promise<{ success: boolean }> { return this.patch<{ success: boolean }>(`/leagues/${leagueId}/members/${targetDriverId}/role`, { performerDriverId, newRole }); } } class DriversApiClient extends BaseApiClient { constructor(baseUrl: string) { super(baseUrl); } /** Get drivers leaderboard */ getLeaderboard(): Promise { return this.get('/drivers/leaderboard'); } /** Get total number of drivers */ getTotal(): Promise { return this.get('/drivers/total-drivers'); } /** Get current driver (based on session) */ getCurrent(): Promise { return this.get('/drivers/current'); } /** Complete driver onboarding */ completeOnboarding(input: CompleteOnboardingInput): Promise { return this.post('/drivers/complete-onboarding', input); } /** Get driver registration status for a race */ getRegistrationStatus(driverId: string, raceId: string): Promise { return this.get(`/drivers/${driverId}/races/${raceId}/registration-status`); } } class TeamsApiClient extends BaseApiClient { constructor(baseUrl: string) { super(baseUrl); } /** Get all teams */ getAll(): Promise { return this.get('/teams/all'); } /** Get team details */ getDetails(teamId: string): Promise { return this.get(`/teams/${teamId}`); } /** Get team members */ getMembers(teamId: string): Promise { return this.get(`/teams/${teamId}/members`); } /** Get team join requests */ getJoinRequests(teamId: string): Promise { return this.get(`/teams/${teamId}/join-requests`); } /** Approve a join request */ approveJoinRequest(teamId: string, requestId: string): Promise<{ success: boolean }> { return this.post<{ success: boolean }>(`/teams/${teamId}/join-requests/approve`, { requestId }); } /** Reject a join request */ rejectJoinRequest(teamId: string, requestId: string, reason?: string): Promise<{ success: boolean }> { return this.post<{ success: boolean }>(`/teams/${teamId}/join-requests/reject`, { requestId, reason }); } /** Create a new team */ create(input: CreateTeamInput): Promise { return this.post('/teams', input); } /** Update team */ update(teamId: string, input: UpdateTeamInput): Promise { return this.patch(`/teams/${teamId}`, input); } /** Get driver's team */ getDriverTeam(driverId: string): Promise { return this.get(`/teams/driver/${driverId}`); } } class RacesApiClient extends BaseApiClient { constructor(baseUrl: string) { super(baseUrl); } /** Get all races */ getAll(): Promise { return this.get('/races/all'); } /** Get total number of races */ getTotal(): Promise { return this.get('/races/total-races'); } } class SponsorsApiClient extends BaseApiClient { constructor(baseUrl: string) { super(baseUrl); } /** Get sponsorship pricing */ getPricing(): Promise { return this.get('/sponsors/pricing'); } /** Get all sponsors */ getAll(): Promise { return this.get('/sponsors'); } /** Create a new sponsor */ create(input: CreateSponsorInput): Promise { return this.post('/sponsors', input); } /** Get sponsor dashboard */ getDashboard(sponsorId: string): Promise { return this.get(`/sponsors/dashboard/${sponsorId}`); } /** Get sponsor sponsorships */ getSponsorships(sponsorId: string): Promise { return this.get(`/sponsors/${sponsorId}/sponsorships`); } } class MediaApiClient extends BaseApiClient { constructor(baseUrl: string) { super(baseUrl); } /** Request avatar generation */ requestAvatarGeneration(input: RequestAvatarGenerationInput): Promise { return this.post('/media/avatar/generate', input); } } class AnalyticsApiClient extends BaseApiClient { constructor(baseUrl: string) { super(baseUrl); } /** Record a page view */ recordPageView(input: RecordPageViewInput): Promise { return this.post('/analytics/page-view', input); } /** Record an engagement event */ recordEngagement(input: RecordEngagementInput): Promise { return this.post('/analytics/engagement', input); } } class AuthApiClient extends BaseApiClient { constructor(baseUrl: string) { super(baseUrl); } /** Sign up with email */ signup(params: SignupParams): Promise { return this.post('/auth/signup', params); } /** Login with email */ login(params: LoginParams): Promise { return this.post('/auth/login', params); } /** Get current session */ getSession(): Promise { return this.get('/auth/session'); } /** Logout */ logout(): Promise { return this.post('/auth/logout', {}); } /** 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}`; } } class PaymentsApiClient extends BaseApiClient { constructor(baseUrl: string) { super(baseUrl); } /** Get payments */ getPayments(leagueId?: string, driverId?: string): Promise { const params = new URLSearchParams(); if (leagueId) params.append('leagueId', leagueId); if (driverId) params.append('driverId', driverId); const query = params.toString(); return this.get(`/payments${query ? `?${query}` : ''}`); } /** Create a payment */ createPayment(input: CreatePaymentInput): Promise { return this.post('/payments', input); } /** Update payment status */ updatePaymentStatus(paymentId: string, status: string): Promise<{ success: boolean }> { return this.patch<{ success: boolean }>('/payments/status', { paymentId, status }); } /** Get membership fees */ getMembershipFees(leagueId: string): Promise { return this.get(`/payments/membership-fees?leagueId=${leagueId}`); } /** Upsert membership fee */ upsertMembershipFee(leagueId: string, amount: number, currency: string, period: string): Promise<{ success: boolean }> { return this.post<{ success: boolean }>('/payments/membership-fees', { leagueId, amount, currency, period }); } /** Update member payment */ updateMemberPayment(leagueId: string, driverId: string, amount: number, paidAt: string): Promise<{ success: boolean }> { return this.patch<{ success: boolean }>('/payments/membership-fees/member-payment', { leagueId, driverId, amount, paidAt }); } /** Get prizes */ getPrizes(leagueId?: string, seasonId?: string): Promise { const params = new URLSearchParams(); if (leagueId) params.append('leagueId', leagueId); if (seasonId) params.append('seasonId', seasonId); const query = params.toString(); return this.get(`/payments/prizes${query ? `?${query}` : ''}`); } /** Create a prize */ createPrize(name: string, amount: number, currency: string, leagueId: string, position?: number): Promise<{ prizeId: string; success: boolean }> { return this.post<{ prizeId: string; success: boolean }>('/payments/prizes', { name, amount, currency, leagueId, position }); } /** Award a prize */ awardPrize(prizeId: string, driverId: string): Promise<{ success: boolean }> { return this.patch<{ success: boolean }>('/payments/prizes/award', { prizeId, driverId }); } /** Delete a prize */ deletePrize(prizeId: string): Promise<{ success: boolean }> { return this.delete<{ success: boolean }>(`/payments/prizes?prizeId=${prizeId}`); } /** Get wallet */ getWallet(driverId: string): Promise { return this.get(`/payments/wallets?driverId=${driverId}`); } /** Process wallet transaction */ processWalletTransaction(driverId: string, type: 'deposit' | 'withdrawal', amount: number, description?: string): Promise<{ success: boolean }> { return this.post<{ success: boolean }>('/payments/wallets/transactions', { driverId, type, amount, description }); } } // ============================================================================ // Main API Client with Domain Namespaces // ============================================================================ class ApiClient { public readonly leagues: LeaguesApiClient; public readonly drivers: DriversApiClient; public readonly teams: TeamsApiClient; public readonly races: RacesApiClient; public readonly sponsors: SponsorsApiClient; public readonly media: MediaApiClient; public readonly analytics: AnalyticsApiClient; public readonly auth: AuthApiClient; public readonly payments: PaymentsApiClient; constructor(baseUrl: string) { this.leagues = new LeaguesApiClient(baseUrl); this.drivers = new DriversApiClient(baseUrl); this.teams = new TeamsApiClient(baseUrl); this.races = new RacesApiClient(baseUrl); this.sponsors = new SponsorsApiClient(baseUrl); this.media = new MediaApiClient(baseUrl); this.analytics = new AnalyticsApiClient(baseUrl); this.auth = new AuthApiClient(baseUrl); this.payments = new PaymentsApiClient(baseUrl); } } // ============================================================================ // Singleton Instance // ============================================================================ const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001'; export const apiClient = new ApiClient(API_BASE_URL); // Default export for convenience export default apiClient;