website refactor

This commit is contained in:
2026-01-20 21:35:50 +01:00
parent 06207bf835
commit 51288234f4
42 changed files with 892 additions and 449 deletions

View File

@@ -1,9 +1,10 @@
import type { DriverLeaderboardItemDTO } from '@/lib/types/generated/DriverLeaderboardItemDTO';
import type { TeamListItemDTO } from '@/lib/types/generated/TeamListItemDTO';
import type { LeaderboardsViewData } from '@/lib/view-data/LeaderboardsViewData';
export class LeaderboardsViewDataBuilder {
static build(
apiDto: { drivers: { drivers: DriverLeaderboardItemDTO[] }; teams: { teams: [] } }
apiDto: { drivers: { drivers: DriverLeaderboardItemDTO[] }; teams: { teams: TeamListItemDTO[] } }
): LeaderboardsViewData {
return {
drivers: apiDto.drivers.drivers.slice(0, 10).map(driver => ({
@@ -17,7 +18,19 @@ export class LeaderboardsViewDataBuilder {
avatarUrl: driver.avatarUrl || '',
position: driver.rank,
})),
teams: [], // Teams leaderboard not implemented
teams: apiDto.teams.teams.slice(0, 10).map((team, index) => ({
id: team.id,
name: team.name,
tag: team.tag,
memberCount: team.memberCount,
category: team.category,
totalWins: team.totalWins || 0,
logoUrl: team.logoUrl || '',
position: index + 1,
isRecruiting: team.isRecruiting,
performanceLevel: team.performanceLevel || 'N/A',
rating: team.rating,
})),
};
}
}

View File

@@ -2,7 +2,7 @@ import { Result } from '@/lib/contracts/Result';
import { PageQuery } from '@/lib/contracts/page-queries/PageQuery';
import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError';
import { TeamService } from '@/lib/services/teams/TeamService';
import type { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
import { TeamSummaryViewModel } from '@/lib/view-models/TeamSummaryViewModel';
export interface TeamLeaderboardPageData {
teams: TeamSummaryViewModel[];
@@ -18,15 +18,7 @@ export class TeamLeaderboardPageQuery implements PageQuery<TeamLeaderboardPageDa
return Result.err(mapToPresentationError(result.getError()));
}
const teams = result.unwrap().map((t: any) => ({
id: t.id,
name: t.name,
logoUrl: t.logoUrl,
memberCount: t.memberCount,
totalWins: t.totalWins,
totalRaces: t.totalRaces,
rating: 1450, // Mocked as in original
} as TeamSummaryViewModel));
const teams = result.unwrap().map((t: any) => new TeamSummaryViewModel(t));
return Result.ok({ teams });
} catch (error) {

View File

@@ -0,0 +1,58 @@
import { Metadata } from 'next';
import { getWebsitePublicEnv } from '@/lib/config/env';
interface MetadataOptions {
title: string;
description: string;
path: string;
image?: string;
type?: 'website' | 'article' | 'profile';
}
export class MetadataHelper {
private static readonly DEFAULT_IMAGE = '/og-image.png';
private static readonly SITE_NAME = 'GridPilot';
static generate({
title,
description,
path,
image = this.DEFAULT_IMAGE,
type = 'website',
}: MetadataOptions): Metadata {
const env = getWebsitePublicEnv();
const baseUrl = env.NEXT_PUBLIC_SITE_URL || 'https://gridpilot.com';
const url = `${baseUrl}${path}`;
const fullTitle = `${title} | ${this.SITE_NAME}`;
return {
title: fullTitle,
description,
alternates: {
canonical: url,
},
openGraph: {
title: fullTitle,
description,
url,
siteName: this.SITE_NAME,
images: [
{
url: image.startsWith('http') ? image : `${baseUrl}${image}`,
width: 1200,
height: 630,
alt: title,
},
],
locale: 'en_US',
type,
},
twitter: {
card: 'summary_large_image',
title: fullTitle,
description,
images: [image.startsWith('http') ? image : `${baseUrl}${image}`],
},
};
}
}

View File

@@ -1,4 +1,5 @@
import { DriversApiClient } from '@/lib/api/drivers/DriversApiClient';
import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient';
import { Result } from '@/lib/contracts/Result';
import { Service, DomainError } from '@/lib/contracts/services/Service';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
@@ -15,8 +16,12 @@ export class LeaderboardsService implements Service {
const logger = new ConsoleLogger();
const driversApiClient = new DriversApiClient(baseUrl, errorReporter, logger);
const teamsApiClient = new TeamsApiClient(baseUrl, errorReporter, logger);
const driverResult = await driversApiClient.getLeaderboard();
const [driverResult, teamResult] = await Promise.all([
driversApiClient.getLeaderboard(),
teamsApiClient.getAll()
]);
if (!driverResult) {
return Result.err({ type: 'notFound', message: 'No leaderboard data available' });
@@ -24,7 +29,7 @@ export class LeaderboardsService implements Service {
const data: LeaderboardsData = {
drivers: driverResult,
teams: { teams: [] }, // Teams leaderboard not implemented
teams: teamResult,
};
return Result.ok(data);

View File

@@ -1,6 +1,7 @@
import type { DriverLeaderboardItemDTO } from '@/lib/types/generated/DriverLeaderboardItemDTO';
import type { TeamListItemDTO } from '@/lib/types/generated/TeamListItemDTO';
export interface LeaderboardsData {
drivers: { drivers: DriverLeaderboardItemDTO[] };
teams: { teams: [] };
teams: { teams: TeamListItemDTO[] };
}

View File

@@ -7,4 +7,7 @@ export interface LeaderboardTeamItem {
totalWins: number;
logoUrl: string;
position: number;
isRecruiting: boolean;
performanceLevel: string;
rating?: number;
}