From 18133aef4cdef698712026f7a796720f27f95059 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Thu, 22 Jan 2026 23:40:38 +0100 Subject: [PATCH] view data fixes --- .../eslint-rules/view-data-implements.js | 41 +++++++++++---- .../lib/contracts/builders/ViewDataBuilder.ts | 23 ++++---- .../contracts/builders/ViewModelBuilder.ts | 23 ++++---- .../lib/contracts/view-data/ViewData.ts | 4 +- .../display-objects/ActivityLevelDisplay.ts | 33 ++++++++++++ .../lib/display-objects/AvatarDisplay.ts | 37 +++++++++++++ .../lib/display-objects/CurrencyDisplay.ts | 29 +++++++---- .../lib/display-objects/LeagueTierDisplay.ts | 39 ++++++++++++++ .../OnboardingStatusDisplay.ts | 38 ++++++++++++++ .../display-objects/SeasonStatusDisplay.ts | 35 +++++++++++++ .../lib/display-objects/UserRoleDisplay.ts | 19 +++++++ .../lib/display-objects/UserStatusDisplay.ts | 52 +++++++++++++++++++ .../LeagueProtestDetailPageQuery.ts | 6 +-- .../page-queries/LeagueRulebookPageQuery.ts | 6 +-- .../page-queries/LeagueSettingsPageQuery.ts | 6 +-- .../LeagueSponsorshipsPageQuery.ts | 6 +-- .../page-queries/LeagueStewardingPageQuery.ts | 6 +-- .../lib/page-queries/LeagueWalletPageQuery.ts | 6 +-- .../auth/ForgotPasswordPageQuery.ts | 6 +-- .../lib/page-queries/auth/LoginPageQuery.ts | 6 +-- .../auth/ResetPasswordPageQuery.ts | 6 +-- .../lib/page-queries/auth/SignupPageQuery.ts | 4 +- .../page-queries/races/RaceDetailPageQuery.ts | 4 +- .../races/RaceResultsPageQuery.ts | 4 +- .../races/RaceStewardingPageQuery.ts | 4 +- apps/website/lib/view-data/ActionsViewData.ts | 4 +- .../lib/view-data/ActivityItemViewData.ts | 14 +++++ .../lib/view-data/AdminDashboardViewData.ts | 7 ++- .../lib/view-data/AdminUsersViewData.ts | 7 ++- .../view-data/AnalyticsDashboardViewData.ts | 13 +++++ .../lib/view-data/AvailableLeaguesViewData.ts | 42 +++++++++++++++ .../lib/view-data/AvatarGenerationViewData.ts | 12 +++++ apps/website/lib/view-data/AvatarViewData.ts | 5 +- .../lib/view-data/CategoryIconViewData.ts | 6 ++- .../lib/view-data/CreateLeagueViewData.ts | 14 +++++ .../lib/view-data/DashboardViewData.ts | 5 +- .../lib/view-data/DriverRankingItem.ts | 5 +- .../lib/view-data/DriverRankingsViewData.ts | 4 +- .../lib/view-data/ForgotPasswordViewData.ts | 18 +++++++ apps/website/lib/view-data/HealthViewData.ts | 5 +- .../lib/view-data/LeaderboardDriverItem.ts | 5 +- .../lib/view-data/LeaderboardTeamItem.ts | 5 +- .../lib/view-data/LeaderboardsViewData.ts | 4 +- .../view-data/LeagueAdminScheduleViewData.ts | 17 ++---- .../lib/view-data/LeagueCoverViewData.ts | 5 +- .../lib/view-data/LeagueDetailViewData.ts | 1 + .../lib/view-data/LeagueLogoViewData.ts | 7 ++- .../view-data/LeagueRosterAdminViewData.ts | 5 +- .../lib/view-data/LeagueRulebookViewData.ts | 12 ++--- .../lib/view-data/LeagueScheduleViewData.ts | 5 +- .../{leagues => }/LeagueSettingsViewData.ts | 5 +- .../LeagueSponsorshipsViewData.ts | 5 +- .../lib/view-data/LeagueStandingsViewData.ts | 32 ------------ .../lib/view-data/LeagueWalletViewData.ts | 16 ++++++ apps/website/lib/view-data/LeaguesViewData.ts | 5 +- apps/website/lib/view-data/LoginViewData.ts | 20 +++++++ apps/website/lib/view-data/MediaViewData.ts | 4 +- .../lib/view-data/OnboardingPageViewData.ts | 5 +- ...odiumDriver.ts => PodiumDriverViewData.ts} | 5 +- .../lib/view-data/ProfileLayoutViewData.ts | 5 +- .../lib/view-data/ProfileLeaguesViewData.ts | 8 ++- .../lib/view-data/ProfileLiveriesViewData.ts | 8 ++- apps/website/lib/view-data/ProfileViewData.ts | 5 +- .../{leagues => }/ProtestDetailViewData.ts | 5 +- .../{races => }/RaceDetailViewData.ts | 5 +- .../{races => }/RaceResultsViewData.ts | 5 +- .../{races => }/RaceStewardingViewData.ts | 5 +- apps/website/lib/view-data/RacesViewData.ts | 8 ++- .../lib/view-data/ResetPasswordViewData.ts | 18 +++++++ .../{leagues => }/RulebookViewData.ts | 5 +- apps/website/lib/view-data/SignupViewData.ts | 15 ++++++ .../lib/view-data/SponsorDashboardViewData.ts | 5 +- .../lib/view-data/SponsorLogoViewData.ts | 5 +- .../{leagues => }/StewardingViewData.ts | 5 +- .../lib/view-data/TeamDetailViewData.ts | 5 +- .../lib/view-data/TeamLeaderboardViewData.ts | 4 +- .../website/lib/view-data/TeamLogoViewData.ts | 5 +- .../lib/view-data/TeamRankingsViewData.ts | 4 +- apps/website/lib/view-data/TeamsViewData.ts | 1 + .../lib/view-data/TrackImageViewData.ts | 5 +- .../leagues/LeagueScheduleViewData.ts | 24 --------- .../view-data/leagues/LeagueWalletViewData.ts | 27 ---------- apps/website/templates/FatalErrorTemplate.tsx | 3 +- apps/website/templates/HomeTemplate.tsx | 11 ++-- .../templates/LeagueSettingsTemplate.tsx | 6 +-- .../templates/LeagueSponsorshipsTemplate.tsx | 4 +- .../templates/LeagueWalletTemplate.tsx | 4 +- apps/website/templates/NotFoundTemplate.tsx | 4 +- apps/website/templates/RaceDetailTemplate.tsx | 4 +- .../website/templates/RaceResultsTemplate.tsx | 4 +- .../templates/RaceStewardingTemplate.tsx | 4 +- apps/website/templates/RulebookTemplate.tsx | 4 +- .../website/templates/ServerErrorTemplate.tsx | 3 +- .../templates/SponsorBillingTemplate.tsx | 16 +++--- .../templates/SponsorCampaignsTemplate.tsx | 14 ++--- .../templates/SponsorLeagueDetailTemplate.tsx | 36 ++++++------- .../templates/SponsorLeaguesTemplate.tsx | 16 +++--- .../templates/SponsorSettingsTemplate.tsx | 12 ++--- apps/website/templates/StewardingTemplate.tsx | 6 +-- .../templates/auth/ForgotPasswordTemplate.tsx | 2 +- apps/website/templates/auth/LoginTemplate.tsx | 2 +- .../templates/auth/ResetPasswordTemplate.tsx | 2 +- .../website/templates/auth/SignupTemplate.tsx | 4 +- .../templates/layout/GlobalFooterTemplate.tsx | 8 +-- .../layout/GlobalSidebarTemplate.tsx | 6 +-- .../layout/HeaderContentTemplate.tsx | 4 +- .../templates/layout/RootAppShellTemplate.tsx | 7 +-- .../onboarding/OnboardingTemplate.tsx | 2 +- docs/architecture/website/BUILDERS.md | 4 +- docs/architecture/website/VIEW_DATA.md | 22 ++++++-- docs/architecture/website/VIEW_MODELS.md | 2 +- 111 files changed, 841 insertions(+), 324 deletions(-) create mode 100644 apps/website/lib/display-objects/ActivityLevelDisplay.ts create mode 100644 apps/website/lib/display-objects/AvatarDisplay.ts create mode 100644 apps/website/lib/display-objects/LeagueTierDisplay.ts create mode 100644 apps/website/lib/display-objects/OnboardingStatusDisplay.ts create mode 100644 apps/website/lib/display-objects/SeasonStatusDisplay.ts create mode 100644 apps/website/lib/display-objects/UserRoleDisplay.ts create mode 100644 apps/website/lib/display-objects/UserStatusDisplay.ts create mode 100644 apps/website/lib/view-data/ActivityItemViewData.ts create mode 100644 apps/website/lib/view-data/AnalyticsDashboardViewData.ts create mode 100644 apps/website/lib/view-data/AvailableLeaguesViewData.ts create mode 100644 apps/website/lib/view-data/AvatarGenerationViewData.ts create mode 100644 apps/website/lib/view-data/CreateLeagueViewData.ts create mode 100644 apps/website/lib/view-data/ForgotPasswordViewData.ts rename apps/website/lib/view-data/{leagues => }/LeagueSettingsViewData.ts (73%) rename apps/website/lib/view-data/{leagues => }/LeagueSponsorshipsViewData.ts (84%) create mode 100644 apps/website/lib/view-data/LeagueWalletViewData.ts create mode 100644 apps/website/lib/view-data/LoginViewData.ts rename apps/website/lib/view-data/{PodiumDriver.ts => PodiumDriverViewData.ts} (51%) rename apps/website/lib/view-data/{leagues => }/ProtestDetailViewData.ts (78%) rename apps/website/lib/view-data/{races => }/RaceDetailViewData.ts (90%) rename apps/website/lib/view-data/{races => }/RaceResultsViewData.ts (88%) rename apps/website/lib/view-data/{races => }/RaceStewardingViewData.ts (88%) create mode 100644 apps/website/lib/view-data/ResetPasswordViewData.ts rename apps/website/lib/view-data/{leagues => }/RulebookViewData.ts (73%) create mode 100644 apps/website/lib/view-data/SignupViewData.ts rename apps/website/lib/view-data/{leagues => }/StewardingViewData.ts (89%) delete mode 100644 apps/website/lib/view-data/leagues/LeagueScheduleViewData.ts delete mode 100644 apps/website/lib/view-data/leagues/LeagueWalletViewData.ts diff --git a/apps/website/eslint-rules/view-data-implements.js b/apps/website/eslint-rules/view-data-implements.js index 30ff237f8..825ff91b2 100644 --- a/apps/website/eslint-rules/view-data-implements.js +++ b/apps/website/eslint-rules/view-data-implements.js @@ -24,7 +24,7 @@ module.exports = { create(context) { const filename = context.getFilename(); - const isInViewData = filename.includes('/lib/view-data/'); + const isInViewData = filename.includes('/lib/view-data/') && !filename.includes('/contracts/'); if (!isInViewData) return {}; @@ -42,9 +42,15 @@ module.exports = { // Check if it extends ViewData if (node.extends && node.extends.length > 0) { for (const ext of node.extends) { - if (ext.type === 'TSExpressionWithTypeArguments' && - ext.expression.type === 'Identifier' && - ext.expression.name === 'ViewData') { + // Use context.getSourceCode().getText(ext) to be absolutely sure + const extendsText = context.getSourceCode().getText(ext).trim(); + // We check for 'ViewData' but must be careful not to match 'SomethingViewData' + // unless it's exactly 'ViewData' or part of a qualified name + if (extendsText === 'ViewData' || + extendsText.endsWith('.ViewData') || + extendsText.startsWith('ViewData<') || + extendsText.startsWith('ViewData ') || + /\bViewData\b/.test(extendsText)) { // Use regex for word boundary hasViewDataExtends = true; } } @@ -74,16 +80,31 @@ module.exports = { }, 'Program:exit'() { - if (!hasCorrectName) { + // Only report if we are in a file that should be a ViewData + // and we didn't find a valid declaration + const baseName = filename.split('/').pop(); + + // All files in lib/view-data/ must end with ViewData.ts + if (baseName && !baseName.endsWith('ViewData.ts') && !baseName.endsWith('ViewData.tsx')) { context.report({ node: context.getSourceCode().ast, messageId: 'notAnInterface', }); - } else if (!hasViewDataExtends) { - context.report({ - node: context.getSourceCode().ast, - messageId: 'missingExtends', - }); + return; + } + + if (baseName && (baseName.endsWith('ViewData.ts') || baseName.endsWith('ViewData.tsx'))) { + if (!hasCorrectName) { + context.report({ + node: context.getSourceCode().ast, + messageId: 'notAnInterface', + }); + } else if (!hasViewDataExtends) { + context.report({ + node: context.getSourceCode().ast, + messageId: 'missingExtends', + }); + } } }, }; diff --git a/apps/website/lib/contracts/builders/ViewDataBuilder.ts b/apps/website/lib/contracts/builders/ViewDataBuilder.ts index b7b556804..b58855443 100644 --- a/apps/website/lib/contracts/builders/ViewDataBuilder.ts +++ b/apps/website/lib/contracts/builders/ViewDataBuilder.ts @@ -1,25 +1,28 @@ /** * ViewData Builder Contract - * - * Purpose: Transform ViewModels into ViewData for templates - * + * + * Purpose: Transform API Transport DTOs into ViewData for templates + * * Rules: * - Deterministic and side-effect free * - No HTTP/API calls - * - Input: ViewModel - * - Output: ViewData (JSON-serializable) + * - Input: API Transport DTO (must be JSON-serializable) + * - Output: ViewData (JSON-serializable template-ready data) * - Must be in lib/builders/view-data/ * - Must be named *ViewDataBuilder * - Must have 'use client' directive * - Must implement static build() method */ -export interface ViewDataBuilder { +import { JsonValue } from '../types/primitives'; +import { ViewData } from '../view-data/ViewData'; + +export interface ViewDataBuilder { /** - * Transform ViewModel into ViewData - * - * @param viewModel - Client-side ViewModel + * Transform DTO into ViewData + * + * @param dto - API Transport DTO (JSON-serializable) * @returns ViewData for template */ - build(viewModel: TInput): TOutput; + build(dto: TDTO): TViewData; } \ No newline at end of file diff --git a/apps/website/lib/contracts/builders/ViewModelBuilder.ts b/apps/website/lib/contracts/builders/ViewModelBuilder.ts index fd9686838..a00c17ab1 100644 --- a/apps/website/lib/contracts/builders/ViewModelBuilder.ts +++ b/apps/website/lib/contracts/builders/ViewModelBuilder.ts @@ -1,25 +1,28 @@ /** * ViewModel Builder Contract - * - * Purpose: Transform API Transport DTOs into ViewModels - * + * + * Purpose: Transform ViewData into ViewModels for client-side state management + * * Rules: * - Deterministic and side-effect free * - No HTTP/API calls - * - Input: API Transport DTO - * - Output: ViewModel + * - Input: ViewData (JSON-serializable template-ready data) + * - Output: ViewModel (client-only class) * - Must be in lib/builders/view-models/ * - Must be named *ViewModelBuilder * - Must have 'use client' directive * - Must implement static build() method */ -export interface ViewModelBuilder { +import { ViewData } from '../view-data/ViewData'; +import { ViewModel } from '../view-models/ViewModel'; + +export interface ViewModelBuilder { /** - * Transform DTO into ViewModel - * - * @param dto - API Transport DTO + * Transform ViewData into ViewModel + * + * @param viewData - ViewData (JSON-serializable template-ready data) * @returns ViewModel */ - build(dto: TInput): TOutput; + build(viewData: TViewData): TViewModel; } \ No newline at end of file diff --git a/apps/website/lib/contracts/view-data/ViewData.ts b/apps/website/lib/contracts/view-data/ViewData.ts index 0d390478b..1c7a513ca 100644 --- a/apps/website/lib/contracts/view-data/ViewData.ts +++ b/apps/website/lib/contracts/view-data/ViewData.ts @@ -1,6 +1,6 @@ /** * Base interface for ViewData objects - * + * * All ViewData must be JSON-serializable. * This type ensures no class instances or functions are included. */ @@ -10,7 +10,7 @@ export interface ViewData { /** * Helper type to ensure a type is ViewData-compatible - * + * * Usage: * ```typescript * type MyViewData = ViewData & { diff --git a/apps/website/lib/display-objects/ActivityLevelDisplay.ts b/apps/website/lib/display-objects/ActivityLevelDisplay.ts new file mode 100644 index 000000000..cd0794390 --- /dev/null +++ b/apps/website/lib/display-objects/ActivityLevelDisplay.ts @@ -0,0 +1,33 @@ +/** + * ActivityLevelDisplay + * + * Deterministic mapping of engagement rates to activity level labels. + */ + +export class ActivityLevelDisplay { + /** + * Maps engagement rate to activity level label. + */ + static levelLabel(engagementRate: number): string { + if (engagementRate < 20) { + return 'Low'; + } else if (engagementRate < 50) { + return 'Medium'; + } else { + return 'High'; + } + } + + /** + * Maps engagement rate to activity level value. + */ + static levelValue(engagementRate: number): 'low' | 'medium' | 'high' { + if (engagementRate < 20) { + return 'low'; + } else if (engagementRate < 50) { + return 'medium'; + } else { + return 'high'; + } + } +} diff --git a/apps/website/lib/display-objects/AvatarDisplay.ts b/apps/website/lib/display-objects/AvatarDisplay.ts new file mode 100644 index 000000000..c915bb998 --- /dev/null +++ b/apps/website/lib/display-objects/AvatarDisplay.ts @@ -0,0 +1,37 @@ +/** + * AvatarDisplay + * + * Deterministic mapping of avatar-related data to display formats. + */ + +export class AvatarDisplay { + /** + * Converts binary buffer to base64 string for display. + */ + static bufferToBase64(buffer: ArrayBuffer): string { + const bytes = new Uint8Array(buffer); + let binary = ''; + for (let i = 0; i < bytes.byteLength; i++) { + binary += String.fromCharCode(bytes[i]); + } + return btoa(binary); + } + + /** + * Determines if avatar data is valid for display. + */ + static hasValidData(buffer: ArrayBuffer, contentType: string): boolean { + return buffer.byteLength > 0 && contentType.length > 0; + } + + /** + * Formats content type for display (e.g., "image/png" → "PNG"). + */ + static formatContentType(contentType: string): string { + const parts = contentType.split('/'); + if (parts.length === 2) { + return parts[1].toUpperCase(); + } + return contentType; + } +} diff --git a/apps/website/lib/display-objects/CurrencyDisplay.ts b/apps/website/lib/display-objects/CurrencyDisplay.ts index b033c385f..21fe6fc59 100644 --- a/apps/website/lib/display-objects/CurrencyDisplay.ts +++ b/apps/website/lib/display-objects/CurrencyDisplay.ts @@ -1,36 +1,47 @@ /** * CurrencyDisplay - * + * * Deterministic currency formatting for display. * Avoids Intl and toLocaleString to prevent SSR/hydration mismatches. */ export class CurrencyDisplay { /** - * Formats an amount as currency (e.g., "$10.00"). + * Formats an amount as currency (e.g., "$10.00" or "€1.000,00"). * Default currency is USD. */ static format(amount: number, currency: string = 'USD'): string { - const symbol = currency === 'USD' ? '$' : currency + ' '; + const symbol = currency === 'USD' ? '$' : currency === 'EUR' ? '€' : currency + ' '; const formattedAmount = amount.toFixed(2); // Add thousands separators const parts = formattedAmount.split('.'); - parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); - return `${symbol}${parts.join('.')}`; + // Use dot as thousands separator for EUR, comma for USD + if (currency === 'EUR') { + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, '.'); + return `${symbol}${parts[0]},${parts[1]}`; + } else { + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); + return `${symbol}${parts.join('.')}`; + } } /** - * Formats an amount as a compact currency (e.g., "$10"). + * Formats an amount as a compact currency (e.g., "$10" or "€1.000"). */ static formatCompact(amount: number, currency: string = 'USD'): string { - const symbol = currency === 'USD' ? '$' : currency + ' '; + const symbol = currency === 'USD' ? '$' : currency === 'EUR' ? '€' : currency + ' '; const roundedAmount = Math.round(amount); // Add thousands separators - const formattedAmount = roundedAmount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); + const formattedAmount = roundedAmount.toString(); - return `${symbol}${formattedAmount}`; + // Use dot as thousands separator for EUR, comma for USD + if (currency === 'EUR') { + return `${symbol}${formattedAmount.replace(/\B(?=(\d{3})+(?!\d))/g, '.')}`; + } else { + return `${symbol}${formattedAmount.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}`; + } } } diff --git a/apps/website/lib/display-objects/LeagueTierDisplay.ts b/apps/website/lib/display-objects/LeagueTierDisplay.ts new file mode 100644 index 000000000..0fa1ae8d2 --- /dev/null +++ b/apps/website/lib/display-objects/LeagueTierDisplay.ts @@ -0,0 +1,39 @@ +/** + * LeagueTierDisplay + * + * Deterministic display logic for league tiers. + */ + +export interface LeagueTierDisplayData { + color: string; + bgColor: string; + border: string; + icon: string; +} + +export class LeagueTierDisplay { + private static readonly CONFIG: Record = { + premium: { + color: 'text-yellow-400', + bgColor: 'bg-yellow-500/10', + border: 'border-yellow-500/30', + icon: '⭐' + }, + standard: { + color: 'text-primary-blue', + bgColor: 'bg-primary-blue/10', + border: 'border-primary-blue/30', + icon: '🏆' + }, + starter: { + color: 'text-gray-400', + bgColor: 'bg-gray-500/10', + border: 'border-gray-500/30', + icon: '🚀' + }, + }; + + static getDisplay(tier: 'premium' | 'standard' | 'starter'): LeagueTierDisplayData { + return this.CONFIG[tier]; + } +} \ No newline at end of file diff --git a/apps/website/lib/display-objects/OnboardingStatusDisplay.ts b/apps/website/lib/display-objects/OnboardingStatusDisplay.ts new file mode 100644 index 000000000..164a5fafd --- /dev/null +++ b/apps/website/lib/display-objects/OnboardingStatusDisplay.ts @@ -0,0 +1,38 @@ +/** + * OnboardingStatusDisplay + * + * Deterministic mapping of onboarding status to display labels and variants. + */ + +export class OnboardingStatusDisplay { + /** + * Maps onboarding success status to display label. + */ + static statusLabel(success: boolean): string { + return success ? 'Onboarding Complete' : 'Onboarding Failed'; + } + + /** + * Maps onboarding success status to badge variant. + */ + static statusVariant(success: boolean): string { + return success ? 'performance-green' : 'racing-red'; + } + + /** + * Maps onboarding success status to icon. + */ + static statusIcon(success: boolean): string { + return success ? '✅' : '❌'; + } + + /** + * Maps onboarding success status to message. + */ + static statusMessage(success: boolean, errorMessage?: string): string { + if (success) { + return 'Your onboarding has been completed successfully.'; + } + return errorMessage || 'Failed to complete onboarding. Please try again.'; + } +} diff --git a/apps/website/lib/display-objects/SeasonStatusDisplay.ts b/apps/website/lib/display-objects/SeasonStatusDisplay.ts new file mode 100644 index 000000000..24db070fe --- /dev/null +++ b/apps/website/lib/display-objects/SeasonStatusDisplay.ts @@ -0,0 +1,35 @@ +/** + * SeasonStatusDisplay + * + * Deterministic display logic for season status. + */ + +export interface SeasonStatusDisplayData { + color: string; + bg: string; + label: string; +} + +export class SeasonStatusDisplay { + private static readonly CONFIG: Record = { + active: { + color: 'text-performance-green', + bg: 'bg-performance-green/10', + label: 'Active Season' + }, + upcoming: { + color: 'text-warning-amber', + bg: 'bg-warning-amber/10', + label: 'Starting Soon' + }, + completed: { + color: 'text-gray-400', + bg: 'bg-gray-400/10', + label: 'Season Ended' + }, + }; + + static getDisplay(status: 'active' | 'upcoming' | 'completed'): SeasonStatusDisplayData { + return this.CONFIG[status]; + } +} \ No newline at end of file diff --git a/apps/website/lib/display-objects/UserRoleDisplay.ts b/apps/website/lib/display-objects/UserRoleDisplay.ts new file mode 100644 index 000000000..5b9dfd0ba --- /dev/null +++ b/apps/website/lib/display-objects/UserRoleDisplay.ts @@ -0,0 +1,19 @@ +/** + * UserRoleDisplay + * + * Deterministic mapping of user role codes to display labels. + */ + +export class UserRoleDisplay { + /** + * Maps user role to display label. + */ + static roleLabel(role: string): string { + const map: Record = { + owner: 'Owner', + admin: 'Admin', + user: 'User', + }; + return map[role] || role; + } +} diff --git a/apps/website/lib/display-objects/UserStatusDisplay.ts b/apps/website/lib/display-objects/UserStatusDisplay.ts new file mode 100644 index 000000000..15c5d2a29 --- /dev/null +++ b/apps/website/lib/display-objects/UserStatusDisplay.ts @@ -0,0 +1,52 @@ +/** + * UserStatusDisplay + * + * Deterministic mapping of user status codes to display labels and variants. + */ + +export class UserStatusDisplay { + /** + * Maps user status to display label. + */ + static statusLabel(status: string): string { + const map: Record = { + active: 'Active', + suspended: 'Suspended', + deleted: 'Deleted', + }; + return map[status] || status; + } + + /** + * Maps user status to badge variant. + */ + static statusVariant(status: string): string { + const map: Record = { + active: 'performance-green', + suspended: 'yellow-500', + deleted: 'racing-red', + }; + return map[status] || 'gray-500'; + } + + /** + * Determines if a user can be suspended. + */ + static canSuspend(status: string): boolean { + return status === 'active'; + } + + /** + * Determines if a user can be activated. + */ + static canActivate(status: string): boolean { + return status === 'suspended'; + } + + /** + * Determines if a user can be deleted. + */ + static canDelete(status: string): boolean { + return status !== 'deleted'; + } +} diff --git a/apps/website/lib/page-queries/LeagueProtestDetailPageQuery.ts b/apps/website/lib/page-queries/LeagueProtestDetailPageQuery.ts index a1414cdcd..32a062797 100644 --- a/apps/website/lib/page-queries/LeagueProtestDetailPageQuery.ts +++ b/apps/website/lib/page-queries/LeagueProtestDetailPageQuery.ts @@ -1,9 +1,9 @@ +import { ProtestDetailViewDataBuilder } from '@/lib/builders/view-data/ProtestDetailViewDataBuilder'; import { PageQuery } from '@/lib/contracts/page-queries/PageQuery'; +import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError'; import { Result } from '@/lib/contracts/Result'; import { ProtestDetailService } from '@/lib/services/leagues/ProtestDetailService'; -import { ProtestDetailViewDataBuilder } from '@/lib/builders/view-data/ProtestDetailViewDataBuilder'; -import { ProtestDetailViewData } from '@/lib/view-data/leagues/ProtestDetailViewData'; -import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError'; +import { ProtestDetailViewData } from '@/lib/view-data/ProtestDetailViewData'; export class LeagueProtestDetailPageQuery implements PageQuery { async execute(params: { leagueId: string; protestId: string }): Promise> { diff --git a/apps/website/lib/page-queries/LeagueRulebookPageQuery.ts b/apps/website/lib/page-queries/LeagueRulebookPageQuery.ts index f47cf914b..7881624a0 100644 --- a/apps/website/lib/page-queries/LeagueRulebookPageQuery.ts +++ b/apps/website/lib/page-queries/LeagueRulebookPageQuery.ts @@ -1,9 +1,9 @@ +import { RulebookViewDataBuilder } from '@/lib/builders/view-data/RulebookViewDataBuilder'; import { PageQuery } from '@/lib/contracts/page-queries/PageQuery'; +import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError'; import { Result } from '@/lib/contracts/Result'; import { LeagueRulebookService } from '@/lib/services/leagues/LeagueRulebookService'; -import { RulebookViewDataBuilder } from '@/lib/builders/view-data/RulebookViewDataBuilder'; -import { RulebookViewData } from '@/lib/view-data/leagues/RulebookViewData'; -import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError'; +import { RulebookViewData } from '@/lib/view-data/RulebookViewData'; export class LeagueRulebookPageQuery implements PageQuery { async execute(leagueId: string): Promise> { diff --git a/apps/website/lib/page-queries/LeagueSettingsPageQuery.ts b/apps/website/lib/page-queries/LeagueSettingsPageQuery.ts index 34bd65544..15b423447 100644 --- a/apps/website/lib/page-queries/LeagueSettingsPageQuery.ts +++ b/apps/website/lib/page-queries/LeagueSettingsPageQuery.ts @@ -1,9 +1,9 @@ +import { LeagueSettingsViewDataBuilder } from '@/lib/builders/view-data/LeagueSettingsViewDataBuilder'; import { PageQuery } from '@/lib/contracts/page-queries/PageQuery'; +import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError'; import { Result } from '@/lib/contracts/Result'; import { LeagueSettingsService } from '@/lib/services/leagues/LeagueSettingsService'; -import { LeagueSettingsViewDataBuilder } from '@/lib/builders/view-data/LeagueSettingsViewDataBuilder'; -import { LeagueSettingsViewData } from '@/lib/view-data/leagues/LeagueSettingsViewData'; -import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError'; +import { LeagueSettingsViewData } from '@/lib/view-data/LeagueSettingsViewData'; export class LeagueSettingsPageQuery implements PageQuery { async execute(leagueId: string): Promise> { diff --git a/apps/website/lib/page-queries/LeagueSponsorshipsPageQuery.ts b/apps/website/lib/page-queries/LeagueSponsorshipsPageQuery.ts index cb6a0c5ce..dd690fb41 100644 --- a/apps/website/lib/page-queries/LeagueSponsorshipsPageQuery.ts +++ b/apps/website/lib/page-queries/LeagueSponsorshipsPageQuery.ts @@ -1,9 +1,9 @@ +import { LeagueSponsorshipsViewDataBuilder } from '@/lib/builders/view-data/LeagueSponsorshipsViewDataBuilder'; import { PageQuery } from '@/lib/contracts/page-queries/PageQuery'; +import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError'; import { Result } from '@/lib/contracts/Result'; import { LeagueSponsorshipsService } from '@/lib/services/leagues/LeagueSponsorshipsService'; -import { LeagueSponsorshipsViewDataBuilder } from '@/lib/builders/view-data/LeagueSponsorshipsViewDataBuilder'; -import { LeagueSponsorshipsViewData } from '@/lib/view-data/leagues/LeagueSponsorshipsViewData'; -import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError'; +import { LeagueSponsorshipsViewData } from '@/lib/view-data/LeagueSponsorshipsViewData'; export class LeagueSponsorshipsPageQuery implements PageQuery { async execute(leagueId: string): Promise> { diff --git a/apps/website/lib/page-queries/LeagueStewardingPageQuery.ts b/apps/website/lib/page-queries/LeagueStewardingPageQuery.ts index ead682643..32a6732b5 100644 --- a/apps/website/lib/page-queries/LeagueStewardingPageQuery.ts +++ b/apps/website/lib/page-queries/LeagueStewardingPageQuery.ts @@ -1,9 +1,9 @@ +import { StewardingViewDataBuilder } from '@/lib/builders/view-data/StewardingViewDataBuilder'; import { Result } from '@/lib/contracts/Result'; import { PageQuery } from '@/lib/contracts/page-queries/PageQuery'; -import { LeagueStewardingService } from '@/lib/services/leagues/LeagueStewardingService'; -import { StewardingViewDataBuilder } from '@/lib/builders/view-data/StewardingViewDataBuilder'; -import { StewardingViewData } from '@/lib/view-data/leagues/StewardingViewData'; import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError'; +import { LeagueStewardingService } from '@/lib/services/leagues/LeagueStewardingService'; +import { StewardingViewData } from '@/lib/view-data/StewardingViewData'; export class LeagueStewardingPageQuery implements PageQuery { async execute(leagueId: string): Promise> { diff --git a/apps/website/lib/page-queries/LeagueWalletPageQuery.ts b/apps/website/lib/page-queries/LeagueWalletPageQuery.ts index f59bde4df..4a7401ebc 100644 --- a/apps/website/lib/page-queries/LeagueWalletPageQuery.ts +++ b/apps/website/lib/page-queries/LeagueWalletPageQuery.ts @@ -1,9 +1,9 @@ +import { LeagueWalletViewDataBuilder } from '@/lib/builders/view-data/LeagueWalletViewDataBuilder'; import { PageQuery } from '@/lib/contracts/page-queries/PageQuery'; +import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError'; import { Result } from '@/lib/contracts/Result'; import { LeagueWalletService } from '@/lib/services/leagues/LeagueWalletService'; -import { LeagueWalletViewDataBuilder } from '@/lib/builders/view-data/LeagueWalletViewDataBuilder'; -import { LeagueWalletViewData } from '@/lib/view-data/leagues/LeagueWalletViewData'; -import { type PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError'; +import { LeagueWalletViewData } from '@/lib/view-data/LeagueWalletViewData'; export class LeagueWalletPageQuery implements PageQuery { async execute(leagueId: string): Promise> { diff --git a/apps/website/lib/page-queries/auth/ForgotPasswordPageQuery.ts b/apps/website/lib/page-queries/auth/ForgotPasswordPageQuery.ts index 98daf89fa..3d1bf4d62 100644 --- a/apps/website/lib/page-queries/auth/ForgotPasswordPageQuery.ts +++ b/apps/website/lib/page-queries/auth/ForgotPasswordPageQuery.ts @@ -1,9 +1,9 @@ +import { ForgotPasswordViewDataBuilder } from '@/lib/builders/view-data/ForgotPasswordViewDataBuilder'; import { Result } from '@/lib/contracts/Result'; import { PageQuery } from '@/lib/contracts/page-queries/PageQuery'; -import { ForgotPasswordViewDataBuilder } from '@/lib/builders/view-data/ForgotPasswordViewDataBuilder'; -import { ForgotPasswordViewData } from '@/lib/builders/view-data/types/ForgotPasswordViewData'; -import { AuthPageService } from '@/lib/services/auth/AuthPageService'; import { SearchParamParser } from '@/lib/routing/search-params/SearchParamParser'; +import { AuthPageService } from '@/lib/services/auth/AuthPageService'; +import { ForgotPasswordViewData } from '@/lib/view-data/ForgotPasswordViewData'; export class ForgotPasswordPageQuery implements PageQuery> { async execute(searchParams: URLSearchParams | Record): Promise> { diff --git a/apps/website/lib/page-queries/auth/LoginPageQuery.ts b/apps/website/lib/page-queries/auth/LoginPageQuery.ts index b871e87b5..bb9bc42b4 100644 --- a/apps/website/lib/page-queries/auth/LoginPageQuery.ts +++ b/apps/website/lib/page-queries/auth/LoginPageQuery.ts @@ -1,9 +1,9 @@ +import { LoginViewDataBuilder } from '@/lib/builders/view-data/LoginViewDataBuilder'; import { Result } from '@/lib/contracts/Result'; import { PageQuery } from '@/lib/contracts/page-queries/PageQuery'; -import { LoginViewDataBuilder } from '@/lib/builders/view-data/LoginViewDataBuilder'; -import { LoginViewData } from '@/lib/builders/view-data/types/LoginViewData'; -import { AuthPageService } from '@/lib/services/auth/AuthPageService'; import { SearchParamParser } from '@/lib/routing/search-params/SearchParamParser'; +import { AuthPageService } from '@/lib/services/auth/AuthPageService'; +import { LoginViewData } from '@/lib/view-data/LoginViewData'; export class LoginPageQuery implements PageQuery> { async execute(searchParams: URLSearchParams | Record): Promise> { diff --git a/apps/website/lib/page-queries/auth/ResetPasswordPageQuery.ts b/apps/website/lib/page-queries/auth/ResetPasswordPageQuery.ts index 61e48bde6..5b0b08eef 100644 --- a/apps/website/lib/page-queries/auth/ResetPasswordPageQuery.ts +++ b/apps/website/lib/page-queries/auth/ResetPasswordPageQuery.ts @@ -1,9 +1,9 @@ +import { ResetPasswordViewDataBuilder } from '@/lib/builders/view-data/ResetPasswordViewDataBuilder'; import { Result } from '@/lib/contracts/Result'; import { PageQuery } from '@/lib/contracts/page-queries/PageQuery'; -import { ResetPasswordViewDataBuilder } from '@/lib/builders/view-data/ResetPasswordViewDataBuilder'; -import { ResetPasswordViewData } from '@/lib/builders/view-data/types/ResetPasswordViewData'; -import { AuthPageService } from '@/lib/services/auth/AuthPageService'; import { SearchParamParser } from '@/lib/routing/search-params/SearchParamParser'; +import { AuthPageService } from '@/lib/services/auth/AuthPageService'; +import { ResetPasswordViewData } from '@/lib/view-data/ResetPasswordViewData'; export class ResetPasswordPageQuery implements PageQuery> { async execute(searchParams: URLSearchParams | Record): Promise> { diff --git a/apps/website/lib/page-queries/auth/SignupPageQuery.ts b/apps/website/lib/page-queries/auth/SignupPageQuery.ts index 51dabb446..439a2259a 100644 --- a/apps/website/lib/page-queries/auth/SignupPageQuery.ts +++ b/apps/website/lib/page-queries/auth/SignupPageQuery.ts @@ -1,9 +1,9 @@ import { SignupViewDataBuilder } from '@/lib/builders/view-data/SignupViewDataBuilder'; -import { SignupViewData } from '@/lib/builders/view-data/types/SignupViewData'; import { Result } from '@/lib/contracts/Result'; import { PageQuery } from '@/lib/contracts/page-queries/PageQuery'; -import { AuthPageService } from '@/lib/services/auth/AuthPageService'; import { SearchParamParser } from '@/lib/routing/search-params/SearchParamParser'; +import { AuthPageService } from '@/lib/services/auth/AuthPageService'; +import { SignupViewData } from '@/lib/view-data/SignupViewData'; export class SignupPageQuery implements PageQuery> { async execute(searchParams: URLSearchParams | Record): Promise> { diff --git a/apps/website/lib/page-queries/races/RaceDetailPageQuery.ts b/apps/website/lib/page-queries/races/RaceDetailPageQuery.ts index 3e45962e5..d08a26acb 100644 --- a/apps/website/lib/page-queries/races/RaceDetailPageQuery.ts +++ b/apps/website/lib/page-queries/races/RaceDetailPageQuery.ts @@ -1,9 +1,9 @@ +import { RaceDetailViewDataBuilder } from '@/lib/builders/view-data/RaceDetailViewDataBuilder'; import { Result } from '@/lib/contracts/Result'; import { PageQuery } from '@/lib/contracts/page-queries/PageQuery'; import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError'; -import { RaceDetailViewData } from '@/lib/view-data/races/RaceDetailViewData'; import { RacesService } from '@/lib/services/races/RacesService'; -import { RaceDetailViewDataBuilder } from '@/lib/builders/view-data/RaceDetailViewDataBuilder'; +import { RaceDetailViewData } from '@/lib/view-data/RaceDetailViewData'; interface RaceDetailPageQueryParams { raceId: string; diff --git a/apps/website/lib/page-queries/races/RaceResultsPageQuery.ts b/apps/website/lib/page-queries/races/RaceResultsPageQuery.ts index 7f59d128f..1d7244d35 100644 --- a/apps/website/lib/page-queries/races/RaceResultsPageQuery.ts +++ b/apps/website/lib/page-queries/races/RaceResultsPageQuery.ts @@ -1,9 +1,9 @@ +import { RaceResultsViewDataBuilder } from '@/lib/builders/view-data/RaceResultsViewDataBuilder'; import { Result } from '@/lib/contracts/Result'; import { PageQuery } from '@/lib/contracts/page-queries/PageQuery'; import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError'; -import { RaceResultsViewData } from '@/lib/view-data/races/RaceResultsViewData'; import { RaceResultsService } from '@/lib/services/races/RaceResultsService'; -import { RaceResultsViewDataBuilder } from '@/lib/builders/view-data/RaceResultsViewDataBuilder'; +import { RaceResultsViewData } from '@/lib/view-data/RaceResultsViewData'; interface RaceResultsPageQueryParams { raceId: string; diff --git a/apps/website/lib/page-queries/races/RaceStewardingPageQuery.ts b/apps/website/lib/page-queries/races/RaceStewardingPageQuery.ts index 882962069..aae610984 100644 --- a/apps/website/lib/page-queries/races/RaceStewardingPageQuery.ts +++ b/apps/website/lib/page-queries/races/RaceStewardingPageQuery.ts @@ -1,9 +1,9 @@ +import { RaceStewardingViewDataBuilder } from '@/lib/builders/view-data/RaceStewardingViewDataBuilder'; import { Result } from '@/lib/contracts/Result'; import { PageQuery } from '@/lib/contracts/page-queries/PageQuery'; import { PresentationError, mapToPresentationError } from '@/lib/contracts/page-queries/PresentationError'; -import { RaceStewardingViewData } from '@/lib/view-data/races/RaceStewardingViewData'; import { RaceStewardingService } from '@/lib/services/races/RaceStewardingService'; -import { RaceStewardingViewDataBuilder } from '@/lib/builders/view-data/RaceStewardingViewDataBuilder'; +import { RaceStewardingViewData } from '@/lib/view-data/RaceStewardingViewData'; interface RaceStewardingPageQueryParams { raceId: string; diff --git a/apps/website/lib/view-data/ActionsViewData.ts b/apps/website/lib/view-data/ActionsViewData.ts index 582a26415..f2f52e9cb 100644 --- a/apps/website/lib/view-data/ActionsViewData.ts +++ b/apps/website/lib/view-data/ActionsViewData.ts @@ -1,5 +1,7 @@ +import { ViewData } from '@/lib/contracts/view-data/ViewData'; import { ActionItem } from '@/lib/queries/ActionsPageQuery'; -export interface ActionsViewData { + +export interface ActionsViewData extends ViewData { actions: ActionItem[]; } diff --git a/apps/website/lib/view-data/ActivityItemViewData.ts b/apps/website/lib/view-data/ActivityItemViewData.ts new file mode 100644 index 000000000..25c143041 --- /dev/null +++ b/apps/website/lib/view-data/ActivityItemViewData.ts @@ -0,0 +1,14 @@ +import { ViewData } from '../contracts/view-data/ViewData'; + +/** + * ActivityItemViewData + * + * ViewData for activity item rendering. + */ +export interface ActivityItemViewData extends ViewData { + id: string; + type: string; + message: string; + time: string; + impressions?: number; +} diff --git a/apps/website/lib/view-data/AdminDashboardViewData.ts b/apps/website/lib/view-data/AdminDashboardViewData.ts index 1fca33260..15c024610 100644 --- a/apps/website/lib/view-data/AdminDashboardViewData.ts +++ b/apps/website/lib/view-data/AdminDashboardViewData.ts @@ -1,10 +1,13 @@ +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + /** * AdminDashboardViewData - * + * * ViewData for AdminDashboardTemplate. * Template-ready data structure with only primitives. */ -export interface AdminDashboardViewData { + +export interface AdminDashboardViewData extends ViewData { stats: { totalUsers: number; activeUsers: number; diff --git a/apps/website/lib/view-data/AdminUsersViewData.ts b/apps/website/lib/view-data/AdminUsersViewData.ts index 02fd271e9..d186f265b 100644 --- a/apps/website/lib/view-data/AdminUsersViewData.ts +++ b/apps/website/lib/view-data/AdminUsersViewData.ts @@ -1,10 +1,13 @@ +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + /** * AdminUsersViewData - * + * * ViewData for AdminUsersTemplate. * Template-ready data structure with only primitives. */ -export interface AdminUsersViewData { + +export interface AdminUsersViewData extends ViewData { users: Array<{ id: string; email: string; diff --git a/apps/website/lib/view-data/AnalyticsDashboardViewData.ts b/apps/website/lib/view-data/AnalyticsDashboardViewData.ts new file mode 100644 index 000000000..5c0c93958 --- /dev/null +++ b/apps/website/lib/view-data/AnalyticsDashboardViewData.ts @@ -0,0 +1,13 @@ +import { ViewData } from "../contracts/view-data/ViewData"; + +export interface AnalyticsDashboardViewData extends ViewData { + metrics: { + totalUsers: number; + activeUsers: number; + totalRaces: number; + totalLeagues: number; + userEngagementRate: number; + formattedEngagementRate: string; + activityLevel: string; + }; +} \ No newline at end of file diff --git a/apps/website/lib/view-data/AvailableLeaguesViewData.ts b/apps/website/lib/view-data/AvailableLeaguesViewData.ts new file mode 100644 index 000000000..5d581e042 --- /dev/null +++ b/apps/website/lib/view-data/AvailableLeaguesViewData.ts @@ -0,0 +1,42 @@ +import { ViewData } from "../contracts/view-data/ViewData"; + +export interface AvailableLeaguesViewData extends ViewData { + leagues: AvailableLeagueViewData[]; +} + +export interface AvailableLeagueViewData { + id: string; + name: string; + game: string; + drivers: number; + avgViewsPerRace: number; + formattedAvgViews: string; + mainSponsorSlot: { + available: boolean; + price: number; + }; + secondarySlots: { + available: number; + total: number; + price: number; + }; + cpm: number; + formattedCpm: string; + hasAvailableSlots: boolean; + rating: number; + tier: 'premium' | 'standard' | 'starter'; + tierConfig: { + color: string; + bgColor: string; + border: string; + icon: string; + }; + nextRace?: string; + seasonStatus: 'active' | 'upcoming' | 'completed'; + statusConfig: { + color: string; + bg: string; + label: string; + }; + description: string; +} \ No newline at end of file diff --git a/apps/website/lib/view-data/AvatarGenerationViewData.ts b/apps/website/lib/view-data/AvatarGenerationViewData.ts new file mode 100644 index 000000000..d7154b0be --- /dev/null +++ b/apps/website/lib/view-data/AvatarGenerationViewData.ts @@ -0,0 +1,12 @@ +import { ViewData } from "../contracts/view-data/ViewData"; + +/** + * AvatarGenerationViewData + * + * ViewData for avatar generation process. + */ +export interface AvatarGenerationViewData extends ViewData { + success: boolean; + avatarUrls: string[]; + errorMessage?: string; +} diff --git a/apps/website/lib/view-data/AvatarViewData.ts b/apps/website/lib/view-data/AvatarViewData.ts index 7a943d01d..d91d9d27c 100644 --- a/apps/website/lib/view-data/AvatarViewData.ts +++ b/apps/website/lib/view-data/AvatarViewData.ts @@ -1,9 +1,12 @@ +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + /** * AvatarViewData * * ViewData for avatar media rendering. */ -export interface AvatarViewData { + +export interface AvatarViewData extends ViewData { buffer: string; // base64 encoded contentType: string; } \ No newline at end of file diff --git a/apps/website/lib/view-data/CategoryIconViewData.ts b/apps/website/lib/view-data/CategoryIconViewData.ts index 5ab1bce53..192c4ddbf 100644 --- a/apps/website/lib/view-data/CategoryIconViewData.ts +++ b/apps/website/lib/view-data/CategoryIconViewData.ts @@ -1,9 +1,13 @@ +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + /** * CategoryIconViewData * * ViewData for category icon media rendering. */ -export interface CategoryIconViewData { +import { ViewData } from "../contracts/view-data/ViewData"; + +export interface CategoryIconViewData extends ViewData { buffer: string; // base64 encoded contentType: string; } \ No newline at end of file diff --git a/apps/website/lib/view-data/CreateLeagueViewData.ts b/apps/website/lib/view-data/CreateLeagueViewData.ts new file mode 100644 index 000000000..9acb5d2b9 --- /dev/null +++ b/apps/website/lib/view-data/CreateLeagueViewData.ts @@ -0,0 +1,14 @@ +import { ViewData } from '../contracts/view-data/ViewData'; + +/** + * CreateLeagueViewData + * + * ViewData for the create league result page. + * Contains only raw serializable data, no methods or computed properties + */ + +export interface CreateLeagueViewData extends ViewData { + leagueId: string; + success: boolean; + successMessage: string; +} diff --git a/apps/website/lib/view-data/DashboardViewData.ts b/apps/website/lib/view-data/DashboardViewData.ts index 86ca183fa..a3cdc6195 100644 --- a/apps/website/lib/view-data/DashboardViewData.ts +++ b/apps/website/lib/view-data/DashboardViewData.ts @@ -1,3 +1,5 @@ +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + /** * Dashboard ViewData * @@ -6,7 +8,8 @@ * for display and ISO string timestamps for JSON serialization. */ -export interface DashboardViewData { + +export interface DashboardViewData extends ViewData { currentDriver: { name: string; avatarUrl: string; diff --git a/apps/website/lib/view-data/DriverRankingItem.ts b/apps/website/lib/view-data/DriverRankingItem.ts index 9521221fd..ae8e988b2 100644 --- a/apps/website/lib/view-data/DriverRankingItem.ts +++ b/apps/website/lib/view-data/DriverRankingItem.ts @@ -1,4 +1,7 @@ -export interface DriverRankingItem { +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + + +export interface DriverRankingItem extends ViewData { id: string; name: string; rating: number; diff --git a/apps/website/lib/view-data/DriverRankingsViewData.ts b/apps/website/lib/view-data/DriverRankingsViewData.ts index 9cbeda0ff..b5ca65518 100644 --- a/apps/website/lib/view-data/DriverRankingsViewData.ts +++ b/apps/website/lib/view-data/DriverRankingsViewData.ts @@ -1,7 +1,9 @@ +import { ViewData } from '@/lib/contracts/view-data/ViewData'; import type { DriverRankingItem } from './DriverRankingItem'; import type { PodiumDriver } from './PodiumDriver'; -export interface DriverRankingsViewData { + +export interface DriverRankingsViewData extends ViewData { drivers: DriverRankingItem[]; podium: PodiumDriver[]; searchQuery: string; diff --git a/apps/website/lib/view-data/ForgotPasswordViewData.ts b/apps/website/lib/view-data/ForgotPasswordViewData.ts new file mode 100644 index 000000000..3bdd2a68a --- /dev/null +++ b/apps/website/lib/view-data/ForgotPasswordViewData.ts @@ -0,0 +1,18 @@ +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + +/** + * Forgot Password View Data + * + * ViewData for the forgot password template. + */ + + +export interface ForgotPasswordViewData extends ViewData { + returnTo: string; + showSuccess: boolean; + successMessage?: string; + magicLink?: string; + formState: any; // Will be managed by client component + isSubmitting: boolean; + submitError?: string; +} \ No newline at end of file diff --git a/apps/website/lib/view-data/HealthViewData.ts b/apps/website/lib/view-data/HealthViewData.ts index 382d951d9..3e0c0102c 100644 --- a/apps/website/lib/view-data/HealthViewData.ts +++ b/apps/website/lib/view-data/HealthViewData.ts @@ -1,3 +1,4 @@ + /** * Health View Data Types * @@ -52,7 +53,9 @@ export interface HealthAlert { severityColor: string; } -export interface HealthViewData { +import { ViewData } from "../contracts/view-data/ViewData"; + +export interface HealthViewData extends ViewData { overallStatus: HealthStatus; metrics: HealthMetrics; components: HealthComponent[]; diff --git a/apps/website/lib/view-data/LeaderboardDriverItem.ts b/apps/website/lib/view-data/LeaderboardDriverItem.ts index e89a9239a..70180bb9b 100644 --- a/apps/website/lib/view-data/LeaderboardDriverItem.ts +++ b/apps/website/lib/view-data/LeaderboardDriverItem.ts @@ -1,4 +1,7 @@ -export interface LeaderboardDriverItem { +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + + +export interface LeaderboardDriverItem extends ViewData { id: string; name: string; rating: number; diff --git a/apps/website/lib/view-data/LeaderboardTeamItem.ts b/apps/website/lib/view-data/LeaderboardTeamItem.ts index 3d38e48f4..4a3d95afb 100644 --- a/apps/website/lib/view-data/LeaderboardTeamItem.ts +++ b/apps/website/lib/view-data/LeaderboardTeamItem.ts @@ -1,4 +1,7 @@ -export interface LeaderboardTeamItem { +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + + +export interface LeaderboardTeamItem extends ViewData { id: string; name: string; tag: string; diff --git a/apps/website/lib/view-data/LeaderboardsViewData.ts b/apps/website/lib/view-data/LeaderboardsViewData.ts index a286a99c3..314f005e0 100644 --- a/apps/website/lib/view-data/LeaderboardsViewData.ts +++ b/apps/website/lib/view-data/LeaderboardsViewData.ts @@ -1,7 +1,9 @@ +import { ViewData } from '@/lib/contracts/view-data/ViewData'; import type { LeaderboardDriverItem } from './LeaderboardDriverItem'; import type { LeaderboardTeamItem } from './LeaderboardTeamItem'; -export interface LeaderboardsViewData { + +export interface LeaderboardsViewData extends ViewData { drivers: LeaderboardDriverItem[]; teams: LeaderboardTeamItem[]; } \ No newline at end of file diff --git a/apps/website/lib/view-data/LeagueAdminScheduleViewData.ts b/apps/website/lib/view-data/LeagueAdminScheduleViewData.ts index 8d8178501..63c5e3455 100644 --- a/apps/website/lib/view-data/LeagueAdminScheduleViewData.ts +++ b/apps/website/lib/view-data/LeagueAdminScheduleViewData.ts @@ -1,17 +1,10 @@ -export interface AdminScheduleRaceData { +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + + +export interface AdminScheduleRaceData extends ViewData { id: string; name: string; track: string; car: string; scheduledAt: string; // ISO string -} - -export interface LeagueAdminScheduleViewData { - published: boolean; - races: AdminScheduleRaceData[]; - seasons: Array<{ - seasonId: string; - name: string; - }>; - seasonId: string; -} +} \ No newline at end of file diff --git a/apps/website/lib/view-data/LeagueCoverViewData.ts b/apps/website/lib/view-data/LeagueCoverViewData.ts index 3591f0c21..3de937afa 100644 --- a/apps/website/lib/view-data/LeagueCoverViewData.ts +++ b/apps/website/lib/view-data/LeagueCoverViewData.ts @@ -1,9 +1,12 @@ +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + /** * LeagueCoverViewData * * ViewData for league cover media rendering. */ -export interface LeagueCoverViewData { + +export interface LeagueCoverViewData extends ViewData { buffer: string; // base64 encoded contentType: string; } \ No newline at end of file diff --git a/apps/website/lib/view-data/LeagueDetailViewData.ts b/apps/website/lib/view-data/LeagueDetailViewData.ts index ebcb02b5b..342997540 100644 --- a/apps/website/lib/view-data/LeagueDetailViewData.ts +++ b/apps/website/lib/view-data/LeagueDetailViewData.ts @@ -87,6 +87,7 @@ export interface RecentResult { finishedAt: string; } + export interface LeagueDetailViewData extends ViewData { // Basic info leagueId: string; diff --git a/apps/website/lib/view-data/LeagueLogoViewData.ts b/apps/website/lib/view-data/LeagueLogoViewData.ts index 07b65e0ca..f499a9dfc 100644 --- a/apps/website/lib/view-data/LeagueLogoViewData.ts +++ b/apps/website/lib/view-data/LeagueLogoViewData.ts @@ -1,9 +1,12 @@ +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + /** * LeagueLogoViewData * - * ViewData for league logo media rendering. + * ViewData for league logoViewData extends ViewData {dering. */ -export interface LeagueLogoViewData { + +export interface LeagueLogoViewData extends ViewData { buffer: string; // base64 encoded contentType: string; } \ No newline at end of file diff --git a/apps/website/lib/view-data/LeagueRosterAdminViewData.ts b/apps/website/lib/view-data/LeagueRosterAdminViewData.ts index ba8da6d93..5a6ecf2e9 100644 --- a/apps/website/lib/view-data/LeagueRosterAdminViewData.ts +++ b/apps/website/lib/view-data/LeagueRosterAdminViewData.ts @@ -1,3 +1,5 @@ +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + /** * LeagueRosterAdminViewData - Pure ViewData for RosterAdminPage * Contains only raw serializable data, no methods or computed properties @@ -25,7 +27,8 @@ export interface JoinRequestData { message?: string; } -export interface LeagueRosterAdminViewData { + +export interface LeagueRosterAdminViewData extends ViewData { leagueId: string; members: RosterMemberData[]; joinRequests: JoinRequestData[]; diff --git a/apps/website/lib/view-data/LeagueRulebookViewData.ts b/apps/website/lib/view-data/LeagueRulebookViewData.ts index 65fbd1ac5..6df07374f 100644 --- a/apps/website/lib/view-data/LeagueRulebookViewData.ts +++ b/apps/website/lib/view-data/LeagueRulebookViewData.ts @@ -1,4 +1,7 @@ -export interface RulebookScoringConfig { +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + + +export interface RulebookScoringConfig extends ViewData { scoringPresetName: string | null; gameName: string; championships: Array<{ @@ -12,9 +15,4 @@ export interface RulebookScoringConfig { bonusSummary: string[]; }>; dropPolicySummary: string; -} - -export interface LeagueRulebookViewData { - scoringConfig: RulebookScoringConfig | null; - positionPoints: Array<{ position: number; points: number }>; -} +} \ No newline at end of file diff --git a/apps/website/lib/view-data/LeagueScheduleViewData.ts b/apps/website/lib/view-data/LeagueScheduleViewData.ts index 1044ef497..e16ba9bd5 100644 --- a/apps/website/lib/view-data/LeagueScheduleViewData.ts +++ b/apps/website/lib/view-data/LeagueScheduleViewData.ts @@ -1,3 +1,5 @@ +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + /** * LeagueScheduleViewData - Pure ViewData for LeagueScheduleTemplate * Contains only raw serializable data, no methods or computed properties @@ -12,7 +14,8 @@ export interface ScheduleRaceData { status: string; } -export interface LeagueScheduleViewData { + +export interface LeagueScheduleViewData extends ViewData { leagueId: string; races: ScheduleRaceData[]; seasons: Array<{ diff --git a/apps/website/lib/view-data/leagues/LeagueSettingsViewData.ts b/apps/website/lib/view-data/LeagueSettingsViewData.ts similarity index 73% rename from apps/website/lib/view-data/leagues/LeagueSettingsViewData.ts rename to apps/website/lib/view-data/LeagueSettingsViewData.ts index 100796fd5..ad4674cf2 100644 --- a/apps/website/lib/view-data/leagues/LeagueSettingsViewData.ts +++ b/apps/website/lib/view-data/LeagueSettingsViewData.ts @@ -1,4 +1,7 @@ -export interface LeagueSettingsViewData { +import { ViewData } from "../contracts/view-data/ViewData"; + + +export interface LeagueSettingsViewData extends ViewData { leagueId: string; league: { id: string; diff --git a/apps/website/lib/view-data/leagues/LeagueSponsorshipsViewData.ts b/apps/website/lib/view-data/LeagueSponsorshipsViewData.ts similarity index 84% rename from apps/website/lib/view-data/leagues/LeagueSponsorshipsViewData.ts rename to apps/website/lib/view-data/LeagueSponsorshipsViewData.ts index 59d2b71dc..95ebfdc6f 100644 --- a/apps/website/lib/view-data/leagues/LeagueSponsorshipsViewData.ts +++ b/apps/website/lib/view-data/LeagueSponsorshipsViewData.ts @@ -1,4 +1,7 @@ -export interface LeagueSponsorshipsViewData { +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + + +export interface LeagueSponsorshipsViewData extends ViewData { leagueId: string; activeTab: 'overview' | 'editor'; onTabChange: (tab: 'overview' | 'editor') => void; diff --git a/apps/website/lib/view-data/LeagueStandingsViewData.ts b/apps/website/lib/view-data/LeagueStandingsViewData.ts index 2e0580de0..5b8e664e7 100644 --- a/apps/website/lib/view-data/LeagueStandingsViewData.ts +++ b/apps/website/lib/view-data/LeagueStandingsViewData.ts @@ -1,25 +1,4 @@ -/** - * LeagueStandingsViewData - Pure ViewData for LeagueStandingsTemplate - * Contains only raw serializable data, no methods or computed properties - */ -export interface StandingEntryData { - driverId: string; - position: number; - totalPoints: number; - racesFinished: number; - racesStarted: number; - avgFinish: number | null; - penaltyPoints: number; - bonusPoints: number; - teamName?: string; - // New fields from Phase 3 - positionChange: number; - lastRacePoints: number; - droppedRaceIds: string[]; - wins: number; - podiums: number; -} export interface DriverData { id: string; @@ -36,15 +15,4 @@ export interface LeagueMembershipData { role: 'owner' | 'admin' | 'steward' | 'member'; joinedAt: string; status: 'active' | 'pending' | 'banned'; -} - -export interface LeagueStandingsViewData { - standings: StandingEntryData[]; - drivers: DriverData[]; - memberships: LeagueMembershipData[]; - leagueId: string; - currentDriverId: string | null; - isAdmin: boolean; - // New fields for team standings toggle - isTeamChampionship?: boolean; } \ No newline at end of file diff --git a/apps/website/lib/view-data/LeagueWalletViewData.ts b/apps/website/lib/view-data/LeagueWalletViewData.ts new file mode 100644 index 000000000..819465ef8 --- /dev/null +++ b/apps/website/lib/view-data/LeagueWalletViewData.ts @@ -0,0 +1,16 @@ +import { ViewData } from "../contracts/view-data/ViewData"; + + +export interface LeagueWalletTransactionViewData extends ViewData { + id: string; + type: 'deposit' | 'withdrawal' | 'sponsorship' | 'prize'; + amount: number; + formattedAmount: string; + amountColor: string; + description: string; + createdAt: string; + formattedDate: string; + status: 'completed' | 'pending' | 'failed'; + statusColor: string; + typeColor: string; +} \ No newline at end of file diff --git a/apps/website/lib/view-data/LeaguesViewData.ts b/apps/website/lib/view-data/LeaguesViewData.ts index 5bed7d60c..65d3ae226 100644 --- a/apps/website/lib/view-data/LeaguesViewData.ts +++ b/apps/website/lib/view-data/LeaguesViewData.ts @@ -1,3 +1,5 @@ +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + /** * Leagues ViewData * @@ -6,7 +8,8 @@ * for display and ISO string timestamps for JSON serialization. */ -export interface LeaguesViewData { + +export interface LeaguesViewData extends ViewData { leagues: Array<{ id: string; name: string; diff --git a/apps/website/lib/view-data/LoginViewData.ts b/apps/website/lib/view-data/LoginViewData.ts new file mode 100644 index 000000000..f0e6f1c51 --- /dev/null +++ b/apps/website/lib/view-data/LoginViewData.ts @@ -0,0 +1,20 @@ +/** + * Login View Data + * + * ViewData for the login template. + */ + +import { FormState } from '../builders/view-data/types/FormState'; + +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + + +export interface LoginViewData extends ViewData { + returnTo: string; + hasInsufficientPermissions: boolean; + showPassword: boolean; + showErrorDetails: boolean; + formState: FormState; + isSubmitting: boolean; + submitError?: string; +} \ No newline at end of file diff --git a/apps/website/lib/view-data/MediaViewData.ts b/apps/website/lib/view-data/MediaViewData.ts index cf22679df..e7b990e77 100644 --- a/apps/website/lib/view-data/MediaViewData.ts +++ b/apps/website/lib/view-data/MediaViewData.ts @@ -1,6 +1,8 @@ import { MediaAsset } from '@/components/media/MediaGallery'; +import { ViewData } from '../contracts/view-data/ViewData'; -export interface MediaViewData { + +export interface MediaViewData extends ViewData { assets: MediaAsset[]; categories: { label: string; value: string }[]; title: string; diff --git a/apps/website/lib/view-data/OnboardingPageViewData.ts b/apps/website/lib/view-data/OnboardingPageViewData.ts index 2f96c65f4..cf380bf95 100644 --- a/apps/website/lib/view-data/OnboardingPageViewData.ts +++ b/apps/website/lib/view-data/OnboardingPageViewData.ts @@ -1,3 +1,6 @@ -export interface OnboardingPageViewData { +import { ViewData } from "../contracts/view-data/ViewData"; + + +export interface OnboardingPageViewData extends ViewData { isAlreadyOnboarded: boolean; } \ No newline at end of file diff --git a/apps/website/lib/view-data/PodiumDriver.ts b/apps/website/lib/view-data/PodiumDriverViewData.ts similarity index 51% rename from apps/website/lib/view-data/PodiumDriver.ts rename to apps/website/lib/view-data/PodiumDriverViewData.ts index 64fc40bd0..1091ee6b1 100644 --- a/apps/website/lib/view-data/PodiumDriver.ts +++ b/apps/website/lib/view-data/PodiumDriverViewData.ts @@ -1,4 +1,7 @@ -export interface PodiumDriver { +import { ViewData } from '@/lib/contracts/view-data/ViewData'; + + +export interface PodiumDriverViewData extends ViewData { id: string; name: string; rating: number; diff --git a/apps/website/lib/view-data/ProfileLayoutViewData.ts b/apps/website/lib/view-data/ProfileLayoutViewData.ts index 6a40bf0b2..c8d10832b 100644 --- a/apps/website/lib/view-data/ProfileLayoutViewData.ts +++ b/apps/website/lib/view-data/ProfileLayoutViewData.ts @@ -1,3 +1,6 @@ -export interface ProfileLayoutViewData { +import { ViewData } from "../contracts/view-data/ViewData"; + + +export interface ProfileLayoutViewData extends ViewData { // Empty for now } diff --git a/apps/website/lib/view-data/ProfileLeaguesViewData.ts b/apps/website/lib/view-data/ProfileLeaguesViewData.ts index 0a973fc53..0ee9927f0 100644 --- a/apps/website/lib/view-data/ProfileLeaguesViewData.ts +++ b/apps/website/lib/view-data/ProfileLeaguesViewData.ts @@ -3,14 +3,18 @@ * Pure, JSON-serializable data structure for Template rendering */ -export interface ProfileLeaguesLeagueViewData { +import { ViewData } from "../contracts/view-data/ViewData"; + + +export interface ProfileLeaguesLeagueViewData extends ViewData { leagueId: string; name: string; description: string; membershipRole: 'owner' | 'admin' | 'steward' | 'member'; } -export interface ProfileLeaguesViewData { + +export interface ProfileLeaguesViewData extends ViewData { ownedLeagues: ProfileLeaguesLeagueViewData[]; memberLeagues: ProfileLeaguesLeagueViewData[]; } diff --git a/apps/website/lib/view-data/ProfileLiveriesViewData.ts b/apps/website/lib/view-data/ProfileLiveriesViewData.ts index 3babc39af..b7cd29ac2 100644 --- a/apps/website/lib/view-data/ProfileLiveriesViewData.ts +++ b/apps/website/lib/view-data/ProfileLiveriesViewData.ts @@ -1,4 +1,7 @@ -export interface ProfileLiveryViewData { +import { ViewData } from "../contracts/view-data/ViewData"; + + +export interface ProfileLiveryViewData extends ViewData { id: string; carId: string; carName: string; @@ -7,6 +10,7 @@ export interface ProfileLiveryViewData { isValidated: boolean; } -export interface ProfileLiveriesViewData { + +export interface ProfileLiveriesViewData extends ViewData { liveries: ProfileLiveryViewData[]; } diff --git a/apps/website/lib/view-data/ProfileViewData.ts b/apps/website/lib/view-data/ProfileViewData.ts index fc9844b01..ae67cc6de 100644 --- a/apps/website/lib/view-data/ProfileViewData.ts +++ b/apps/website/lib/view-data/ProfileViewData.ts @@ -1,4 +1,7 @@ -export interface ProfileViewData { +import { ViewData } from "../contracts/view-data/ViewData"; + + +export interface ProfileViewData extends ViewData { driver: { id: string; name: string; diff --git a/apps/website/lib/view-data/leagues/ProtestDetailViewData.ts b/apps/website/lib/view-data/ProtestDetailViewData.ts similarity index 78% rename from apps/website/lib/view-data/leagues/ProtestDetailViewData.ts rename to apps/website/lib/view-data/ProtestDetailViewData.ts index e52b9be23..2f7d9bc69 100644 --- a/apps/website/lib/view-data/leagues/ProtestDetailViewData.ts +++ b/apps/website/lib/view-data/ProtestDetailViewData.ts @@ -1,4 +1,7 @@ -export interface ProtestDetailViewData { +import { ViewData } from "../contracts/view-data/ViewData"; + + +export interface ProtestDetailViewData extends ViewData { protestId: string; leagueId: string; status: string; diff --git a/apps/website/lib/view-data/races/RaceDetailViewData.ts b/apps/website/lib/view-data/RaceDetailViewData.ts similarity index 90% rename from apps/website/lib/view-data/races/RaceDetailViewData.ts rename to apps/website/lib/view-data/RaceDetailViewData.ts index 513a630e2..0930137aa 100644 --- a/apps/website/lib/view-data/races/RaceDetailViewData.ts +++ b/apps/website/lib/view-data/RaceDetailViewData.ts @@ -5,6 +5,8 @@ * JSON-serializable, template-ready data structure. */ +import { ViewData } from "../contracts/view-data/ViewData"; + export interface RaceDetailEntry { id: string; name: string; @@ -48,7 +50,8 @@ export interface RaceDetailRegistration { canRegister: boolean; } -export interface RaceDetailViewData { + +export interface RaceDetailViewData extends ViewData { race: RaceDetailRace; league?: RaceDetailLeague; entryList: RaceDetailEntry[]; diff --git a/apps/website/lib/view-data/races/RaceResultsViewData.ts b/apps/website/lib/view-data/RaceResultsViewData.ts similarity index 88% rename from apps/website/lib/view-data/races/RaceResultsViewData.ts rename to apps/website/lib/view-data/RaceResultsViewData.ts index eadfb9780..18abe752f 100644 --- a/apps/website/lib/view-data/races/RaceResultsViewData.ts +++ b/apps/website/lib/view-data/RaceResultsViewData.ts @@ -5,6 +5,8 @@ * JSON-serializable, template-ready data structure. */ +import { ViewData } from "../contracts/view-data/ViewData"; + export interface RaceResultsResult { position: number; driverId: string; @@ -29,7 +31,8 @@ export interface RaceResultsPenalty { notes?: string; } -export interface RaceResultsViewData { + +export interface RaceResultsViewData extends ViewData { raceTrack?: string; raceScheduledAt?: string; totalDrivers?: number; diff --git a/apps/website/lib/view-data/races/RaceStewardingViewData.ts b/apps/website/lib/view-data/RaceStewardingViewData.ts similarity index 88% rename from apps/website/lib/view-data/races/RaceStewardingViewData.ts rename to apps/website/lib/view-data/RaceStewardingViewData.ts index 17dfed1d0..8d025f68a 100644 --- a/apps/website/lib/view-data/races/RaceStewardingViewData.ts +++ b/apps/website/lib/view-data/RaceStewardingViewData.ts @@ -5,6 +5,8 @@ * JSON-serializable, template-ready data structure. */ +import { ViewData } from "../contracts/view-data/ViewData"; + export interface Protest { id: string; protestingDriverId: string; @@ -33,7 +35,8 @@ export interface Driver { name: string; } -export interface RaceStewardingViewData { + +export interface RaceStewardingViewData extends ViewData { race?: { id: string; track: string; diff --git a/apps/website/lib/view-data/RacesViewData.ts b/apps/website/lib/view-data/RacesViewData.ts index db6b83017..6dea751c3 100644 --- a/apps/website/lib/view-data/RacesViewData.ts +++ b/apps/website/lib/view-data/RacesViewData.ts @@ -1,4 +1,7 @@ -export interface RaceViewData { +import { ViewData } from "../contracts/view-data/ViewData"; + + +export interface RaceViewData extends ViewData { id: string; track: string; car: string; @@ -19,7 +22,8 @@ export interface RaceViewData { isPast: boolean; } -export interface RacesViewData { + +export interface RacesViewData extends ViewData { races: RaceViewData[]; totalCount: number; scheduledCount: number; diff --git a/apps/website/lib/view-data/ResetPasswordViewData.ts b/apps/website/lib/view-data/ResetPasswordViewData.ts new file mode 100644 index 000000000..897c54b87 --- /dev/null +++ b/apps/website/lib/view-data/ResetPasswordViewData.ts @@ -0,0 +1,18 @@ +/** + * Reset Password View Data + * + * ViewData for the reset password template. + */ + +import { ViewData } from "../contracts/view-data/ViewData"; + + +export interface ResetPasswordViewData extends ViewData { + token: string; + returnTo: string; + showSuccess: boolean; + successMessage?: string; + formState: any; // Will be managed by client component + isSubmitting: boolean; + submitError?: string; +} \ No newline at end of file diff --git a/apps/website/lib/view-data/leagues/RulebookViewData.ts b/apps/website/lib/view-data/RulebookViewData.ts similarity index 73% rename from apps/website/lib/view-data/leagues/RulebookViewData.ts rename to apps/website/lib/view-data/RulebookViewData.ts index 6a1aba369..b3e4652ff 100644 --- a/apps/website/lib/view-data/leagues/RulebookViewData.ts +++ b/apps/website/lib/view-data/RulebookViewData.ts @@ -1,4 +1,7 @@ -export interface RulebookViewData { +import { ViewData } from "../contracts/view-data/ViewData"; + + +export interface RulebookViewData extends ViewData { leagueId: string; gameName: string; scoringPresetName: string; diff --git a/apps/website/lib/view-data/SignupViewData.ts b/apps/website/lib/view-data/SignupViewData.ts new file mode 100644 index 000000000..5bef90db7 --- /dev/null +++ b/apps/website/lib/view-data/SignupViewData.ts @@ -0,0 +1,15 @@ +/** + * Signup View Data + * + * ViewData for the signup template. + */ + +import { ViewData } from "../contracts/view-data/ViewData"; + + +export interface SignupViewData extends ViewData { + returnTo: string; + formState: any; // Will be managed by client component + isSubmitting: boolean; + submitError?: string; +} \ No newline at end of file diff --git a/apps/website/lib/view-data/SponsorDashboardViewData.ts b/apps/website/lib/view-data/SponsorDashboardViewData.ts index da1bd1f4b..51404ab48 100644 --- a/apps/website/lib/view-data/SponsorDashboardViewData.ts +++ b/apps/website/lib/view-data/SponsorDashboardViewData.ts @@ -1,4 +1,7 @@ -export interface SponsorDashboardViewData { +import { ViewData } from "../contracts/view-data/ViewData"; + + +export interface SponsorDashboardViewData extends ViewData { sponsorName: string; totalImpressions: string; totalInvestment: string; diff --git a/apps/website/lib/view-data/SponsorLogoViewData.ts b/apps/website/lib/view-data/SponsorLogoViewData.ts index 462069bd6..d9e5c90b2 100644 --- a/apps/website/lib/view-data/SponsorLogoViewData.ts +++ b/apps/website/lib/view-data/SponsorLogoViewData.ts @@ -1,9 +1,12 @@ +import { ViewData } from "../contracts/view-data/ViewData"; + /** * SponsorLogoViewData * * ViewData for sponsor logo media rendering. */ -export interface SponsorLogoViewData { + +export interface SponsorLogoViewData extends ViewData { buffer: string; // base64 encoded contentType: string; } \ No newline at end of file diff --git a/apps/website/lib/view-data/leagues/StewardingViewData.ts b/apps/website/lib/view-data/StewardingViewData.ts similarity index 89% rename from apps/website/lib/view-data/leagues/StewardingViewData.ts rename to apps/website/lib/view-data/StewardingViewData.ts index c19eb9153..712adc026 100644 --- a/apps/website/lib/view-data/leagues/StewardingViewData.ts +++ b/apps/website/lib/view-data/StewardingViewData.ts @@ -1,4 +1,7 @@ -export interface StewardingViewData { +import { ViewData } from "../contracts/view-data/ViewData"; + + +export interface StewardingViewData extends ViewData { leagueId: string; totalPending: number; totalResolved: number; diff --git a/apps/website/lib/view-data/TeamDetailViewData.ts b/apps/website/lib/view-data/TeamDetailViewData.ts index d349d848c..8cb1bfe5e 100644 --- a/apps/website/lib/view-data/TeamDetailViewData.ts +++ b/apps/website/lib/view-data/TeamDetailViewData.ts @@ -3,6 +3,8 @@ * Contains only raw serializable data, no methods or computed properties */ +import { ViewData } from "../contracts/view-data/ViewData"; + export interface SponsorMetric { icon: string; // Icon name (e.g. 'users', 'zap', 'calendar') label: string; @@ -51,7 +53,8 @@ export interface TeamTab { visible: boolean; } -export interface TeamDetailViewData { + +export interface TeamDetailViewData extends ViewData { team: TeamDetailData; memberships: TeamMemberData[]; currentDriverId: string; diff --git a/apps/website/lib/view-data/TeamLeaderboardViewData.ts b/apps/website/lib/view-data/TeamLeaderboardViewData.ts index 1c314aa8c..9aafcf987 100644 --- a/apps/website/lib/view-data/TeamLeaderboardViewData.ts +++ b/apps/website/lib/view-data/TeamLeaderboardViewData.ts @@ -1,9 +1,11 @@ +import { ViewData } from '../contracts/view-data/ViewData'; import type { TeamSummaryViewModel } from '../view-models/TeamSummaryViewModel'; export type SkillLevel = 'pro' | 'advanced' | 'intermediate' | 'beginner'; export type SortBy = 'rating' | 'wins' | 'winRate' | 'races'; -export interface TeamLeaderboardViewData { + +export interface TeamLeaderboardViewData extends ViewData { teams: TeamSummaryViewModel[]; searchQuery: string; filterLevel: SkillLevel | 'all'; diff --git a/apps/website/lib/view-data/TeamLogoViewData.ts b/apps/website/lib/view-data/TeamLogoViewData.ts index 6e7634481..06b2c1f3b 100644 --- a/apps/website/lib/view-data/TeamLogoViewData.ts +++ b/apps/website/lib/view-data/TeamLogoViewData.ts @@ -1,9 +1,12 @@ +import { ViewData } from "../contracts/view-data/ViewData"; + /** * TeamLogoViewData * * ViewData for team logo media rendering. */ -export interface TeamLogoViewData { + +export interface TeamLogoViewData extends ViewData { buffer: string; // base64 encoded contentType: string; } \ No newline at end of file diff --git a/apps/website/lib/view-data/TeamRankingsViewData.ts b/apps/website/lib/view-data/TeamRankingsViewData.ts index 642a3f7ae..622f3d66b 100644 --- a/apps/website/lib/view-data/TeamRankingsViewData.ts +++ b/apps/website/lib/view-data/TeamRankingsViewData.ts @@ -1,6 +1,8 @@ +import { ViewData } from '../contracts/view-data/ViewData'; import type { LeaderboardTeamItem } from './LeaderboardTeamItem'; -export interface TeamRankingsViewData { + +export interface TeamRankingsViewData extends ViewData { teams: LeaderboardTeamItem[]; podium: LeaderboardTeamItem[]; recruitingCount: number; diff --git a/apps/website/lib/view-data/TeamsViewData.ts b/apps/website/lib/view-data/TeamsViewData.ts index 20cc2a92c..cef6125fc 100644 --- a/apps/website/lib/view-data/TeamsViewData.ts +++ b/apps/website/lib/view-data/TeamsViewData.ts @@ -22,6 +22,7 @@ export interface TeamSummaryData { countryCode?: string; } + export interface TeamsViewData extends ViewData { teams: TeamSummaryData[]; } diff --git a/apps/website/lib/view-data/TrackImageViewData.ts b/apps/website/lib/view-data/TrackImageViewData.ts index cf415c78a..9f0772785 100644 --- a/apps/website/lib/view-data/TrackImageViewData.ts +++ b/apps/website/lib/view-data/TrackImageViewData.ts @@ -1,9 +1,12 @@ +import { ViewData } from "../contracts/view-data/ViewData"; + /** * TrackImageViewData * * ViewData for track image media rendering. */ -export interface TrackImageViewData { + +export interface TrackImageViewData extends ViewData { buffer: string; // base64 encoded contentType: string; } \ No newline at end of file diff --git a/apps/website/lib/view-data/leagues/LeagueScheduleViewData.ts b/apps/website/lib/view-data/leagues/LeagueScheduleViewData.ts deleted file mode 100644 index d78834f31..000000000 --- a/apps/website/lib/view-data/leagues/LeagueScheduleViewData.ts +++ /dev/null @@ -1,24 +0,0 @@ -export interface LeagueScheduleViewData { - leagueId: string; - races: Array<{ - id: string; - name: string; - scheduledAt: string; // ISO string - track?: string; - car?: string; - sessionType?: string; - isPast: boolean; - isUpcoming: boolean; - status: 'scheduled' | 'completed'; - strengthOfField?: number; - // Registration info - isUserRegistered?: boolean; - canRegister?: boolean; - // Admin info - canEdit?: boolean; - canReschedule?: boolean; - }>; - // User permissions - currentDriverId?: string; - isAdmin: boolean; -} \ No newline at end of file diff --git a/apps/website/lib/view-data/leagues/LeagueWalletViewData.ts b/apps/website/lib/view-data/leagues/LeagueWalletViewData.ts deleted file mode 100644 index ffdabae1d..000000000 --- a/apps/website/lib/view-data/leagues/LeagueWalletViewData.ts +++ /dev/null @@ -1,27 +0,0 @@ -export interface LeagueWalletTransactionViewData { - id: string; - type: 'deposit' | 'withdrawal' | 'sponsorship' | 'prize'; - amount: number; - formattedAmount: string; - amountColor: string; - description: string; - createdAt: string; - formattedDate: string; - status: 'completed' | 'pending' | 'failed'; - statusColor: string; - typeColor: string; -} - -export interface LeagueWalletViewData { - leagueId: string; - balance: number; - formattedBalance: string; - totalRevenue: number; - formattedTotalRevenue: string; - totalFees: number; - formattedTotalFees: string; - pendingPayouts: number; - formattedPendingPayouts: string; - currency: string; - transactions: LeagueWalletTransactionViewData[]; -} diff --git a/apps/website/templates/FatalErrorTemplate.tsx b/apps/website/templates/FatalErrorTemplate.tsx index fa9517c11..8414d37bd 100644 --- a/apps/website/templates/FatalErrorTemplate.tsx +++ b/apps/website/templates/FatalErrorTemplate.tsx @@ -1,7 +1,6 @@ -import React from 'react'; import { ErrorScreen } from '@/components/errors/ErrorScreen'; -export interface FatalErrorViewData { +export interface FatalErrorViewData extends ViewData { error: Error & { digest?: string }; } diff --git a/apps/website/templates/HomeTemplate.tsx b/apps/website/templates/HomeTemplate.tsx index 0973bba59..0c8d02035 100644 --- a/apps/website/templates/HomeTemplate.tsx +++ b/apps/website/templates/HomeTemplate.tsx @@ -1,15 +1,16 @@ 'use client'; +import { CtaSection } from '@/components/home/CtaSection'; import { Hero } from '@/components/home/Hero'; -import { TelemetryStrip } from '@/components/home/TelemetryStrip'; -import { ValuePillars } from '@/components/home/ValuePillars'; -import { StewardingPreview } from '@/components/home/StewardingPreview'; import { LeagueIdentityPreview } from '@/components/home/LeagueIdentityPreview'; import { MigrationSection } from '@/components/home/MigrationSection'; -import { CtaSection } from '@/components/home/CtaSection'; +import { StewardingPreview } from '@/components/home/StewardingPreview'; +import { TelemetryStrip } from '@/components/home/TelemetryStrip'; +import { ValuePillars } from '@/components/home/ValuePillars'; +import { ViewData } from '@/lib/contracts/view-data/ViewData'; import { Stack } from '@/ui/Stack'; -export interface HomeViewData { +export interface HomeViewData extends ViewData { isAlpha: boolean; upcomingRaces: Array<{ id: string; diff --git a/apps/website/templates/LeagueSettingsTemplate.tsx b/apps/website/templates/LeagueSettingsTemplate.tsx index bd978cd45..05a201c4c 100644 --- a/apps/website/templates/LeagueSettingsTemplate.tsx +++ b/apps/website/templates/LeagueSettingsTemplate.tsx @@ -1,11 +1,11 @@ 'use client'; -import type { LeagueSettingsViewData } from '@/lib/view-data/leagues/LeagueSettingsViewData'; +import type { LeagueSettingsViewData } from '@/lib/view-data/LeagueSettingsViewData'; import { Box } from '@/ui/Box'; -import { Heading } from '@/ui/Heading'; -import { Icon } from '@/ui/Icon'; import { Grid } from '@/ui/Grid'; import { GridItem } from '@/ui/GridItem'; +import { Heading } from '@/ui/Heading'; +import { Icon } from '@/ui/Icon'; import { Stack } from '@/ui/Stack'; import { Surface } from '@/ui/Surface'; import { Text } from '@/ui/Text'; diff --git a/apps/website/templates/LeagueSponsorshipsTemplate.tsx b/apps/website/templates/LeagueSponsorshipsTemplate.tsx index 57963f05f..7888c9730 100644 --- a/apps/website/templates/LeagueSponsorshipsTemplate.tsx +++ b/apps/website/templates/LeagueSponsorshipsTemplate.tsx @@ -3,12 +3,12 @@ import { LeagueDecalPlacementEditor } from '@/components/leagues/LeagueDecalPlacementEditor'; import { SponsorshipRequestCard } from '@/components/leagues/SponsorshipRequestCard'; import { SponsorshipSlotCard } from '@/components/leagues/SponsorshipSlotCard'; -import type { LeagueSponsorshipsViewData } from '@/lib/view-data/leagues/LeagueSponsorshipsViewData'; +import type { LeagueSponsorshipsViewData } from '@/lib/view-data/LeagueSponsorshipsViewData'; import { Box } from '@/ui/Box'; import { Card } from '@/ui/Card'; +import { Grid } from '@/ui/Grid'; import { Heading } from '@/ui/Heading'; import { Icon } from '@/ui/Icon'; -import { Grid } from '@/ui/Grid'; import { Stack } from '@/ui/Stack'; import { Surface } from '@/ui/Surface'; import { Text } from '@/ui/Text'; diff --git a/apps/website/templates/LeagueWalletTemplate.tsx b/apps/website/templates/LeagueWalletTemplate.tsx index a2c2722cf..451134cd4 100644 --- a/apps/website/templates/LeagueWalletTemplate.tsx +++ b/apps/website/templates/LeagueWalletTemplate.tsx @@ -1,7 +1,8 @@ 'use client'; import { WalletSummaryPanel } from '@/components/leagues/WalletSummaryPanel'; -import type { LeagueWalletViewData } from '@/lib/view-data/leagues/LeagueWalletViewData'; +import { TemplateProps } from '@/lib/contracts/components/ComponentContracts'; +import type { LeagueWalletViewData } from '@/lib/view-data/LeagueWalletViewData'; import { Box } from '@/ui/Box'; import { Button } from '@/ui/Button'; import { Container } from '@/ui/Container'; @@ -10,7 +11,6 @@ import { Icon } from '@/ui/Icon'; import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; import { Download } from 'lucide-react'; -import { TemplateProps } from '@/lib/contracts/components/ComponentContracts'; interface LeagueWalletTemplateProps extends TemplateProps { onWithdraw?: (amount: number) => void; diff --git a/apps/website/templates/NotFoundTemplate.tsx b/apps/website/templates/NotFoundTemplate.tsx index 0b3eaf517..c15086ec3 100644 --- a/apps/website/templates/NotFoundTemplate.tsx +++ b/apps/website/templates/NotFoundTemplate.tsx @@ -1,9 +1,9 @@ 'use client'; -import React from 'react'; import { NotFoundScreen } from '@/components/errors/NotFoundScreen'; +import { ViewData } from '@/lib/contracts/view-data/ViewData'; -export interface NotFoundViewData { +export interface NotFoundViewData extends ViewData { errorCode: string; title: string; message: string; diff --git a/apps/website/templates/RaceDetailTemplate.tsx b/apps/website/templates/RaceDetailTemplate.tsx index 989d9bbca..6fda1e5f8 100644 --- a/apps/website/templates/RaceDetailTemplate.tsx +++ b/apps/website/templates/RaceDetailTemplate.tsx @@ -11,8 +11,8 @@ import { Box } from '@/ui/Box'; import { Container } from '@/ui/Container'; import { Grid } from '@/ui/Grid'; import { GridItem } from '@/ui/GridItem'; -import { Stack } from '@/ui/Stack'; import { Skeleton } from '@/ui/Skeleton'; +import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; export interface RaceDetailEntryViewModel { @@ -58,7 +58,7 @@ export interface RaceDetailRegistration { canRegister: boolean; } -export interface RaceDetailViewData { +export interface RaceDetailViewData extends ViewData { race: RaceDetailRace; league?: RaceDetailLeague; entryList: RaceDetailEntryViewModel[]; diff --git a/apps/website/templates/RaceResultsTemplate.tsx b/apps/website/templates/RaceResultsTemplate.tsx index 7343340e8..8439c8617 100644 --- a/apps/website/templates/RaceResultsTemplate.tsx +++ b/apps/website/templates/RaceResultsTemplate.tsx @@ -2,12 +2,12 @@ import { RaceDetailsHeader } from '@/components/races/RaceDetailsHeader'; import { RaceResultsTable } from '@/components/races/RaceResultsTable'; -import type { RaceResultsViewData } from '@/lib/view-data/races/RaceResultsViewData'; +import type { RaceResultsViewData } from '@/lib/view-data/RaceResultsViewData'; import { Box } from '@/ui/Box'; import { Container } from '@/ui/Container'; -import { Icon } from '@/ui/Icon'; import { Grid } from '@/ui/Grid'; import { GridItem } from '@/ui/GridItem'; +import { Icon } from '@/ui/Icon'; import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; import { AlertTriangle, Trophy, Zap, type LucideIcon } from 'lucide-react'; diff --git a/apps/website/templates/RaceStewardingTemplate.tsx b/apps/website/templates/RaceStewardingTemplate.tsx index 13b57e373..47ec89837 100644 --- a/apps/website/templates/RaceStewardingTemplate.tsx +++ b/apps/website/templates/RaceStewardingTemplate.tsx @@ -5,12 +5,12 @@ import { StewardingTabs } from '@/components/leagues/StewardingTabs'; import { RaceDetailsHeader } from '@/components/races/RaceDetailsHeader'; import { RacePenaltyRow } from '@/components/races/RacePenaltyRowWrapper'; import { RaceStewardingStats } from '@/components/races/RaceStewardingStats'; -import type { RaceStewardingViewData } from '@/lib/view-data/races/RaceStewardingViewData'; +import type { RaceStewardingViewData } from '@/lib/view-data/RaceStewardingViewData'; import { Box } from '@/ui/Box'; import { Container } from '@/ui/Container'; -import { Icon } from '@/ui/Icon'; import { Grid } from '@/ui/Grid'; import { GridItem } from '@/ui/GridItem'; +import { Icon } from '@/ui/Icon'; import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; import { CheckCircle, Flag, Gavel, Info } from 'lucide-react'; diff --git a/apps/website/templates/RulebookTemplate.tsx b/apps/website/templates/RulebookTemplate.tsx index b11f6d5cf..68a084baf 100644 --- a/apps/website/templates/RulebookTemplate.tsx +++ b/apps/website/templates/RulebookTemplate.tsx @@ -2,11 +2,11 @@ import { RulebookTabs, type RulebookSection } from '@/components/leagues/RulebookTabs'; import { PointsTable } from '@/components/races/PointsTable'; -import type { RulebookViewData } from '@/lib/view-data/leagues/RulebookViewData'; +import type { RulebookViewData } from '@/lib/view-data/RulebookViewData'; import { Box } from '@/ui/Box'; +import { Grid } from '@/ui/Grid'; import { Heading } from '@/ui/Heading'; import { Icon } from '@/ui/Icon'; -import { Grid } from '@/ui/Grid'; import { Stack } from '@/ui/Stack'; import { Surface } from '@/ui/Surface'; import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from '@/ui/Table'; diff --git a/apps/website/templates/ServerErrorTemplate.tsx b/apps/website/templates/ServerErrorTemplate.tsx index 5aee57b6e..1311f7310 100644 --- a/apps/website/templates/ServerErrorTemplate.tsx +++ b/apps/website/templates/ServerErrorTemplate.tsx @@ -3,12 +3,13 @@ import { ErrorDetails } from '@/components/errors/ErrorDetails'; import { RecoveryActions } from '@/components/errors/RecoveryActions'; import { ServerErrorPanel } from '@/components/errors/ServerErrorPanel'; +import { ViewData } from '@/lib/contracts/view-data/ViewData'; import { Box } from '@/ui/Box'; import { Glow } from '@/ui/Glow'; import { Stack } from '@/ui/Stack'; import { Surface } from '@/ui/Surface'; -export interface ServerErrorViewData { +export interface ServerErrorViewData extends ViewData { error: Error & { digest?: string }; incidentId?: string; } diff --git a/apps/website/templates/SponsorBillingTemplate.tsx b/apps/website/templates/SponsorBillingTemplate.tsx index e2a36115d..ef2d8f9bd 100644 --- a/apps/website/templates/SponsorBillingTemplate.tsx +++ b/apps/website/templates/SponsorBillingTemplate.tsx @@ -15,16 +15,16 @@ import { InfoBanner } from '@/ui/InfoBanner'; import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; import { - Building2, - CreditCard, - Download, - ExternalLink, - LucideIcon, - Percent, - Receipt + Building2, + CreditCard, + Download, + ExternalLink, + LucideIcon, + Percent, + Receipt } from 'lucide-react'; -export interface SponsorBillingViewData { +export interface SponsorBillingViewData extends ViewData { stats: { totalSpent: number; pendingAmount: number; diff --git a/apps/website/templates/SponsorCampaignsTemplate.tsx b/apps/website/templates/SponsorCampaignsTemplate.tsx index b63487455..50e1888a3 100644 --- a/apps/website/templates/SponsorCampaignsTemplate.tsx +++ b/apps/website/templates/SponsorCampaignsTemplate.tsx @@ -7,19 +7,19 @@ import { Container } from '@/ui/Container'; import { Icon } from '@/ui/Icon'; import { Stack } from '@/ui/Stack'; import { - BarChart3, - Check, - Clock, - Eye, - LucideIcon, - Search + BarChart3, + Check, + Clock, + Eye, + LucideIcon, + Search } from 'lucide-react'; import React from 'react'; export type SponsorshipType = 'all' | 'leagues' | 'teams' | 'drivers' | 'races' | 'platform'; export type SponsorshipStatus = 'all' | 'active' | 'pending_approval' | 'approved' | 'rejected' | 'expired'; -export interface SponsorCampaignsViewData { +export interface SponsorCampaignsViewData extends ViewData { sponsorships: Array<{ id: string; type: string; diff --git a/apps/website/templates/SponsorLeagueDetailTemplate.tsx b/apps/website/templates/SponsorLeagueDetailTemplate.tsx index 4b85a5b16..b51e1a5a1 100644 --- a/apps/website/templates/SponsorLeagueDetailTemplate.tsx +++ b/apps/website/templates/SponsorLeagueDetailTemplate.tsx @@ -5,36 +5,36 @@ import { PricingTableShell, PricingTier } from '@/components/sponsors/PricingTab import { SponsorBrandingPreview } from '@/components/sponsors/SponsorBrandingPreview'; import { SponsorDashboardHeader } from '@/components/sponsors/SponsorDashboardHeader'; import { SponsorStatusChip } from '@/components/sponsors/SponsorStatusChip'; +import { TemplateProps } from '@/lib/contracts/components/ComponentContracts'; +import { ViewData } from '@/lib/contracts/view-data/ViewData'; import { routes } from '@/lib/routing/RouteConfig'; import { siteConfig } from '@/lib/siteConfig'; import { Box } from '@/ui/Box'; import { Button } from '@/ui/Button'; -import { Stack } from '@/ui/Stack'; -import { Text } from '@/ui/Text'; -import { Icon } from '@/ui/Icon'; import { Card } from '@/ui/Card'; import { Container } from '@/ui/Container'; -import { Heading } from '@/ui/Heading'; -import { Link } from '@/ui/Link'; import { Grid } from '@/ui/Grid'; import { GridItem } from '@/ui/GridItem'; +import { Heading } from '@/ui/Heading'; +import { Icon } from '@/ui/Icon'; +import { Link } from '@/ui/Link'; +import { Stack } from '@/ui/Stack'; import { Surface } from '@/ui/Surface'; +import { Text } from '@/ui/Text'; import { - BarChart3, - Calendar, - CreditCard, - Eye, - FileText, - Flag, - Megaphone, - TrendingUp, - Trophy, - type LucideIcon + BarChart3, + Calendar, + CreditCard, + Eye, + FileText, + Flag, + Megaphone, + TrendingUp, + Trophy, + type LucideIcon } from 'lucide-react'; -import { TemplateProps } from '@/lib/contracts/components/ComponentContracts'; -import { ViewData } from '@/lib/contracts/view-data/ViewData'; -export interface SponsorLeagueDetailViewData extends ViewData { +export interface SponsorLeagueDetailViewData extends ViewData extends ViewData { league: { id: string; name: string; diff --git a/apps/website/templates/SponsorLeaguesTemplate.tsx b/apps/website/templates/SponsorLeaguesTemplate.tsx index e6c3e63dc..b1f42eaeb 100644 --- a/apps/website/templates/SponsorLeaguesTemplate.tsx +++ b/apps/website/templates/SponsorLeaguesTemplate.tsx @@ -8,21 +8,21 @@ import { Box } from '@/ui/Box'; import { Button } from '@/ui/Button'; import { Card } from '@/ui/Card'; import { Container } from '@/ui/Container'; +import { Grid } from '@/ui/Grid'; +import { GridItem } from '@/ui/GridItem'; import { Heading } from '@/ui/Heading'; import { Icon } from '@/ui/Icon'; import { Input } from '@/ui/Input'; import { Link } from '@/ui/Link'; -import { Grid } from '@/ui/Grid'; -import { GridItem } from '@/ui/GridItem'; import { Stack } from '@/ui/Stack'; import { Surface } from '@/ui/Surface'; import { Text } from '@/ui/Text'; import { - Car, - Megaphone, - Search, - Trophy, - Users, + Car, + Megaphone, + Search, + Trophy, + Users, } from 'lucide-react'; interface AvailableLeague { @@ -47,7 +47,7 @@ export type SortOption = 'rating' | 'drivers' | 'price' | 'views'; export type TierFilter = 'all' | 'premium' | 'standard' | 'starter'; export type AvailabilityFilter = 'all' | 'main' | 'secondary'; -export interface SponsorLeaguesViewData { +export interface SponsorLeaguesViewData extends ViewData { leagues: AvailableLeague[]; stats: { total: number; diff --git a/apps/website/templates/SponsorSettingsTemplate.tsx b/apps/website/templates/SponsorSettingsTemplate.tsx index 42a189544..ec37bf623 100644 --- a/apps/website/templates/SponsorSettingsTemplate.tsx +++ b/apps/website/templates/SponsorSettingsTemplate.tsx @@ -11,15 +11,15 @@ import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; import { Toggle } from '@/ui/Toggle'; import { - AlertCircle, - Bell, - Building2, - RefreshCw, - Save + AlertCircle, + Bell, + Building2, + RefreshCw, + Save } from 'lucide-react'; import React from 'react'; -export interface SponsorSettingsViewData { +export interface SponsorSettingsViewData extends ViewData { profile: { companyName: string; contactName: string; diff --git a/apps/website/templates/StewardingTemplate.tsx b/apps/website/templates/StewardingTemplate.tsx index 93fd90ec3..b3e6bfe92 100644 --- a/apps/website/templates/StewardingTemplate.tsx +++ b/apps/website/templates/StewardingTemplate.tsx @@ -6,13 +6,13 @@ import { ReviewProtestModal } from '@/components/leagues/ReviewProtestModal'; import { StewardingQueuePanel } from '@/components/leagues/StewardingQueuePanel'; import { StewardingStats } from '@/components/leagues/StewardingStats'; import { PenaltyFAB } from '@/components/races/PenaltyFAB'; -import type { StewardingViewData } from '@/lib/view-data/leagues/StewardingViewData'; +import { TemplateProps } from '@/lib/contracts/components/ComponentContracts'; +import type { StewardingViewData } from '@/lib/view-data/StewardingViewData'; import { Box } from '@/ui/Box'; import { Button } from '@/ui/Button'; +import { Card } from '@/ui/Card'; import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; -import { Card } from '@/ui/Card'; -import { TemplateProps } from '@/lib/contracts/components/ComponentContracts'; interface StewardingTemplateProps extends TemplateProps { activeTab: 'pending' | 'history'; diff --git a/apps/website/templates/auth/ForgotPasswordTemplate.tsx b/apps/website/templates/auth/ForgotPasswordTemplate.tsx index 8cf72787d..c74ef9794 100644 --- a/apps/website/templates/auth/ForgotPasswordTemplate.tsx +++ b/apps/website/templates/auth/ForgotPasswordTemplate.tsx @@ -3,8 +3,8 @@ import { AuthCard } from '@/components/auth/AuthCard'; import { AuthFooterLinks } from '@/components/auth/AuthFooterLinks'; import { AuthForm } from '@/components/auth/AuthForm'; -import { ForgotPasswordViewData } from '@/lib/builders/view-data/types/ForgotPasswordViewData'; import { routes } from '@/lib/routing/RouteConfig'; +import { ForgotPasswordViewData } from '@/lib/view-data/ForgotPasswordViewData'; import { Button } from '@/ui/Button'; import { Group } from '@/ui/Group'; import { Icon } from '@/ui/Icon'; diff --git a/apps/website/templates/auth/LoginTemplate.tsx b/apps/website/templates/auth/LoginTemplate.tsx index 2900fc9ef..156a3d283 100644 --- a/apps/website/templates/auth/LoginTemplate.tsx +++ b/apps/website/templates/auth/LoginTemplate.tsx @@ -5,8 +5,8 @@ import { AuthFooterLinks } from '@/components/auth/AuthFooterLinks'; import { AuthForm } from '@/components/auth/AuthForm'; import { EnhancedFormError } from '@/components/errors/EnhancedFormError'; import { FormState } from '@/lib/builders/view-data/types/FormState'; -import { LoginViewData } from '@/lib/builders/view-data/types/LoginViewData'; import { routes } from '@/lib/routing/RouteConfig'; +import { LoginViewData } from '@/lib/view-data/LoginViewData'; import { Button } from '@/ui/Button'; import { Checkbox } from '@/ui/Checkbox'; import { Group } from '@/ui/Group'; diff --git a/apps/website/templates/auth/ResetPasswordTemplate.tsx b/apps/website/templates/auth/ResetPasswordTemplate.tsx index d4fad8b86..9762a0b34 100644 --- a/apps/website/templates/auth/ResetPasswordTemplate.tsx +++ b/apps/website/templates/auth/ResetPasswordTemplate.tsx @@ -3,8 +3,8 @@ import { AuthCard } from '@/components/auth/AuthCard'; import { AuthFooterLinks } from '@/components/auth/AuthFooterLinks'; import { AuthForm } from '@/components/auth/AuthForm'; -import { ResetPasswordViewData } from '@/lib/builders/view-data/types/ResetPasswordViewData'; import { routes } from '@/lib/routing/RouteConfig'; +import { ResetPasswordViewData } from '@/lib/view-data/ResetPasswordViewData'; import { Button } from '@/ui/Button'; import { Group } from '@/ui/Group'; import { Icon } from '@/ui/Icon'; diff --git a/apps/website/templates/auth/SignupTemplate.tsx b/apps/website/templates/auth/SignupTemplate.tsx index 7ecd519e4..194a8a5d9 100644 --- a/apps/website/templates/auth/SignupTemplate.tsx +++ b/apps/website/templates/auth/SignupTemplate.tsx @@ -3,8 +3,8 @@ import { AuthCard } from '@/components/auth/AuthCard'; import { AuthFooterLinks } from '@/components/auth/AuthFooterLinks'; import { AuthForm } from '@/components/auth/AuthForm'; -import { SignupViewData } from '@/lib/builders/view-data/types/SignupViewData'; import { checkPasswordStrength } from '@/lib/utils/validation'; +import { SignupViewData } from '@/lib/view-data/SignupViewData'; import { Button } from '@/ui/Button'; import { Grid } from '@/ui/Grid'; import { Group } from '@/ui/Group'; @@ -13,8 +13,8 @@ import { Input } from '@/ui/Input'; import { Link } from '@/ui/Link'; import { LoadingSpinner } from '@/ui/LoadingSpinner'; import { PasswordField } from '@/ui/PasswordField'; -import { Text } from '@/ui/Text'; import { ProgressBar } from '@/ui/ProgressBar'; +import { Text } from '@/ui/Text'; import { AlertCircle, Check, Mail, User, UserPlus, X } from 'lucide-react'; import React from 'react'; diff --git a/apps/website/templates/layout/GlobalFooterTemplate.tsx b/apps/website/templates/layout/GlobalFooterTemplate.tsx index e235dd2d0..df53d619b 100644 --- a/apps/website/templates/layout/GlobalFooterTemplate.tsx +++ b/apps/website/templates/layout/GlobalFooterTemplate.tsx @@ -1,9 +1,9 @@ -import { Surface } from '@/ui/Surface'; -import { Stack } from '@/ui/Stack'; -import { Text } from '@/ui/Text'; import { Box } from '@/ui/Box'; +import { Stack } from '@/ui/Stack'; +import { Surface } from '@/ui/Surface'; +import { Text } from '@/ui/Text'; -export interface GlobalFooterViewData {} +export interface GlobalFooterViewData extends ViewData {} export function GlobalFooterTemplate(_props: GlobalFooterViewData) { return ( diff --git a/apps/website/templates/layout/GlobalSidebarTemplate.tsx b/apps/website/templates/layout/GlobalSidebarTemplate.tsx index 3a1c44539..3e33469de 100644 --- a/apps/website/templates/layout/GlobalSidebarTemplate.tsx +++ b/apps/website/templates/layout/GlobalSidebarTemplate.tsx @@ -1,15 +1,15 @@ 'use client'; +import { DashboardRail } from '@/components/dashboard/DashboardRail'; import { AuthedNav } from '@/components/layout/AuthedNav'; import { PublicNav } from '@/components/layout/PublicNav'; import { useCurrentSession } from '@/hooks/auth/useCurrentSession'; import { Box } from '@/ui/Box'; -import { DashboardRail } from '@/components/dashboard/DashboardRail'; -import { Text } from '@/ui/Text'; import { Surface } from '@/ui/Surface'; +import { Text } from '@/ui/Text'; import { usePathname } from 'next/navigation'; -export interface GlobalSidebarViewData {} +export interface GlobalSidebarViewData extends ViewData {} export function GlobalSidebarTemplate(_props: GlobalSidebarViewData) { const pathname = usePathname(); diff --git a/apps/website/templates/layout/HeaderContentTemplate.tsx b/apps/website/templates/layout/HeaderContentTemplate.tsx index 7888401eb..360b7d19f 100644 --- a/apps/website/templates/layout/HeaderContentTemplate.tsx +++ b/apps/website/templates/layout/HeaderContentTemplate.tsx @@ -1,14 +1,14 @@ -import { BrandMark } from '@/ui/BrandMark'; import { HeaderActions } from '@/components/layout/HeaderActions'; import { PublicNav } from '@/components/layout/PublicNav'; import { useCurrentSession } from '@/hooks/auth/useCurrentSession'; import { routes } from '@/lib/routing/RouteConfig'; import { Box } from '@/ui/Box'; +import { BrandMark } from '@/ui/BrandMark'; import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; import { usePathname } from 'next/navigation'; -export interface HeaderContentViewData {} +export interface HeaderContentViewData extends ViewData {} export function HeaderContentTemplate(_props: HeaderContentViewData) { const pathname = usePathname(); diff --git a/apps/website/templates/layout/RootAppShellTemplate.tsx b/apps/website/templates/layout/RootAppShellTemplate.tsx index f189bd920..f6b322a0e 100644 --- a/apps/website/templates/layout/RootAppShellTemplate.tsx +++ b/apps/website/templates/layout/RootAppShellTemplate.tsx @@ -3,12 +3,13 @@ import { AppFooter } from '@/components/layout/AppFooter'; import { AppHeader } from '@/components/layout/AppHeader'; import { AppSidebar } from '@/components/layout/AppSidebar'; -import { Layout } from '@/ui/Layout'; -import { Box } from '@/ui/Box'; import { SidebarProvider } from '@/components/layout/SidebarContext'; +import { ViewData } from '@/lib/contracts/view-data/ViewData'; +import { Box } from '@/ui/Box'; +import { Layout } from '@/ui/Layout'; import React from 'react'; -export interface RootAppShellViewData { +export interface RootAppShellViewData extends ViewData { children: React.ReactNode; } diff --git a/apps/website/templates/onboarding/OnboardingTemplate.tsx b/apps/website/templates/onboarding/OnboardingTemplate.tsx index b69ae36a2..2cc91e9e1 100644 --- a/apps/website/templates/onboarding/OnboardingTemplate.tsx +++ b/apps/website/templates/onboarding/OnboardingTemplate.tsx @@ -26,7 +26,7 @@ interface FormErrors { submit?: string; } -export interface OnboardingViewData { +export interface OnboardingViewData extends ViewData { onCompleted: () => void; onCompleteOnboarding: (data: { firstName: string; diff --git a/docs/architecture/website/BUILDERS.md b/docs/architecture/website/BUILDERS.md index 2de4d0bfb..38dab4840 100644 --- a/docs/architecture/website/BUILDERS.md +++ b/docs/architecture/website/BUILDERS.md @@ -36,7 +36,7 @@ Transform API DTOs directly into ViewData for templates. **Pattern**: ```typescript export class LeagueViewDataBuilder { - static build(apiDto: LeagueApiDto): LeagueDetailViewData { + static build(apiDto: LeagueApiDto): LeagueDetailViewData extends ViewData { return { leagueId: apiDto.id, name: apiDto.name, @@ -192,7 +192,7 @@ export class RaceResultsDataTransformer { ✅ **Correct**: Use ViewDataBuilder ```typescript export class RaceResultsViewDataBuilder { - static build(...): RaceResultsViewData { ... } + static build(...): RaceResultsViewData extends ViewData { ... } } ``` diff --git a/docs/architecture/website/VIEW_DATA.md b/docs/architecture/website/VIEW_DATA.md index fed684e72..effd6d192 100644 --- a/docs/architecture/website/VIEW_DATA.md +++ b/docs/architecture/website/VIEW_DATA.md @@ -39,18 +39,21 @@ const [viewModel, setViewModel] = useState(null); useEffect(() => { const apiDto = await apiClient.get(); - const vm = ViewModelBuilder.build(apiDto); + const viewData = ViewDataBuilder.build(apiDto); + const vm = ViewModelBuilder.build(viewData); setViewModel(vm); }, []); -// Template receives ViewData from ViewModel -return viewModel ?