view data fixes
Some checks failed
Contract Testing / contract-tests (pull_request) Failing after 6m4s
Contract Testing / contract-snapshot (pull_request) Has been skipped

This commit is contained in:
2026-01-23 11:59:49 +01:00
parent ae58839eb2
commit d97f50ed72
191 changed files with 2889 additions and 1019 deletions

View File

@@ -1,3 +1,4 @@
import { ViewData } from '@/lib/contracts/view-data/ViewData';
import type { DashboardStats } from '@/lib/types/admin';
import type { AdminDashboardViewData } from '@/lib/view-data/AdminDashboardViewData';
@@ -7,7 +8,14 @@ import type { AdminDashboardViewData } from '@/lib/view-data/AdminDashboardViewD
* Transforms DashboardStats API DTO into AdminDashboardViewData for server-side rendering.
* Deterministic; side-effect free; no HTTP calls.
*/
export class AdminDashboardViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class AdminDashboardViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return AdminDashboardViewDataBuilder.build(input);
}
static build(
static build(apiDto: DashboardStats): AdminDashboardViewData {
return {
stats: {

View File

@@ -1,27 +1,19 @@
import { ViewData } from '@/lib/contracts/view-data/ViewData';
import type { UserListResponse } from '@/lib/types/admin';
import { AdminUsersViewData } from '@/lib/view-data/AdminUsersViewData';
/**
* AdminUsersViewDataBuilder
*
* Server-side builder that transforms API DTO
* into ViewData for the AdminUsersTemplate.
*
* Deterministic, side-effect free.
*/
export class AdminUsersViewDataBuilder {
static build(apiDto: UserListResponse): AdminUsersViewData {
const users = apiDto.users.map(user => ({
id: user.id,
email: user.email,
displayName: user.displayName,
roles: user.roles,
status: user.status,
isSystemAdmin: user.isSystemAdmin,
createdAt: typeof user.createdAt === 'string' ? user.createdAt : (user.createdAt as unknown as Date).toISOString(),
updatedAt: typeof user.updatedAt === 'string' ? user.updatedAt : (user.updatedAt as unknown as Date).toISOString(),
lastLoginAt: user.lastLoginAt ? (typeof user.lastLoginAt === 'string' ? user.lastLoginAt : (user.lastLoginAt as unknown as Date).toISOString()) : undefined,
primaryDriverId: user.primaryDriverId,
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class AdminUsersViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return AdminUsersViewDataBuilder.build(input);
}
static build(
public static build(apiDto: UserListResponse): AdminUsersViewData {
const users = apiDto.users.map(u => ({
...u,
joinedAt: new Date(u.joinedAt),
}));
return {
@@ -35,4 +27,4 @@ export class AdminUsersViewDataBuilder {
adminCount: users.filter(u => u.isSystemAdmin).length,
};
}
}
}

View File

@@ -0,0 +1,78 @@
import { describe, it, expect } from 'vitest';
import { AnalyticsDashboardViewDataBuilder } from './AnalyticsDashboardViewDataBuilder';
import { AnalyticsDashboardInputViewData } from '@/lib/view-data/AnalyticsDashboardInputViewData';
describe('AnalyticsDashboardViewDataBuilder', () => {
it('builds ViewData from AnalyticsDashboardInputViewData', () => {
const inputViewData: AnalyticsDashboardInputViewData = {
totalUsers: 100,
activeUsers: 40,
totalRaces: 10,
totalLeagues: 5,
};
const viewData = AnalyticsDashboardViewDataBuilder.build(inputViewData);
expect(viewData.metrics.totalUsers).toBe(100);
expect(viewData.metrics.activeUsers).toBe(40);
expect(viewData.metrics.totalRaces).toBe(10);
expect(viewData.metrics.totalLeagues).toBe(5);
expect(viewData.metrics.userEngagementRate).toBeCloseTo(40);
expect(viewData.metrics.formattedEngagementRate).toBe('40.0%');
expect(viewData.metrics.activityLevel).toBe('Low');
});
it('computes engagement rate and formatted engagement rate', () => {
const inputViewData: AnalyticsDashboardInputViewData = {
totalUsers: 200,
activeUsers: 50,
totalRaces: 0,
totalLeagues: 0,
};
const viewData = AnalyticsDashboardViewDataBuilder.build(inputViewData);
expect(viewData.metrics.userEngagementRate).toBeCloseTo(25);
expect(viewData.metrics.formattedEngagementRate).toBe('25.0%');
});
it('handles zero users safely', () => {
const inputViewData: AnalyticsDashboardInputViewData = {
totalUsers: 0,
activeUsers: 0,
totalRaces: 0,
totalLeagues: 0,
};
const viewData = AnalyticsDashboardViewDataBuilder.build(inputViewData);
expect(viewData.metrics.userEngagementRate).toBe(0);
expect(viewData.metrics.formattedEngagementRate).toBe('0.0%');
expect(viewData.metrics.activityLevel).toBe('Low');
});
it('derives activity level buckets from engagement rate', () => {
const low = AnalyticsDashboardViewDataBuilder.build({
totalUsers: 100,
activeUsers: 30,
totalRaces: 0,
totalLeagues: 0,
});
const medium = AnalyticsDashboardViewDataBuilder.build({
totalUsers: 100,
activeUsers: 50,
totalRaces: 0,
totalLeagues: 0,
});
const high = AnalyticsDashboardViewDataBuilder.build({
totalUsers: 100,
activeUsers: 90,
totalRaces: 0,
totalLeagues: 0,
});
expect(low.metrics.activityLevel).toBe('Low');
expect(medium.metrics.activityLevel).toBe('Medium');
expect(high.metrics.activityLevel).toBe('High');
});
});

View File

@@ -0,0 +1,34 @@
import { AnalyticsDashboardInputViewData } from '@/lib/view-data/AnalyticsDashboardInputViewData';
import { AnalyticsDashboardViewData } from '@/lib/view-data/AnalyticsDashboardViewData';
/**
* AnalyticsDashboardViewDataBuilder
*
* Transforms AnalyticsDashboardInputViewData into AnalyticsDashboardViewData for server-side rendering.
* Deterministic; side-effect free; no HTTP calls.
*/
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class AnalyticsDashboardViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return AnalyticsDashboardViewDataBuilder.build(input);
}
static build(viewData: AnalyticsDashboardInputViewData): AnalyticsDashboardViewData {
const userEngagementRate = viewData.totalUsers > 0 ? (viewData.activeUsers / viewData.totalUsers) * 100 : 0;
const formattedEngagementRate = `${userEngagementRate.toFixed(1)}%`;
const activityLevel = userEngagementRate > 70 ? 'High' : userEngagementRate > 40 ? 'Medium' : 'Low';
return {
metrics: {
totalUsers: viewData.totalUsers,
activeUsers: viewData.activeUsers,
totalRaces: viewData.totalRaces,
totalLeagues: viewData.totalLeagues,
userEngagementRate,
formattedEngagementRate,
activityLevel,
},
};
}
}

View File

@@ -8,7 +8,14 @@
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
import { AvatarViewData } from '@/lib/view-data/AvatarViewData';
export class AvatarViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class AvatarViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return AvatarViewDataBuilder.build(input);
}
static build(
static build(apiDto: MediaBinaryDTO): AvatarViewData {
return {
buffer: Buffer.from(apiDto.buffer).toString('base64'),

View File

@@ -8,7 +8,14 @@
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
import { CategoryIconViewData } from '@/lib/view-data/CategoryIconViewData';
export class CategoryIconViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class CategoryIconViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return CategoryIconViewDataBuilder.build(input);
}
static build(
static build(apiDto: MediaBinaryDTO): CategoryIconViewData {
return {
buffer: Buffer.from(apiDto.buffer).toString('base64'),

View File

@@ -1,4 +1,6 @@
export interface CompleteOnboardingViewData {
import { ViewData } from "@/lib/contracts/view-data/ViewData";
export interface CompleteOnboardingViewData extends ViewData {
success: boolean;
driverId?: string;
errorMessage?: string;

View File

@@ -6,8 +6,16 @@
import { CompleteOnboardingOutputDTO } from '@/lib/types/generated/CompleteOnboardingOutputDTO';
import { CompleteOnboardingViewData } from './CompleteOnboardingViewData';
import { ViewData } from '@/lib/contracts/view-data/ViewData';
export class CompleteOnboardingViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class CompleteOnboardingViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return CompleteOnboardingViewDataBuilder.build(input);
}
static build(
/**
* Transform DTO into ViewData
*

View File

@@ -6,6 +6,8 @@ import { DashboardRankDisplay } from '@/lib/display-objects/DashboardRankDisplay
import { DashboardConsistencyDisplay } from '@/lib/display-objects/DashboardConsistencyDisplay';
import { DashboardCountDisplay } from '@/lib/display-objects/DashboardCountDisplay';
import { DashboardLeaguePositionDisplay } from '@/lib/display-objects/DashboardLeaguePositionDisplay';
import { ViewData } from '@/lib/contracts/view-data/ViewData';
import { number } from 'zod';
/**
* DashboardViewDataBuilder
@@ -13,7 +15,14 @@ import { DashboardLeaguePositionDisplay } from '@/lib/display-objects/DashboardL
* Transforms DashboardOverviewDTO (API DTO) into DashboardViewData for server-side rendering.
* Deterministic; side-effect free; no HTTP calls.
*/
export class DashboardViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class DashboardViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return DashboardViewDataBuilder.build(input);
}
static build(
static build(apiDto: DashboardOverviewDTO): DashboardViewData {
return {
currentDriver: {

View File

@@ -0,0 +1,6 @@
import { ViewData } from "@/lib/contracts/view-data/ViewData";
export interface DeleteMediaViewData extends ViewData {
success: boolean;
error?: string;
}

View File

@@ -0,0 +1,122 @@
import { describe, it, expect } from 'vitest';
import { DeleteMediaViewDataBuilder } from './DeleteMediaViewDataBuilder';
import type { DeleteMediaOutputDTO } from '@/lib/types/generated/DeleteMediaOutputDTO';
describe('DeleteMediaViewDataBuilder', () => {
describe('happy paths', () => {
it('should transform successful deletion DTO to ViewData correctly', () => {
const apiDto: DeleteMediaOutputDTO = {
success: true,
};
const result = DeleteMediaViewDataBuilder.build(apiDto);
expect(result).toEqual({
success: true,
error: undefined,
});
});
it('should handle deletion with error message', () => {
const apiDto: DeleteMediaOutputDTO = {
success: false,
error: 'Failed to delete media',
};
const result = DeleteMediaViewDataBuilder.build(apiDto);
expect(result).toEqual({
success: false,
error: 'Failed to delete media',
});
});
it('should handle deletion with only success field', () => {
const apiDto: DeleteMediaOutputDTO = {
success: true,
};
const result = DeleteMediaViewDataBuilder.build(apiDto);
expect(result).toEqual({
success: true,
error: undefined,
});
});
});
describe('data transformation', () => {
it('should preserve all DTO fields in the output', () => {
const apiDto: DeleteMediaOutputDTO = {
success: false,
error: 'Something went wrong',
};
const result = DeleteMediaViewDataBuilder.build(apiDto);
expect(result.success).toBe(apiDto.success);
expect(result.error).toBe(apiDto.error);
});
it('should not modify the input DTO', () => {
const apiDto: DeleteMediaOutputDTO = {
success: false,
error: 'Error',
};
const originalDto = { ...apiDto };
DeleteMediaViewDataBuilder.build(apiDto);
expect(apiDto).toEqual(originalDto);
});
});
describe('edge cases', () => {
it('should handle false success value', () => {
const apiDto: DeleteMediaOutputDTO = {
success: false,
error: 'Error occurred',
};
const result = DeleteMediaViewDataBuilder.build(apiDto);
expect(result.success).toBe(false);
expect(result.error).toBe('Error occurred');
});
it('should handle empty string error message', () => {
const apiDto: DeleteMediaOutputDTO = {
success: false,
error: '',
};
const result = DeleteMediaViewDataBuilder.build(apiDto);
expect(result.success).toBe(false);
expect(result.error).toBe('');
});
it('should handle very long error message', () => {
const longError = 'Error: ' + 'a'.repeat(1000);
const apiDto: DeleteMediaOutputDTO = {
success: false,
error: longError,
};
const result = DeleteMediaViewDataBuilder.build(apiDto);
expect(result.error).toBe(longError);
});
it('should handle special characters in error message', () => {
const apiDto: DeleteMediaOutputDTO = {
success: false,
error: 'Error: "Failed to delete media" (code: 500)',
};
const result = DeleteMediaViewDataBuilder.build(apiDto);
expect(result.error).toBe('Error: "Failed to delete media" (code: 500)');
});
});
});

View File

@@ -0,0 +1,30 @@
/**
* DeleteMedia ViewData Builder
*
* Transforms media deletion result into ViewData for templates.
*/
import { DeleteMediaOutputDTO } from '@/lib/types/generated/DeleteMediaOutputDTO';
import { DeleteMediaViewData } from './DeleteMediaViewData';
import { ViewData } from '@/lib/contracts/view-data/ViewData';
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class DeleteMediaViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return DeleteMediaViewDataBuilder.build(input);
}
/**
* Transform DTO into ViewData
*
* @param apiDto - The API DTO to transform
* @returns ViewData for templates
*/
static build(apiDto: DeleteMediaOutputDTO): DeleteMediaViewData {
return {
success: apiDto.success,
error: apiDto.error,
};
}
}

View File

@@ -1,5 +1,5 @@
import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO';
import type { DriverProfileViewData } from '@/lib/types/view-data/DriverProfileViewData';
import type { DriverProfileViewData } from '@/lib/view-data/DriverProfileViewData';
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
import { RatingDisplay } from '@/lib/display-objects/RatingDisplay';
import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
@@ -12,7 +12,14 @@ import { PercentDisplay } from '@/lib/display-objects/PercentDisplay';
* Transforms GetDriverProfileOutputDTO into ViewData for the driver profile page.
* Deterministic, side-effect free, no HTTP calls.
*/
export class DriverProfileViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class DriverProfileViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return DriverProfileViewDataBuilder.build(input);
}
static build(
static build(apiDto: GetDriverProfileOutputDTO): DriverProfileViewData {
return {
currentDriver: apiDto.currentDriver ? {

View File

@@ -1,9 +1,15 @@
import type { DriverLeaderboardItemDTO } from '@/lib/types/generated/DriverLeaderboardItemDTO';
import type { DriverRankingsViewData } from '@/lib/view-data/DriverRankingsViewData';
import { WinRateDisplay } from '@/lib/display-objects/WinRateDisplay';
import { MedalDisplay } from '@/lib/display-objects/MedalDisplay';
import { ViewData } from '@/lib/contracts/view-data/ViewData';
export class DriverRankingsViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class DriverRankingsViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return DriverRankingsViewDataBuilder.build(input);
}
static build(
static build(apiDto: DriverLeaderboardItemDTO[]): DriverRankingsViewData {
if (!apiDto || apiDto.length === 0) {
return {

View File

@@ -1,9 +1,16 @@
import type { DriversLeaderboardDTO } from '@/lib/types/generated/DriversLeaderboardDTO';
import type { DriversViewData } from '@/lib/types/view-data/DriversViewData';
import type { DriversViewData } from '@/lib/view-data/DriversViewData';
import { RatingDisplay } from '@/lib/display-objects/RatingDisplay';
import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
export class DriversViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class DriversViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return DriversViewDataBuilder.build(input);
}
static build(
static build(dto: DriversLeaderboardDTO): DriversViewData {
return {
drivers: dto.drivers.map(driver => ({

View File

@@ -6,9 +6,18 @@
*/
import { ForgotPasswordPageDTO } from '@/lib/services/auth/types/ForgotPasswordPageDTO';
import { ForgotPasswordViewData } from './types/ForgotPasswordViewData';
import { ForgotPasswordViewData } from '../../view-data/ForgotPasswordViewData';
import { ViewData } from '@/lib/contracts/view-data/ViewData';
import { error } from 'console';
export class ForgotPasswordViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class ForgotPasswordViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return ForgotPasswordViewDataBuilder.build(input);
}
static build(
static build(apiDto: ForgotPasswordPageDTO): ForgotPasswordViewData {
return {
returnTo: apiDto.returnTo,

View File

@@ -1,4 +1,6 @@
export interface GenerateAvatarsViewData {
import { ViewData } from "@/lib/contracts/view-data/ViewData";
export interface GenerateAvatarsViewData extends ViewData {
success: boolean;
avatarUrls: string[];
errorMessage?: string;

View File

@@ -7,8 +7,16 @@
import { RequestAvatarGenerationOutputDTO } from '@/lib/types/generated/RequestAvatarGenerationOutputDTO';
import { GenerateAvatarsViewData } from './GenerateAvatarsViewData';
import { ViewData } from '@/lib/contracts/view-data/ViewData';
export class GenerateAvatarsViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class GenerateAvatarsViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return GenerateAvatarsViewDataBuilder.build(input);
}
static build(
/**
* Transform DTO into ViewData
*

View File

@@ -37,7 +37,14 @@ export interface HealthDTO {
}>;
}
export class HealthViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class HealthViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return HealthViewDataBuilder.build(input);
}
static build(
static build(dto: HealthDTO): HealthViewData {
const now = new Date();
const lastUpdated = dto.timestamp || now.toISOString();

View File

@@ -1,12 +1,20 @@
import type { HomeViewData } from '@/templates/HomeTemplate';
import type { HomeDataDTO } from '@/lib/types/dtos/HomeDataDTO';
import { ViewData } from '@/lib/contracts/view-data/ViewData';
/**
* HomeViewDataBuilder
*
* Transforms HomeDataDTO to HomeViewData.
*/
export class HomeViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class HomeViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return HomeViewDataBuilder.build(input);
}
static build(
/**
* Build HomeViewData from HomeDataDTO
*

View File

@@ -1,8 +1,16 @@
import { ViewData } from '@/lib/contracts/view-data/ViewData';
import type { DriverLeaderboardItemDTO } from '@/lib/types/generated/DriverLeaderboardItemDTO';
import type { GetTeamsLeaderboardOutputDTO } from '@/lib/types/generated/GetTeamsLeaderboardOutputDTO';
import type { LeaderboardsViewData } from '@/lib/view-data/LeaderboardsViewData';
export class LeaderboardsViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class LeaderboardsViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return LeaderboardsViewDataBuilder.build(input);
}
static build(
static build(
apiDto: { drivers: { drivers: DriverLeaderboardItemDTO[] }; teams: GetTeamsLeaderboardOutputDTO }
): LeaderboardsViewData {

View File

@@ -8,7 +8,14 @@
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
import { LeagueCoverViewData } from '@/lib/view-data/LeagueCoverViewData';
export class LeagueCoverViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class LeagueCoverViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return LeagueCoverViewDataBuilder.build(input);
}
static build(
static build(apiDto: MediaBinaryDTO): LeagueCoverViewData {
return {
buffer: Buffer.from(apiDto.buffer).toString('base64'),

View File

@@ -11,7 +11,14 @@ import type { LeagueDetailViewData, LeagueInfoData, LiveRaceData, DriverSummaryD
* Transforms API DTOs into LeagueDetailViewData for server-side rendering.
* Deterministic; side-effect free; no HTTP calls.
*/
export class LeagueDetailViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class LeagueDetailViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return LeagueDetailViewDataBuilder.build(input);
}
static build(
static build(input: {
league: LeagueWithCapacityAndScoringDTO;
owner: GetDriverOutputDTO | null;

View File

@@ -8,7 +8,14 @@
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
import { LeagueLogoViewData } from '@/lib/view-data/LeagueLogoViewData';
export class LeagueLogoViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class LeagueLogoViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return LeagueLogoViewDataBuilder.build(input);
}
static build(
static build(apiDto: MediaBinaryDTO): LeagueLogoViewData {
return {
buffer: Buffer.from(apiDto.buffer).toString('base64'),

View File

@@ -9,7 +9,14 @@ import { DateDisplay } from '@/lib/display-objects/DateDisplay';
* Transforms API DTOs into LeagueRosterAdminViewData for server-side rendering.
* Deterministic; side-effect free; no HTTP calls.
*/
export class LeagueRosterAdminViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class LeagueRosterAdminViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return LeagueRosterAdminViewDataBuilder.build(input);
}
static build(
static build(input: {
leagueId: string;
members: LeagueRosterMemberDTO[];

View File

@@ -1,7 +1,14 @@
import { LeagueScheduleViewData } from '@/lib/view-data/leagues/LeagueScheduleViewData';
import { LeagueScheduleApiDto } from '@/lib/types/tbd/LeagueScheduleApiDto';
export class LeagueScheduleViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class LeagueScheduleViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return LeagueScheduleViewDataBuilder.build(input);
}
static build(
static build(apiDto: LeagueScheduleApiDto, currentDriverId?: string, isAdmin: boolean = false): LeagueScheduleViewData {
const now = new Date();

View File

@@ -1,7 +1,14 @@
import { LeagueSettingsViewData } from '@/lib/view-data/leagues/LeagueSettingsViewData';
import { LeagueSettingsApiDto } from '@/lib/types/tbd/LeagueSettingsApiDto';
import { LeagueSettingsViewData } from '@/lib/view-data/LeagueSettingsViewData';
export class LeagueSettingsViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class LeagueSettingsViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return LeagueSettingsViewDataBuilder.build(input);
}
static build(
static build(apiDto: LeagueSettingsApiDto): LeagueSettingsViewData {
return {
leagueId: apiDto.leagueId,

View File

@@ -1,9 +1,16 @@
import { LeagueSponsorshipsViewData } from '@/lib/view-data/leagues/LeagueSponsorshipsViewData';
import { LeagueSponsorshipsApiDto } from '@/lib/types/tbd/LeagueSponsorshipsApiDto';
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
import { StatusDisplay } from '@/lib/display-objects/StatusDisplay';
import { LeagueSponsorshipsApiDto } from '@/lib/types/tbd/LeagueSponsorshipsApiDto';
import { LeagueSponsorshipsViewData } from '@/lib/view-data/LeagueSponsorshipsViewData';
export class LeagueSponsorshipsViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class LeagueSponsorshipsViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return LeagueSponsorshipsViewDataBuilder.build(input);
}
static build(
static build(apiDto: LeagueSponsorshipsApiDto): LeagueSponsorshipsViewData {
return {
leagueId: apiDto.leagueId,

View File

@@ -16,7 +16,14 @@ interface LeagueMembershipsApiDto {
* Transforms API DTOs into LeagueStandingsViewData for server-side rendering.
* Deterministic; side-effect free; no HTTP calls.
*/
export class LeagueStandingsViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class LeagueStandingsViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return LeagueStandingsViewDataBuilder.build(input);
}
static build(
static build(
standingsDto: LeagueStandingsApiDto,
membershipsDto: LeagueMembershipsApiDto,

View File

@@ -1,9 +1,16 @@
import { LeagueWalletViewData, LeagueWalletTransactionViewData } from '@/lib/view-data/leagues/LeagueWalletViewData';
import { LeagueWalletApiDto } from '@/lib/types/tbd/LeagueWalletApiDto';
import { CurrencyDisplay } from '@/lib/display-objects/CurrencyDisplay';
import { DateDisplay } from '@/lib/display-objects/DateDisplay';
import { LeagueWalletApiDto } from '@/lib/types/tbd/LeagueWalletApiDto';
import { LeagueWalletTransactionViewData, LeagueWalletViewData } from '@/lib/view-data/LeagueWalletViewData';
export class LeagueWalletViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class LeagueWalletViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return LeagueWalletViewDataBuilder.build(input);
}
static build(
static build(apiDto: LeagueWalletApiDto): LeagueWalletViewData {
const transactions: LeagueWalletTransactionViewData[] = apiDto.transactions.map(t => ({
...t,

View File

@@ -7,7 +7,14 @@ import type { LeaguesViewData } from '@/lib/view-data/LeaguesViewData';
* Transforms AllLeaguesWithCapacityAndScoringDTO (API DTO) into LeaguesViewData for server-side rendering.
* Deterministic; side-effect free; no HTTP calls.
*/
export class LeaguesViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class LeaguesViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return LeaguesViewDataBuilder.build(input);
}
static build(
static build(apiDto: AllLeaguesWithCapacityAndScoringDTO): LeaguesViewData {
return {
leagues: apiDto.leagues.map((league) => ({

View File

@@ -6,9 +6,18 @@
*/
import { LoginPageDTO } from '@/lib/services/auth/types/LoginPageDTO';
import { LoginViewData } from './types/LoginViewData';
import { LoginViewData } from '../../view-data/LoginViewData';
import { ViewData } from '@/lib/contracts/view-data/ViewData';
import { error } from 'console';
export class LoginViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class LoginViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return LoginViewDataBuilder.build(input);
}
static build(
static build(apiDto: LoginPageDTO): LoginViewData {
return {
returnTo: apiDto.returnTo,

View File

@@ -6,7 +6,14 @@
import { OnboardingPageViewData } from '@/lib/view-data/OnboardingPageViewData';
export class OnboardingPageViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class OnboardingPageViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return OnboardingPageViewDataBuilder.build(input);
}
static build(
/**
* Transform driver data into ViewData
*

View File

@@ -9,7 +9,14 @@ import { Result } from '@/lib/contracts/Result';
import { PresentationError } from '@/lib/contracts/page-queries/PresentationError';
import { OnboardingPageViewData } from '@/lib/view-data/OnboardingPageViewData';
export class OnboardingViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class OnboardingViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return OnboardingViewDataBuilder.build(input);
}
static build(
static build(apiDto: Result<{ isAlreadyOnboarded: boolean }, PresentationError>): Result<OnboardingPageViewData, PresentationError> {
if (apiDto.isErr()) {
return Result.err(apiDto.getError());

View File

@@ -19,7 +19,14 @@ interface ProfileLeaguesPageDto {
* ViewData Builder for Profile Leagues page
* Transforms Page DTO to ViewData for templates
*/
export class ProfileLeaguesViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class ProfileLeaguesViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return ProfileLeaguesViewDataBuilder.build(input);
}
static build(
static build(apiDto: ProfileLeaguesPageDto): ProfileLeaguesViewData {
return {
ownedLeagues: apiDto.ownedLeagues.map((league: { leagueId: string; name: string; description: string; membershipRole: 'owner' | 'admin' | 'steward' | 'member'; }) => ({

View File

@@ -8,7 +8,14 @@ import { PercentDisplay } from '@/lib/display-objects/PercentDisplay';
import { RatingDisplay } from '@/lib/display-objects/RatingDisplay';
import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
export class ProfileViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class ProfileViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return ProfileViewDataBuilder.build(input);
}
static build(
static build(apiDto: GetDriverProfileOutputDTO): ProfileViewData {
const driver = apiDto.currentDriver;

View File

@@ -1,4 +1,4 @@
import { ProtestDetailViewData } from '@/lib/view-data/leagues/ProtestDetailViewData';
import { ProtestDetailViewData } from '@/lib/view-data/ProtestDetailViewData';
interface ProtestDetailApiDto {
id: string;
@@ -29,7 +29,14 @@ interface ProtestDetailApiDto {
}>;
}
export class ProtestDetailViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class ProtestDetailViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return ProtestDetailViewDataBuilder.build(input);
}
static build(
static build(apiDto: ProtestDetailApiDto): ProtestDetailViewData {
return {
protestId: apiDto.id,

View File

@@ -1,4 +1,4 @@
import { RaceDetailViewData, RaceDetailRace, RaceDetailLeague, RaceDetailEntry, RaceDetailRegistration, RaceDetailUserResult } from '@/lib/view-data/races/RaceDetailViewData';
import { RaceDetailEntry, RaceDetailLeague, RaceDetailRace, RaceDetailRegistration, RaceDetailUserResult, RaceDetailViewData } from '@/lib/view-data/RaceDetailViewData';
/**
* Race Detail View Data Builder
@@ -6,7 +6,14 @@ import { RaceDetailViewData, RaceDetailRace, RaceDetailLeague, RaceDetailEntry,
* Transforms API DTO into ViewData for the race detail template.
* Deterministic, side-effect free.
*/
export class RaceDetailViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class RaceDetailViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return RaceDetailViewDataBuilder.build(input);
}
static build(
static build(apiDto: any): RaceDetailViewData {
if (!apiDto || !apiDto.race) {
return {

View File

@@ -1,4 +1,4 @@
import { RaceResultsViewData, RaceResultsResult, RaceResultsPenalty } from '@/lib/view-data/races/RaceResultsViewData';
import { RaceResultsPenalty, RaceResultsResult, RaceResultsViewData } from '@/lib/view-data/RaceResultsViewData';
/**
* Race Results View Data Builder
@@ -6,7 +6,14 @@ import { RaceResultsViewData, RaceResultsResult, RaceResultsPenalty } from '@/li
* Transforms API DTO into ViewData for the race results template.
* Deterministic, side-effect free.
*/
export class RaceResultsViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class RaceResultsViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return RaceResultsViewDataBuilder.build(input);
}
static build(
static build(apiDto: unknown): RaceResultsViewData {
if (!apiDto) {
return {

View File

@@ -1,4 +1,4 @@
import { RaceStewardingViewData, Protest, Penalty, Driver } from '@/lib/view-data/races/RaceStewardingViewData';
import { Driver, Penalty, Protest, RaceStewardingViewData } from '@/lib/view-data/RaceStewardingViewData';
/**
* Race Stewarding View Data Builder
@@ -6,7 +6,14 @@ import { RaceStewardingViewData, Protest, Penalty, Driver } from '@/lib/view-dat
* Transforms API DTO into ViewData for the race stewarding template.
* Deterministic, side-effect free.
*/
export class RaceStewardingViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class RaceStewardingViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return RaceStewardingViewDataBuilder.build(input);
}
static build(
static build(apiDto: unknown): RaceStewardingViewData {
if (!apiDto) {
return {

View File

@@ -4,7 +4,14 @@ import { DateDisplay } from '@/lib/display-objects/DateDisplay';
import { RaceStatusDisplay } from '@/lib/display-objects/RaceStatusDisplay';
import { RelativeTimeDisplay } from '@/lib/display-objects/RelativeTimeDisplay';
export class RacesViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class RacesViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return RacesViewDataBuilder.build(input);
}
static build(
static build(apiDto: RacesPageDataDTO): RacesViewData {
const now = new Date();
const races = apiDto.races.map((race): RaceViewData => {

View File

@@ -6,9 +6,18 @@
*/
import { ResetPasswordPageDTO } from '@/lib/services/auth/types/ResetPasswordPageDTO';
import { ResetPasswordViewData } from './types/ResetPasswordViewData';
import { ResetPasswordViewData } from '../../view-data/ResetPasswordViewData';
import { ViewData } from '@/lib/contracts/view-data/ViewData';
import { error } from 'console';
export class ResetPasswordViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class ResetPasswordViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return ResetPasswordViewDataBuilder.build(input);
}
static build(
static build(apiDto: ResetPasswordPageDTO): ResetPasswordViewData {
return {
token: apiDto.token,

View File

@@ -1,7 +1,14 @@
import { RulebookViewData } from '@/lib/view-data/leagues/RulebookViewData';
import { RulebookApiDto } from '@/lib/types/tbd/RulebookApiDto';
import { RulebookViewData } from '@/lib/view-data/RulebookViewData';
export class RulebookViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class RulebookViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return RulebookViewDataBuilder.build(input);
}
static build(
static build(apiDto: RulebookApiDto): RulebookViewData {
const primaryChampionship = apiDto.scoringConfig.championships.find(c => c.type === 'driver') ?? apiDto.scoringConfig.championships[0];
const positionPoints: { position: number; points: number }[] = primaryChampionship?.pointsPreview

View File

@@ -6,9 +6,14 @@
*/
import { SignupPageDTO } from '@/lib/services/auth/types/SignupPageDTO';
import { SignupViewData } from './types/SignupViewData';
import { SignupViewData } from '../../view-data/SignupViewData';
import { ViewDataBuilder } from '../../contracts/builders/ViewDataBuilder';
export class SignupViewDataBuilder implements ViewDataBuilder<SignupPageDTO, SignupViewData> {
build(apiDto: SignupPageDTO): SignupViewData {
return SignupViewDataBuilder.build(apiDto);
}
export class SignupViewDataBuilder {
static build(apiDto: SignupPageDTO): SignupViewData {
return {
returnTo: apiDto.returnTo,

View File

@@ -9,7 +9,14 @@ import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
* Transforms SponsorDashboardDTO into ViewData for templates.
* Deterministic and side-effect free.
*/
export class SponsorDashboardViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class SponsorDashboardViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return SponsorDashboardViewDataBuilder.build(input);
}
static build(
static build(apiDto: SponsorDashboardDTO): SponsorDashboardViewData {
const totalInvestmentValue = apiDto.investment.activeSponsorships * 1000;

View File

@@ -8,7 +8,14 @@
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
import { SponsorLogoViewData } from '@/lib/view-data/SponsorLogoViewData';
export class SponsorLogoViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class SponsorLogoViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return SponsorLogoViewDataBuilder.build(input);
}
static build(
static build(apiDto: MediaBinaryDTO): SponsorLogoViewData {
return {
buffer: Buffer.from(apiDto.buffer).toString('base64'),

View File

@@ -5,7 +5,14 @@ import type { SponsorshipRequestsViewData } from '@/lib/view-data/SponsorshipReq
* ViewData Builder for Sponsorship Requests page
* Transforms API DTO to ViewData for templates
*/
export class SponsorshipRequestsPageViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class SponsorshipRequestsPageViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return SponsorshipRequestsPageViewDataBuilder.build(input);
}
static build(
static build(apiDto: GetPendingSponsorshipRequestsOutputDTO): SponsorshipRequestsViewData {
return {
sections: [{

View File

@@ -1,7 +1,14 @@
import type { GetPendingSponsorshipRequestsOutputDTO } from '@/lib/types/generated/GetPendingSponsorshipRequestsOutputDTO';
import type { SponsorshipRequestsViewData } from '@/lib/view-data/SponsorshipRequestsViewData';
export class SponsorshipRequestsViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class SponsorshipRequestsViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return SponsorshipRequestsViewDataBuilder.build(input);
}
static build(
static build(apiDto: GetPendingSponsorshipRequestsOutputDTO): SponsorshipRequestsViewData {
return {
sections: [

View File

@@ -1,8 +1,15 @@
import { StewardingApiDto } from '@/lib/types/tbd/StewardingApiDto';
import { StewardingViewData } from '@/lib/view-data/leagues/StewardingViewData';
import { StewardingViewData } from '@/lib/view-data/StewardingViewData';
export class StewardingViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class StewardingViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return StewardingViewDataBuilder.build(input);
}
static build(
static build(apiDto: StewardingApiDto): StewardingViewData {
return {
leagueId: apiDto.leagueId,

View File

@@ -9,7 +9,14 @@ import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
* TeamDetailViewDataBuilder - Transforms TeamDetailPageDto into ViewData
* Deterministic; side-effect free; no HTTP calls
*/
export class TeamDetailViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class TeamDetailViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return TeamDetailViewDataBuilder.build(input);
}
static build(
static build(apiDto: TeamDetailPageDto): TeamDetailViewData {
const team: TeamDetailData = {
id: apiDto.team.id,

View File

@@ -8,7 +8,14 @@
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
import { TeamLogoViewData } from '@/lib/view-data/TeamLogoViewData';
export class TeamLogoViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class TeamLogoViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return TeamLogoViewDataBuilder.build(input);
}
static build(
static build(apiDto: MediaBinaryDTO): TeamLogoViewData {
return {
buffer: Buffer.from(apiDto.buffer).toString('base64'),

View File

@@ -1,21 +1,18 @@
import { ViewData } from '@/lib/contracts/view-data/ViewData';
import type { GetTeamsLeaderboardOutputDTO } from '@/lib/types/generated/GetTeamsLeaderboardOutputDTO';
import type { TeamRankingsViewData } from '@/lib/view-data/TeamRankingsViewData';
export class TeamRankingsViewDataBuilder {
static build(apiDto: GetTeamsLeaderboardOutputDTO): TeamRankingsViewData {
const allTeams = apiDto.teams.map((team, index) => ({
id: team.id,
name: team.name,
tag: team.tag,
memberCount: team.memberCount,
category: undefined,
totalWins: team.totalWins || 0,
logoUrl: team.logoUrl || '',
position: index + 1,
isRecruiting: team.isRecruiting,
performanceLevel: team.performanceLevel || 'N/A',
rating: team.rating || 0,
totalRaces: team.totalRaces || 0,
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class TeamRankingsViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return TeamRankingsViewDataBuilder.build(input);
}
static build(
public static build(apiDto: GetTeamsLeaderboardOutputDTO): TeamRankingsViewData {
const allTeams = apiDto.teams.map(t => ({
...t,
}));
return {

View File

@@ -8,7 +8,14 @@ import { NumberDisplay } from '@/lib/display-objects/NumberDisplay';
* TeamsViewDataBuilder - Transforms TeamsPageDto into ViewData for TeamsTemplate
* Deterministic; side-effect free; no HTTP calls
*/
export class TeamsViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class TeamsViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return TeamsViewDataBuilder.build(input);
}
static build(
static build(apiDto: TeamsPageDto): TeamsViewData {
const teams: TeamSummaryData[] = apiDto.teams.map((team: TeamListItemDTO): TeamSummaryData => ({
teamId: team.id,

View File

@@ -8,7 +8,14 @@
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
import { TrackImageViewData } from '@/lib/view-data/TrackImageViewData';
export class TrackImageViewDataBuilder {
import { ViewDataBuilder } from "../../contracts/builders/ViewDataBuilder";
export class TrackImageViewDataBuilder implements ViewDataBuilder<any, any> {
build(input: any): any {
return TrackImageViewDataBuilder.build(input);
}
static build(
static build(apiDto: MediaBinaryDTO): TrackImageViewData {
return {
buffer: Buffer.from(apiDto.buffer).toString('base64'),

View File

@@ -1,15 +0,0 @@
/**
* Forgot Password View Data
*
* ViewData for the forgot password template.
*/
export interface ForgotPasswordViewData {
returnTo: string;
showSuccess: boolean;
successMessage?: string;
magicLink?: string;
formState: any; // Will be managed by client component
isSubmitting: boolean;
submitError?: string;
}

View File

@@ -1,17 +0,0 @@
/**
* Login View Data
*
* ViewData for the login template.
*/
import { FormState } from './FormState';
export interface LoginViewData {
returnTo: string;
hasInsufficientPermissions: boolean;
showPassword: boolean;
showErrorDetails: boolean;
formState: FormState;
isSubmitting: boolean;
submitError?: string;
}

View File

@@ -1,15 +0,0 @@
/**
* Reset Password View Data
*
* ViewData for the reset password template.
*/
export interface ResetPasswordViewData {
token: string;
returnTo: string;
showSuccess: boolean;
successMessage?: string;
formState: any; // Will be managed by client component
isSubmitting: boolean;
submitError?: string;
}

View File

@@ -1,12 +0,0 @@
/**
* Signup View Data
*
* ViewData for the signup template.
*/
export interface SignupViewData {
returnTo: string;
formState: any; // Will be managed by client component
isSubmitting: boolean;
submitError?: string;
}