diff --git a/apps/website/app/onboarding/page.tsx b/apps/website/app/onboarding/page.tsx index 88b895114..2a2680c75 100644 --- a/apps/website/app/onboarding/page.tsx +++ b/apps/website/app/onboarding/page.tsx @@ -1,59 +1,25 @@ 'use client'; -import { useEffect, useState } from 'react'; -import { useRouter } from 'next/navigation'; import { Loader2 } from 'lucide-react'; +import { useRouter } from 'next/navigation'; import OnboardingWizard from '@/components/onboarding/OnboardingWizard'; +import { useCurrentDriver } from '@/hooks/useDriverService'; import { useAuth } from '@/lib/auth/AuthContext'; -import { useServices } from '@/lib/services/ServiceProvider'; export default function OnboardingPage() { const router = useRouter(); const { session } = useAuth(); - const { driverService } = useServices(); - const [checking, setChecking] = useState(true); + const { data: driver, isLoading } = useCurrentDriver(); - useEffect(() => { - // If user is not authenticated, redirect to login - if (!session) { - router.replace('/auth/login?returnTo=/onboarding'); - return; - } + // If user is not authenticated, redirect to login + if (!session) { + router.replace('/auth/login?returnTo=/onboarding'); + return null; + } - let cancelled = false; - - const checkOnboarding = async () => { - try { - const driver = await driverService.getCurrentDriver(); - - if (cancelled) return; - - // If driver profile exists, onboarding is complete – go to dashboard - if (driver) { - router.replace('/dashboard'); - return; - } - - // Otherwise allow onboarding wizard to render - setChecking(false); - } catch { - // On error, allow onboarding to proceed so user isn't blocked - if (!cancelled) { - setChecking(false); - } - } - }; - - checkOnboarding(); - - return () => { - cancelled = true; - }; - }, [session, driverService, router]); - - // Show loading while checking auth/onboarding status - if (checking) { + // Show loading while checking driver data + if (isLoading) { return (
@@ -61,6 +27,12 @@ export default function OnboardingPage() { ); } + // If driver profile exists, onboarding is complete – go to dashboard + if (driver) { + router.replace('/dashboard'); + return null; + } + return (
diff --git a/apps/website/app/races/[id]/page.tsx b/apps/website/app/races/[id]/page.tsx index c46fa49fc..7683f94a7 100644 --- a/apps/website/app/races/[id]/page.tsx +++ b/apps/website/app/races/[id]/page.tsx @@ -8,7 +8,8 @@ import Button from '@/components/ui/Button'; import Card from '@/components/ui/Card'; import Heading from '@/components/ui/Heading'; import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId'; -import { useServices } from '@/lib/services/ServiceProvider'; +import { useRaceDetail, useRegisterForRace, useWithdrawFromRace, useCancelRace, useCompleteRace, useReopenRace } from '@/hooks/useRaceService'; +import { useLeagueMembership } from '@/hooks/useLeagueMembershipService'; import { LeagueMembershipUtility } from '@/lib/utilities/LeagueMembershipUtility'; import type { RaceDetailViewModel } from '@/lib/view-models/RaceDetailViewModel'; import { @@ -37,54 +38,29 @@ export default function RaceDetailPage() { const router = useRouter(); const params = useParams(); const raceId = params.id as string; - const { raceService, leagueMembershipService } = useServices(); + const currentDriverId = useEffectiveDriverId(); + const isSponsorMode = useSponsorMode(); + + const { data: viewModel, isLoading: loading, error } = useRaceDetail(raceId, currentDriverId); + const { data: membership } = useLeagueMembership(viewModel?.league?.id || '', currentDriverId); - const [viewModel, setViewModel] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [cancelling, setCancelling] = useState(false); - const [registering, setRegistering] = useState(false); - const [reopening, setReopening] = useState(false); const [ratingChange, setRatingChange] = useState(null); const [animatedRatingChange, setAnimatedRatingChange] = useState(0); const [showProtestModal, setShowProtestModal] = useState(false); const [showEndRaceModal, setShowEndRaceModal] = useState(false); - const [membership, setMembership] = useState(null); - const currentDriverId = useEffectiveDriverId(); - const isSponsorMode = useSponsorMode(); - - const loadRaceData = async () => { - setLoading(true); - setError(null); - try { - const vm = await raceService.getRaceDetail(raceId, currentDriverId); - setViewModel(vm); - - // Fetch league membership for admin controls - if (vm.league) { - await leagueMembershipService.fetchLeagueMemberships(vm.league.id); - const leagueMembership = leagueMembershipService.getMembership(vm.league.id, currentDriverId); - setMembership(leagueMembership); - } - - const userResultRatingChange = vm.userResult?.ratingChange ?? null; - setRatingChange(userResultRatingChange); - if (userResultRatingChange === null) { - setAnimatedRatingChange(0); - } - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load race'); - setViewModel(null); - } finally { - setLoading(false); - } - }; + const registerMutation = useRegisterForRace(); + const withdrawMutation = useWithdrawFromRace(); + const cancelMutation = useCancelRace(); + const completeMutation = useCompleteRace(); + const reopenMutation = useReopenRace(); + // Set rating change when viewModel changes useEffect(() => { - loadRaceData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [raceId]); + if (viewModel?.userResult?.ratingChange !== undefined) { + setRatingChange(viewModel.userResult.ratingChange); + } + }, [viewModel?.userResult?.ratingChange]); // Animate rating change when it changes useEffect(() => { diff --git a/apps/website/app/races/[id]/results/page.tsx b/apps/website/app/races/[id]/results/page.tsx index 433072b6e..a0d2c382b 100644 --- a/apps/website/app/races/[id]/results/page.tsx +++ b/apps/website/app/races/[id]/results/page.tsx @@ -7,7 +7,8 @@ import ResultsTable from '@/components/races/ResultsTable'; import Button from '@/components/ui/Button'; import Card from '@/components/ui/Card'; import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId'; -import { useServices } from '@/lib/services/ServiceProvider'; +import { useRaceResultsDetail, useRaceWithSOF } from '@/hooks/useRaceService'; +import { useLeagueMembership } from '@/hooks/useLeagueMembershipService'; import { LeagueRoleUtility } from '@/lib/utilities/LeagueRoleUtility'; import type { RaceResultsDetailViewModel } from '@/lib/view-models'; import { ArrowLeft, Calendar, Trophy, Users, Zap } from 'lucide-react'; @@ -19,54 +20,18 @@ export default function RaceResultsPage() { const params = useParams(); const raceId = params.id as string; const currentDriverId = useEffectiveDriverId(); - const { raceResultsService, leagueMembershipService } = useServices(); - const [raceData, setRaceData] = useState(null); - const [raceSOF, setRaceSOF] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + const { data: raceData, isLoading: loading, error } = useRaceResultsDetail(raceId, currentDriverId); + const { data: sofData } = useRaceWithSOF(raceId); + const { data: membership } = useLeagueMembership(raceData?.league?.id || '', currentDriverId); + const [importing, setImporting] = useState(false); const [importSuccess, setImportSuccess] = useState(false); - const [isAdmin, setIsAdmin] = useState(false); const [showQuickPenaltyModal, setShowQuickPenaltyModal] = useState(false); const [preSelectedDriver, setPreSelectedDriver] = useState<{ id: string; name: string } | undefined>(undefined); - const loadData = async () => { - try { - const raceData = await raceResultsService.getResultsDetail(raceId, currentDriverId); - setRaceData(raceData); - setError(null); - - try { - const sofData = await raceResultsService.getWithSOF(raceId); - setRaceSOF(sofData.strengthOfField); - } catch (sofErr) { - console.error('Failed to load SOF:', sofErr); - } - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load race data'); - setRaceData(null); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - loadData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [raceId]); - - useEffect(() => { - const leagueId = raceData?.league?.id; - if (leagueId && currentDriverId) { - const checkAdmin = async () => { - await leagueMembershipService.fetchLeagueMemberships(leagueId); - const membership = leagueMembershipService.getMembership(leagueId, currentDriverId); - setIsAdmin(membership ? LeagueRoleUtility.isLeagueAdminOrHigherRole(membership.role) : false); - }; - checkAdmin(); - } - }, [raceData?.league?.id, currentDriverId, leagueMembershipService]); + const raceSOF = sofData?.strengthOfField || null; + const isAdmin = membership ? LeagueRoleUtility.isLeagueAdminOrHigherRole(membership.role) : false; const handleImportSuccess = async (importedResults: any[]) => { setImporting(true); diff --git a/apps/website/app/races/[id]/stewarding/page.tsx b/apps/website/app/races/[id]/stewarding/page.tsx index 9e170c97d..6c2218328 100644 --- a/apps/website/app/races/[id]/stewarding/page.tsx +++ b/apps/website/app/races/[id]/stewarding/page.tsx @@ -4,8 +4,8 @@ import Breadcrumbs from '@/components/layout/Breadcrumbs'; import Button from '@/components/ui/Button'; import Card from '@/components/ui/Card'; import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId'; -import { useServices } from '@/lib/services/ServiceProvider'; -import { RaceStewardingViewModel } from '@/lib/view-models/RaceStewardingViewModel'; +import { useRaceStewardingData } from '@/hooks/useRaceStewardingService'; +import { useLeagueMembership } from '@/hooks/useLeagueMembershipService'; import { LeagueRoleUtility } from '@/lib/utilities/LeagueRoleUtility'; import { AlertCircle, @@ -20,42 +20,20 @@ import { } from 'lucide-react'; import Link from 'next/link'; import { useParams, useRouter } from 'next/navigation'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; export default function RaceStewardingPage() { const params = useParams(); const router = useRouter(); - const { raceStewardingService, leagueMembershipService } = useServices(); const raceId = params.id as string; const currentDriverId = useEffectiveDriverId(); - const [stewardingData, setStewardingData] = useState(null); - const [loading, setLoading] = useState(true); - const [isAdmin, setIsAdmin] = useState(false); + const { data: stewardingData, isLoading: loading } = useRaceStewardingData(raceId, currentDriverId); + const { data: membership } = useLeagueMembership(stewardingData?.league?.id || '', currentDriverId); + const [activeTab, setActiveTab] = useState<'pending' | 'resolved' | 'penalties'>('pending'); - useEffect(() => { - async function loadData() { - setLoading(true); - try { - const data = await raceStewardingService.getRaceStewardingData(raceId, currentDriverId); - setStewardingData(data); - - if (data.league?.id) { - const membership = await leagueMembershipService.getMembership(data.league.id, currentDriverId); - setIsAdmin(membership ? LeagueRoleUtility.isLeagueAdminOrHigherRole(membership.role) : false); - } else { - setIsAdmin(false); - } - } catch (err) { - console.error('Failed to load data:', err); - } finally { - setLoading(false); - } - } - - loadData(); - }, [raceId, currentDriverId, raceStewardingService, leagueMembershipService]); + const isAdmin = membership ? LeagueRoleUtility.isLeagueAdminOrHigherRole(membership.role) : false; const pendingProtests = stewardingData?.pendingProtests ?? []; const resolvedProtests = stewardingData?.resolvedProtests ?? []; @@ -153,7 +131,7 @@ export default function RaceStewardingPage() {