website refactor

This commit is contained in:
2026-01-14 02:02:24 +01:00
parent 8d7c709e0c
commit 4522d41aef
291 changed files with 12763 additions and 9309 deletions

View File

@@ -0,0 +1,47 @@
'use client';
import type { UserDto, DashboardStats, UserListResponse } from '@/lib/api/admin/AdminApiClient';
import { AdminUserViewModel, DashboardStatsViewModel, UserListViewModel } from './AdminUserViewModel';
/**
* AdminViewModelPresenter
*
* Presenter layer for transforming API DTOs to ViewModels.
* Runs in client code only ('use client').
* Deterministic, side-effect free transformations.
*/
export class AdminViewModelPresenter {
/**
* Map a single user DTO to a View Model
*/
static mapUser(apiDto: UserDto): AdminUserViewModel {
return new AdminUserViewModel(apiDto);
}
/**
* Map an array of user DTOs to View Models
*/
static mapUsers(apiDtos: UserDto[]): AdminUserViewModel[] {
return apiDtos.map(apiDto => this.mapUser(apiDto));
}
/**
* Map dashboard stats DTO to View Model
*/
static mapDashboardStats(apiDto: DashboardStats): DashboardStatsViewModel {
return new DashboardStatsViewModel(apiDto);
}
/**
* Map user list response to View Model
*/
static mapUserList(viewData: UserListResponse): UserListViewModel {
return new UserListViewModel({
users: viewData.users,
total: viewData.total,
page: viewData.page,
limit: viewData.limit,
totalPages: viewData.totalPages,
});
}
}

View File

@@ -1,146 +0,0 @@
import { describe, it, expect } from 'vitest';
import {
DashboardOverviewViewModel,
DriverViewModel,
RaceViewModel,
LeagueStandingViewModel,
DashboardFeedItemSummaryViewModel,
FriendViewModel,
} from './DashboardOverviewViewModel';
import type { DashboardOverviewDto } from '../api/dashboard/DashboardApiClient';
const createDashboardOverviewDto = (): DashboardOverviewDto => ({
currentDriver: {
id: 'driver-1',
name: 'Test Driver',
avatarUrl: 'https://example.com/avatar.jpg',
country: 'DE',
totalRaces: 10,
wins: 3,
podiums: 5,
rating: 2500,
globalRank: 42,
consistency: 88,
},
nextRace: {
id: 'race-1',
track: 'Spa-Francorchamps',
car: 'GT3',
scheduledAt: '2025-01-01T12:00:00Z',
isMyLeague: true,
leagueName: 'Pro League',
},
upcomingRaces: [
{
id: 'race-2',
track: 'Nürburgring',
car: 'GT4',
scheduledAt: '2025-01-02T12:00:00Z',
isMyLeague: false,
leagueName: undefined,
},
],
leagueStandings: [
{
leagueId: 'league-1',
leagueName: 'Pro League',
position: 1,
points: 120,
totalDrivers: 50,
},
],
feedItems: [
{
id: 'feed-1',
type: 'news',
headline: 'Big race announced',
body: 'Details about the big race.',
timestamp: '2025-01-01T10:00:00Z',
ctaHref: '/races/race-1',
ctaLabel: 'View race',
},
],
friends: [
{
id: 'friend-1',
name: 'Racing Buddy',
avatarUrl: 'https://example.com/friend.jpg',
country: 'US',
},
],
activeLeaguesCount: 3,
});
describe('DashboardOverviewViewModel', () => {
it('wraps the current driver DTO in a DriverViewModel', () => {
const dto = createDashboardOverviewDto();
const viewModel = new DashboardOverviewViewModel(dto);
const currentDriver = viewModel.currentDriver;
expect(currentDriver).toBeInstanceOf(DriverViewModel);
expect(currentDriver.id).toBe('driver-1');
expect(currentDriver.name).toBe('Test Driver');
expect(currentDriver.avatarUrl).toBe('https://example.com/avatar.jpg');
expect(currentDriver.country).toBe('DE');
expect(currentDriver.totalRaces).toBe(10);
expect(currentDriver.wins).toBe(3);
expect(currentDriver.podiums).toBe(5);
expect(currentDriver.rating).toBe(2500);
expect(currentDriver.globalRank).toBe(42);
expect(currentDriver.consistency).toBe(88);
});
it('wraps nextRace DTO into a RaceViewModel and returns null when absent', () => {
const dtoWithRace = createDashboardOverviewDto();
const viewModelWithRace = new DashboardOverviewViewModel(dtoWithRace);
const nextRace = viewModelWithRace.nextRace;
expect(nextRace).toBeInstanceOf(RaceViewModel);
expect(nextRace?.id).toBe('race-1');
expect(nextRace?.track).toBe('Spa-Francorchamps');
expect(nextRace?.car).toBe('GT3');
expect(nextRace?.isMyLeague).toBe(true);
expect(nextRace?.leagueName).toBe('Pro League');
expect(nextRace?.scheduledAt).toBeInstanceOf(Date);
const dtoWithoutRace: DashboardOverviewDto = {
...dtoWithRace,
nextRace: null,
};
const viewModelWithoutRace = new DashboardOverviewViewModel(dtoWithoutRace);
expect(viewModelWithoutRace.nextRace).toBeNull();
});
it('maps upcoming races, league standings, feed items and friends into their respective view models', () => {
const dto = createDashboardOverviewDto();
const viewModel = new DashboardOverviewViewModel(dto);
expect(viewModel.upcomingRaces).toHaveLength(1);
expect(viewModel.upcomingRaces[0]).toBeInstanceOf(RaceViewModel);
expect(viewModel.upcomingRaces[0].id).toBe('race-2');
expect(viewModel.leagueStandings).toHaveLength(1);
expect(viewModel.leagueStandings[0]).toBeInstanceOf(LeagueStandingViewModel);
expect(viewModel.leagueStandings[0].leagueId).toBe('league-1');
expect(viewModel.feedItems).toHaveLength(1);
expect(viewModel.feedItems[0]).toBeInstanceOf(DashboardFeedItemSummaryViewModel);
expect(viewModel.feedItems[0].id).toBe('feed-1');
expect(viewModel.feedItems[0].timestamp).toBeInstanceOf(Date);
expect(viewModel.friends).toHaveLength(1);
expect(viewModel.friends[0]).toBeInstanceOf(FriendViewModel);
expect(viewModel.friends[0].id).toBe('friend-1');
});
it('exposes the activeLeaguesCount from the DTO', () => {
const dto = createDashboardOverviewDto();
const viewModel = new DashboardOverviewViewModel(dto);
expect(viewModel.activeLeaguesCount).toBe(3);
});
});

View File

@@ -1,198 +0,0 @@
import type { DashboardOverviewViewModelData } from './DashboardOverviewViewModelData';
import {
dashboardStatDisplay,
formatDashboardDate,
formatRating,
formatRank,
formatConsistency,
formatRaceCount,
formatFriendCount,
formatLeaguePosition,
formatPoints,
formatTotalDrivers,
} from '@/lib/display-objects/DashboardDisplay';
/**
* Dashboard Overview ViewModel
*
* Clean class that accepts DTO only and exposes derived values.
* This is client-only and instantiated after hydration.
*/
export class DashboardOverviewViewModel {
constructor(private readonly dto: DashboardOverviewViewModelData) {}
// Current Driver - Derived Values
get currentDriverName(): string {
return this.dto.currentDriver?.name || '';
}
get currentDriverAvatarUrl(): string {
return this.dto.currentDriver?.avatarUrl || '';
}
get currentDriverCountry(): string {
return this.dto.currentDriver?.country || '';
}
get currentDriverRating(): string {
return this.dto.currentDriver ? formatRating(this.dto.currentDriver.rating) : '0.0';
}
get currentDriverRank(): string {
return this.dto.currentDriver ? formatRank(this.dto.currentDriver.globalRank) : '0';
}
get currentDriverTotalRaces(): string {
return this.dto.currentDriver ? formatRaceCount(this.dto.currentDriver.totalRaces) : '0';
}
get currentDriverWins(): string {
return this.dto.currentDriver ? formatRaceCount(this.dto.currentDriver.wins) : '0';
}
get currentDriverPodiums(): string {
return this.dto.currentDriver ? formatRaceCount(this.dto.currentDriver.podiums) : '0';
}
get currentDriverConsistency(): string {
return this.dto.currentDriver ? formatConsistency(this.dto.currentDriver.consistency) : '0%';
}
// Next Race - Derived Values
get nextRace(): {
id: string;
track: string;
car: string;
scheduledAt: string;
status: string;
isMyLeague: boolean;
formattedDate: string;
formattedTime: string;
timeUntil: string;
} | null {
if (!this.dto.nextRace) return null;
const dateInfo = formatDashboardDate(new Date(this.dto.nextRace.scheduledAt));
return {
id: this.dto.nextRace.id,
track: this.dto.nextRace.track,
car: this.dto.nextRace.car,
scheduledAt: this.dto.nextRace.scheduledAt,
status: this.dto.nextRace.status,
isMyLeague: this.dto.nextRace.isMyLeague,
formattedDate: dateInfo.date,
formattedTime: dateInfo.time,
timeUntil: dateInfo.relative,
};
}
// Upcoming Races - Derived Values
get upcomingRaces(): Array<{
id: string;
track: string;
car: string;
scheduledAt: string;
status: string;
isMyLeague: boolean;
formattedDate: string;
formattedTime: string;
timeUntil: string;
}> {
return this.dto.upcomingRaces.map((race) => {
const dateInfo = formatDashboardDate(new Date(race.scheduledAt));
return {
id: race.id,
track: race.track,
car: race.car,
scheduledAt: race.scheduledAt,
status: race.status,
isMyLeague: race.isMyLeague,
formattedDate: dateInfo.date,
formattedTime: dateInfo.time,
timeUntil: dateInfo.relative,
};
});
}
// League Standings - Derived Values
get leagueStandings(): Array<{
leagueId: string;
leagueName: string;
position: string;
points: string;
totalDrivers: string;
}> {
return this.dto.leagueStandingsSummaries.map((standing) => ({
leagueId: standing.leagueId,
leagueName: standing.leagueName,
position: formatLeaguePosition(standing.position),
points: formatPoints(standing.points),
totalDrivers: formatTotalDrivers(standing.totalDrivers),
}));
}
// Feed Items - Derived Values
get feedItems(): Array<{
id: string;
type: string;
headline: string;
body?: string;
timestamp: string;
ctaHref?: string;
ctaLabel?: string;
formattedTime: string;
}> {
return this.dto.feedSummary.items.map((item) => ({
id: item.id,
type: item.type,
headline: item.headline,
body: item.body,
timestamp: item.timestamp,
ctaHref: item.ctaHref,
ctaLabel: item.ctaLabel,
formattedTime: formatDashboardDate(new Date(item.timestamp)).relative,
}));
}
// Friends - Derived Values
get friends(): Array<{
id: string;
name: string;
avatarUrl: string;
country: string;
}> {
// No additional formatting needed for friends
return this.dto.friends;
}
// Active Leagues Count
get activeLeaguesCount(): string {
return formatRaceCount(this.dto.activeLeaguesCount);
}
// Convenience getters for display
get hasNextRace(): boolean {
return this.dto.nextRace !== undefined;
}
get hasUpcomingRaces(): boolean {
return this.dto.upcomingRaces.length > 0;
}
get hasLeagueStandings(): boolean {
return this.dto.leagueStandingsSummaries.length > 0;
}
get hasFeedItems(): boolean {
return this.dto.feedSummary.items.length > 0;
}
get hasFriends(): boolean {
return this.dto.friends.length > 0;
}
get friendCount(): string {
return formatFriendCount(this.dto.friends.length);
}
}

View File

@@ -1,84 +0,0 @@
/**
* Dashboard Page DTO
* This is the data transfer object that gets passed from server to client
* Contains ISO string timestamps for JSON serialization
*/
export interface DashboardOverviewViewModelData {
currentDriver?: {
id: string;
name: string;
avatarUrl: string;
country: string;
totalRaces: number;
wins: number;
podiums: number;
rating: number;
globalRank: number;
consistency: number;
};
myUpcomingRaces: Array<{
id: string;
track: string;
car: string;
scheduledAt: string; // ISO string
status: string;
isMyLeague: boolean;
}>;
otherUpcomingRaces: Array<{
id: string;
track: string;
car: string;
scheduledAt: string; // ISO string
status: string;
isMyLeague: boolean;
}>;
upcomingRaces: Array<{
id: string;
track: string;
car: string;
scheduledAt: string; // ISO string
status: string;
isMyLeague: boolean;
}>;
activeLeaguesCount: number;
nextRace?: {
id: string;
track: string;
car: string;
scheduledAt: string; // ISO string
status: string;
isMyLeague: boolean;
};
recentResults: Array<{
id: string;
track: string;
car: string;
position: number;
date: string; // ISO string
}>;
leagueStandingsSummaries: Array<{
leagueId: string;
leagueName: string;
position: number;
points: number;
totalDrivers: number;
}>;
feedSummary: {
notificationCount: number;
items: Array<{
id: string;
type: string;
headline: string;
body?: string;
timestamp: string; // ISO string
ctaHref?: string;
ctaLabel?: string;
}>;
};
friends: Array<{
id: string;
name: string;
avatarUrl: string;
country: string;
}>;
}

View File

@@ -0,0 +1,3 @@
export interface OnboardingViewModel {
isAlreadyOnboarded: boolean;
}

View File

@@ -0,0 +1,87 @@
import type { TeamDetailPageDto } from '@/lib/page-queries/page-queries/TeamDetailPageQuery';
import type { TeamDetailViewData, TeamDetailData, TeamMemberData, SponsorMetric, TeamTab } from '@/lib/view-data/TeamDetailViewData';
import { Users, Zap, Calendar } from 'lucide-react';
/**
* TeamDetailPresenter - Client-side presenter for team detail page
* Transforms PageQuery DTO into ViewData for the template
* Deterministic; no hooks; no side effects
*/
export class TeamDetailPresenter {
static createViewData(pageDto: TeamDetailPageDto): TeamDetailViewData {
const team: TeamDetailData = {
id: pageDto.team.id,
name: pageDto.team.name,
tag: pageDto.team.tag,
description: pageDto.team.description,
ownerId: pageDto.team.ownerId,
leagues: pageDto.team.leagues,
createdAt: pageDto.team.createdAt,
specialization: pageDto.team.specialization,
region: pageDto.team.region,
languages: pageDto.team.languages,
category: pageDto.team.category,
membership: pageDto.team.membership,
canManage: pageDto.team.canManage,
};
const memberships: TeamMemberData[] = pageDto.memberships.map(membership => ({
driverId: membership.driverId,
driverName: membership.driverName,
role: membership.role,
joinedAt: membership.joinedAt,
isActive: membership.isActive,
avatarUrl: membership.avatarUrl,
}));
// Calculate isAdmin based on current driver's role
const currentDriverMembership = memberships.find(m => m.driverId === pageDto.currentDriverId);
const isAdmin = currentDriverMembership?.role === 'owner' || currentDriverMembership?.role === 'manager';
// Build sponsor metrics
const leagueCount = team.leagues?.length ?? 0;
const teamMetrics: SponsorMetric[] = [
{
icon: Users,
label: 'Members',
value: memberships.length,
color: 'text-primary-blue',
},
{
icon: Zap,
label: 'Est. Reach',
value: memberships.length * 15,
color: 'text-purple-400',
},
{
icon: Calendar,
label: 'Races',
value: leagueCount,
color: 'text-neon-aqua',
},
{
icon: Users,
label: 'Engagement',
value: '82%',
color: 'text-performance-green',
},
];
// Build tabs
const tabs: TeamTab[] = [
{ id: 'overview', label: 'Overview', visible: true },
{ id: 'roster', label: 'Roster', visible: true },
{ id: 'standings', label: 'Standings', visible: true },
{ id: 'admin', label: 'Admin', visible: isAdmin },
];
return {
team,
memberships,
currentDriverId: pageDto.currentDriverId,
isAdmin,
teamMetrics,
tabs,
};
}
}

View File

@@ -0,0 +1,79 @@
/**
* Forgot Password ViewModel
*
* Client-side state management for forgot password flow.
* Immutable, class-based, contains only UI state.
*/
export interface ForgotPasswordFormField {
value: string;
error?: string;
touched: boolean;
validating: boolean;
}
export interface ForgotPasswordFormState {
fields: {
email: ForgotPasswordFormField;
};
isValid: boolean;
isSubmitting: boolean;
submitError?: string;
submitCount: number;
}
export class ForgotPasswordViewModel {
constructor(
public readonly returnTo: string,
public readonly formState: ForgotPasswordFormState,
public readonly showSuccess: boolean = false,
public readonly successMessage: string | null = null,
public readonly magicLink: string | null = null,
public readonly mutationPending: boolean = false,
public readonly mutationError: string | null = null
) {}
withFormState(formState: ForgotPasswordFormState): ForgotPasswordViewModel {
return new ForgotPasswordViewModel(
this.returnTo,
formState,
this.showSuccess,
this.successMessage,
this.magicLink,
this.mutationPending,
this.mutationError
);
}
withSuccess(successMessage: string, magicLink: string | null = null): ForgotPasswordViewModel {
return new ForgotPasswordViewModel(
this.returnTo,
this.formState,
true,
successMessage,
magicLink,
false,
null
);
}
withMutationState(pending: boolean, error: string | null): ForgotPasswordViewModel {
return new ForgotPasswordViewModel(
this.returnTo,
this.formState,
this.showSuccess,
this.successMessage,
this.magicLink,
pending,
error
);
}
get isSubmitting(): boolean {
return this.formState.isSubmitting || this.mutationPending;
}
get submitError(): string | undefined {
return this.formState.submitError || this.mutationError || undefined;
}
}

View File

@@ -0,0 +1,96 @@
/**
* Login ViewModel
*
* Client-side state management for login flow.
* Immutable, class-based, contains only UI state.
*/
export interface LoginFormField {
value: string | boolean;
error?: string;
touched: boolean;
validating: boolean;
}
export interface LoginFormState {
fields: {
email: LoginFormField;
password: LoginFormField;
rememberMe: LoginFormField;
};
isValid: boolean;
isSubmitting: boolean;
submitError?: string;
submitCount: number;
}
export interface LoginUIState {
showPassword: boolean;
showErrorDetails: boolean;
}
export class LoginViewModel {
constructor(
public readonly returnTo: string,
public readonly hasInsufficientPermissions: boolean,
public readonly formState: LoginFormState,
public readonly uiState: LoginUIState,
public readonly mutationPending: boolean = false,
public readonly mutationError: string | null = null
) {}
// Immutable updates
withFormState(formState: LoginFormState): LoginViewModel {
return new LoginViewModel(
this.returnTo,
this.hasInsufficientPermissions,
formState,
this.uiState,
this.mutationPending,
this.mutationError
);
}
withUIState(uiState: LoginUIState): LoginViewModel {
return new LoginViewModel(
this.returnTo,
this.hasInsufficientPermissions,
this.formState,
uiState,
this.mutationPending,
this.mutationError
);
}
withMutationState(pending: boolean, error: string | null): LoginViewModel {
return new LoginViewModel(
this.returnTo,
this.hasInsufficientPermissions,
this.formState,
this.uiState,
pending,
error
);
}
// Getters for template consumption
get showPassword(): boolean {
return this.uiState.showPassword;
}
get showErrorDetails(): boolean {
return this.uiState.showErrorDetails;
}
get isSubmitting(): boolean {
return this.formState.isSubmitting || this.mutationPending;
}
get submitError(): string | undefined {
return this.formState.submitError || this.mutationError || undefined;
}
get formFields() {
return this.formState.fields;
}
}

View File

@@ -0,0 +1,102 @@
/**
* Reset Password ViewModel
*
* Client-side state management for reset password flow.
* Immutable, class-based, contains only UI state.
*/
export interface ResetPasswordFormField {
value: string;
error?: string;
touched: boolean;
validating: boolean;
}
export interface ResetPasswordFormState {
fields: {
newPassword: ResetPasswordFormField;
confirmPassword: ResetPasswordFormField;
};
isValid: boolean;
isSubmitting: boolean;
submitError?: string;
submitCount: number;
}
export interface ResetPasswordUIState {
showPassword: boolean;
showConfirmPassword: boolean;
}
export class ResetPasswordViewModel {
constructor(
public readonly token: string,
public readonly returnTo: string,
public readonly formState: ResetPasswordFormState,
public readonly uiState: ResetPasswordUIState,
public readonly showSuccess: boolean = false,
public readonly successMessage: string | null = null,
public readonly mutationPending: boolean = false,
public readonly mutationError: string | null = null
) {}
withFormState(formState: ResetPasswordFormState): ResetPasswordViewModel {
return new ResetPasswordViewModel(
this.token,
this.returnTo,
formState,
this.uiState,
this.showSuccess,
this.successMessage,
this.mutationPending,
this.mutationError
);
}
withUIState(uiState: ResetPasswordUIState): ResetPasswordViewModel {
return new ResetPasswordViewModel(
this.token,
this.returnTo,
this.formState,
uiState,
this.showSuccess,
this.successMessage,
this.mutationPending,
this.mutationError
);
}
withSuccess(successMessage: string): ResetPasswordViewModel {
return new ResetPasswordViewModel(
this.token,
this.returnTo,
this.formState,
this.uiState,
true,
successMessage,
false,
null
);
}
withMutationState(pending: boolean, error: string | null): ResetPasswordViewModel {
return new ResetPasswordViewModel(
this.token,
this.returnTo,
this.formState,
this.uiState,
this.showSuccess,
this.successMessage,
pending,
error
);
}
get isSubmitting(): boolean {
return this.formState.isSubmitting || this.mutationPending;
}
get submitError(): string | undefined {
return this.formState.submitError || this.mutationError || undefined;
}
}

View File

@@ -0,0 +1,80 @@
/**
* Signup ViewModel
*
* Client-side state management for signup flow.
* Immutable, class-based, contains only UI state.
*/
export interface SignupFormField {
value: string;
error?: string;
touched: boolean;
validating: boolean;
}
export interface SignupFormState {
fields: {
firstName: SignupFormField;
lastName: SignupFormField;
email: SignupFormField;
password: SignupFormField;
confirmPassword: SignupFormField;
};
isValid: boolean;
isSubmitting: boolean;
submitError?: string;
submitCount: number;
}
export interface SignupUIState {
showPassword: boolean;
showConfirmPassword: boolean;
}
export class SignupViewModel {
constructor(
public readonly returnTo: string,
public readonly formState: SignupFormState,
public readonly uiState: SignupUIState,
public readonly mutationPending: boolean = false,
public readonly mutationError: string | null = null
) {}
withFormState(formState: SignupFormState): SignupViewModel {
return new SignupViewModel(
this.returnTo,
formState,
this.uiState,
this.mutationPending,
this.mutationError
);
}
withUIState(uiState: SignupUIState): SignupViewModel {
return new SignupViewModel(
this.returnTo,
this.formState,
uiState,
this.mutationPending,
this.mutationError
);
}
withMutationState(pending: boolean, error: string | null): SignupViewModel {
return new SignupViewModel(
this.returnTo,
this.formState,
this.uiState,
pending,
error
);
}
get isSubmitting(): boolean {
return this.formState.isSubmitting || this.mutationPending;
}
get submitError(): string | undefined {
return this.formState.submitError || this.mutationError || undefined;
}
}

View File

@@ -1,100 +1,91 @@
export * from './view-models/ActivityItemViewModel';
export * from './view-models/AnalyticsDashboardViewModel';
export * from './view-models/AnalyticsMetricsViewModel';
export * from './view-models/AvailableLeaguesViewModel';
export * from './view-models/AvatarGenerationViewModel';
export * from './view-models/AvatarViewModel';
export * from './view-models/BillingViewModel';
export * from './view-models/CompleteOnboardingViewModel';
export * from './view-models/CreateLeagueViewModel';
export * from './view-models/CreateTeamViewModel';
export {
DashboardOverviewViewModel,
} from './view-models/DashboardOverviewViewModel';
export * from './view-models/DashboardOverviewViewModelData';
export * from './view-models/DeleteMediaViewModel';
export * from './view-models/DriverLeaderboardItemViewModel';
export * from './view-models/DriverLeaderboardViewModel';
export * from './view-models/DriverProfileViewModel';
export * from './view-models/DriverRegistrationStatusViewModel';
export * from './view-models/DriverSummaryViewModel';
export * from './view-models/DriverTeamViewModel';
export * from './view-models/DriverViewModel';
export * from './view-models/EmailSignupViewModel';
export * from './view-models/HomeDiscoveryViewModel';
export * from './view-models/ImportRaceResultsSummaryViewModel';
export * from './view-models/LeagueAdminViewModel';
export * from './view-models/LeagueCardViewModel';
export * from './view-models/LeagueDetailPageViewModel';
export { LeagueDetailViewModel, LeagueViewModel } from './view-models/LeagueDetailViewModel';
export * from './view-models/LeagueJoinRequestViewModel';
export * from './view-models/LeagueMembershipsViewModel';
export * from './view-models/LeagueMemberViewModel';
export * from './view-models/LeaguePageDetailViewModel';
export * from './view-models/LeagueScheduleViewModel';
export * from './view-models/LeagueScoringChampionshipViewModel';
export * from './view-models/LeagueScoringConfigViewModel';
export * from './view-models/LeagueScoringPresetsViewModel';
export * from './view-models/LeagueScoringPresetViewModel';
export * from './view-models/LeagueScoringSectionViewModel';
export * from './view-models/LeagueSettingsViewModel';
export * from './view-models/LeagueStandingsViewModel';
export * from './view-models/LeagueStatsViewModel';
export * from './view-models/LeagueStewardingViewModel';
export * from './view-models/LeagueSummaryViewModel';
export * from './view-models/LeagueWalletViewModel';
export * from './view-models/MediaViewModel';
export * from './view-models/MembershipFeeViewModel';
export * from './view-models/PaymentViewModel';
export * from './view-models/PrizeViewModel';
export * from './view-models/ProfileOverviewViewModel';
export * from './view-models/ProtestDriverViewModel';
export * from './view-models/ProtestViewModel';
export * from './view-models/RaceDetailEntryViewModel';
export * from './view-models/RaceDetailUserResultViewModel';
export * from './view-models/RaceDetailViewModel';
export * from './view-models/RaceListItemViewModel';
export * from './view-models/RaceResultsDataTransformer';
export * from './view-models/RaceResultsDetailViewModel';
export * from './view-models/RaceResultViewModel';
export * from './view-models/RacesPageViewModel';
export * from './view-models/RaceStatsViewModel';
export * from './view-models/RaceStewardingViewModel';
export * from './view-models/RaceViewModel';
export * from './view-models/RaceWithSOFViewModel';
export * from './view-models/RecordEngagementInputViewModel';
export * from './view-models/RecordEngagementOutputViewModel';
export * from './view-models/RecordPageViewInputViewModel';
export * from './view-models/RecordPageViewOutputViewModel';
export * from './view-models/RemoveMemberViewModel';
export * from './view-models/RenewalAlertViewModel';
export * from './view-models/RequestAvatarGenerationViewModel';
export * from './view-models/ScoringConfigurationViewModel';
export * from './view-models/SessionViewModel';
export * from './view-models/SponsorDashboardViewModel';
export * from './view-models/SponsorSettingsViewModel';
export * from './view-models/SponsorshipDetailViewModel';
export * from './view-models/SponsorshipPricingViewModel';
export * from './view-models/SponsorshipRequestViewModel';
export * from './view-models/SponsorshipViewModel';
export * from './view-models/SponsorSponsorshipsViewModel';
export * from './view-models/SponsorViewModel';
export * from './view-models/StandingEntryViewModel';
export * from './view-models/TeamCardViewModel';
export * from './view-models/TeamDetailsViewModel';
export * from './view-models/TeamJoinRequestViewModel';
export * from './view-models/TeamMemberViewModel';
export * from './view-models/TeamSummaryViewModel';
export * from './view-models/UpcomingRaceCardViewModel';
export * from './view-models/UpdateAvatarViewModel';
export * from './view-models/UpdateTeamViewModel';
export * from './view-models/UploadMediaViewModel';
export * from './view-models/UserProfileViewModel';
export * from './view-models/WalletTransactionViewModel';
export * from './view-models/WalletViewModel';
export * from './ActivityItemViewModel';
export * from './AnalyticsDashboardViewModel';
export * from './AnalyticsMetricsViewModel';
export * from './AvailableLeaguesViewModel';
export * from './AvatarGenerationViewModel';
export * from './AvatarViewModel';
export * from './BillingViewModel';
export * from './CompleteOnboardingViewModel';
export * from './CreateLeagueViewModel';
export * from './CreateTeamViewModel';
export * from './DeleteMediaViewModel';
export * from './DriverLeaderboardItemViewModel';
export * from './DriverLeaderboardViewModel';
export * from './DriverProfileViewModel';
export * from './DriverRegistrationStatusViewModel';
export * from './DriverSummaryViewModel';
export * from './DriverTeamViewModel';
export * from './DriverViewModel';
export * from './EmailSignupViewModel';
export * from './HomeDiscoveryViewModel';
export * from './ImportRaceResultsSummaryViewModel';
export * from './LeagueAdminViewModel';
export * from './LeagueCardViewModel';
export * from './LeagueDetailPageViewModel';
export { LeagueDetailViewModel, LeagueViewModel } from './LeagueDetailViewModel';
export * from './LeagueJoinRequestViewModel';
export * from './LeagueMembershipsViewModel';
export * from './LeagueMemberViewModel';
export * from './LeaguePageDetailViewModel';
export * from './LeagueScheduleViewModel';
export * from './LeagueScoringChampionshipViewModel';
export * from './LeagueScoringConfigViewModel';
export * from './LeagueScoringPresetsViewModel';
export * from './LeagueScoringPresetViewModel';
export * from './LeagueScoringSectionViewModel';
export * from './LeagueSettingsViewModel';
export * from './LeagueStandingsViewModel';
export * from './LeagueStatsViewModel';
export * from './LeagueStewardingViewModel';
export * from './LeagueSummaryViewModel';
export * from './LeagueWalletViewModel';
export * from './MediaViewModel';
export * from './MembershipFeeViewModel';
export * from './PaymentViewModel';
export * from './PrizeViewModel';
export * from './ProfileOverviewViewModel';
export * from './ProtestDriverViewModel';
export * from './ProtestViewModel';
export * from './RaceDetailEntryViewModel';
export * from './RaceDetailUserResultViewModel';
export * from './RaceDetailsViewModel';
export * from './RaceListItemViewModel';
export * from './RaceResultsDataTransformer';
export * from './RaceResultsDetailViewModel';
export * from './RaceResultViewModel';
export * from './RacesPageViewModel';
export * from './RaceStatsViewModel';
export * from './RaceStewardingViewModel';
export * from './RaceViewModel';
export * from './RaceWithSOFViewModel';
export * from './RecordEngagementInputViewModel';
export * from './RecordEngagementOutputViewModel';
export * from './RecordPageViewInputViewModel';
export * from './RecordPageViewOutputViewModel';
export * from './RemoveMemberViewModel';
export * from './RenewalAlertViewModel';
export * from './RequestAvatarGenerationViewModel';
export * from './ScoringConfigurationViewModel';
export * from './SessionViewModel';
export * from './SponsorSettingsViewModel';
export * from './SponsorshipDetailViewModel';
export * from './SponsorshipPricingViewModel';
export * from './SponsorshipRequestViewModel';
export * from './SponsorshipViewModel';
export * from './SponsorSponsorshipsViewModel';
export * from './SponsorViewModel';
export * from './StandingEntryViewModel';
export * from './TeamCardViewModel';
export * from './TeamDetailsViewModel';
export * from './TeamJoinRequestViewModel';
export * from './TeamMemberViewModel';
export * from './TeamSummaryViewModel';
export * from './UpcomingRaceCardViewModel';
export * from './UpdateAvatarViewModel';
export * from './UpdateTeamViewModel';
export * from './UploadMediaViewModel';
export * from './UserProfileViewModel';
export * from './WalletTransactionViewModel';
export * from './WalletViewModel';
export * from './presenters/DashboardPresenter';
export * from './presenters/ProfileLeaguesPresenter';
export * from './presenters/TeamDetailPresenter';
export * from './presenters/TeamsPresenter';
export * from './presenters/AdminViewModelPresenter';
export * from './AdminViewModelPresenter';