import { RacesApiClient } from '@/lib/api/races/RacesApiClient'; import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient'; import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient'; import { AuthApiClient } from '@/lib/api/auth/AuthApiClient'; import type { AllLeaguesWithCapacityDTO } from '@/lib/types/generated/AllLeaguesWithCapacityDTO'; import type { GetAllTeamsOutputDTO } from '@/lib/types/generated/GetAllTeamsOutputDTO'; import type { RacesPageDataDTO } from '@/lib/types/generated/RacesPageDataDTO'; import type { LeagueWithCapacityDTO } from '@/lib/types/generated/LeagueWithCapacityDTO'; import type { TeamListItemDTO } from '@/lib/types/generated/TeamListItemDTO'; import type { SignupParamsDTO } from '@/lib/types/generated/SignupParamsDTO'; import type { AuthSessionDTO } from '@/lib/types/generated/AuthSessionDTO'; import { RacesPageViewModel } from '@/lib/view-models/RacesPageViewModel'; import { HomeDiscoveryViewModel } from '@/lib/view-models/HomeDiscoveryViewModel'; import { LeagueCardViewModel } from '@/lib/view-models/LeagueCardViewModel'; import { TeamCardViewModel } from '@/lib/view-models/TeamCardViewModel'; import { UpcomingRaceCardViewModel } from '@/lib/view-models/UpcomingRaceCardViewModel'; import { EmailSignupViewModel } from '@/lib/view-models/EmailSignupViewModel'; export class LandingService { constructor( private readonly racesApi: RacesApiClient, private readonly leaguesApi: LeaguesApiClient, private readonly teamsApi: TeamsApiClient, private readonly authApi: AuthApiClient, ) {} async getHomeDiscovery(): Promise { const [racesDto, leaguesDto, teamsDto] = await Promise.all([ this.racesApi.getPageData() as Promise, this.leaguesApi.getAllWithCapacity() as Promise, this.teamsApi.getAll() as Promise, ]); const racesVm = new RacesPageViewModel(racesDto); const topLeagues = (leaguesDto?.leagues || []).slice(0, 4).map( (league: LeagueWithCapacityDTO) => new LeagueCardViewModel({ id: league.id, name: league.name, description: league.description ?? 'Competitive iRacing league', }), ); const teams = (teamsDto?.teams || []).slice(0, 4).map( (team: TeamListItemDTO) => new TeamCardViewModel({ id: team.id, name: team.name, tag: team.tag, description: team.description, logoUrl: team.logoUrl, }), ); const upcomingRaces = racesVm.upcomingRaces.slice(0, 4).map( race => new UpcomingRaceCardViewModel({ id: race.id, track: race.track, car: race.car, scheduledAt: race.scheduledAt, }), ); return new HomeDiscoveryViewModel({ topLeagues, teams, upcomingRaces, }); } /** * Sign up for early access with email * Uses the auth signup endpoint */ async signup(email: string): Promise { try { // Create signup params with default values for early access const signupParams: SignupParamsDTO = { email, password: 'temp_password_' + Math.random().toString(36).substring(7), // Temporary password displayName: email.split('@')[0], // Use email prefix as display name }; const session: AuthSessionDTO = await this.authApi.signup(signupParams); if (session?.user?.userId) { return new EmailSignupViewModel(email, 'Welcome to GridPilot! Check your email to confirm.', 'success'); } else { return new EmailSignupViewModel(email, 'Signup successful but session not created.', 'error'); } } catch (error: any) { // Handle specific error cases if (error?.status === 429) { return new EmailSignupViewModel(email, 'Too many requests. Please try again later.', 'error'); } if (error?.status === 409) { return new EmailSignupViewModel(email, 'This email is already registered.', 'info'); } return new EmailSignupViewModel(email, 'Something broke. Try again?', 'error'); } } }