refactor
This commit is contained in:
@@ -1,11 +1,493 @@
|
||||
export class ApiClient {
|
||||
/**
|
||||
* 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<string, unknown>;
|
||||
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;
|
||||
}
|
||||
|
||||
private async request<T>(method: string, path: string, data?: object): Promise<T | void> {
|
||||
protected async request<T>(method: string, path: string, data?: object): Promise<T> {
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
@@ -13,6 +495,7 @@ export class ApiClient {
|
||||
const config: RequestInit = {
|
||||
method,
|
||||
headers,
|
||||
credentials: 'include', // Include cookies for auth
|
||||
};
|
||||
|
||||
if (data) {
|
||||
@@ -22,41 +505,441 @@ export class ApiClient {
|
||||
const response = await fetch(`${this.baseUrl}${path}`, config);
|
||||
|
||||
if (!response.ok) {
|
||||
// Attempt to read error message from response body
|
||||
let errorData: any;
|
||||
let errorData: { message?: string } = { message: response.statusText };
|
||||
try {
|
||||
errorData = await response.json();
|
||||
} catch (e) {
|
||||
errorData = { message: response.statusText };
|
||||
} catch {
|
||||
// Keep default error message
|
||||
}
|
||||
throw new Error(errorData.message || `API request failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
return text ? JSON.parse(text) : undefined;
|
||||
if (!text) {
|
||||
return null as T;
|
||||
}
|
||||
return JSON.parse(text) as T;
|
||||
}
|
||||
|
||||
get<T>(path: string): Promise<T | void> {
|
||||
protected get<T>(path: string): Promise<T> {
|
||||
return this.request<T>('GET', path);
|
||||
}
|
||||
|
||||
post<T>(path: string, data: object): Promise<T | void> {
|
||||
protected post<T>(path: string, data: object): Promise<T> {
|
||||
return this.request<T>('POST', path, data);
|
||||
}
|
||||
|
||||
put<T>(path: string, data: object): Promise<T | void> {
|
||||
protected put<T>(path: string, data: object): Promise<T> {
|
||||
return this.request<T>('PUT', path, data);
|
||||
}
|
||||
|
||||
delete<T>(path: string): Promise<T | void> {
|
||||
protected delete<T>(path: string): Promise<T> {
|
||||
return this.request<T>('DELETE', path);
|
||||
}
|
||||
|
||||
patch<T>(path: string, data: object): Promise<T | void> {
|
||||
protected patch<T>(path: string, data: object): Promise<T> {
|
||||
return this.request<T>('PATCH', path, data);
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate the API client with your backend's base URL
|
||||
// You might want to get this from an environment variable
|
||||
export const api = new ApiClient(process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001');
|
||||
// ============================================================================
|
||||
// Domain-Specific API Clients
|
||||
// ============================================================================
|
||||
|
||||
class LeaguesApiClient extends BaseApiClient {
|
||||
constructor(baseUrl: string) {
|
||||
super(baseUrl);
|
||||
}
|
||||
|
||||
/** Get all leagues with capacity information */
|
||||
getAllWithCapacity(): Promise<AllLeaguesWithCapacityViewModel> {
|
||||
return this.get<AllLeaguesWithCapacityViewModel>('/leagues/all-with-capacity');
|
||||
}
|
||||
|
||||
/** Get total number of leagues */
|
||||
getTotal(): Promise<LeagueStatsDto> {
|
||||
return this.get<LeagueStatsDto>('/leagues/total-leagues');
|
||||
}
|
||||
|
||||
/** Get league standings */
|
||||
getStandings(leagueId: string): Promise<LeagueStandingsViewModel> {
|
||||
return this.get<LeagueStandingsViewModel>(`/leagues/${leagueId}/standings`);
|
||||
}
|
||||
|
||||
/** Get league schedule */
|
||||
getSchedule(leagueId: string): Promise<LeagueScheduleViewModel> {
|
||||
return this.get<LeagueScheduleViewModel>(`/leagues/${leagueId}/schedule`);
|
||||
}
|
||||
|
||||
/** Get league stats */
|
||||
getStats(leagueId: string): Promise<LeagueStatsViewModel> {
|
||||
return this.get<LeagueStatsViewModel>(`/leagues/${leagueId}/stats`);
|
||||
}
|
||||
|
||||
/** Get league memberships */
|
||||
getMemberships(leagueId: string): Promise<LeagueMembershipsViewModel> {
|
||||
return this.get<LeagueMembershipsViewModel>(`/leagues/${leagueId}/memberships`);
|
||||
}
|
||||
|
||||
/** Get league join requests */
|
||||
getJoinRequests(leagueId: string): Promise<LeagueJoinRequestViewModel[]> {
|
||||
return this.get<LeagueJoinRequestViewModel[]>(`/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<LeagueAdminPermissionsViewModel> {
|
||||
return this.get<LeagueAdminPermissionsViewModel>(`/leagues/${leagueId}/permissions/${performerDriverId}`);
|
||||
}
|
||||
|
||||
/** Get league owner summary */
|
||||
getOwnerSummary(leagueId: string, ownerId: string): Promise<LeagueOwnerSummaryViewModel | null> {
|
||||
return this.get<LeagueOwnerSummaryViewModel | null>(`/leagues/${leagueId}/owner-summary/${ownerId}`);
|
||||
}
|
||||
|
||||
/** Get league full config */
|
||||
getConfig(leagueId: string): Promise<LeagueConfigFormModelDto | null> {
|
||||
return this.get<LeagueConfigFormModelDto | null>(`/leagues/${leagueId}/config`);
|
||||
}
|
||||
|
||||
/** Get league protests */
|
||||
getProtests(leagueId: string): Promise<LeagueAdminProtestsViewModel> {
|
||||
return this.get<LeagueAdminProtestsViewModel>(`/leagues/${leagueId}/protests`);
|
||||
}
|
||||
|
||||
/** Get league seasons */
|
||||
getSeasons(leagueId: string): Promise<LeagueSeasonSummaryViewModel[]> {
|
||||
return this.get<LeagueSeasonSummaryViewModel[]>(`/leagues/${leagueId}/seasons`);
|
||||
}
|
||||
|
||||
/** Get league admin data */
|
||||
getAdmin(leagueId: string): Promise<LeagueAdminViewModel> {
|
||||
return this.get<LeagueAdminViewModel>(`/leagues/${leagueId}/admin`);
|
||||
}
|
||||
|
||||
/** Create a new league */
|
||||
create(input: CreateLeagueInput): Promise<CreateLeagueOutput> {
|
||||
return this.post<CreateLeagueOutput>('/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<DriversLeaderboardViewModel> {
|
||||
return this.get<DriversLeaderboardViewModel>('/drivers/leaderboard');
|
||||
}
|
||||
|
||||
/** Get total number of drivers */
|
||||
getTotal(): Promise<DriverStatsDto> {
|
||||
return this.get<DriverStatsDto>('/drivers/total-drivers');
|
||||
}
|
||||
|
||||
/** Get current driver (based on session) */
|
||||
getCurrent(): Promise<DriverDTO | null> {
|
||||
return this.get<DriverDTO | null>('/drivers/current');
|
||||
}
|
||||
|
||||
/** Complete driver onboarding */
|
||||
completeOnboarding(input: CompleteOnboardingInput): Promise<CompleteOnboardingOutput> {
|
||||
return this.post<CompleteOnboardingOutput>('/drivers/complete-onboarding', input);
|
||||
}
|
||||
|
||||
/** Get driver registration status for a race */
|
||||
getRegistrationStatus(driverId: string, raceId: string): Promise<DriverRegistrationStatusViewModel> {
|
||||
return this.get<DriverRegistrationStatusViewModel>(`/drivers/${driverId}/races/${raceId}/registration-status`);
|
||||
}
|
||||
}
|
||||
|
||||
class TeamsApiClient extends BaseApiClient {
|
||||
constructor(baseUrl: string) {
|
||||
super(baseUrl);
|
||||
}
|
||||
|
||||
/** Get all teams */
|
||||
getAll(): Promise<AllTeamsViewModel> {
|
||||
return this.get<AllTeamsViewModel>('/teams/all');
|
||||
}
|
||||
|
||||
/** Get team details */
|
||||
getDetails(teamId: string): Promise<TeamDetailsViewModel | null> {
|
||||
return this.get<TeamDetailsViewModel | null>(`/teams/${teamId}`);
|
||||
}
|
||||
|
||||
/** Get team members */
|
||||
getMembers(teamId: string): Promise<TeamMembersViewModel> {
|
||||
return this.get<TeamMembersViewModel>(`/teams/${teamId}/members`);
|
||||
}
|
||||
|
||||
/** Get team join requests */
|
||||
getJoinRequests(teamId: string): Promise<TeamJoinRequestsViewModel> {
|
||||
return this.get<TeamJoinRequestsViewModel>(`/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<CreateTeamOutput> {
|
||||
return this.post<CreateTeamOutput>('/teams', input);
|
||||
}
|
||||
|
||||
/** Update team */
|
||||
update(teamId: string, input: UpdateTeamInput): Promise<UpdateTeamOutput> {
|
||||
return this.patch<UpdateTeamOutput>(`/teams/${teamId}`, input);
|
||||
}
|
||||
|
||||
/** Get driver's team */
|
||||
getDriverTeam(driverId: string): Promise<DriverTeamViewModel | null> {
|
||||
return this.get<DriverTeamViewModel | null>(`/teams/driver/${driverId}`);
|
||||
}
|
||||
}
|
||||
|
||||
class RacesApiClient extends BaseApiClient {
|
||||
constructor(baseUrl: string) {
|
||||
super(baseUrl);
|
||||
}
|
||||
|
||||
/** Get all races */
|
||||
getAll(): Promise<AllRacesPageViewModel> {
|
||||
return this.get<AllRacesPageViewModel>('/races/all');
|
||||
}
|
||||
|
||||
/** Get total number of races */
|
||||
getTotal(): Promise<RaceStatsDto> {
|
||||
return this.get<RaceStatsDto>('/races/total-races');
|
||||
}
|
||||
}
|
||||
|
||||
class SponsorsApiClient extends BaseApiClient {
|
||||
constructor(baseUrl: string) {
|
||||
super(baseUrl);
|
||||
}
|
||||
|
||||
/** Get sponsorship pricing */
|
||||
getPricing(): Promise<GetEntitySponsorshipPricingResultDto> {
|
||||
return this.get<GetEntitySponsorshipPricingResultDto>('/sponsors/pricing');
|
||||
}
|
||||
|
||||
/** Get all sponsors */
|
||||
getAll(): Promise<GetSponsorsOutput> {
|
||||
return this.get<GetSponsorsOutput>('/sponsors');
|
||||
}
|
||||
|
||||
/** Create a new sponsor */
|
||||
create(input: CreateSponsorInput): Promise<CreateSponsorOutput> {
|
||||
return this.post<CreateSponsorOutput>('/sponsors', input);
|
||||
}
|
||||
|
||||
/** Get sponsor dashboard */
|
||||
getDashboard(sponsorId: string): Promise<SponsorDashboardDTO | null> {
|
||||
return this.get<SponsorDashboardDTO | null>(`/sponsors/dashboard/${sponsorId}`);
|
||||
}
|
||||
|
||||
/** Get sponsor sponsorships */
|
||||
getSponsorships(sponsorId: string): Promise<SponsorSponsorshipsDTO | null> {
|
||||
return this.get<SponsorSponsorshipsDTO | null>(`/sponsors/${sponsorId}/sponsorships`);
|
||||
}
|
||||
}
|
||||
|
||||
class MediaApiClient extends BaseApiClient {
|
||||
constructor(baseUrl: string) {
|
||||
super(baseUrl);
|
||||
}
|
||||
|
||||
/** Request avatar generation */
|
||||
requestAvatarGeneration(input: RequestAvatarGenerationInput): Promise<RequestAvatarGenerationOutput> {
|
||||
return this.post<RequestAvatarGenerationOutput>('/media/avatar/generate', input);
|
||||
}
|
||||
}
|
||||
|
||||
class AnalyticsApiClient extends BaseApiClient {
|
||||
constructor(baseUrl: string) {
|
||||
super(baseUrl);
|
||||
}
|
||||
|
||||
/** Record a page view */
|
||||
recordPageView(input: RecordPageViewInput): Promise<RecordPageViewOutput> {
|
||||
return this.post<RecordPageViewOutput>('/analytics/page-view', input);
|
||||
}
|
||||
|
||||
/** Record an engagement event */
|
||||
recordEngagement(input: RecordEngagementInput): Promise<RecordEngagementOutput> {
|
||||
return this.post<RecordEngagementOutput>('/analytics/engagement', input);
|
||||
}
|
||||
}
|
||||
|
||||
class AuthApiClient extends BaseApiClient {
|
||||
constructor(baseUrl: string) {
|
||||
super(baseUrl);
|
||||
}
|
||||
|
||||
/** Sign up with email */
|
||||
signup(params: SignupParams): Promise<SessionData> {
|
||||
return this.post<SessionData>('/auth/signup', params);
|
||||
}
|
||||
|
||||
/** Login with email */
|
||||
login(params: LoginParams): Promise<SessionData> {
|
||||
return this.post<SessionData>('/auth/login', params);
|
||||
}
|
||||
|
||||
/** Get current session */
|
||||
getSession(): Promise<SessionData | null> {
|
||||
return this.get<SessionData | null>('/auth/session');
|
||||
}
|
||||
|
||||
/** Logout */
|
||||
logout(): Promise<void> {
|
||||
return this.post<void>('/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<GetPaymentsOutput> {
|
||||
const params = new URLSearchParams();
|
||||
if (leagueId) params.append('leagueId', leagueId);
|
||||
if (driverId) params.append('driverId', driverId);
|
||||
const query = params.toString();
|
||||
return this.get<GetPaymentsOutput>(`/payments${query ? `?${query}` : ''}`);
|
||||
}
|
||||
|
||||
/** Create a payment */
|
||||
createPayment(input: CreatePaymentInput): Promise<CreatePaymentOutput> {
|
||||
return this.post<CreatePaymentOutput>('/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<GetMembershipFeesOutput> {
|
||||
return this.get<GetMembershipFeesOutput>(`/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<GetPrizesOutput> {
|
||||
const params = new URLSearchParams();
|
||||
if (leagueId) params.append('leagueId', leagueId);
|
||||
if (seasonId) params.append('seasonId', seasonId);
|
||||
const query = params.toString();
|
||||
return this.get<GetPrizesOutput>(`/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<GetWalletOutput> {
|
||||
return this.get<GetWalletOutput>(`/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;
|
||||
|
||||
Reference in New Issue
Block a user