217 lines
6.6 KiB
TypeScript
217 lines
6.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { useParams, useRouter } from 'next/navigation';
|
|
import { RaceDetailTemplate } from '@/templates/RaceDetailTemplate';
|
|
import {
|
|
useRaceDetail,
|
|
useRegisterForRace,
|
|
useWithdrawFromRace,
|
|
useCancelRace,
|
|
useCompleteRace,
|
|
useReopenRace
|
|
} from '@/hooks/useRaceService';
|
|
import { useLeagueMembership } from '@/hooks/useLeagueMembershipService';
|
|
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
|
|
import { LeagueMembershipUtility } from '@/lib/utilities/LeagueMembershipUtility';
|
|
|
|
export function RaceDetailInteractive() {
|
|
const router = useRouter();
|
|
const params = useParams();
|
|
const raceId = params.id as string;
|
|
const currentDriverId = useEffectiveDriverId();
|
|
|
|
// Fetch data
|
|
const { data: viewModel, isLoading, error } = useRaceDetail(raceId, currentDriverId);
|
|
const { data: membership } = useLeagueMembership(viewModel?.league?.id || '', currentDriverId);
|
|
|
|
// UI State
|
|
const [showProtestModal, setShowProtestModal] = useState(false);
|
|
const [showEndRaceModal, setShowEndRaceModal] = useState(false);
|
|
|
|
// Mutations
|
|
const registerMutation = useRegisterForRace();
|
|
const withdrawMutation = useWithdrawFromRace();
|
|
const cancelMutation = useCancelRace();
|
|
const completeMutation = useCompleteRace();
|
|
const reopenMutation = useReopenRace();
|
|
|
|
// Determine if user is owner/admin
|
|
const isOwnerOrAdmin = membership
|
|
? LeagueMembershipUtility.isOwnerOrAdmin(viewModel?.league?.id || '', currentDriverId)
|
|
: false;
|
|
|
|
// Actions
|
|
const handleBack = () => {
|
|
router.back();
|
|
};
|
|
|
|
const handleRegister = async () => {
|
|
const race = viewModel?.race;
|
|
const league = viewModel?.league;
|
|
if (!race || !league) return;
|
|
|
|
const confirmed = window.confirm(
|
|
`Register for ${race.track}?\n\nYou'll be added to the entry list for this race.`,
|
|
);
|
|
|
|
if (!confirmed) return;
|
|
|
|
try {
|
|
await registerMutation.mutateAsync({ raceId: race.id, leagueId: league.id, driverId: currentDriverId });
|
|
} catch (err) {
|
|
alert(err instanceof Error ? err.message : 'Failed to register for race');
|
|
}
|
|
};
|
|
|
|
const handleWithdraw = async () => {
|
|
const race = viewModel?.race;
|
|
const league = viewModel?.league;
|
|
if (!race || !league) return;
|
|
|
|
const confirmed = window.confirm(
|
|
'Withdraw from this race?\n\nYou can register again later if you change your mind.',
|
|
);
|
|
|
|
if (!confirmed) return;
|
|
|
|
try {
|
|
await withdrawMutation.mutateAsync({ raceId: race.id, driverId: currentDriverId });
|
|
} catch (err) {
|
|
alert(err instanceof Error ? err.message : 'Failed to withdraw from race');
|
|
}
|
|
};
|
|
|
|
const handleCancel = async () => {
|
|
const race = viewModel?.race;
|
|
if (!race || race.status !== 'scheduled') return;
|
|
|
|
const confirmed = window.confirm(
|
|
'Are you sure you want to cancel this race? This action cannot be undone.',
|
|
);
|
|
|
|
if (!confirmed) return;
|
|
|
|
try {
|
|
await cancelMutation.mutateAsync(race.id);
|
|
} catch (err) {
|
|
alert(err instanceof Error ? err.message : 'Failed to cancel race');
|
|
}
|
|
};
|
|
|
|
const handleReopen = async () => {
|
|
const race = viewModel?.race;
|
|
if (!race || !viewModel?.canReopenRace) return;
|
|
|
|
const confirmed = window.confirm(
|
|
'Re-open this race? This will allow re-registration and re-running. Results will be archived.',
|
|
);
|
|
|
|
if (!confirmed) return;
|
|
|
|
try {
|
|
await reopenMutation.mutateAsync(race.id);
|
|
} catch (err) {
|
|
alert(err instanceof Error ? err.message : 'Failed to re-open race');
|
|
}
|
|
};
|
|
|
|
const handleEndRace = async () => {
|
|
const race = viewModel?.race;
|
|
if (!race) return;
|
|
|
|
setShowEndRaceModal(true);
|
|
};
|
|
|
|
const handleFileProtest = () => {
|
|
setShowProtestModal(true);
|
|
};
|
|
|
|
const handleResultsClick = () => {
|
|
router.push(`/races/${raceId}/results`);
|
|
};
|
|
|
|
const handleStewardingClick = () => {
|
|
router.push(`/races/${raceId}/stewarding`);
|
|
};
|
|
|
|
const handleLeagueClick = (leagueId: string) => {
|
|
router.push(`/leagues/${leagueId}`);
|
|
};
|
|
|
|
const handleDriverClick = (driverId: string) => {
|
|
router.push(`/drivers/${driverId}`);
|
|
};
|
|
|
|
// Transform data for template - handle null values
|
|
const templateViewModel = viewModel && viewModel.race ? {
|
|
race: {
|
|
id: viewModel.race.id,
|
|
track: viewModel.race.track,
|
|
car: viewModel.race.car,
|
|
scheduledAt: viewModel.race.scheduledAt,
|
|
status: viewModel.race.status as 'scheduled' | 'running' | 'completed' | 'cancelled',
|
|
sessionType: viewModel.race.sessionType,
|
|
},
|
|
league: viewModel.league ? {
|
|
id: viewModel.league.id,
|
|
name: viewModel.league.name,
|
|
description: viewModel.league.description || undefined,
|
|
settings: viewModel.league.settings as { maxDrivers: number; qualifyingFormat: string },
|
|
} : undefined,
|
|
entryList: viewModel.entryList.map(entry => ({
|
|
id: entry.id,
|
|
name: entry.name,
|
|
avatarUrl: entry.avatarUrl,
|
|
country: entry.country,
|
|
rating: entry.rating,
|
|
isCurrentUser: entry.isCurrentUser,
|
|
})),
|
|
registration: {
|
|
isUserRegistered: viewModel.registration.isUserRegistered,
|
|
canRegister: viewModel.registration.canRegister,
|
|
},
|
|
userResult: viewModel.userResult ? {
|
|
position: viewModel.userResult.position,
|
|
startPosition: viewModel.userResult.startPosition,
|
|
positionChange: viewModel.userResult.positionChange,
|
|
incidents: viewModel.userResult.incidents,
|
|
isClean: viewModel.userResult.isClean,
|
|
isPodium: viewModel.userResult.isPodium,
|
|
ratingChange: viewModel.userResult.ratingChange,
|
|
} : undefined,
|
|
canReopenRace: viewModel.canReopenRace,
|
|
} : undefined;
|
|
|
|
return (
|
|
<RaceDetailTemplate
|
|
viewModel={templateViewModel}
|
|
isLoading={isLoading}
|
|
error={error}
|
|
onBack={handleBack}
|
|
onRegister={handleRegister}
|
|
onWithdraw={handleWithdraw}
|
|
onCancel={handleCancel}
|
|
onReopen={handleReopen}
|
|
onEndRace={handleEndRace}
|
|
onFileProtest={handleFileProtest}
|
|
onResultsClick={handleResultsClick}
|
|
onStewardingClick={handleStewardingClick}
|
|
onLeagueClick={handleLeagueClick}
|
|
onDriverClick={handleDriverClick}
|
|
currentDriverId={currentDriverId}
|
|
isOwnerOrAdmin={isOwnerOrAdmin}
|
|
showProtestModal={showProtestModal}
|
|
setShowProtestModal={setShowProtestModal}
|
|
showEndRaceModal={showEndRaceModal}
|
|
setShowEndRaceModal={setShowEndRaceModal}
|
|
mutationLoading={{
|
|
register: registerMutation.isPending,
|
|
withdraw: withdrawMutation.isPending,
|
|
cancel: cancelMutation.isPending,
|
|
reopen: reopenMutation.isPending,
|
|
complete: completeMutation.isPending,
|
|
}}
|
|
/>
|
|
);
|
|
} |