remove core from pages
This commit is contained in:
@@ -50,4 +50,9 @@ export class DriversApiClient extends BaseApiClient {
|
||||
getDriverProfile(driverId: string): Promise<DriverProfileDTO> {
|
||||
return this.get<DriverProfileDTO>(`/drivers/${driverId}/profile`);
|
||||
}
|
||||
|
||||
/** Update current driver profile */
|
||||
updateProfile(updates: { bio?: string; country?: string }): Promise<DriverDTO> {
|
||||
return this.put<DriverDTO>('/drivers/profile', updates);
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,11 @@ export class LeaguesApiClient extends BaseApiClient {
|
||||
return this.patch<{ success: boolean }>(`/leagues/${leagueId}/members/${targetDriverId}/remove`, { performerDriverId });
|
||||
}
|
||||
|
||||
/** Update a member's role in league */
|
||||
updateMemberRole(leagueId: string, performerDriverId: string, targetDriverId: string, newRole: string): Promise<{ success: boolean }> {
|
||||
return this.patch<{ success: boolean }>(`/leagues/${leagueId}/members/${targetDriverId}/role`, { performerDriverId, newRole });
|
||||
}
|
||||
|
||||
/** Get league seasons */
|
||||
getSeasons(leagueId: string): Promise<{ seasons: Array<{ id: string; status: string }> }> {
|
||||
return this.get<{ seasons: Array<{ id: string; status: string }> }>(`/leagues/${leagueId}/seasons`);
|
||||
@@ -77,4 +82,9 @@ export class LeaguesApiClient extends BaseApiClient {
|
||||
newOwnerId,
|
||||
});
|
||||
}
|
||||
|
||||
/** Get races for a league */
|
||||
getRaces(leagueId: string): Promise<{ races: any[] }> {
|
||||
return this.get<{ races: any[] }>(`/leagues/${leagueId}/races`);
|
||||
}
|
||||
}
|
||||
18
apps/website/lib/api/penalties/PenaltiesApiClient.ts
Normal file
18
apps/website/lib/api/penalties/PenaltiesApiClient.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { BaseApiClient } from '../base/BaseApiClient';
|
||||
|
||||
/**
|
||||
* Penalties API Client
|
||||
*
|
||||
* Handles all penalty-related API operations.
|
||||
*/
|
||||
export class PenaltiesApiClient extends BaseApiClient {
|
||||
/** Get penalties for a race */
|
||||
getRacePenalties(raceId: string): Promise<{ penalties: any[] }> {
|
||||
return this.get<{ penalties: any[] }>(`/races/${raceId}/penalties`);
|
||||
}
|
||||
|
||||
/** Apply a penalty */
|
||||
applyPenalty(input: any): Promise<void> {
|
||||
return this.post<void>('/races/penalties/apply', input);
|
||||
}
|
||||
}
|
||||
@@ -30,4 +30,14 @@ export class ProtestsApiClient extends BaseApiClient {
|
||||
requestDefense(input: RequestProtestDefenseCommandDTO): Promise<void> {
|
||||
return this.post<void>('/races/protests/defense/request', input);
|
||||
}
|
||||
|
||||
/** Review protest */
|
||||
reviewProtest(input: { protestId: string; stewardId: string; decision: string; decisionNotes: string }): 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`);
|
||||
}
|
||||
}
|
||||
@@ -44,4 +44,19 @@ export class SponsorsApiClient extends BaseApiClient {
|
||||
getSponsor(sponsorId: string): Promise<SponsorDTO | null> {
|
||||
return this.get<SponsorDTO | 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}`);
|
||||
}
|
||||
|
||||
/** Accept a sponsorship request */
|
||||
acceptSponsorshipRequest(requestId: string, respondedBy: string): Promise<void> {
|
||||
return this.post(`/sponsors/requests/${requestId}/accept`, { respondedBy });
|
||||
}
|
||||
|
||||
/** Reject a sponsorship request */
|
||||
rejectSponsorshipRequest(requestId: string, respondedBy: string, reason?: string): Promise<void> {
|
||||
return this.post(`/sponsors/requests/${requestId}/reject`, { respondedBy, reason });
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import { AnalyticsApiClient } from '../api/analytics/AnalyticsApiClient';
|
||||
import { MediaApiClient } from '../api/media/MediaApiClient';
|
||||
import { DashboardApiClient } from '../api/dashboard/DashboardApiClient';
|
||||
import { ProtestsApiClient } from '../api/protests/ProtestsApiClient';
|
||||
import { PenaltiesApiClient } from '../api/penalties/PenaltiesApiClient';
|
||||
import { PenaltyService } from './penalties/PenaltyService';
|
||||
import { ConsoleErrorReporter } from '../infrastructure/logging/ConsoleErrorReporter';
|
||||
import { ConsoleLogger } from '../infrastructure/logging/ConsoleLogger';
|
||||
|
||||
@@ -59,6 +61,7 @@ export class ServiceFactory {
|
||||
media: MediaApiClient;
|
||||
dashboard: DashboardApiClient;
|
||||
protests: ProtestsApiClient;
|
||||
penalties: PenaltiesApiClient;
|
||||
};
|
||||
|
||||
constructor(baseUrl: string) {
|
||||
@@ -75,6 +78,7 @@ export class ServiceFactory {
|
||||
media: new MediaApiClient(baseUrl, this.errorReporter, this.logger),
|
||||
dashboard: new DashboardApiClient(baseUrl, this.errorReporter, this.logger),
|
||||
protests: new ProtestsApiClient(baseUrl, this.errorReporter, this.logger),
|
||||
penalties: new PenaltiesApiClient(baseUrl, this.errorReporter, this.logger),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -231,4 +235,11 @@ export class ServiceFactory {
|
||||
createProtestService(): ProtestService {
|
||||
return new ProtestService(this.apiClients.protests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create PenaltyService instance
|
||||
*/
|
||||
createPenaltyService(): PenaltyService {
|
||||
return new PenaltyService(this.apiClients.penalties);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ 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';
|
||||
|
||||
export interface Services {
|
||||
raceService: RaceService;
|
||||
@@ -48,6 +49,7 @@ export interface Services {
|
||||
authService: AuthService;
|
||||
sessionService: SessionService;
|
||||
protestService: ProtestService;
|
||||
penaltyService: PenaltyService;
|
||||
}
|
||||
|
||||
const ServicesContext = createContext<Services | null>(null);
|
||||
@@ -82,6 +84,7 @@ export function ServiceProvider({ children }: ServiceProviderProps) {
|
||||
authService: serviceFactory.createAuthService(),
|
||||
sessionService: serviceFactory.createSessionService(),
|
||||
protestService: serviceFactory.createProtestService(),
|
||||
penaltyService: serviceFactory.createPenaltyService(),
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -54,10 +54,34 @@ export class DriverService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get driver profile with full details and view model transformation
|
||||
*/
|
||||
* Get driver profile with full details and view model transformation
|
||||
*/
|
||||
async getDriverProfile(driverId: string): Promise<DriverProfileViewModel> {
|
||||
const dto = await this.apiClient.getDriverProfile(driverId);
|
||||
return new DriverProfileViewModel(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update current driver profile with view model transformation
|
||||
*/
|
||||
async updateProfile(updates: { bio?: string; country?: string }): Promise<DriverProfileViewModel> {
|
||||
const dto = await this.apiClient.updateProfile(updates);
|
||||
// After updating, get the full profile again to return updated view model
|
||||
return this.getDriverProfile(dto.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find driver by ID
|
||||
*/
|
||||
async findById(id: string): Promise<DriverDTO | null> {
|
||||
return this.apiClient.getDriver(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find multiple drivers by IDs
|
||||
*/
|
||||
async findByIds(ids: string[]): Promise<DriverDTO[]> {
|
||||
const drivers = await Promise.all(ids.map(id => this.apiClient.getDriver(id)));
|
||||
return drivers.filter((d): d is DriverDTO => d !== null);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { LeagueSummaryViewModel } from "@/lib/view-models/LeagueSummaryViewModel
|
||||
import { RemoveMemberViewModel } from "@/lib/view-models/RemoveMemberViewModel";
|
||||
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";
|
||||
@@ -107,6 +108,13 @@ export class LeagueService {
|
||||
return new RemoveMemberViewModel(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a member's role in league
|
||||
*/
|
||||
async updateMemberRole(leagueId: string, performerDriverId: string, targetDriverId: string, newRole: string): Promise<{ success: boolean }> {
|
||||
return this.apiClient.updateMemberRole(leagueId, performerDriverId, targetDriverId, newRole);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get league detail with owner, membership, and sponsor info
|
||||
*/
|
||||
@@ -192,7 +200,7 @@ export class LeagueService {
|
||||
const memberships = await this.apiClient.getMemberships(leagueId);
|
||||
|
||||
// Get all races for this league - TODO: implement API endpoint
|
||||
const allRaces: RaceDTO[] = []; // TODO: fetch from API
|
||||
const allRaces: RaceViewModel[] = []; // TODO: fetch from API and map to RaceViewModel
|
||||
|
||||
// Get league stats
|
||||
const leagueStats = await this.apiClient.getTotal(); // TODO: get stats for specific league
|
||||
|
||||
@@ -57,12 +57,16 @@ export class LeagueSettingsService {
|
||||
|
||||
// Get members
|
||||
const membershipsDto = await this.leaguesApiClient.getMemberships(leagueId);
|
||||
const members: DriverDTO[] = [];
|
||||
const members: DriverSummaryViewModel[] = [];
|
||||
for (const member of membershipsDto.members) {
|
||||
if (member.driverId !== league.ownerId && member.role !== 'owner') {
|
||||
const driver = await this.driversApiClient.getDriver(member.driverId);
|
||||
if (driver) {
|
||||
members.push(driver);
|
||||
members.push(new DriverSummaryViewModel({
|
||||
driver,
|
||||
rating: driver.rating ?? null,
|
||||
rank: null, // TODO: get from API
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,4 +47,11 @@ export class MediaService {
|
||||
getTeamLogo(teamId: string): string {
|
||||
return `/api/media/teams/${teamId}/logo`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get driver avatar URL
|
||||
*/
|
||||
getDriverAvatar(driverId: string): string {
|
||||
return `/api/media/avatar/${driverId}`;
|
||||
}
|
||||
}
|
||||
28
apps/website/lib/services/penalties/PenaltyService.ts
Normal file
28
apps/website/lib/services/penalties/PenaltyService.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { PenaltiesApiClient } from '../../api/penalties/PenaltiesApiClient';
|
||||
|
||||
/**
|
||||
* Penalty Service
|
||||
*
|
||||
* Orchestrates penalty operations by coordinating API calls and view model creation.
|
||||
* All dependencies are injected via constructor.
|
||||
*/
|
||||
export class PenaltyService {
|
||||
constructor(
|
||||
private readonly apiClient: PenaltiesApiClient
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Find penalties by race ID
|
||||
*/
|
||||
async findByRaceId(raceId: string): Promise<any[]> {
|
||||
const dto = await this.apiClient.getRacePenalties(raceId);
|
||||
return dto.penalties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a penalty
|
||||
*/
|
||||
async applyPenalty(input: any): Promise<void> {
|
||||
await this.apiClient.applyPenalty(input);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ProtestsApiClient } from '../../api/protests/ProtestsApiClient';
|
||||
import { ProtestViewModel } from '../../view-models/ProtestViewModel';
|
||||
import { RaceViewModel } from '../../view-models/RaceViewModel';
|
||||
import { ProtestDriverViewModel } from '../../view-models/ProtestDriverViewModel';
|
||||
import type { LeagueAdminProtestsDTO, ApplyPenaltyCommandDTO, RequestProtestDefenseCommandDTO, DriverSummaryDTO } from '../../types';
|
||||
|
||||
/**
|
||||
@@ -34,9 +36,9 @@ export class ProtestService {
|
||||
*/
|
||||
async getProtestById(leagueId: string, protestId: string): Promise<{
|
||||
protest: ProtestViewModel;
|
||||
race: LeagueAdminProtestsDTO['racesById'][string];
|
||||
protestingDriver: DriverSummaryDTO;
|
||||
accusedDriver: DriverSummaryDTO;
|
||||
race: RaceViewModel;
|
||||
protestingDriver: ProtestDriverViewModel;
|
||||
accusedDriver: ProtestDriverViewModel;
|
||||
} | null> {
|
||||
const dto = await this.apiClient.getLeagueProtest(leagueId, protestId);
|
||||
const protest = dto.protests[0];
|
||||
@@ -48,9 +50,9 @@ export class ProtestService {
|
||||
|
||||
return {
|
||||
protest: new ProtestViewModel(protest),
|
||||
race,
|
||||
protestingDriver,
|
||||
accusedDriver,
|
||||
race: new RaceViewModel(race),
|
||||
protestingDriver: new ProtestDriverViewModel(protestingDriver),
|
||||
accusedDriver: new ProtestDriverViewModel(accusedDriver),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -67,4 +69,19 @@ export class ProtestService {
|
||||
async requestDefense(input: RequestProtestDefenseCommandDTO): Promise<void> {
|
||||
await this.apiClient.requestDefense(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Review protest
|
||||
*/
|
||||
async reviewProtest(input: { protestId: string; stewardId: string; decision: string; decisionNotes: string }): Promise<void> {
|
||||
await this.apiClient.reviewProtest(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find protests by race ID
|
||||
*/
|
||||
async findByRaceId(raceId: string): Promise<any[]> {
|
||||
const dto = await this.apiClient.getRaceProtests(raceId);
|
||||
return dto.protests;
|
||||
}
|
||||
}
|
||||
@@ -83,35 +83,45 @@ export class RaceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform API races page data to view model format
|
||||
* Transform API races page data to view model format
|
||||
*/
|
||||
private transformRacesPageData(dto: RacesPageDataDto): {
|
||||
upcomingRaces: Array<{ id: string; title: string; scheduledTime: string; status: string }>;
|
||||
completedRaces: Array<{ id: string; title: string; scheduledTime: string; status: string }>;
|
||||
totalCount: number;
|
||||
} {
|
||||
const upcomingRaces = dto.races
|
||||
.filter(race => race.status !== 'completed')
|
||||
.map(race => ({
|
||||
id: race.id,
|
||||
title: `${race.track} - ${race.car}`,
|
||||
scheduledTime: race.scheduledAt,
|
||||
status: race.status,
|
||||
}));
|
||||
|
||||
const completedRaces = dto.races
|
||||
.filter(race => race.status === 'completed')
|
||||
.map(race => ({
|
||||
id: race.id,
|
||||
title: `${race.track} - ${race.car}`,
|
||||
scheduledTime: race.scheduledAt,
|
||||
status: race.status,
|
||||
}));
|
||||
|
||||
return {
|
||||
upcomingRaces,
|
||||
completedRaces,
|
||||
totalCount: dto.races.length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Find races by league ID
|
||||
*/
|
||||
private transformRacesPageData(dto: RacesPageDataDto): {
|
||||
upcomingRaces: Array<{ id: string; title: string; scheduledTime: string; status: string }>;
|
||||
completedRaces: Array<{ id: string; title: string; scheduledTime: string; status: string }>;
|
||||
totalCount: number;
|
||||
} {
|
||||
const upcomingRaces = dto.races
|
||||
.filter(race => race.status !== 'completed')
|
||||
.map(race => ({
|
||||
id: race.id,
|
||||
title: `${race.track} - ${race.car}`,
|
||||
scheduledTime: race.scheduledAt,
|
||||
status: race.status,
|
||||
}));
|
||||
|
||||
const completedRaces = dto.races
|
||||
.filter(race => race.status === 'completed')
|
||||
.map(race => ({
|
||||
id: race.id,
|
||||
title: `${race.track} - ${race.car}`,
|
||||
scheduledTime: race.scheduledAt,
|
||||
status: race.status,
|
||||
}));
|
||||
|
||||
return {
|
||||
upcomingRaces,
|
||||
completedRaces,
|
||||
totalCount: dto.races.length,
|
||||
};
|
||||
async findByLeagueId(leagueId: string): Promise<any[]> {
|
||||
// Assuming the API has /races?leagueId=...
|
||||
// TODO: Update when API is implemented
|
||||
const dto = await this.apiClient.get('/races?leagueId=' + leagueId) as { races: any[] };
|
||||
return dto.races;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@ import type { SponsorsApiClient } from '../../api/sponsors/SponsorsApiClient';
|
||||
import type { GetEntitySponsorshipPricingResultDto } from '../../api/sponsors/SponsorsApiClient';
|
||||
import {
|
||||
SponsorshipPricingViewModel,
|
||||
SponsorSponsorshipsViewModel
|
||||
SponsorSponsorshipsViewModel,
|
||||
SponsorshipRequestViewModel
|
||||
} from '../../view-models';
|
||||
import type { SponsorSponsorshipsDTO } from '../../types/generated';
|
||||
|
||||
@@ -39,9 +40,22 @@ export class SponsorshipService {
|
||||
/**
|
||||
* Get pending sponsorship requests for an entity
|
||||
*/
|
||||
async getPendingSponsorshipRequests(params: { entityType: string; entityId: string }): Promise<{ requests: any[] }> {
|
||||
// TODO: Implement API call
|
||||
// For now, return empty
|
||||
return { requests: [] };
|
||||
async getPendingSponsorshipRequests(params: { entityType: string; entityId: string }): Promise<SponsorshipRequestViewModel[]> {
|
||||
const dto = await this.apiClient.getPendingSponsorshipRequests(params);
|
||||
return dto.requests.map(dto => new SponsorshipRequestViewModel(dto));
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept a sponsorship request
|
||||
*/
|
||||
async acceptSponsorshipRequest(requestId: string, respondedBy: string): Promise<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { LeagueStandingsDTO } from '../types/generated/LeagueStandingsDTO';
|
||||
import { DriverDTO } from '../types/DriverDTO';
|
||||
import { RaceDTO } from '../types/generated/RaceDTO';
|
||||
import { LeagueScoringConfigDTO } from '../types/LeagueScoringConfigDTO';
|
||||
import { RaceViewModel } from './RaceViewModel';
|
||||
|
||||
// Sponsor info type
|
||||
export interface SponsorInfo {
|
||||
@@ -59,8 +60,8 @@ export class LeagueDetailPageViewModel {
|
||||
memberships: LeagueMembershipWithRole[];
|
||||
|
||||
// Races
|
||||
allRaces: RaceDTO[];
|
||||
runningRaces: RaceDTO[];
|
||||
allRaces: RaceViewModel[];
|
||||
runningRaces: RaceViewModel[];
|
||||
|
||||
// Stats
|
||||
averageSOF: number | null;
|
||||
@@ -96,7 +97,7 @@ export class LeagueDetailPageViewModel {
|
||||
scoringConfig: LeagueScoringConfigDTO | null,
|
||||
drivers: DriverDTO[],
|
||||
memberships: LeagueMembershipsDTO,
|
||||
allRaces: RaceDTO[],
|
||||
allRaces: RaceViewModel[],
|
||||
leagueStats: LeagueStatsDTO,
|
||||
sponsors: SponsorInfo[]
|
||||
) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { LeagueConfigFormModel } from '../types/LeagueConfigFormModel';
|
||||
import type { LeagueScoringPresetDTO } from '../types/LeagueScoringPresetDTO';
|
||||
import type { DriverDTO } from '../types/DriverDTO';
|
||||
import { LeagueScoringPresetsViewModel } from './LeagueScoringPresetsViewModel';
|
||||
import { DriverSummaryViewModel } from './DriverSummaryViewModel';
|
||||
|
||||
@@ -17,7 +16,7 @@ export class LeagueSettingsViewModel {
|
||||
config: LeagueConfigFormModel;
|
||||
presets: LeagueScoringPresetDTO[];
|
||||
owner: DriverSummaryViewModel | null;
|
||||
members: DriverDTO[];
|
||||
members: DriverSummaryViewModel[];
|
||||
|
||||
constructor(dto: {
|
||||
league: {
|
||||
@@ -28,7 +27,7 @@ export class LeagueSettingsViewModel {
|
||||
config: LeagueConfigFormModel;
|
||||
presets: LeagueScoringPresetDTO[];
|
||||
owner: DriverSummaryViewModel | null;
|
||||
members: DriverDTO[];
|
||||
members: DriverSummaryViewModel[];
|
||||
}) {
|
||||
this.league = dto.league;
|
||||
this.config = dto.config;
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { LeagueStandingDTO } from '../types/generated/LeagueStandingDTO';
|
||||
import { StandingEntryViewModel } from './StandingEntryViewModel';
|
||||
import { DriverDTO } from '../types/DriverDTO';
|
||||
import { LeagueMembership } from '../types/LeagueMembership';
|
||||
|
||||
export class LeagueStandingsViewModel {
|
||||
standings: StandingEntryViewModel[];
|
||||
drivers: DriverDTO[];
|
||||
memberships: LeagueMembership[];
|
||||
|
||||
constructor(dto: { standings: LeagueStandingDTO[] }, currentUserId: string, previousStandings?: LeagueStandingDTO[]) {
|
||||
constructor(dto: { standings: LeagueStandingDTO[]; drivers: DriverDTO[]; 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;
|
||||
const previousPosition = previousStandings?.find(p => p.driverId === entry.driverId)?.position;
|
||||
return new StandingEntryViewModel(entry, leaderPoints, nextPoints, currentUserId, previousPosition);
|
||||
});
|
||||
this.drivers = dto.drivers;
|
||||
this.memberships = dto.memberships;
|
||||
}
|
||||
|
||||
// Note: The generated DTO doesn't have these fields
|
||||
// These will need to be added when the OpenAPI spec is updated
|
||||
drivers: any[] = [];
|
||||
memberships: any[] = [];
|
||||
}
|
||||
13
apps/website/lib/view-models/ProtestDriverViewModel.ts
Normal file
13
apps/website/lib/view-models/ProtestDriverViewModel.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { DriverSummaryDTO } from '../types/generated/LeagueAdminProtestsDTO';
|
||||
|
||||
export class ProtestDriverViewModel {
|
||||
constructor(private readonly dto: DriverSummaryDTO) {}
|
||||
|
||||
get id(): string {
|
||||
return this.dto.id;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.dto.name;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@ export class ProtestViewModel {
|
||||
accusedDriverId: string;
|
||||
description: string;
|
||||
submittedAt: string;
|
||||
status: string;
|
||||
reviewedAt?: string;
|
||||
decisionNotes?: string;
|
||||
|
||||
constructor(dto: ProtestDTO) {
|
||||
this.id = dto.id;
|
||||
@@ -19,6 +22,10 @@ export class ProtestViewModel {
|
||||
this.accusedDriverId = dto.accusedDriverId;
|
||||
this.description = dto.description;
|
||||
this.submittedAt = dto.submittedAt;
|
||||
// TODO: Add these fields to DTO when available
|
||||
this.status = 'pending';
|
||||
this.reviewedAt = undefined;
|
||||
this.decisionNotes = undefined;
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted submitted date */
|
||||
@@ -26,8 +33,8 @@ export class ProtestViewModel {
|
||||
return new Date(this.submittedAt).toLocaleString();
|
||||
}
|
||||
|
||||
/** UI-specific: Status display - placeholder since status not in current DTO */
|
||||
/** UI-specific: Status display */
|
||||
get statusDisplay(): string {
|
||||
return 'Pending'; // TODO: Update when status is added to DTO
|
||||
return 'Pending';
|
||||
}
|
||||
}
|
||||
34
apps/website/lib/view-models/RaceViewModel.ts
Normal file
34
apps/website/lib/view-models/RaceViewModel.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { RaceDTO } from '../types/generated/RaceDTO';
|
||||
|
||||
export class RaceViewModel {
|
||||
constructor(private readonly dto: RaceDTO, private readonly _status?: string, private readonly _registeredCount?: number, private readonly _strengthOfField?: number) {}
|
||||
|
||||
get id(): string {
|
||||
return this.dto.id;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.dto.name;
|
||||
}
|
||||
|
||||
get date(): string {
|
||||
return this.dto.date;
|
||||
}
|
||||
|
||||
get status(): string | undefined {
|
||||
return this._status;
|
||||
}
|
||||
|
||||
get registeredCount(): number | undefined {
|
||||
return this._registeredCount;
|
||||
}
|
||||
|
||||
get strengthOfField(): number | undefined {
|
||||
return this._strengthOfField;
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted date */
|
||||
get formattedDate(): string {
|
||||
return new Date(this.date).toLocaleDateString();
|
||||
}
|
||||
}
|
||||
54
apps/website/lib/view-models/SponsorshipRequestViewModel.ts
Normal file
54
apps/website/lib/view-models/SponsorshipRequestViewModel.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { PendingRequestDTO } from '@/components/sponsors/PendingSponsorshipRequests';
|
||||
|
||||
export class SponsorshipRequestViewModel {
|
||||
id: string;
|
||||
sponsorId: string;
|
||||
sponsorName: string;
|
||||
sponsorLogo?: string;
|
||||
tier: 'main' | 'secondary';
|
||||
offeredAmount: number;
|
||||
currency: string;
|
||||
formattedAmount: string;
|
||||
message?: string;
|
||||
createdAt: Date;
|
||||
platformFee: number;
|
||||
netAmount: number;
|
||||
|
||||
constructor(dto: PendingRequestDTO) {
|
||||
this.id = dto.id;
|
||||
this.sponsorId = dto.sponsorId;
|
||||
this.sponsorName = dto.sponsorName;
|
||||
this.sponsorLogo = dto.sponsorLogo;
|
||||
this.tier = dto.tier;
|
||||
this.offeredAmount = dto.offeredAmount;
|
||||
this.currency = dto.currency;
|
||||
this.formattedAmount = dto.formattedAmount;
|
||||
this.message = dto.message;
|
||||
this.createdAt = dto.createdAt;
|
||||
this.platformFee = dto.platformFee;
|
||||
this.netAmount = dto.netAmount;
|
||||
}
|
||||
|
||||
/** UI-specific: Formatted date */
|
||||
get formattedDate(): string {
|
||||
return this.createdAt.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
/** UI-specific: Net amount in dollars */
|
||||
get netAmountDollars(): string {
|
||||
return `$${(this.netAmount / 100).toFixed(2)}`;
|
||||
}
|
||||
|
||||
/** UI-specific: Tier display */
|
||||
get tierDisplay(): string {
|
||||
return this.tier === 'main' ? 'Main Sponsor' : 'Secondary';
|
||||
}
|
||||
|
||||
/** UI-specific: Tier badge variant */
|
||||
get tierBadgeVariant(): 'primary' | 'secondary' {
|
||||
return this.tier === 'main' ? 'primary' : 'secondary';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user