From d6b94e21df784ed7384917a9e84209bd03ee5343 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Fri, 16 Jan 2026 11:13:42 +0100 Subject: [PATCH] website refactor --- apps/website/app/404/page.tsx | 2 + apps/website/app/500/page.tsx | 2 + apps/website/app/auth/login/LoginClient.tsx | 2 +- apps/website/app/auth/signup/SignupClient.tsx | 2 +- .../admin/LeagueAdminSchedulePageClient.tsx | 6 -- .../app/leagues/create/CreateLeagueWizard.tsx | 2 +- apps/website/app/leagues/create/page.tsx | 2 +- .../app/onboarding/OnboardingWizardClient.tsx | 2 +- .../app/races/all/RacesAllPageClient.tsx | 44 +++----------- .../TeamLeaderboardPageWrapper.tsx | 17 ++++-- apps/website/components/AppWrapper.tsx | 2 +- .../leagues/PendingProtestsList.tsx | 4 ++ apps/website/components/profile/UserPill.tsx | 2 +- .../components/teams/JoinTeamButton.tsx | 4 +- apps/website/components/teams/TeamAdmin.tsx | 5 +- .../website/hooks/race/useAllRacesPageData.ts | 20 +++++-- apps/website/hooks/sponsor/useSponsorMode.ts | 2 +- apps/website/lib/api/ApiClient.ts | 60 +++++++++++++++++++ apps/website/lib/api/index.test.ts | 2 +- apps/website/lib/apiClient.ts | 4 +- .../view-data/RacesAllViewDataBuilder.ts | 27 --------- .../lib/page-queries/TeamDetailPageQuery.ts | 24 ++++---- .../page-queries/races/RacesAllPageQuery.ts | 14 ++--- .../services/error/ErrorAnalyticsService.ts | 4 ++ .../services/leagues/ProfileLeaguesService.ts | 12 ++-- .../website/lib/services/races/RaceService.ts | 9 +++ .../website/lib/services/teams/TeamService.ts | 54 +++++++++++++---- .../lib/view-data/SponsorDashboardViewData.ts | 18 +++++- .../lib/view-data/races/RacesAllViewData.ts | 22 ------- .../lib/view-data/races/RacesViewData.ts | 29 --------- 30 files changed, 217 insertions(+), 182 deletions(-) create mode 100644 apps/website/lib/api/ApiClient.ts delete mode 100644 apps/website/lib/builders/view-data/RacesAllViewDataBuilder.ts delete mode 100644 apps/website/lib/view-data/races/RacesAllViewData.ts delete mode 100644 apps/website/lib/view-data/races/RacesViewData.ts diff --git a/apps/website/app/404/page.tsx b/apps/website/app/404/page.tsx index bb4257972..c0f3b5b85 100644 --- a/apps/website/app/404/page.tsx +++ b/apps/website/app/404/page.tsx @@ -1,3 +1,5 @@ +'use client'; + import { ErrorPageContainer } from '@/ui/ErrorPageContainer'; import { ErrorActionButtons } from '@/ui/ErrorActionButtons'; import { routes } from '@/lib/routing/RouteConfig'; diff --git a/apps/website/app/500/page.tsx b/apps/website/app/500/page.tsx index 0cc4be550..80382c945 100644 --- a/apps/website/app/500/page.tsx +++ b/apps/website/app/500/page.tsx @@ -1,3 +1,5 @@ +'use client'; + import { ErrorPageContainer } from '@/ui/ErrorPageContainer'; import { ErrorActionButtons } from '@/ui/ErrorActionButtons'; import { routes } from '@/lib/routing/RouteConfig'; diff --git a/apps/website/app/auth/login/LoginClient.tsx b/apps/website/app/auth/login/LoginClient.tsx index 1ba313f54..d45429471 100644 --- a/apps/website/app/auth/login/LoginClient.tsx +++ b/apps/website/app/auth/login/LoginClient.tsx @@ -9,7 +9,7 @@ import { useState, useEffect, useMemo } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; -import { useAuth } from '@/lib/auth/AuthContext'; +import { useAuth } from '@/components/auth/AuthContext'; import { LoginFlowController, LoginState } from '@/lib/auth/LoginFlowController'; import { LoginViewData } from '@/lib/builders/view-data/types/LoginViewData'; import { LoginTemplate } from '@/templates/auth/LoginTemplate'; diff --git a/apps/website/app/auth/signup/SignupClient.tsx b/apps/website/app/auth/signup/SignupClient.tsx index 800eb6f49..839c0eb2d 100644 --- a/apps/website/app/auth/signup/SignupClient.tsx +++ b/apps/website/app/auth/signup/SignupClient.tsx @@ -8,7 +8,7 @@ import { useState } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; -import { useAuth } from '@/lib/auth/AuthContext'; +import { useAuth } from '@/components/auth/AuthContext'; import { SignupViewData } from '@/lib/builders/view-data/types/SignupViewData'; import { SignupTemplate } from '@/templates/auth/SignupTemplate'; import { SignupMutation } from '@/lib/mutations/auth/SignupMutation'; diff --git a/apps/website/app/leagues/[id]/schedule/admin/LeagueAdminSchedulePageClient.tsx b/apps/website/app/leagues/[id]/schedule/admin/LeagueAdminSchedulePageClient.tsx index 6e12abe76..e61111802 100644 --- a/apps/website/app/leagues/[id]/schedule/admin/LeagueAdminSchedulePageClient.tsx +++ b/apps/website/app/leagues/[id]/schedule/admin/LeagueAdminSchedulePageClient.tsx @@ -34,7 +34,6 @@ export function LeagueAdminSchedulePageClient() { const [seasonId, setSeasonId] = useState(''); const [form, setForm] = useState(() => new RaceScheduleCommandModel()); const [editingRaceId, setEditingRaceId] = useState(null); - const [errors, setErrors] = useState>({}); // Action state const [isPublishing, setIsPublishing] = useState(false); @@ -60,7 +59,6 @@ export function LeagueAdminSchedulePageClient() { setSeasonId(newSeasonId); setEditingRaceId(null); setForm(new RaceScheduleCommandModel()); - setErrors({}); }; const handlePublishToggle = async () => { @@ -87,7 +85,6 @@ export function LeagueAdminSchedulePageClient() { const validationErrors = form.validate(); if (Object.keys(validationErrors).length > 0) { - setErrors(validationErrors as Record); return; } @@ -100,7 +97,6 @@ export function LeagueAdminSchedulePageClient() { if (result.isOk()) { // Reset form setForm(new RaceScheduleCommandModel()); - setErrors({}); setEditingRaceId(null); router.refresh(); } else { @@ -122,7 +118,6 @@ export function LeagueAdminSchedulePageClient() { car: race.car || '', scheduledAtIso: race.scheduledAt.toISOString(), })); - setErrors({}); }; const handleDelete = async (raceId: string) => { @@ -146,7 +141,6 @@ export function LeagueAdminSchedulePageClient() { const handleCancelEdit = () => { setEditingRaceId(null); setForm(new RaceScheduleCommandModel()); - setErrors({}); }; // Derived states diff --git a/apps/website/app/leagues/create/CreateLeagueWizard.tsx b/apps/website/app/leagues/create/CreateLeagueWizard.tsx index 4555249b3..2bd66f889 100644 --- a/apps/website/app/leagues/create/CreateLeagueWizard.tsx +++ b/apps/website/app/leagues/create/CreateLeagueWizard.tsx @@ -9,7 +9,7 @@ import { Box } from '@/ui/Box'; import { Stack } from '@/ui/Stack'; import { Text } from '@/ui/Text'; import { Icon } from '@/ui/Icon'; -import { useAuth } from '@/lib/auth/AuthContext'; +import { useAuth } from '@/components/auth/AuthContext'; import { AlertCircle, Award, diff --git a/apps/website/app/leagues/create/page.tsx b/apps/website/app/leagues/create/page.tsx index d1cbadc22..1c6fb9e0b 100644 --- a/apps/website/app/leagues/create/page.tsx +++ b/apps/website/app/leagues/create/page.tsx @@ -14,7 +14,7 @@ export default function CreateLeaguePage() { const router = useRouter(); const searchParams = useSearchParams(); - const wizardParams = SearchParamParser.parseWizard(searchParams as any).unwrap(); + const wizardParams = SearchParamParser.parseWizard(searchParams as unknown as URLSearchParams).unwrap(); const rawStep = wizardParams.step; let currentStepName: StepName = 'basics'; diff --git a/apps/website/app/onboarding/OnboardingWizardClient.tsx b/apps/website/app/onboarding/OnboardingWizardClient.tsx index 7041558a5..3fafd1ede 100644 --- a/apps/website/app/onboarding/OnboardingWizardClient.tsx +++ b/apps/website/app/onboarding/OnboardingWizardClient.tsx @@ -4,7 +4,7 @@ import { OnboardingWizard } from '@/components/onboarding/OnboardingWizard'; import { routes } from '@/lib/routing/RouteConfig'; import { completeOnboardingAction } from '@/app/onboarding/completeOnboardingAction'; import { generateAvatarsAction } from '@/app/onboarding/generateAvatarsAction'; -import { useAuth } from '@/lib/auth/AuthContext'; +import { useAuth } from '@/components/auth/AuthContext'; export function OnboardingWizardClient() { const { session } = useAuth(); diff --git a/apps/website/app/races/all/RacesAllPageClient.tsx b/apps/website/app/races/all/RacesAllPageClient.tsx index a3250da55..dff052156 100644 --- a/apps/website/app/races/all/RacesAllPageClient.tsx +++ b/apps/website/app/races/all/RacesAllPageClient.tsx @@ -1,10 +1,10 @@ 'use client'; -import { useState, useEffect, useCallback } from 'react'; +import { useState } from 'react'; import { useRouter } from 'next/navigation'; import { StatefulPageWrapper } from '@/components/shared/state/StatefulPageWrapper'; import { RacesAllTemplate } from '@/templates/RacesAllTemplate'; -import { RacesAllPageQuery } from '@/lib/page-queries/races/RacesAllPageQuery'; +import { useAllRacesPageData } from '@/hooks/race/useAllRacesPageData'; import { type RacesViewData, type RaceViewData } from '@/lib/view-data/RacesViewData'; import { Flag } from 'lucide-react'; @@ -12,7 +12,7 @@ import { routes } from '@/lib/routing/RouteConfig'; const ITEMS_PER_PAGE = 10; -export function RacesAllPageClient({ initialViewData }: { initialViewData: unknown }) { +export function RacesAllPageClient({ initialViewData }: { initialViewData: RacesViewData | null }) { const router = useRouter(); // Client-side state for filters and pagination @@ -23,38 +23,8 @@ export function RacesAllPageClient({ initialViewData }: { initialViewData: unkno const [showFilters, setShowFilters] = useState(false); const [showFilterModal, setShowFilterModal] = useState(false); - // Data state - const [pageData, setPageData] = useState(initialViewData as RacesViewData); - const [isLoading, setIsLoading] = useState(!initialViewData); - const [error, setError] = useState(null); - - // Fetch data - const fetchData = useCallback(async () => { - if (pageData && !isLoading) return; // Already have data from server - setIsLoading(true); - setError(null); - - try { - const result = await RacesAllPageQuery.execute(); - - if (result.isErr()) { - throw new globalThis.Error('Failed to fetch races'); - } - - setPageData(result.unwrap() as unknown as RacesViewData); - } catch (err) { - setError(err instanceof globalThis.Error ? err : new globalThis.Error('Unknown error')); - } finally { - setIsLoading(false); - } - }, [pageData, isLoading]); - - // Fetch on mount if no initial data - useEffect(() => { - if (!initialViewData) { - fetchData(); - } - }, [initialViewData, fetchData]); + // Use React Query hook + const { data: pageData, isLoading, error, refetch } = useAllRacesPageData(initialViewData); // Transform data const races: RaceViewData[] = pageData?.races ?? []; @@ -102,8 +72,8 @@ export function RacesAllPageClient({ initialViewData }: { initialViewData: unkno pageData ? ( ); -} \ No newline at end of file +} diff --git a/apps/website/components/AppWrapper.tsx b/apps/website/components/AppWrapper.tsx index 9f12867cf..1763ab698 100644 --- a/apps/website/components/AppWrapper.tsx +++ b/apps/website/components/AppWrapper.tsx @@ -2,7 +2,7 @@ import { ContainerProvider } from '@/lib/di/providers/ContainerProvider'; import { QueryClientProvider } from '@/lib/providers/QueryClientProvider'; -import { AuthProvider } from '@/lib/auth/AuthContext'; +import { AuthProvider } from '@/components/auth/AuthContext'; import { FeatureFlagProvider } from '@/lib/feature/FeatureFlagProvider'; import { NotificationProvider } from '@/components/notifications/NotificationProvider'; import { NotificationIntegration } from '@/components/errors/NotificationIntegration'; diff --git a/apps/website/components/leagues/PendingProtestsList.tsx b/apps/website/components/leagues/PendingProtestsList.tsx index 17a50e742..16e6e6942 100644 --- a/apps/website/components/leagues/PendingProtestsList.tsx +++ b/apps/website/components/leagues/PendingProtestsList.tsx @@ -2,6 +2,7 @@ import { DriverViewModel } from "@/lib/view-models/DriverViewModel"; import { ProtestViewModel } from "@/lib/view-models/ProtestViewModel"; +import { RaceViewModel } from "@/lib/view-models/RaceViewModel"; import { Box } from "@/ui/Box"; import { Card } from "@/ui/Card"; import { ProtestListItem } from "./ProtestListItem"; @@ -12,7 +13,10 @@ import { Flag } from "lucide-react"; interface PendingProtestsListProps { protests: ProtestViewModel[]; drivers: Record; + races: Record; + leagueId: string; onReviewProtest: (protest: ProtestViewModel) => void; + onProtestReviewed?: () => void; } export function PendingProtestsList({ diff --git a/apps/website/components/profile/UserPill.tsx b/apps/website/components/profile/UserPill.tsx index 1c5c618e1..dfae52dd6 100644 --- a/apps/website/components/profile/UserPill.tsx +++ b/apps/website/components/profile/UserPill.tsx @@ -13,7 +13,7 @@ import { Settings, Shield } from 'lucide-react'; -import { useAuth } from '@/lib/auth/AuthContext'; +import { useAuth } from '@/components/auth/AuthContext'; import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId'; import { DriverViewModel as DriverViewModelClass } from '@/lib/view-models/DriverViewModel'; import { useFindDriverById } from '@/hooks/driver/useFindDriverById'; diff --git a/apps/website/components/teams/JoinTeamButton.tsx b/apps/website/components/teams/JoinTeamButton.tsx index 75224b54e..96301fb26 100644 --- a/apps/website/components/teams/JoinTeamButton.tsx +++ b/apps/website/components/teams/JoinTeamButton.tsx @@ -3,7 +3,9 @@ import React from 'react'; import { Button } from '@/ui/Button'; import { useEffectiveDriverId } from "@/hooks/useEffectiveDriverId"; -import { useTeamMembership, useJoinTeam, useLeaveTeam } from "@/hooks/team"; +import { useTeamMembership } from "@/hooks/team/useTeamMembership"; +import { useJoinTeam } from "@/hooks/team/useJoinTeam"; +import { useLeaveTeam } from "@/hooks/team/useLeaveTeam"; interface JoinTeamButtonProps { teamId: string; diff --git a/apps/website/components/teams/TeamAdmin.tsx b/apps/website/components/teams/TeamAdmin.tsx index e3ff51a3c..24c149ddb 100644 --- a/apps/website/components/teams/TeamAdmin.tsx +++ b/apps/website/components/teams/TeamAdmin.tsx @@ -13,7 +13,10 @@ import { JoinRequestList } from '@/ui/JoinRequestList'; import { JoinRequestItem } from '@/ui/JoinRequestItem'; import { DangerZone } from '@/ui/DangerZone'; import { MinimalEmptyState } from '@/components/shared/state/EmptyState'; -import { useTeamJoinRequests, useUpdateTeam, useApproveJoinRequest, useRejectJoinRequest } from "@/hooks/team"; +import { useTeamJoinRequests } from "@/hooks/team/useTeamJoinRequests"; +import { useUpdateTeam } from "@/hooks/team/useUpdateTeam"; +import { useApproveJoinRequest } from "@/hooks/team/useApproveJoinRequest"; +import { useRejectJoinRequest } from "@/hooks/team/useRejectJoinRequest"; import type { TeamJoinRequestViewModel } from '@/lib/view-models/TeamJoinRequestViewModel'; interface TeamAdminProps { diff --git a/apps/website/hooks/race/useAllRacesPageData.ts b/apps/website/hooks/race/useAllRacesPageData.ts index 5e5100cb8..62a551cc5 100644 --- a/apps/website/hooks/race/useAllRacesPageData.ts +++ b/apps/website/hooks/race/useAllRacesPageData.ts @@ -1,12 +1,22 @@ -import { usePageData } from '@/lib/page/usePageData'; +import { useQuery } from '@tanstack/react-query'; import { useInject } from '@/lib/di/hooks/useInject'; import { RACE_SERVICE_TOKEN } from '@/lib/di/tokens'; +import { RacesViewDataBuilder } from '@/lib/builders/view-data/RacesViewDataBuilder'; +import { RacesViewData } from '@/lib/view-data/RacesViewData'; -export function useAllRacesPageData() { +export function useAllRacesPageData(initialData?: RacesViewData | null) { const raceService = useInject(RACE_SERVICE_TOKEN); - return usePageData({ + return useQuery({ queryKey: ['races', 'all'], - queryFn: () => raceService.getAllRacesPageData(), + queryFn: async () => { + const result = await raceService.getAllRacesPageData(); + if (result.isErr()) { + throw new Error(result.getError().message); + } + return RacesViewDataBuilder.build(result.unwrap()); + }, + initialData: initialData ?? undefined, + staleTime: 1000 * 60 * 5, }); -} \ No newline at end of file +} diff --git a/apps/website/hooks/sponsor/useSponsorMode.ts b/apps/website/hooks/sponsor/useSponsorMode.ts index 8b2a91f15..82cc28d8c 100644 --- a/apps/website/hooks/sponsor/useSponsorMode.ts +++ b/apps/website/hooks/sponsor/useSponsorMode.ts @@ -1,4 +1,4 @@ -import { useAuth } from '@/lib/auth/AuthContext'; +import { useAuth } from '@/components/auth/AuthContext'; import { useState, useEffect } from 'react'; export function useSponsorMode(): boolean { diff --git a/apps/website/lib/api/ApiClient.ts b/apps/website/lib/api/ApiClient.ts new file mode 100644 index 000000000..d9de85585 --- /dev/null +++ b/apps/website/lib/api/ApiClient.ts @@ -0,0 +1,60 @@ +import { AdminApiClient } from './admin/AdminApiClient'; +import { AnalyticsApiClient } from './analytics/AnalyticsApiClient'; +import { AuthApiClient } from './auth/AuthApiClient'; +import { DashboardApiClient } from './dashboard/DashboardApiClient'; +import { DriversApiClient } from './drivers/DriversApiClient'; +import { LeaguesApiClient } from './leagues/LeaguesApiClient'; +import { MediaApiClient } from './media/MediaApiClient'; +import { PaymentsApiClient } from './payments/PaymentsApiClient'; +import { PenaltiesApiClient } from './penalties/PenaltiesApiClient'; +import { PolicyApiClient } from './policy/PolicyApiClient'; +import { ProtestsApiClient } from './protests/ProtestsApiClient'; +import { RacesApiClient } from './races/RacesApiClient'; +import { SponsorsApiClient } from './sponsors/SponsorsApiClient'; +import { TeamsApiClient } from './teams/TeamsApiClient'; +import { WalletsApiClient } from './wallets/WalletsApiClient'; +import { ErrorReporter } from '../interfaces/ErrorReporter'; +import { Logger } from '../interfaces/Logger'; + +export class ApiClient { + public readonly admin: AdminApiClient; + public readonly analytics: AnalyticsApiClient; + public readonly auth: AuthApiClient; + public readonly dashboard: DashboardApiClient; + public readonly drivers: DriversApiClient; + public readonly leagues: LeaguesApiClient; + public readonly media: MediaApiClient; + public readonly payments: PaymentsApiClient; + public readonly penalties: PenaltiesApiClient; + public readonly policy: PolicyApiClient; + public readonly protests: ProtestsApiClient; + public readonly races: RacesApiClient; + public readonly sponsors: SponsorsApiClient; + public readonly teams: TeamsApiClient; + public readonly wallets: WalletsApiClient; + + constructor(baseUrl: string) { + // Default implementations for logger and error reporter if needed + const logger: Logger = console; + const errorReporter: ErrorReporter = { report: (error) => console.error(error) }; + + this.admin = new AdminApiClient(baseUrl, errorReporter, logger); + this.analytics = new AnalyticsApiClient(baseUrl, errorReporter, logger); + this.auth = new AuthApiClient(baseUrl, errorReporter, logger); + this.dashboard = new DashboardApiClient(baseUrl, errorReporter, logger); + this.drivers = new DriversApiClient(baseUrl, errorReporter, logger); + this.leagues = new LeaguesApiClient(baseUrl, errorReporter, logger); + this.media = new MediaApiClient(baseUrl, errorReporter, logger); + this.payments = new PaymentsApiClient(baseUrl, errorReporter, logger); + this.penalties = new PenaltiesApiClient(baseUrl, errorReporter, logger); + this.policy = new PolicyApiClient(baseUrl, errorReporter, logger); + this.protests = new ProtestsApiClient(baseUrl, errorReporter, logger); + this.races = new RacesApiClient(baseUrl, errorReporter, logger); + this.sponsors = new SponsorsApiClient(baseUrl, errorReporter, logger); + this.teams = new TeamsApiClient(baseUrl, errorReporter, logger); + this.wallets = new WalletsApiClient(baseUrl, errorReporter, logger); + } +} + +// Export a default instance if needed, but apiClient.ts seems to handle it +export const api = new ApiClient(process.env.NEXT_PUBLIC_API_URL || ''); diff --git a/apps/website/lib/api/index.test.ts b/apps/website/lib/api/index.test.ts index a2436b0fb..a2680d378 100644 --- a/apps/website/lib/api/index.test.ts +++ b/apps/website/lib/api/index.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { ApiClient, api } from './index'; +import { ApiClient, api } from './ApiClient'; describe('ApiClient', () => { it('should be defined', () => { diff --git a/apps/website/lib/apiClient.ts b/apps/website/lib/apiClient.ts index b6ba359e0..7e3196a9a 100644 --- a/apps/website/lib/apiClient.ts +++ b/apps/website/lib/apiClient.ts @@ -1,4 +1,4 @@ -import { ApiClient } from './api/index'; +import { ApiClient } from './api/ApiClient'; import { getWebsiteApiBaseUrl } from './config/apiBaseUrl'; -export const apiClient = new ApiClient(getWebsiteApiBaseUrl()); \ No newline at end of file +export const apiClient = new ApiClient(getWebsiteApiBaseUrl()); diff --git a/apps/website/lib/builders/view-data/RacesAllViewDataBuilder.ts b/apps/website/lib/builders/view-data/RacesAllViewDataBuilder.ts deleted file mode 100644 index 1b6aa1011..000000000 --- a/apps/website/lib/builders/view-data/RacesAllViewDataBuilder.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { RacesAllViewData } from '@/lib/view-data/races/RacesAllViewData'; - -/** - * Races All View Data Builder - * - * Transforms API DTO into ViewData for the all races template. - * Deterministic, side-effect free. - */ -export class RacesAllViewDataBuilder { - static build(apiDto: any): RacesAllViewData { - const races = apiDto.races.map((race: any) => ({ - id: race.id, - track: race.track, - car: race.car, - scheduledAt: race.scheduledAt, - status: race.status as 'scheduled' | 'running' | 'completed' | 'cancelled', - sessionType: 'race', - leagueId: race.leagueId, - leagueName: race.leagueName, - strengthOfField: race.strengthOfField ?? undefined, - })); - - return { - races, - }; - } -} \ No newline at end of file diff --git a/apps/website/lib/page-queries/TeamDetailPageQuery.ts b/apps/website/lib/page-queries/TeamDetailPageQuery.ts index 80aae1ef2..c27b1f26f 100644 --- a/apps/website/lib/page-queries/TeamDetailPageQuery.ts +++ b/apps/website/lib/page-queries/TeamDetailPageQuery.ts @@ -68,7 +68,7 @@ export class TeamDetailPageQuery implements PageQuery + * Returns Result */ -export class RacesAllPageQuery implements PageQuery { - async execute(): Promise> { +export class RacesAllPageQuery implements PageQuery { + async execute(): Promise> { // Manual wiring: Service creates its own dependencies const service = new RacesService(); @@ -24,12 +24,12 @@ export class RacesAllPageQuery implements PageQuery { } // Transform to ViewData using builder - const viewData = RacesAllViewDataBuilder.build(result.unwrap()); + const viewData = RacesViewDataBuilder.build(result.unwrap()); return Result.ok(viewData); } // Static method to avoid object construction in server code - static async execute(): Promise> { + static async execute(): Promise> { const query = new RacesAllPageQuery(); return await query.execute(); } diff --git a/apps/website/lib/services/error/ErrorAnalyticsService.ts b/apps/website/lib/services/error/ErrorAnalyticsService.ts index 07364421d..233eddcaa 100644 --- a/apps/website/lib/services/error/ErrorAnalyticsService.ts +++ b/apps/website/lib/services/error/ErrorAnalyticsService.ts @@ -29,6 +29,10 @@ export interface ErrorStats { }; } +export function getErrorAnalyticsStats(): ErrorStats { + return ErrorAnalyticsService.getErrorAnalyticsStats(); +} + export class ErrorAnalyticsService implements Service { static getErrorAnalyticsStats(): ErrorStats { const globalHandler = getGlobalErrorHandler(); diff --git a/apps/website/lib/services/leagues/ProfileLeaguesService.ts b/apps/website/lib/services/leagues/ProfileLeaguesService.ts index 748af9bc5..5a81b3dee 100644 --- a/apps/website/lib/services/leagues/ProfileLeaguesService.ts +++ b/apps/website/lib/services/leagues/ProfileLeaguesService.ts @@ -1,4 +1,6 @@ -import { ApiClient } from '@/lib/api'; +import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient'; +import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger'; +import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter'; import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl'; import { Result } from '@/lib/contracts/Result'; import { Service, type DomainError } from '@/lib/contracts/services/Service'; @@ -28,9 +30,11 @@ export class ProfileLeaguesService implements Service { async getProfileLeagues(driverId: string): Promise> { try { const baseUrl = getWebsiteApiBaseUrl(); - const apiClient = new ApiClient(baseUrl); + const logger = new ConsoleLogger(); + const errorReporter = new ConsoleErrorReporter(); + const leaguesApiClient = new LeaguesApiClient(baseUrl, errorReporter, logger); - const leaguesDto = await apiClient.leagues.getAllWithCapacity(); + const leaguesDto = await leaguesApiClient.getAllWithCapacity(); if (!leaguesDto?.leagues) { return Result.err({ type: 'notFound', message: 'Leagues not found' }); @@ -40,7 +44,7 @@ export class ProfileLeaguesService implements Service { const leagueMemberships = await Promise.all( leaguesDto.leagues.map(async (league) => { try { - const membershipsDto = await apiClient.leagues.getMemberships(league.id); + const membershipsDto = await leaguesApiClient.getMemberships(league.id); let memberships: MembershipDTO[] = []; if (membershipsDto && typeof membershipsDto === 'object') { diff --git a/apps/website/lib/services/races/RaceService.ts b/apps/website/lib/services/races/RaceService.ts index db75847c9..c7eb112f5 100644 --- a/apps/website/lib/services/races/RaceService.ts +++ b/apps/website/lib/services/races/RaceService.ts @@ -41,6 +41,15 @@ export class RaceService implements Service { } } + async getAllRacesPageData(): Promise> { + try { + const data = await this.apiClient.getPageData(); + return Result.ok(data); + } catch (error: unknown) { + return Result.err(this.mapError(error, 'Failed to fetch all races page data')); + } + } + async findByLeagueId(leagueId: string): Promise> { try { const result = await this.apiClient.getPageData(leagueId); diff --git a/apps/website/lib/services/teams/TeamService.ts b/apps/website/lib/services/teams/TeamService.ts index 12826645a..b110be229 100644 --- a/apps/website/lib/services/teams/TeamService.ts +++ b/apps/website/lib/services/teams/TeamService.ts @@ -1,5 +1,12 @@ import { TeamsApiClient } from '@/lib/api/teams/TeamsApiClient'; import type { TeamListItemDTO } from '@/lib/types/generated/TeamListItemDTO'; +import type { CreateTeamInputDTO } from '@/lib/types/generated/CreateTeamInputDTO'; +import type { CreateTeamOutputDTO } from '@/lib/types/generated/CreateTeamOutputDTO'; +import type { UpdateTeamInputDTO } from '@/lib/types/generated/UpdateTeamInputDTO'; +import type { UpdateTeamOutputDTO } from '@/lib/types/generated/UpdateTeamOutputDTO'; +import type { GetDriverTeamOutputDTO } from '@/lib/types/generated/GetDriverTeamOutputDTO'; +import type { GetTeamMembershipOutputDTO } from '@/lib/types/generated/GetTeamMembershipOutputDTO'; +import type { GetTeamJoinRequestsOutputDTO } from '@/lib/types/generated/GetTeamJoinRequestsOutputDTO'; import { TeamMemberViewModel } from '@/lib/view-models/TeamMemberViewModel'; import { Result } from '@/lib/contracts/Result'; import { DomainError, Service } from '@/lib/contracts/services/Service'; @@ -37,7 +44,7 @@ export class TeamService implements Service { } } - async getTeamDetails(teamId: string, _: string): Promise> { + async getTeamDetails(teamId: string, _: string): Promise> { try { const result = await this.apiClient.getDetails(teamId); if (!result) { @@ -58,23 +65,48 @@ export class TeamService implements Service { } } - async getTeamJoinRequests(_: string): Promise> { - return Result.err({ type: 'notImplemented', message: 'getTeamJoinRequests' }); + async getTeamJoinRequests(teamId: string): Promise> { + try { + const result = await this.apiClient.getJoinRequests(teamId); + return Result.ok(result); + } catch (error: unknown) { + return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to fetch team join requests' }); + } } - async createTeam(__: unknown): Promise> { - return Result.err({ type: 'notImplemented', message: 'createTeam' }); + async createTeam(input: CreateTeamInputDTO): Promise> { + try { + const result = await this.apiClient.create(input); + return Result.ok(result); + } catch (error: unknown) { + return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to create team' }); + } } - async updateTeam(_: string, __: unknown): Promise> { - return Result.err({ type: 'notImplemented', message: 'updateTeam' }); + async updateTeam(teamId: string, input: UpdateTeamInputDTO): Promise> { + try { + const result = await this.apiClient.update(teamId, input); + return Result.ok(result); + } catch (error: unknown) { + return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to update team' }); + } } - async getDriverTeam(_: string): Promise> { - return Result.err({ type: 'notImplemented', message: 'getDriverTeam' }); + async getDriverTeam(driverId: string): Promise> { + try { + const result = await this.apiClient.getDriverTeam(driverId); + return Result.ok(result); + } catch (error: unknown) { + return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to fetch driver team' }); + } } - async getMembership(_: string, __: string): Promise> { - return Result.err({ type: 'notImplemented', message: 'getMembership' }); + async getMembership(teamId: string, driverId: string): Promise> { + try { + const result = await this.apiClient.getMembership(teamId, driverId); + return Result.ok(result); + } catch (error: unknown) { + return Result.err({ type: 'unknown', message: (error as Error).message || 'Failed to fetch team membership' }); + } } } diff --git a/apps/website/lib/view-data/SponsorDashboardViewData.ts b/apps/website/lib/view-data/SponsorDashboardViewData.ts index 957c6a35d..bd5e7446a 100644 --- a/apps/website/lib/view-data/SponsorDashboardViewData.ts +++ b/apps/website/lib/view-data/SponsorDashboardViewData.ts @@ -18,6 +18,18 @@ export interface SponsorDashboardViewData { activeSponsorships: number; formattedTotalInvestment: string; costPerThousandViews: string; - upcomingRenewals: unknown[]; - recentActivity: unknown[]; -} \ No newline at end of file + upcomingRenewals: Array<{ + id: string; + type: 'league' | 'team' | 'driver' | 'race' | 'platform'; + name: string; + formattedRenewDate: string; + formattedPrice: string; + }>; + recentActivity: Array<{ + id: string; + message: string; + time: string; + typeColor: string; + formattedImpressions?: string | null; + }>; +} diff --git a/apps/website/lib/view-data/races/RacesAllViewData.ts b/apps/website/lib/view-data/races/RacesAllViewData.ts deleted file mode 100644 index f4a0bb3e2..000000000 --- a/apps/website/lib/view-data/races/RacesAllViewData.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Races All View Data - * - * ViewData for the all races page template. - * JSON-serializable, template-ready data structure. - */ - -export interface RacesAllRace { - id: string; - track: string; - car: string; - scheduledAt: string; - status: 'scheduled' | 'running' | 'completed' | 'cancelled'; - sessionType: string; - leagueId?: string; - leagueName?: string; - strengthOfField?: number; -} - -export interface RacesAllViewData { - races: RacesAllRace[]; -} \ No newline at end of file diff --git a/apps/website/lib/view-data/races/RacesViewData.ts b/apps/website/lib/view-data/races/RacesViewData.ts deleted file mode 100644 index e6bdb3017..000000000 --- a/apps/website/lib/view-data/races/RacesViewData.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Races View Data - * - * ViewData for the main races page template. - * JSON-serializable, template-ready data structure. - */ - -export interface RacesRace { - id: string; - track: string; - car: string; - scheduledAt: string; - status: 'scheduled' | 'running' | 'completed' | 'cancelled'; - sessionType: string; - leagueId?: string; - leagueName?: string; - strengthOfField?: number; - isUpcoming: boolean; - isLive: boolean; - isPast: boolean; -} - -export interface RacesViewData { - races: RacesRace[]; - totalCount: number; - scheduledRaces: RacesRace[]; - runningRaces: RacesRace[]; - completedRaces: RacesRace[]; -} \ No newline at end of file