website refactor
This commit is contained in:
@@ -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,
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
58
apps/website/lib/seo/MetadataHelper.ts
Normal file
58
apps/website/lib/seo/MetadataHelper.ts
Normal 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}`],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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[] };
|
||||
}
|
||||
@@ -7,4 +7,7 @@ export interface LeaderboardTeamItem {
|
||||
totalWins: number;
|
||||
logoUrl: string;
|
||||
position: number;
|
||||
isRecruiting: boolean;
|
||||
performanceLevel: string;
|
||||
rating?: number;
|
||||
}
|
||||
Reference in New Issue
Block a user