remove core from pages

This commit is contained in:
2025-12-18 19:14:50 +01:00
parent 9814d9682c
commit 4a3087ae35
35 changed files with 552 additions and 354 deletions

View File

@@ -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);
}
}

View File

@@ -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(),
};
}, []);

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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
}));
}
}
}

View File

@@ -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}`;
}
}

View 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}