website refactor
This commit is contained in:
18
apps/website/lib/builders/view-data/AvatarViewDataBuilder.ts
Normal file
18
apps/website/lib/builders/view-data/AvatarViewDataBuilder.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* AvatarViewDataBuilder
|
||||
*
|
||||
* Transforms MediaBinaryDTO into AvatarViewData for server-side rendering.
|
||||
* Deterministic; side-effect free; no HTTP calls.
|
||||
*/
|
||||
|
||||
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
|
||||
import { AvatarViewData } from '@/lib/view-data/AvatarViewData';
|
||||
|
||||
export class AvatarViewDataBuilder {
|
||||
static build(apiDto: MediaBinaryDTO): AvatarViewData {
|
||||
return {
|
||||
buffer: apiDto.buffer,
|
||||
contentType: apiDto.contentType,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* CategoryIconViewDataBuilder
|
||||
*
|
||||
* Transforms MediaBinaryDTO into CategoryIconViewData for server-side rendering.
|
||||
* Deterministic; side-effect free; no HTTP calls.
|
||||
*/
|
||||
|
||||
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
|
||||
import { CategoryIconViewData } from '@/lib/view-data/CategoryIconViewData';
|
||||
|
||||
export class CategoryIconViewDataBuilder {
|
||||
static build(apiDto: MediaBinaryDTO): CategoryIconViewData {
|
||||
return {
|
||||
buffer: apiDto.buffer,
|
||||
contentType: apiDto.contentType,
|
||||
};
|
||||
}
|
||||
}
|
||||
15
apps/website/lib/builders/view-data/DriverProfileViewData.ts
Normal file
15
apps/website/lib/builders/view-data/DriverProfileViewData.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { DriverProfileDriverSummaryDTO } from '@/lib/types/generated/DriverProfileDriverSummaryDTO';
|
||||
import type { DriverProfileStatsDTO } from '@/lib/types/generated/DriverProfileStatsDTO';
|
||||
import type { DriverProfileFinishDistributionDTO } from '@/lib/types/generated/DriverProfileFinishDistributionDTO';
|
||||
import type { DriverProfileTeamMembershipDTO } from '@/lib/types/generated/DriverProfileTeamMembershipDTO';
|
||||
import type { DriverProfileSocialSummaryDTO } from '@/lib/types/generated/DriverProfileSocialSummaryDTO';
|
||||
import type { DriverProfileExtendedProfileDTO } from '@/lib/types/generated/DriverProfileExtendedProfileDTO';
|
||||
|
||||
export interface DriverProfileViewData {
|
||||
currentDriver: DriverProfileDriverSummaryDTO | null;
|
||||
stats: DriverProfileStatsDTO | null;
|
||||
finishDistribution: DriverProfileFinishDistributionDTO | null;
|
||||
teamMemberships: DriverProfileTeamMembershipDTO[];
|
||||
socialSummary: DriverProfileSocialSummaryDTO;
|
||||
extendedProfile: DriverProfileExtendedProfileDTO | null;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { GetDriverProfileOutputDTO } from '@/lib/types/generated/GetDriverProfileOutputDTO';
|
||||
import type { DriverProfileViewData } from './DriverProfileViewData';
|
||||
|
||||
/**
|
||||
* DriverProfileViewDataBuilder
|
||||
*
|
||||
* Transforms GetDriverProfileOutputDTO into ViewData for the driver profile page.
|
||||
* Deterministic, side-effect free, no HTTP calls.
|
||||
*/
|
||||
export class DriverProfileViewDataBuilder {
|
||||
static build(apiDto: GetDriverProfileOutputDTO): DriverProfileViewData {
|
||||
return {
|
||||
currentDriver: apiDto.currentDriver || null,
|
||||
stats: apiDto.stats || null,
|
||||
finishDistribution: apiDto.finishDistribution || null,
|
||||
teamMemberships: apiDto.teamMemberships,
|
||||
socialSummary: apiDto.socialSummary,
|
||||
extendedProfile: apiDto.extendedProfile || null,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { DriverRankingsPageDto } from '@/lib/page-queries/page-dtos/DriverRankingsPageDto';
|
||||
import type { DriverLeaderboardItemDTO } from '@/lib/types/generated/DriverLeaderboardItemDTO';
|
||||
import type { DriverRankingsViewData } from '@/lib/view-data/DriverRankingsViewData';
|
||||
|
||||
export class DriverRankingsViewDataBuilder {
|
||||
static build(dto: DriverRankingsPageDto | null): DriverRankingsViewData {
|
||||
if (!dto || !dto.drivers) {
|
||||
static build(apiDto: DriverLeaderboardItemDTO[]): DriverRankingsViewData {
|
||||
if (!apiDto || apiDto.length === 0) {
|
||||
return {
|
||||
drivers: [],
|
||||
podium: [],
|
||||
@@ -15,7 +15,7 @@ export class DriverRankingsViewDataBuilder {
|
||||
}
|
||||
|
||||
return {
|
||||
drivers: dto.drivers.map(driver => ({
|
||||
drivers: apiDto.map(driver => ({
|
||||
id: driver.id,
|
||||
name: driver.name,
|
||||
rating: driver.rating,
|
||||
@@ -36,7 +36,7 @@ export class DriverRankingsViewDataBuilder {
|
||||
driver.rank === 3 ? 'text-amber-600' :
|
||||
'text-gray-500',
|
||||
})),
|
||||
podium: dto.drivers.slice(0, 3).map((driver, index) => {
|
||||
podium: apiDto.slice(0, 3).map((driver, index) => {
|
||||
const positions = [2, 1, 3]; // Display order: 2nd, 1st, 3rd
|
||||
const position = positions[index];
|
||||
return {
|
||||
|
||||
9
apps/website/lib/builders/view-data/DriversViewData.ts
Normal file
9
apps/website/lib/builders/view-data/DriversViewData.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { DriversLeaderboardDTO } from '@/lib/types/generated/DriversLeaderboardDTO';
|
||||
import type { DriverLeaderboardItemDTO } from '@/lib/types/generated/DriverLeaderboardItemDTO';
|
||||
|
||||
export interface DriversViewData {
|
||||
drivers: DriverLeaderboardItemDTO[];
|
||||
totalRaces: number;
|
||||
totalWins: number;
|
||||
activeCount: number;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { DriversLeaderboardDTO } from '@/lib/types/generated/DriversLeaderboardDTO';
|
||||
import type { DriversViewData } from './DriversViewData';
|
||||
|
||||
/**
|
||||
* DriversViewDataBuilder
|
||||
*
|
||||
* Transforms DriversLeaderboardDTO into ViewData for the drivers listing page.
|
||||
* Deterministic, side-effect free, no HTTP calls.
|
||||
*
|
||||
* This builder does NOT perform filtering or sorting - that belongs in the API.
|
||||
* If the API doesn't support filtering, it should be marked as NotImplemented.
|
||||
*/
|
||||
export class DriversViewDataBuilder {
|
||||
static build(apiDto: DriversLeaderboardDTO): DriversViewData {
|
||||
return {
|
||||
drivers: apiDto.drivers,
|
||||
totalRaces: apiDto.totalRaces,
|
||||
totalWins: apiDto.totalWins,
|
||||
activeCount: apiDto.activeCount,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,17 @@
|
||||
/**
|
||||
* Forgot Password View Data Builder
|
||||
*
|
||||
*
|
||||
* Transforms ForgotPasswordPageDTO into ViewData for the forgot password template.
|
||||
* Deterministic, side-effect free, no business logic.
|
||||
*/
|
||||
|
||||
import { ForgotPasswordPageDTO } from '@/lib/services/auth/types/ForgotPasswordPageDTO';
|
||||
|
||||
export interface ForgotPasswordViewData {
|
||||
returnTo: string;
|
||||
showSuccess: boolean;
|
||||
successMessage?: string;
|
||||
magicLink?: string;
|
||||
formState: any; // Will be managed by client component
|
||||
isSubmitting: boolean;
|
||||
submitError?: string;
|
||||
}
|
||||
import { ForgotPasswordViewData } from './types/ForgotPasswordViewData';
|
||||
|
||||
export class ForgotPasswordViewDataBuilder {
|
||||
static build(data: ForgotPasswordPageDTO): ForgotPasswordViewData {
|
||||
static build(apiDto: ForgotPasswordPageDTO): ForgotPasswordViewData {
|
||||
return {
|
||||
returnTo: data.returnTo,
|
||||
returnTo: apiDto.returnTo,
|
||||
showSuccess: false,
|
||||
formState: {
|
||||
fields: {
|
||||
|
||||
@@ -4,11 +4,10 @@ import type { LeaderboardsViewData } from '@/lib/view-data/LeaderboardsViewData'
|
||||
|
||||
export class LeaderboardsViewDataBuilder {
|
||||
static build(
|
||||
driversDto: { drivers: DriverLeaderboardItemDTO[] } | null,
|
||||
teamsDto: { teams: TeamListItemDTO[] } | null
|
||||
apiDto: { drivers: { drivers: DriverLeaderboardItemDTO[] }; teams: { teams: TeamListItemDTO[] } }
|
||||
): LeaderboardsViewData {
|
||||
return {
|
||||
drivers: driversDto?.drivers.slice(0, 5).map((driver, index) => ({
|
||||
drivers: apiDto.drivers.drivers.map(driver => ({
|
||||
id: driver.id,
|
||||
name: driver.name,
|
||||
rating: driver.rating,
|
||||
@@ -17,9 +16,9 @@ export class LeaderboardsViewDataBuilder {
|
||||
wins: driver.wins,
|
||||
rank: driver.rank,
|
||||
avatarUrl: driver.avatarUrl || '',
|
||||
position: index + 1,
|
||||
})) || [],
|
||||
teams: teamsDto?.teams.slice(0, 5).map((team, index) => ({
|
||||
position: driver.rank,
|
||||
})),
|
||||
teams: apiDto.teams.teams.map(team => ({
|
||||
id: team.id,
|
||||
name: team.name,
|
||||
tag: team.tag,
|
||||
@@ -27,8 +26,8 @@ export class LeaderboardsViewDataBuilder {
|
||||
category: team.category,
|
||||
totalWins: team.totalWins || 0,
|
||||
logoUrl: team.logoUrl || '',
|
||||
position: index + 1,
|
||||
})) || [],
|
||||
position: 0, // API doesn't provide team ranking
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* LeagueCoverViewDataBuilder
|
||||
*
|
||||
* Transforms MediaBinaryDTO into LeagueCoverViewData for server-side rendering.
|
||||
* Deterministic; side-effect free; no HTTP calls.
|
||||
*/
|
||||
|
||||
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
|
||||
import { LeagueCoverViewData } from '@/lib/view-data/LeagueCoverViewData';
|
||||
|
||||
export class LeagueCoverViewDataBuilder {
|
||||
static build(apiDto: MediaBinaryDTO): LeagueCoverViewData {
|
||||
return {
|
||||
buffer: apiDto.buffer,
|
||||
contentType: apiDto.contentType,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -22,22 +22,22 @@ export class LeagueDetailViewDataBuilder {
|
||||
}): LeagueDetailViewData {
|
||||
const { league, owner, scoringConfig, memberships, races, sponsors } = input;
|
||||
|
||||
// Calculate running races
|
||||
// Calculate running races - using available fields from RaceDTO
|
||||
const runningRaces: LiveRaceData[] = races
|
||||
.filter(r => r.status === 'running')
|
||||
.filter(r => r.name.includes('Running')) // Placeholder filter
|
||||
.map(r => ({
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
date: r.scheduledAt,
|
||||
registeredCount: r.registeredCount,
|
||||
strengthOfField: r.strengthOfField,
|
||||
date: r.date,
|
||||
registeredCount: 0,
|
||||
strengthOfField: 0,
|
||||
}));
|
||||
|
||||
// Calculate info data
|
||||
const membersCount = Array.isArray(memberships.members) ? memberships.members.length : 0;
|
||||
const completedRacesCount = races.filter(r => r.status === 'completed').length;
|
||||
const avgSOF = races.length > 0
|
||||
? Math.round(races.reduce((sum, r) => sum + (r.strengthOfField || 0), 0) / races.length)
|
||||
const completedRacesCount = races.filter(r => r.name.includes('Completed')).length; // Placeholder
|
||||
const avgSOF = races.length > 0
|
||||
? Math.round(races.reduce((sum, r) => sum + 0, 0) / races.length)
|
||||
: null;
|
||||
|
||||
const info: LeagueInfoData = {
|
||||
@@ -47,7 +47,7 @@ export class LeagueDetailViewDataBuilder {
|
||||
racesCount: completedRacesCount,
|
||||
avgSOF,
|
||||
structure: `Solo • ${league.settings?.maxDrivers ?? 32} max`,
|
||||
scoring: scoringConfig?.name || 'Standard',
|
||||
scoring: scoringConfig?.scoringPresetId || 'Standard',
|
||||
createdAt: league.createdAt,
|
||||
discordUrl: league.socialLinks?.discordUrl,
|
||||
youtubeUrl: league.socialLinks?.youtubeUrl,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* LeagueLogoViewDataBuilder
|
||||
*
|
||||
* Transforms MediaBinaryDTO into LeagueLogoViewData for server-side rendering.
|
||||
* Deterministic; side-effect free; no HTTP calls.
|
||||
*/
|
||||
|
||||
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
|
||||
import { LeagueLogoViewData } from '@/lib/view-data/LeagueLogoViewData';
|
||||
|
||||
export class LeagueLogoViewDataBuilder {
|
||||
static build(apiDto: MediaBinaryDTO): LeagueLogoViewData {
|
||||
return {
|
||||
buffer: apiDto.buffer,
|
||||
contentType: apiDto.contentType,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { LeagueRosterMemberDTO } from '@/lib/types/generated/LeagueRosterMemberDTO';
|
||||
import type { LeagueRosterJoinRequestDTO } from '@/lib/types/generated/LeagueRosterJoinRequestDTO';
|
||||
import type { LeagueRosterAdminViewData, RosterMemberData, JoinRequestData } from '@/lib/view-data/LeagueRosterAdminViewData';
|
||||
|
||||
/**
|
||||
* LeagueRosterAdminViewDataBuilder
|
||||
*
|
||||
* Transforms API DTOs into LeagueRosterAdminViewData for server-side rendering.
|
||||
* Deterministic; side-effect free; no HTTP calls.
|
||||
*/
|
||||
export class LeagueRosterAdminViewDataBuilder {
|
||||
static build(input: {
|
||||
leagueId: string;
|
||||
members: LeagueRosterMemberDTO[];
|
||||
joinRequests: LeagueRosterJoinRequestDTO[];
|
||||
}): LeagueRosterAdminViewData {
|
||||
const { leagueId, members, joinRequests } = input;
|
||||
|
||||
// Transform members
|
||||
const rosterMembers: RosterMemberData[] = members.map(member => ({
|
||||
driverId: member.driverId,
|
||||
driver: {
|
||||
id: member.driverId,
|
||||
name: member.driver?.name || 'Unknown Driver',
|
||||
},
|
||||
role: member.role,
|
||||
joinedAt: member.joinedAt,
|
||||
}));
|
||||
|
||||
// Transform join requests
|
||||
const requests: JoinRequestData[] = joinRequests.map(req => ({
|
||||
id: req.id,
|
||||
driver: {
|
||||
id: req.driverId,
|
||||
name: 'Unknown Driver', // driver field is unknown type
|
||||
},
|
||||
requestedAt: req.requestedAt,
|
||||
message: req.message,
|
||||
}));
|
||||
|
||||
return {
|
||||
leagueId,
|
||||
members: rosterMembers,
|
||||
joinRequests: requests,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { LeagueScheduleDTO } from '@/lib/types/generated/LeagueScheduleDTO';
|
||||
import type { LeagueSeasonSummaryDTO } from '@/lib/types/generated/LeagueSeasonSummaryDTO';
|
||||
import type { LeagueScheduleViewData, ScheduleRaceData } from '@/lib/view-data/LeagueScheduleViewData';
|
||||
|
||||
/**
|
||||
* LeagueScheduleViewDataBuilder
|
||||
*
|
||||
* Transforms API DTOs into LeagueScheduleViewData for server-side rendering.
|
||||
* Deterministic; side-effect free; no HTTP calls.
|
||||
*/
|
||||
export class LeagueScheduleViewDataBuilder {
|
||||
static build(input: {
|
||||
schedule: LeagueScheduleDTO;
|
||||
seasons: LeagueSeasonSummaryDTO[];
|
||||
leagueId: string;
|
||||
}): LeagueScheduleViewData {
|
||||
const { schedule, seasons, leagueId } = input;
|
||||
|
||||
// Transform races - using available fields from RaceDTO
|
||||
const races: ScheduleRaceData[] = (schedule.races || []).map(race => ({
|
||||
id: race.id,
|
||||
name: race.name,
|
||||
track: race.leagueName || 'Unknown Track',
|
||||
car: 'Unknown Car',
|
||||
scheduledAt: race.date,
|
||||
status: 'scheduled',
|
||||
}));
|
||||
|
||||
return {
|
||||
leagueId,
|
||||
races,
|
||||
seasons: seasons.map(s => ({
|
||||
seasonId: s.seasonId,
|
||||
name: s.name,
|
||||
status: s.status,
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,18 @@
|
||||
/**
|
||||
* Login View Data Builder
|
||||
*
|
||||
*
|
||||
* Transforms LoginPageDTO into ViewData for the login template.
|
||||
* Deterministic, side-effect free, no business logic.
|
||||
*/
|
||||
|
||||
import { LoginPageDTO } from '@/lib/services/auth/types/LoginPageDTO';
|
||||
|
||||
export interface FormFieldState {
|
||||
value: string | boolean;
|
||||
error?: string;
|
||||
touched: boolean;
|
||||
validating: boolean;
|
||||
}
|
||||
|
||||
export interface FormState {
|
||||
fields: {
|
||||
email: FormFieldState;
|
||||
password: FormFieldState;
|
||||
rememberMe: FormFieldState;
|
||||
};
|
||||
isValid: boolean;
|
||||
isSubmitting: boolean;
|
||||
submitError?: string;
|
||||
submitCount: number;
|
||||
}
|
||||
|
||||
export interface LoginViewData {
|
||||
returnTo: string;
|
||||
hasInsufficientPermissions: boolean;
|
||||
showPassword: boolean;
|
||||
showErrorDetails: boolean;
|
||||
formState: FormState;
|
||||
isSubmitting: boolean;
|
||||
submitError?: string;
|
||||
}
|
||||
import { LoginViewData } from './types/LoginViewData';
|
||||
|
||||
export class LoginViewDataBuilder {
|
||||
static build(data: LoginPageDTO): LoginViewData {
|
||||
static build(apiDto: LoginPageDTO): LoginViewData {
|
||||
return {
|
||||
returnTo: data.returnTo,
|
||||
hasInsufficientPermissions: data.hasInsufficientPermissions,
|
||||
returnTo: apiDto.returnTo,
|
||||
hasInsufficientPermissions: apiDto.hasInsufficientPermissions,
|
||||
showPassword: false,
|
||||
showErrorDetails: false,
|
||||
formState: {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { ProfileLeaguesViewData } from '@/lib/view-data/ProfileLeaguesViewData';
|
||||
|
||||
interface ProfileLeaguesPageDto {
|
||||
ownedLeagues: Array<{
|
||||
leagueId: string;
|
||||
name: string;
|
||||
description: string;
|
||||
membershipRole: 'owner' | 'admin' | 'steward' | 'member';
|
||||
}>;
|
||||
memberLeagues: Array<{
|
||||
leagueId: string;
|
||||
name: string;
|
||||
description: string;
|
||||
membershipRole: 'owner' | 'admin' | 'steward' | 'member';
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* ViewData Builder for Profile Leagues page
|
||||
* Transforms Page DTO to ViewData for templates
|
||||
*/
|
||||
export class ProfileLeaguesViewDataBuilder {
|
||||
static build(apiDto: ProfileLeaguesPageDto): ProfileLeaguesViewData {
|
||||
return {
|
||||
ownedLeagues: apiDto.ownedLeagues.map((league: { leagueId: string; name: string; description: string; membershipRole: 'owner' | 'admin' | 'steward' | 'member'; }) => ({
|
||||
leagueId: league.leagueId,
|
||||
name: league.name,
|
||||
description: league.description,
|
||||
membershipRole: league.membershipRole,
|
||||
})),
|
||||
memberLeagues: apiDto.memberLeagues.map((league: { leagueId: string; name: string; description: string; membershipRole: 'owner' | 'admin' | 'steward' | 'member'; }) => ({
|
||||
leagueId: league.leagueId,
|
||||
name: league.name,
|
||||
description: league.description,
|
||||
membershipRole: league.membershipRole,
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { RaceDetailViewData, RaceDetailRace, RaceDetailLeague, RaceDetailEntry, RaceDetailRegistration, RaceDetailUserResult } from '@/lib/view-data/races/RaceDetailViewData';
|
||||
|
||||
/**
|
||||
* Race Detail View Data Builder
|
||||
*
|
||||
* Transforms API DTO into ViewData for the race detail template.
|
||||
* Deterministic, side-effect free.
|
||||
*/
|
||||
export class RaceDetailViewDataBuilder {
|
||||
static build(apiDto: any): RaceDetailViewData {
|
||||
if (!apiDto || !apiDto.race) {
|
||||
return {
|
||||
race: {
|
||||
id: '',
|
||||
track: '',
|
||||
car: '',
|
||||
scheduledAt: '',
|
||||
status: 'scheduled',
|
||||
sessionType: 'race',
|
||||
},
|
||||
entryList: [],
|
||||
registration: {
|
||||
isUserRegistered: false,
|
||||
canRegister: false,
|
||||
},
|
||||
canReopenRace: false,
|
||||
};
|
||||
}
|
||||
|
||||
const race: RaceDetailRace = {
|
||||
id: apiDto.race.id,
|
||||
track: apiDto.race.track,
|
||||
car: apiDto.race.car,
|
||||
scheduledAt: apiDto.race.scheduledAt,
|
||||
status: apiDto.race.status as 'scheduled' | 'running' | 'completed' | 'cancelled',
|
||||
sessionType: apiDto.race.sessionType,
|
||||
};
|
||||
|
||||
const league: RaceDetailLeague | undefined = apiDto.league ? {
|
||||
id: apiDto.league.id,
|
||||
name: apiDto.league.name,
|
||||
description: apiDto.league.description || undefined,
|
||||
settings: {
|
||||
maxDrivers: apiDto.league.settings?.maxDrivers || 32,
|
||||
qualifyingFormat: apiDto.league.settings?.qualifyingFormat || 'Open',
|
||||
},
|
||||
} : undefined;
|
||||
|
||||
const entryList: RaceDetailEntry[] = apiDto.entryList.map((entry: any) => ({
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
avatarUrl: entry.avatarUrl,
|
||||
country: entry.country,
|
||||
rating: entry.rating,
|
||||
isCurrentUser: entry.isCurrentUser,
|
||||
}));
|
||||
|
||||
const registration: RaceDetailRegistration = {
|
||||
isUserRegistered: apiDto.registration.isUserRegistered,
|
||||
canRegister: apiDto.registration.canRegister,
|
||||
};
|
||||
|
||||
const userResult: RaceDetailUserResult | undefined = apiDto.userResult ? {
|
||||
position: apiDto.userResult.position,
|
||||
startPosition: apiDto.userResult.startPosition,
|
||||
positionChange: apiDto.userResult.positionChange,
|
||||
incidents: apiDto.userResult.incidents,
|
||||
isClean: apiDto.userResult.isClean,
|
||||
isPodium: apiDto.userResult.isPodium,
|
||||
ratingChange: apiDto.userResult.ratingChange,
|
||||
} : undefined;
|
||||
|
||||
return {
|
||||
race,
|
||||
league,
|
||||
entryList,
|
||||
registration,
|
||||
userResult,
|
||||
canReopenRace: apiDto.canReopenRace || false,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { RaceResultsViewData, RaceResultsResult, RaceResultsPenalty } from '@/lib/view-data/races/RaceResultsViewData';
|
||||
|
||||
/**
|
||||
* Race Results View Data Builder
|
||||
*
|
||||
* Transforms API DTO into ViewData for the race results template.
|
||||
* Deterministic, side-effect free.
|
||||
*/
|
||||
export class RaceResultsViewDataBuilder {
|
||||
static build(apiDto: any): RaceResultsViewData {
|
||||
if (!apiDto) {
|
||||
return {
|
||||
raceSOF: null,
|
||||
results: [],
|
||||
penalties: [],
|
||||
pointsSystem: {},
|
||||
fastestLapTime: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Transform results
|
||||
const results: RaceResultsResult[] = (apiDto.results || []).map((result: any) => ({
|
||||
position: result.position,
|
||||
driverId: result.driverId,
|
||||
driverName: result.driverName,
|
||||
driverAvatar: result.avatarUrl,
|
||||
country: result.country || 'US',
|
||||
car: result.car || 'Unknown',
|
||||
laps: result.laps || 0,
|
||||
time: result.time || '0:00.00',
|
||||
fastestLap: result.fastestLap?.toString() || '0.00',
|
||||
points: result.points || 0,
|
||||
incidents: result.incidents || 0,
|
||||
isCurrentUser: result.isCurrentUser || false,
|
||||
}));
|
||||
|
||||
// Transform penalties
|
||||
const penalties: RaceResultsPenalty[] = (apiDto.penalties || []).map((penalty: any) => ({
|
||||
driverId: penalty.driverId,
|
||||
driverName: penalty.driverName || 'Unknown',
|
||||
type: penalty.type as 'time_penalty' | 'grid_penalty' | 'points_deduction' | 'disqualification' | 'warning' | 'license_points',
|
||||
value: penalty.value || 0,
|
||||
reason: penalty.reason || 'Penalty applied',
|
||||
notes: penalty.notes,
|
||||
}));
|
||||
|
||||
return {
|
||||
raceTrack: apiDto.race?.track,
|
||||
raceScheduledAt: apiDto.race?.scheduledAt,
|
||||
totalDrivers: apiDto.stats?.totalDrivers,
|
||||
leagueName: apiDto.league?.name,
|
||||
raceSOF: apiDto.strengthOfField || null,
|
||||
results,
|
||||
penalties,
|
||||
pointsSystem: apiDto.pointsSystem || {},
|
||||
fastestLapTime: apiDto.fastestLapTime || 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { RaceStewardingViewData, Protest, Penalty, Driver } from '@/lib/view-data/races/RaceStewardingViewData';
|
||||
|
||||
/**
|
||||
* Race Stewarding View Data Builder
|
||||
*
|
||||
* Transforms API DTO into ViewData for the race stewarding template.
|
||||
* Deterministic, side-effect free.
|
||||
*/
|
||||
export class RaceStewardingViewDataBuilder {
|
||||
static build(apiDto: any): RaceStewardingViewData {
|
||||
if (!apiDto) {
|
||||
return {
|
||||
race: null,
|
||||
league: null,
|
||||
pendingProtests: [],
|
||||
resolvedProtests: [],
|
||||
penalties: [],
|
||||
driverMap: {},
|
||||
pendingCount: 0,
|
||||
resolvedCount: 0,
|
||||
penaltiesCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const race = apiDto.race ? {
|
||||
id: apiDto.race.id,
|
||||
track: apiDto.race.track,
|
||||
scheduledAt: apiDto.race.scheduledAt,
|
||||
} : null;
|
||||
|
||||
const league = apiDto.league ? {
|
||||
id: apiDto.league.id,
|
||||
} : null;
|
||||
|
||||
const pendingProtests: Protest[] = (apiDto.pendingProtests || []).map((p: any) => ({
|
||||
id: p.id,
|
||||
protestingDriverId: p.protestingDriverId,
|
||||
accusedDriverId: p.accusedDriverId,
|
||||
incident: {
|
||||
lap: p.incident?.lap || 0,
|
||||
description: p.incident?.description || '',
|
||||
},
|
||||
filedAt: p.filedAt,
|
||||
status: p.status,
|
||||
proofVideoUrl: p.proofVideoUrl,
|
||||
decisionNotes: p.decisionNotes,
|
||||
}));
|
||||
|
||||
const resolvedProtests: Protest[] = (apiDto.resolvedProtests || []).map((p: any) => ({
|
||||
id: p.id,
|
||||
protestingDriverId: p.protestingDriverId,
|
||||
accusedDriverId: p.accusedDriverId,
|
||||
incident: {
|
||||
lap: p.incident?.lap || 0,
|
||||
description: p.incident?.description || '',
|
||||
},
|
||||
filedAt: p.filedAt,
|
||||
status: p.status,
|
||||
proofVideoUrl: p.proofVideoUrl,
|
||||
decisionNotes: p.decisionNotes,
|
||||
}));
|
||||
|
||||
const penalties: Penalty[] = (apiDto.penalties || []).map((p: any) => ({
|
||||
id: p.id,
|
||||
driverId: p.driverId,
|
||||
type: p.type,
|
||||
value: p.value || 0,
|
||||
reason: p.reason || '',
|
||||
notes: p.notes,
|
||||
}));
|
||||
|
||||
const driverMap: Record<string, Driver> = apiDto.driverMap || {};
|
||||
|
||||
return {
|
||||
race,
|
||||
league,
|
||||
pendingProtests,
|
||||
resolvedProtests,
|
||||
penalties,
|
||||
driverMap,
|
||||
pendingCount: apiDto.pendingCount || pendingProtests.length,
|
||||
resolvedCount: apiDto.resolvedCount || resolvedProtests.length,
|
||||
penaltiesCount: apiDto.penaltiesCount || penalties.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { RacesAllViewData, RacesAllRace } from '@/lib/view-data/races/RacesAllViewData';
|
||||
|
||||
/**
|
||||
* Races All View Data Builder
|
||||
*
|
||||
* Transforms API DTO into ViewData for the all races template.
|
||||
* Deterministic, side-effect free.
|
||||
*/
|
||||
export class RacesAllViewDataBuilder {
|
||||
static build(apiDto: any): RacesAllViewData {
|
||||
const races = apiDto.races.map((race: any) => ({
|
||||
id: race.id,
|
||||
track: race.track,
|
||||
car: race.car,
|
||||
scheduledAt: race.scheduledAt,
|
||||
status: race.status as 'scheduled' | 'running' | 'completed' | 'cancelled',
|
||||
sessionType: 'race',
|
||||
leagueId: race.leagueId,
|
||||
leagueName: race.leagueName,
|
||||
strengthOfField: race.strengthOfField ?? undefined,
|
||||
}));
|
||||
|
||||
return {
|
||||
races,
|
||||
};
|
||||
}
|
||||
}
|
||||
39
apps/website/lib/builders/view-data/RacesViewDataBuilder.ts
Normal file
39
apps/website/lib/builders/view-data/RacesViewDataBuilder.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { RacesViewData, RacesRace } from '@/lib/view-data/races/RacesViewData';
|
||||
|
||||
/**
|
||||
* Races View Data Builder
|
||||
*
|
||||
* Transforms API DTO into ViewData for the races template.
|
||||
* Deterministic, side-effect free.
|
||||
*/
|
||||
export class RacesViewDataBuilder {
|
||||
static build(apiDto: any): RacesViewData {
|
||||
const races = apiDto.races.map((race: any) => ({
|
||||
id: race.id,
|
||||
track: race.track,
|
||||
car: race.car,
|
||||
scheduledAt: race.scheduledAt,
|
||||
status: race.status as 'scheduled' | 'running' | 'completed' | 'cancelled',
|
||||
sessionType: 'race',
|
||||
leagueId: race.leagueId,
|
||||
leagueName: race.leagueName,
|
||||
strengthOfField: race.strengthOfField ?? undefined,
|
||||
isUpcoming: race.status === 'scheduled',
|
||||
isLive: race.status === 'running',
|
||||
isPast: race.status === 'completed',
|
||||
}));
|
||||
|
||||
const totalCount = races.length;
|
||||
const scheduledRaces = races.filter((r: RacesRace) => r.isUpcoming);
|
||||
const runningRaces = races.filter((r: RacesRace) => r.isLive);
|
||||
const completedRaces = races.filter((r: RacesRace) => r.isPast);
|
||||
|
||||
return {
|
||||
races,
|
||||
totalCount,
|
||||
scheduledRaces,
|
||||
runningRaces,
|
||||
completedRaces,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,18 @@
|
||||
/**
|
||||
* Reset Password View Data Builder
|
||||
*
|
||||
*
|
||||
* Transforms ResetPasswordPageDTO into ViewData for the reset password template.
|
||||
* Deterministic, side-effect free, no business logic.
|
||||
*/
|
||||
|
||||
import { ResetPasswordPageDTO } from '@/lib/services/auth/types/ResetPasswordPageDTO';
|
||||
|
||||
export interface ResetPasswordViewData {
|
||||
token: string;
|
||||
returnTo: string;
|
||||
showSuccess: boolean;
|
||||
successMessage?: string;
|
||||
formState: any; // Will be managed by client component
|
||||
isSubmitting: boolean;
|
||||
submitError?: string;
|
||||
}
|
||||
import { ResetPasswordViewData } from './types/ResetPasswordViewData';
|
||||
|
||||
export class ResetPasswordViewDataBuilder {
|
||||
static build(data: ResetPasswordPageDTO): ResetPasswordViewData {
|
||||
static build(apiDto: ResetPasswordPageDTO): ResetPasswordViewData {
|
||||
return {
|
||||
token: data.token,
|
||||
returnTo: data.returnTo,
|
||||
token: apiDto.token,
|
||||
returnTo: apiDto.returnTo,
|
||||
showSuccess: false,
|
||||
formState: {
|
||||
fields: {
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
/**
|
||||
* Signup View Data Builder
|
||||
*
|
||||
*
|
||||
* Transforms SignupPageDTO into ViewData for the signup template.
|
||||
* Deterministic, side-effect free, no business logic.
|
||||
*/
|
||||
|
||||
import { SignupPageDTO } from '@/lib/services/auth/types/SignupPageDTO';
|
||||
|
||||
export interface SignupViewData {
|
||||
returnTo: string;
|
||||
formState: any; // Will be managed by client component
|
||||
isSubmitting: boolean;
|
||||
submitError?: string;
|
||||
}
|
||||
import { SignupViewData } from './types/SignupViewData';
|
||||
|
||||
export class SignupViewDataBuilder {
|
||||
static build(data: SignupPageDTO): SignupViewData {
|
||||
static build(apiDto: SignupPageDTO): SignupViewData {
|
||||
return {
|
||||
returnTo: data.returnTo,
|
||||
returnTo: apiDto.returnTo,
|
||||
formState: {
|
||||
fields: {
|
||||
firstName: { value: '', error: undefined, touched: false, validating: false },
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* SponsorLogoViewDataBuilder
|
||||
*
|
||||
* Transforms MediaBinaryDTO into SponsorLogoViewData for server-side rendering.
|
||||
* Deterministic; side-effect free; no HTTP calls.
|
||||
*/
|
||||
|
||||
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
|
||||
import { SponsorLogoViewData } from '@/lib/view-data/SponsorLogoViewData';
|
||||
|
||||
export class SponsorLogoViewDataBuilder {
|
||||
static build(apiDto: MediaBinaryDTO): SponsorLogoViewData {
|
||||
return {
|
||||
buffer: apiDto.buffer,
|
||||
contentType: apiDto.contentType,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,26 @@
|
||||
import type { SponsorshipRequestDTO } from '@/lib/types/generated/SponsorshipRequestDTO';
|
||||
import type { GetPendingSponsorshipRequestsOutputDTO } from '@/lib/types/generated/GetPendingSponsorshipRequestsOutputDTO';
|
||||
import type { SponsorshipRequestsViewData } from '@/lib/view-data/SponsorshipRequestsViewData';
|
||||
|
||||
export interface SponsorshipRequestsViewData {
|
||||
requests: SponsorshipRequestDTO[];
|
||||
isEmpty: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* ViewData Builder for Sponsorship Requests page
|
||||
* Transforms API DTO to ViewData for templates
|
||||
*/
|
||||
export class SponsorshipRequestsPageViewDataBuilder {
|
||||
build(queryResult: GetPendingSponsorshipRequestsOutputDTO): SponsorshipRequestsViewData {
|
||||
static build(apiDto: GetPendingSponsorshipRequestsOutputDTO): SponsorshipRequestsViewData {
|
||||
return {
|
||||
requests: queryResult.requests,
|
||||
isEmpty: queryResult.requests.length === 0,
|
||||
sections: [{
|
||||
entityType: apiDto.entityType as 'driver' | 'team' | 'season',
|
||||
entityId: apiDto.entityId,
|
||||
entityName: apiDto.entityType,
|
||||
requests: apiDto.requests.map(request => ({
|
||||
id: request.id,
|
||||
sponsorId: request.sponsorId,
|
||||
sponsorName: request.sponsorName,
|
||||
sponsorLogoUrl: request.sponsorLogo || null,
|
||||
message: request.message || null,
|
||||
createdAtIso: request.createdAt,
|
||||
})),
|
||||
}],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import type { SponsorshipRequestsPageDto } from '@/lib/page-queries/page-queries/SponsorshipRequestsPageQuery';
|
||||
import type { GetPendingSponsorshipRequestsOutputDTO } from '@/lib/types/generated/GetPendingSponsorshipRequestsOutputDTO';
|
||||
import type { SponsorshipRequestsViewData } from '@/lib/view-data/SponsorshipRequestsViewData';
|
||||
|
||||
export class SponsorshipRequestsViewDataBuilder {
|
||||
static build(apiDto: SponsorshipRequestsPageDto): SponsorshipRequestsViewData {
|
||||
static build(apiDto: GetPendingSponsorshipRequestsOutputDTO): SponsorshipRequestsViewData {
|
||||
return {
|
||||
sections: apiDto.sections.map((section) => ({
|
||||
entityType: section.entityType,
|
||||
entityId: section.entityId,
|
||||
entityName: section.entityName,
|
||||
requests: section.requests.map((request) => ({
|
||||
id: request.requestId,
|
||||
sponsorId: request.sponsorId,
|
||||
sponsorName: request.sponsorName,
|
||||
sponsorLogoUrl: null,
|
||||
message: request.message,
|
||||
createdAtIso: request.createdAtIso,
|
||||
})),
|
||||
})),
|
||||
sections: [
|
||||
{
|
||||
entityType: apiDto.entityType as 'driver' | 'team' | 'season',
|
||||
entityId: apiDto.entityId,
|
||||
entityName: apiDto.entityType === 'driver' ? 'Driver' : apiDto.entityType,
|
||||
requests: apiDto.requests.map((request) => ({
|
||||
id: request.id,
|
||||
sponsorId: request.sponsorId,
|
||||
sponsorName: request.sponsorName,
|
||||
sponsorLogoUrl: request.sponsorLogo || null,
|
||||
message: request.message || null,
|
||||
createdAtIso: request.createdAt,
|
||||
})),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import type { TeamDetailPageDto } from '@/lib/page-queries/page-queries/TeamDetailPageQuery';
|
||||
import type { TeamDetailViewData, TeamDetailData, TeamMemberData, SponsorMetric, TeamTab } from '@/lib/view-data/TeamDetailViewData';
|
||||
import { Users, Zap, Calendar } from 'lucide-react';
|
||||
|
||||
/**
|
||||
* TeamDetailViewDataBuilder - Transforms TeamDetailPageDto into ViewData
|
||||
* Deterministic; side-effect free; no HTTP calls
|
||||
*/
|
||||
export class TeamDetailViewDataBuilder {
|
||||
static build(apiDto: TeamDetailPageDto): TeamDetailViewData {
|
||||
const team: TeamDetailData = {
|
||||
id: apiDto.team.id,
|
||||
name: apiDto.team.name,
|
||||
tag: apiDto.team.tag,
|
||||
description: apiDto.team.description,
|
||||
ownerId: apiDto.team.ownerId,
|
||||
leagues: apiDto.team.leagues,
|
||||
createdAt: apiDto.team.createdAt,
|
||||
specialization: apiDto.team.specialization,
|
||||
region: apiDto.team.region,
|
||||
languages: apiDto.team.languages,
|
||||
category: apiDto.team.category,
|
||||
membership: apiDto.team.membership,
|
||||
canManage: apiDto.team.canManage,
|
||||
};
|
||||
|
||||
const memberships: TeamMemberData[] = apiDto.memberships.map((membership) => ({
|
||||
driverId: membership.driverId,
|
||||
driverName: membership.driverName,
|
||||
role: membership.role,
|
||||
joinedAt: membership.joinedAt,
|
||||
isActive: membership.isActive,
|
||||
avatarUrl: membership.avatarUrl,
|
||||
}));
|
||||
|
||||
// Calculate isAdmin based on current driver's role
|
||||
const currentDriverMembership = memberships.find(m => m.driverId === apiDto.currentDriverId);
|
||||
const isAdmin = currentDriverMembership?.role === 'owner' || currentDriverMembership?.role === 'manager';
|
||||
|
||||
// Build sponsor metrics
|
||||
const leagueCount = team.leagues?.length ?? 0;
|
||||
const teamMetrics: SponsorMetric[] = [
|
||||
{
|
||||
icon: Users,
|
||||
label: 'Members',
|
||||
value: memberships.length,
|
||||
color: 'text-primary-blue',
|
||||
},
|
||||
{
|
||||
icon: Zap,
|
||||
label: 'Est. Reach',
|
||||
value: memberships.length * 15,
|
||||
color: 'text-purple-400',
|
||||
},
|
||||
{
|
||||
icon: Calendar,
|
||||
label: 'Races',
|
||||
value: leagueCount,
|
||||
color: 'text-neon-aqua',
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
label: 'Engagement',
|
||||
value: '82%',
|
||||
color: 'text-performance-green',
|
||||
},
|
||||
];
|
||||
|
||||
// Build tabs
|
||||
const tabs: TeamTab[] = [
|
||||
{ id: 'overview', label: 'Overview', visible: true },
|
||||
{ id: 'roster', label: 'Roster', visible: true },
|
||||
{ id: 'standings', label: 'Standings', visible: true },
|
||||
{ id: 'admin', label: 'Admin', visible: isAdmin },
|
||||
];
|
||||
|
||||
return {
|
||||
team,
|
||||
memberships,
|
||||
currentDriverId: apiDto.currentDriverId,
|
||||
isAdmin,
|
||||
teamMetrics,
|
||||
tabs,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* TeamLogoViewDataBuilder
|
||||
*
|
||||
* Transforms MediaBinaryDTO into TeamLogoViewData for server-side rendering.
|
||||
* Deterministic; side-effect free; no HTTP calls.
|
||||
*/
|
||||
|
||||
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
|
||||
import { TeamLogoViewData } from '@/lib/view-data/TeamLogoViewData';
|
||||
|
||||
export class TeamLogoViewDataBuilder {
|
||||
static build(apiDto: MediaBinaryDTO): TeamLogoViewData {
|
||||
return {
|
||||
buffer: apiDto.buffer,
|
||||
contentType: apiDto.contentType,
|
||||
};
|
||||
}
|
||||
}
|
||||
20
apps/website/lib/builders/view-data/TeamsViewDataBuilder.ts
Normal file
20
apps/website/lib/builders/view-data/TeamsViewDataBuilder.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { TeamsPageDto } from '@/lib/page-queries/page-queries/TeamsPageQuery';
|
||||
import type { TeamsViewData, TeamSummaryData } from '@/lib/view-data/TeamsViewData';
|
||||
|
||||
/**
|
||||
* TeamsViewDataBuilder - Transforms TeamsPageDto into ViewData for TeamsTemplate
|
||||
* Deterministic; side-effect free; no HTTP calls
|
||||
*/
|
||||
export class TeamsViewDataBuilder {
|
||||
static build(apiDto: TeamsPageDto): TeamsViewData {
|
||||
const teams: TeamSummaryData[] = apiDto.teams.map((team): TeamSummaryData => ({
|
||||
teamId: team.id,
|
||||
teamName: team.name,
|
||||
leagueName: team.leagues[0] || '',
|
||||
memberCount: team.memberCount,
|
||||
logoUrl: team.logoUrl,
|
||||
}));
|
||||
|
||||
return { teams };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* TrackImageViewDataBuilder
|
||||
*
|
||||
* Transforms MediaBinaryDTO into TrackImageViewData for server-side rendering.
|
||||
* Deterministic; side-effect free; no HTTP calls.
|
||||
*/
|
||||
|
||||
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
|
||||
import { TrackImageViewData } from '@/lib/view-data/TrackImageViewData';
|
||||
|
||||
export class TrackImageViewDataBuilder {
|
||||
static build(apiDto: MediaBinaryDTO): TrackImageViewData {
|
||||
return {
|
||||
buffer: apiDto.buffer,
|
||||
contentType: apiDto.contentType,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
12
apps/website/lib/builders/view-data/types/FormFieldState.ts
Normal file
12
apps/website/lib/builders/view-data/types/FormFieldState.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Form Field State
|
||||
*
|
||||
* State for a single form field.
|
||||
*/
|
||||
|
||||
export interface FormFieldState {
|
||||
value: string | boolean;
|
||||
error?: string;
|
||||
touched: boolean;
|
||||
validating: boolean;
|
||||
}
|
||||
19
apps/website/lib/builders/view-data/types/FormState.ts
Normal file
19
apps/website/lib/builders/view-data/types/FormState.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Form State
|
||||
*
|
||||
* Complete state for a form.
|
||||
*/
|
||||
|
||||
import { FormFieldState } from './FormFieldState';
|
||||
|
||||
export interface FormState {
|
||||
fields: {
|
||||
email: FormFieldState;
|
||||
password: FormFieldState;
|
||||
rememberMe: FormFieldState;
|
||||
};
|
||||
isValid: boolean;
|
||||
isSubmitting: boolean;
|
||||
submitError?: string;
|
||||
submitCount: number;
|
||||
}
|
||||
17
apps/website/lib/builders/view-data/types/LoginViewData.ts
Normal file
17
apps/website/lib/builders/view-data/types/LoginViewData.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
12
apps/website/lib/builders/view-data/types/SignupViewData.ts
Normal file
12
apps/website/lib/builders/view-data/types/SignupViewData.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user