website refactor

This commit is contained in:
2026-01-14 10:51:05 +01:00
parent 4522d41aef
commit 0d89ad027e
291 changed files with 6887 additions and 3685 deletions

View File

@@ -1,142 +1,47 @@
'use client';
import { useState, useEffect } from 'react';
import { useParams, useRouter } from 'next/navigation';
import { notFound } from 'next/navigation';
import { PageWrapper } from '@/components/shared/state/PageWrapper';
import { RaceStewardingTemplate, StewardingTab } from '@/templates/RaceStewardingTemplate';
import { RacesApiClient } from '@/lib/api/races/RacesApiClient';
import { ProtestsApiClient } from '@/lib/api/protests/ProtestsApiClient';
import { PenaltiesApiClient } from '@/lib/api/penalties/PenaltiesApiClient';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { useLeagueMemberships } from "@/lib/hooks/league/useLeagueMemberships";
import { useEffectiveDriverId } from "@/lib/hooks/useEffectiveDriverId";
import { LeagueRoleUtility } from '@/lib/utilities/LeagueRoleUtility';
import { RaceStewardingPageQuery } from '@/lib/page-queries/races/RaceStewardingPageQuery';
import { Gavel } from 'lucide-react';
import { useState } from 'react';
// Define the view model structure locally to avoid type issues
interface RaceStewardingViewModel {
race: any;
league: any;
protests: any[];
penalties: any[];
driverMap: Record<string, any>;
pendingProtests: any[];
resolvedProtests: any[];
pendingCount: number;
resolvedCount: number;
penaltiesCount: number;
interface RaceStewardingPageProps {
params: {
id: string;
};
}
export default function RaceStewardingPage() {
const router = useRouter();
const params = useParams();
const raceId = params.id as string;
const currentDriverId = useEffectiveDriverId() || '';
export default function RaceStewardingPage({ params }: RaceStewardingPageProps) {
const raceId = params.id;
const [activeTab, setActiveTab] = useState<StewardingTab>('pending');
if (!raceId) {
notFound();
}
// Data state
const [pageData, setPageData] = useState<RaceStewardingViewModel | null>(null);
const [pageData, setPageData] = useState<any>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
// UI State
const [activeTab, setActiveTab] = useState<StewardingTab>('pending');
// Fetch data on mount and when raceId/currentDriverId changes
useEffect(() => {
async function fetchData() {
if (!raceId) return;
try {
setIsLoading(true);
setError(null);
// Manual wiring: create dependencies
const baseUrl = getWebsiteApiBaseUrl();
const logger = new ConsoleLogger();
const errorReporter = new EnhancedErrorReporter(logger, {
showUserNotifications: true,
logToConsole: true,
reportToExternal: process.env.NODE_ENV === 'production',
});
// Create API clients
const racesApiClient = new RacesApiClient(baseUrl, errorReporter, logger);
const protestsApiClient = new ProtestsApiClient(baseUrl, errorReporter, logger);
const penaltiesApiClient = new PenaltiesApiClient(baseUrl, errorReporter, logger);
// Fetch data in parallel
const [raceDetail, protests, penalties] = await Promise.all([
racesApiClient.getDetail(raceId, currentDriverId),
protestsApiClient.getRaceProtests(raceId),
penaltiesApiClient.getRacePenalties(raceId),
]);
// Transform data to match view model structure
const data: RaceStewardingViewModel = {
race: raceDetail.race,
league: raceDetail.league,
protests: protests.protests.map(p => ({
id: p.id,
protestingDriverId: p.protestingDriverId,
accusedDriverId: p.accusedDriverId,
incident: {
lap: p.lap,
description: p.description,
},
filedAt: p.filedAt,
status: p.status,
})),
penalties: penalties.penalties,
driverMap: { ...protests.driverMap, ...penalties.driverMap },
pendingProtests: [],
resolvedProtests: [],
pendingCount: 0,
resolvedCount: 0,
penaltiesCount: 0,
};
// Calculate derived properties
data.pendingProtests = data.protests.filter(p => p.status === 'pending' || p.status === 'under_review');
data.resolvedProtests = data.protests.filter(p =>
p.status === 'upheld' ||
p.status === 'dismissed' ||
p.status === 'withdrawn'
);
data.pendingCount = data.pendingProtests.length;
data.resolvedCount = data.resolvedProtests.length;
data.penaltiesCount = data.penalties.length;
if (data) {
setPageData(data);
} else {
setPageData(null);
}
} catch (err) {
setError(err instanceof Error ? err : new Error('Failed to fetch stewarding data'));
setPageData(null);
} finally {
setIsLoading(false);
}
}
// Fetch function
const fetchData = async () => {
setIsLoading(true);
setError(null);
fetchData();
}, [raceId, currentDriverId]);
// Fetch membership
const { data: membershipsData } = useLeagueMemberships(pageData?.league?.id || '', currentDriverId || '');
const currentMembership = membershipsData?.members.find(m => m.driverId === currentDriverId);
const isAdmin = currentMembership ? LeagueRoleUtility.isLeagueAdminOrHigherRole(currentMembership.role) : false;
// Actions
const handleBack = () => {
router.push(`/races/${raceId}`);
};
const handleReviewProtest = (protestId: string) => {
// Navigate to protest review page
router.push(`/leagues/${pageData?.league?.id}/stewarding/protests/${protestId}`);
try {
const result = await RaceStewardingPageQuery.execute({ raceId });
if (result.isErr()) {
throw new Error('Failed to fetch stewarding data');
}
setPageData(result.unwrap());
} catch (err) {
setError(err instanceof Error ? err : new Error('Unknown error'));
} finally {
setIsLoading(false);
}
};
// Transform data for template
@@ -152,74 +57,14 @@ export default function RaceStewardingPage() {
penaltiesCount: pageData.penaltiesCount,
} : undefined;
const retry = async () => {
try {
setIsLoading(true);
setError(null);
// Manual wiring: create dependencies
const baseUrl = getWebsiteApiBaseUrl();
const logger = new ConsoleLogger();
const errorReporter = new EnhancedErrorReporter(logger, {
showUserNotifications: true,
logToConsole: true,
reportToExternal: process.env.NODE_ENV === 'production',
});
// Actions
const handleBack = () => {
window.history.back();
};
// Create API clients
const racesApiClient = new RacesApiClient(baseUrl, errorReporter, logger);
const protestsApiClient = new ProtestsApiClient(baseUrl, errorReporter, logger);
const penaltiesApiClient = new PenaltiesApiClient(baseUrl, errorReporter, logger);
// Fetch data in parallel
const [raceDetail, protests, penalties] = await Promise.all([
racesApiClient.getDetail(raceId, currentDriverId),
protestsApiClient.getRaceProtests(raceId),
penaltiesApiClient.getRacePenalties(raceId),
]);
// Transform data to match view model structure
const data: RaceStewardingViewModel = {
race: raceDetail.race,
league: raceDetail.league,
protests: protests.protests.map(p => ({
id: p.id,
protestingDriverId: p.protestingDriverId,
accusedDriverId: p.accusedDriverId,
incident: {
lap: p.lap,
description: p.description,
},
filedAt: p.filedAt,
status: p.status,
})),
penalties: penalties.penalties,
driverMap: { ...protests.driverMap, ...penalties.driverMap },
pendingProtests: [],
resolvedProtests: [],
pendingCount: 0,
resolvedCount: 0,
penaltiesCount: 0,
};
// Calculate derived properties
data.pendingProtests = data.protests.filter(p => p.status === 'pending' || p.status === 'under_review');
data.resolvedProtests = data.protests.filter(p =>
p.status === 'upheld' ||
p.status === 'dismissed' ||
p.status === 'withdrawn'
);
data.pendingCount = data.pendingProtests.length;
data.resolvedCount = data.resolvedProtests.length;
data.penaltiesCount = data.penalties.length;
if (data) {
setPageData(data);
}
} catch (err) {
setError(err instanceof Error ? err : new Error('Failed to fetch stewarding data'));
} finally {
setIsLoading(false);
const handleReviewProtest = (protestId: string) => {
if (templateData?.league?.id) {
window.location.href = `/leagues/${templateData.league.id}/stewarding/protests/${protestId}`;
}
};
@@ -228,15 +73,15 @@ export default function RaceStewardingPage() {
data={pageData}
isLoading={isLoading}
error={error}
retry={retry}
Template={({ data }) => (
retry={fetchData}
Template={({ data: _data }) => (
<RaceStewardingTemplate
stewardingData={templateData}
isLoading={false}
error={null}
onBack={handleBack}
onReviewProtest={handleReviewProtest}
isAdmin={isAdmin}
isAdmin={false}
activeTab={activeTab}
setActiveTab={setActiveTab}
/>