website refactor

This commit is contained in:
2026-01-19 01:45:41 +01:00
parent edc4cd7f21
commit 489c5f7858
4 changed files with 86 additions and 83 deletions

View File

@@ -1,7 +1,7 @@
'use client'; 'use client';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { PageWrapper } from '@/ui/PageWrapper'; import { PageWrapper } from '@/components/shared/state/PageWrapper';
import { RaceStewardingTemplate, type StewardingTab } from '@/templates/RaceStewardingTemplate'; import { RaceStewardingTemplate, type StewardingTab } from '@/templates/RaceStewardingTemplate';
import { RaceStewardingPageQuery } from '@/lib/page-queries/races/RaceStewardingPageQuery'; import { RaceStewardingPageQuery } from '@/lib/page-queries/races/RaceStewardingPageQuery';
import { type RaceStewardingViewData } from '@/lib/view-data/races/RaceStewardingViewData'; import { type RaceStewardingViewData } from '@/lib/view-data/races/RaceStewardingViewData';

View File

@@ -7,7 +7,7 @@ import {
unpublishScheduleAction, unpublishScheduleAction,
updateRaceAction updateRaceAction
} from '@/app/actions/leagueScheduleActions'; } from '@/app/actions/leagueScheduleActions';
import { PageWrapper } from '@/ui/PageWrapper'; import { PageWrapper } from '@/components/shared/state/PageWrapper';
import { ConfirmDialog } from '@/ui/ConfirmDialog'; import { ConfirmDialog } from '@/ui/ConfirmDialog';
import { import {
useLeagueAdminSchedule, useLeagueAdminSchedule,

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { UploadDropzone } from '@/components/media/UploadDropzone'; import { UploadDropzone } from '@/ui/UploadDropzone';
import { routes } from '@/lib/routing/RouteConfig'; import { routes } from '@/lib/routing/RouteConfig';
import { Box } from '@/ui/Box'; import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button'; import { Button } from '@/ui/Button';
@@ -78,7 +78,9 @@ export function ProfileLiveryUploadPageClient() {
{previewUrl ? ( {previewUrl ? (
<Box display="flex" flexDirection="col" gap={6}> <Box display="flex" flexDirection="col" gap={6}>
<MediaPreviewCard <MediaPreviewCard
type="image"
src={previewUrl} src={previewUrl}
alt={selectedFile?.name || 'Livery preview'}
title={selectedFile?.name} title={selectedFile?.name}
subtitle="Preview" subtitle="Preview"
aspectRatio="16/9" aspectRatio="16/9"

View File

@@ -34,7 +34,7 @@ import { useEffect, useMemo, useState } from 'react';
// Shared state components // Shared state components
import { LoadingWrapper } from '@/ui/LoadingWrapper'; import { LoadingWrapper } from '@/ui/LoadingWrapper';
import { StateContainer } from '@/ui/StateContainer'; import { StateContainer } from '@/components/shared/state/StateContainer';
import { useLeagueAdminStatus } from "@/hooks/league/useLeagueAdminStatus"; import { useLeagueAdminStatus } from "@/hooks/league/useLeagueAdminStatus";
import { useProtestDetail } from "@/hooks/league/useProtestDetail"; import { useProtestDetail } from "@/hooks/league/useProtestDetail";
import { routes } from '@/lib/routing/RouteConfig'; import { routes } from '@/lib/routing/RouteConfig';
@@ -43,10 +43,15 @@ import { Heading } from '@/ui/Heading';
import { Icon as UIIcon } from '@/ui/Icon'; import { Icon as UIIcon } from '@/ui/Icon';
import { Link as UILink } from '@/ui/Link'; import { Link as UILink } from '@/ui/Link';
import { Grid } from '@/ui/Grid'; import { Grid } from '@/ui/Grid';
import { GridItem } from '@/ui/GridItem';
import { Stack } from '@/ui/Stack'; import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text'; import { Text } from '@/ui/Text';
const GridItem = ({ children, colSpan, lgSpan }: { children: React.ReactNode; colSpan?: number; lgSpan?: number }) => (
<Box gridCols={colSpan} style={{ gridColumn: `span ${colSpan} / span ${colSpan}` }} className={lgSpan ? `lg:col-span-${lgSpan}` : ''}>
{children}
</Box>
);
type PenaltyUiConfig = { type PenaltyUiConfig = {
label: string; label: string;
description: string; description: string;
@@ -377,7 +382,7 @@ export function ProtestDetailPageClient({ initialViewData }: { initialViewData:
<Box w={10} h={10} rounded="full" bg="bg-blue-500/20" display="flex" alignItems="center" justifyContent="center" flexShrink={0}> <Box w={10} h={10} rounded="full" bg="bg-blue-500/20" display="flex" alignItems="center" justifyContent="center" flexShrink={0}>
<UIIcon icon={User} size={5} color="text-blue-400" /> <UIIcon icon={User} size={5} color="text-blue-400" />
</Box> </Box>
<Box flexGrow={1} minWidth={0}> <Box flexGrow={1} minWidth="0">
<Text size="xs" color="text-blue-400" weight="medium" block>Protesting</Text> <Text size="xs" color="text-blue-400" weight="medium" block>Protesting</Text>
<Text size="sm" weight="semibold" color="text-white" truncate block>{protestingDriver?.name || 'Unknown'}</Text> <Text size="sm" weight="semibold" color="text-white" truncate block>{protestingDriver?.name || 'Unknown'}</Text>
</Box> </Box>
@@ -391,7 +396,7 @@ export function ProtestDetailPageClient({ initialViewData }: { initialViewData:
<Box w={10} h={10} rounded="full" bg="bg-orange-500/20" display="flex" alignItems="center" justifyContent="center" flexShrink={0}> <Box w={10} h={10} rounded="full" bg="bg-orange-500/20" display="flex" alignItems="center" justifyContent="center" flexShrink={0}>
<UIIcon icon={User} size={5} color="text-orange-400" /> <UIIcon icon={User} size={5} color="text-orange-400" />
</Box> </Box>
<Box flexGrow={1} minWidth={0}> <Box flexGrow={1} minWidth="0">
<Text size="xs" color="text-orange-400" weight="medium" block>Accused</Text> <Text size="xs" color="text-orange-400" weight="medium" block>Accused</Text>
<Text size="sm" weight="semibold" color="text-white" truncate block>{accusedDriver?.name || 'Unknown'}</Text> <Text size="sm" weight="semibold" color="text-white" truncate block>{accusedDriver?.name || 'Unknown'}</Text>
</Box> </Box>
@@ -407,18 +412,19 @@ export function ProtestDetailPageClient({ initialViewData }: { initialViewData:
<Box p={4}> <Box p={4}>
<Heading level={3} fontSize="xs" weight="semibold" color="text-gray-500" mb={3}>Race Details</Heading> <Heading level={3} fontSize="xs" weight="semibold" color="text-gray-500" mb={3}>Race Details</Heading>
<UILink <Box marginBottom={3}>
href={routes.race.detail(race?.id || '')} <UILink
block href={routes.race.detail(race?.id || '')}
mb={3} block
> >
<Box p={3} rounded="lg" bg="bg-deep-graphite" border borderColor="border-charcoal-outline" hoverBorderColor="border-primary-blue/50" hoverBg="bg-primary-blue/5" transition> <Box p={3} rounded="lg" bg="bg-deep-graphite" border borderColor="border-charcoal-outline" hoverBorderColor="border-primary-blue/50" hoverBg="bg-primary-blue/5" transition>
<Box display="flex" alignItems="center" justifyContent="between"> <Box display="flex" alignItems="center" justifyContent="between">
<Text size="sm" weight="medium" color="text-white">{race?.name || 'Unknown Race'}</Text> <Text size="sm" weight="medium" color="text-white">{race?.name || 'Unknown Race'}</Text>
<UIIcon icon={ExternalLink} size={3} color="text-gray-500" /> <UIIcon icon={ExternalLink} size={3} color="text-gray-500" />
</Box>
</Box> </Box>
</Box> </UILink>
</UILink> </Box>
<Stack gap={2}> <Stack gap={2}>
<Box display="flex" alignItems="center" gap={2}> <Box display="flex" alignItems="center" gap={2}>
@@ -500,7 +506,7 @@ export function ProtestDetailPageClient({ initialViewData }: { initialViewData:
<Box w={10} h={10} rounded="full" bg="bg-blue-500/20" display="flex" alignItems="center" justifyContent="center" flexShrink={0}> <Box w={10} h={10} rounded="full" bg="bg-blue-500/20" display="flex" alignItems="center" justifyContent="center" flexShrink={0}>
<UIIcon icon={AlertCircle} size={5} color="text-blue-400" /> <UIIcon icon={AlertCircle} size={5} color="text-blue-400" />
</Box> </Box>
<Box flexGrow={1} minWidth={0}> <Box flexGrow={1} minWidth="0">
<Box display="flex" alignItems="center" gap={2} mb={1}> <Box display="flex" alignItems="center" gap={2} mb={1}>
<Text weight="semibold" color="text-white" size="sm">{protestingDriver?.name || 'Unknown'}</Text> <Text weight="semibold" color="text-white" size="sm">{protestingDriver?.name || 'Unknown'}</Text>
<Text size="xs" color="text-blue-400" weight="medium">filed protest</Text> <Text size="xs" color="text-blue-400" weight="medium">filed protest</Text>
@@ -544,7 +550,7 @@ export function ProtestDetailPageClient({ initialViewData }: { initialViewData:
<Box w={10} h={10} rounded="full" display="flex" alignItems="center" justifyContent="center" flexShrink={0} bg={protest.status === 'upheld' ? 'bg-red-500/20' : 'bg-gray-500/20'}> <Box w={10} h={10} rounded="full" display="flex" alignItems="center" justifyContent="center" flexShrink={0} bg={protest.status === 'upheld' ? 'bg-red-500/20' : 'bg-gray-500/20'}>
<UIIcon icon={Gavel} size={5} color={protest.status === 'upheld' ? 'text-red-400' : 'text-gray-400'} /> <UIIcon icon={Gavel} size={5} color={protest.status === 'upheld' ? 'text-red-400' : 'text-gray-400'} />
</Box> </Box>
<Box flexGrow={1} minWidth={0}> <Box flexGrow={1} minWidth="0">
<Box display="flex" alignItems="center" gap={2} mb={1}> <Box display="flex" alignItems="center" gap={2} mb={1}>
<Text weight="semibold" color="text-white" size="sm">Steward Decision</Text> <Text weight="semibold" color="text-white" size="sm">Steward Decision</Text>
<Text size="xs" weight="medium" color={protest.status === 'upheld' ? 'text-red-400' : 'text-gray-400'}> <Text size="xs" weight="medium" color={protest.status === 'upheld' ? 'text-red-400' : 'text-gray-400'}>
@@ -575,21 +581,21 @@ export function ProtestDetailPageClient({ initialViewData }: { initialViewData:
<UIIcon icon={User} size={5} color="text-gray-500" /> <UIIcon icon={User} size={5} color="text-gray-500" />
</Box> </Box>
<Box flexGrow={1}> <Box flexGrow={1}>
<Box as="textarea" <Box as="textarea"
value={newComment} value={newComment}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setNewComment(e.target.value)} onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setNewComment(e.target.value)}
placeholder="Add a comment or request more information..." placeholder="Add a comment or request more information..."
rows={2} style={{ height: '4rem' }}
w="full" w="full"
px={4} px={4}
py={3} py={3}
bg="bg-deep-graphite" bg="bg-deep-graphite"
border border
borderColor="border-charcoal-outline" borderColor="border-charcoal-outline"
rounded="lg" rounded="lg"
color="text-white" color="text-white"
fontSize="sm" fontSize="sm"
/> />
<Box display="flex" justifyContent="end" mt={2}> <Box display="flex" justifyContent="end" mt={2}>
<Button variant="secondary" disabled={!newComment.trim()}> <Button variant="secondary" disabled={!newComment.trim()}>
<UIIcon icon={Send} size={3} mr={1} /> <UIIcon icon={Send} size={3} mr={1} />
@@ -651,32 +657,30 @@ export function ProtestDetailPageClient({ initialViewData }: { initialViewData:
{/* Decision Selection */} {/* Decision Selection */}
<Grid cols={2} gap={2} mb={4}> <Grid cols={2} gap={2} mb={4}>
<Button <Box padding={3} border borderColor={decision === 'uphold' ? 'border-racing-red' : 'border-charcoal-outline'} bg={decision === 'uphold' ? 'bg-racing-red/10' : 'transparent'} rounded="lg">
variant="ghost" <Button
onClick={() => setDecision('uphold')} variant="ghost"
p={3} onClick={() => setDecision('uphold')}
border fullWidth
borderColor={decision === 'uphold' ? 'border-racing-red' : 'border-charcoal-outline'} >
bg={decision === 'uphold' ? 'bg-racing-red/10' : 'transparent'} <Stack align="center" gap={1}>
> <UIIcon icon={CheckCircle} size={5} color={decision === 'uphold' ? 'text-red-400' : 'text-gray-500'} />
<Stack align="center" gap={1}> <Text size="xs" weight="medium" color={decision === 'uphold' ? 'text-red-400' : 'text-gray-400'}>Uphold</Text>
<UIIcon icon={CheckCircle} size={5} color={decision === 'uphold' ? 'text-red-400' : 'text-gray-500'} /> </Stack>
<Text size="xs" weight="medium" color={decision === 'uphold' ? 'text-red-400' : 'text-gray-400'}>Uphold</Text> </Button>
</Stack> </Box>
</Button> <Box padding={3} border borderColor={decision === 'dismiss' ? 'border-gray-500' : 'border-charcoal-outline'} bg={decision === 'dismiss' ? 'bg-gray-500/10' : 'transparent'} rounded="lg">
<Button <Button
variant="ghost" variant="ghost"
onClick={() => setDecision('dismiss')} onClick={() => setDecision('dismiss')}
p={3} fullWidth
border >
borderColor={decision === 'dismiss' ? 'border-gray-500' : 'border-charcoal-outline'} <Stack align="center" gap={1}>
bg={decision === 'dismiss' ? 'bg-gray-500/10' : 'transparent'} <UIIcon icon={XCircle} size={5} color={decision === 'dismiss' ? 'text-gray-300' : 'text-gray-500'} />
> <Text size="xs" weight="medium" color={decision === 'dismiss' ? 'text-gray-300' : 'text-gray-400'}>Dismiss</Text>
<Stack align="center" gap={1}> </Stack>
<UIIcon icon={XCircle} size={5} color={decision === 'dismiss' ? 'text-gray-300' : 'text-gray-500'} /> </Button>
<Text size="xs" weight="medium" color={decision === 'dismiss' ? 'text-gray-300' : 'text-gray-400'}>Dismiss</Text> </Box>
</Stack>
</Button>
</Grid> </Grid>
{/* Penalty Selection (if upholding) */} {/* Penalty Selection (if upholding) */}
@@ -696,27 +700,24 @@ export function ProtestDetailPageClient({ initialViewData }: { initialViewData:
const Icon = penalty.icon; const Icon = penalty.icon;
const isSelected = penaltyType === penalty.type; const isSelected = penaltyType === penalty.type;
return ( return (
<Button <Box key={penalty.type} padding={2} border borderColor={isSelected ? undefined : 'border-charcoal-outline'} bg={isSelected ? undefined : 'bg-iron-gray/30'} rounded="lg">
key={penalty.type} <Button
variant="ghost" variant="ghost"
onClick={() => { onClick={() => {
setPenaltyType(penalty.type); setPenaltyType(penalty.type);
setPenaltyValue(penalty.defaultValue); setPenaltyValue(penalty.defaultValue);
}} }}
p={2} fullWidth
border title={penalty.description}
borderColor={isSelected ? undefined : 'border-charcoal-outline'} >
bg={isSelected ? undefined : 'bg-iron-gray/30'} <Stack align="start" gap={0.5}>
color={isSelected ? penalty.color : undefined} <UIIcon icon={Icon} size={3.5} color={isSelected ? penalty.color : 'text-gray-500'} />
title={penalty.description} <Text size="xs" weight="medium" fontSize="10px" color={isSelected ? penalty.color : 'text-gray-500'}>
> {penalty.label}
<Stack align="start" gap={0.5}> </Text>
<UIIcon icon={Icon} size={3.5} color={isSelected ? undefined : 'text-gray-500'} /> </Stack>
<Text size="xs" weight="medium" fontSize="10px" color={isSelected ? undefined : 'text-gray-500'}> </Button>
{penalty.label} </Box>
</Text>
</Stack>
</Button>
); );
})} })}
</Grid> </Grid>
@@ -755,7 +756,7 @@ export function ProtestDetailPageClient({ initialViewData }: { initialViewData:
value={stewardNotes} value={stewardNotes}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setStewardNotes(e.target.value)} onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setStewardNotes(e.target.value)}
placeholder="Explain your decision..." placeholder="Explain your decision..."
rows={4} style={{ height: '8rem' }}
w="full" w="full"
px={3} px={3}
py={2} py={2}