185 lines
5.5 KiB
TypeScript
185 lines
5.5 KiB
TypeScript
'use client';
|
|
|
|
import { Badge } from '@/ui/Badge';
|
|
import { Button } from '@/ui/Button';
|
|
import { Icon } from '@/ui/Icon';
|
|
import { Link } from '@/ui/Link';
|
|
import { Stack } from '@/ui/Stack';
|
|
import { Surface } from '@/ui/Surface';
|
|
import { Text } from '@/ui/Text';
|
|
import { Calendar, Clock, MapPin, type LucideIcon } from 'lucide-react';
|
|
import { useEffect, useState } from 'react';
|
|
|
|
interface NextRaceCountdownWidgetProps {
|
|
raceId: string;
|
|
raceName: string;
|
|
date: string;
|
|
track?: string;
|
|
car?: string;
|
|
isRegistered?: boolean;
|
|
}
|
|
|
|
interface CountdownState {
|
|
days: number;
|
|
hours: number;
|
|
minutes: number;
|
|
seconds: number;
|
|
}
|
|
|
|
export function NextRaceCountdownWidget({
|
|
raceId,
|
|
raceName,
|
|
date,
|
|
track,
|
|
car,
|
|
isRegistered = false,
|
|
}: NextRaceCountdownWidgetProps) {
|
|
const [countdown, setCountdown] = useState<CountdownState | null>(null);
|
|
const [isExpired, setIsExpired] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const calculateCountdown = () => {
|
|
const now = new Date();
|
|
const raceDate = new Date(date);
|
|
const diff = raceDate.getTime() - now.getTime();
|
|
|
|
if (diff <= 0) {
|
|
setIsExpired(true);
|
|
setCountdown({ days: 0, hours: 0, minutes: 0, seconds: 0 });
|
|
return;
|
|
}
|
|
|
|
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
|
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
|
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
|
|
|
|
setCountdown({ days, hours, minutes, seconds });
|
|
setIsExpired(false);
|
|
};
|
|
|
|
calculateCountdown();
|
|
const interval = setInterval(calculateCountdown, 1000);
|
|
return () => clearInterval(interval);
|
|
}, [date]);
|
|
|
|
const formatTime = (value: number) => value.toString().padStart(2, '0');
|
|
|
|
return (
|
|
<Surface
|
|
variant="precision"
|
|
rounded="xl"
|
|
padding={6}
|
|
style={{
|
|
position: 'relative',
|
|
overflow: 'hidden',
|
|
}}
|
|
data-testid="next-race-countdown"
|
|
>
|
|
<Stack
|
|
position="absolute"
|
|
top="0"
|
|
right="0"
|
|
w="40"
|
|
h="40"
|
|
style={{
|
|
background: 'linear-gradient(to bottom left, var(--ui-color-intent-primary), transparent)',
|
|
opacity: 0.2,
|
|
borderBottomLeftRadius: '9999px',
|
|
}}
|
|
/>
|
|
<Stack position="relative" gap={4}>
|
|
{/* Header */}
|
|
<Stack direction="row" align="center" gap={2}>
|
|
<Badge variant="primary" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }}>
|
|
Next Race
|
|
</Badge>
|
|
{isRegistered && (
|
|
<Badge variant="success">
|
|
Registered
|
|
</Badge>
|
|
)}
|
|
</Stack>
|
|
|
|
{/* Race Info */}
|
|
<Stack gap={2}>
|
|
<Text size="xl" weight="bold" color="text-white">
|
|
{raceName}
|
|
</Text>
|
|
{track && (
|
|
<Stack direction="row" align="center" gap={1.5}>
|
|
<Icon icon={MapPin as LucideIcon} size={4} intent="low" />
|
|
<Text size="sm" variant="low">
|
|
{track}
|
|
</Text>
|
|
</Stack>
|
|
)}
|
|
{car && (
|
|
<Stack direction="row" align="center" gap={1.5}>
|
|
<Icon icon={Calendar as LucideIcon} size={4} intent="low" />
|
|
<Text size="sm" variant="low">
|
|
{car}
|
|
</Text>
|
|
</Stack>
|
|
)}
|
|
</Stack>
|
|
|
|
{/* Countdown Timer */}
|
|
<Stack gap={2}>
|
|
<Text
|
|
size="xs"
|
|
variant="low"
|
|
style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }}
|
|
block
|
|
>
|
|
{isExpired ? 'Race Started' : 'Starts in'}
|
|
</Text>
|
|
{countdown && (
|
|
<Stack direction="row" gap={2} align="center">
|
|
<Stack align="center" gap={0.5}>
|
|
<Text size="2xl" weight="bold" variant="primary" font="mono">
|
|
{formatTime(countdown.days)}
|
|
</Text>
|
|
<Text size="xs" variant="low">Days</Text>
|
|
</Stack>
|
|
<Text size="2xl" weight="bold" variant="med">:</Text>
|
|
<Stack align="center" gap={0.5}>
|
|
<Text size="2xl" weight="bold" variant="primary" font="mono">
|
|
{formatTime(countdown.hours)}
|
|
</Text>
|
|
<Text size="xs" variant="low">Hours</Text>
|
|
</Stack>
|
|
<Text size="2xl" weight="bold" variant="med">:</Text>
|
|
<Stack align="center" gap={0.5}>
|
|
<Text size="2xl" weight="bold" variant="primary" font="mono">
|
|
{formatTime(countdown.minutes)}
|
|
</Text>
|
|
<Text size="xs" variant="low">Mins</Text>
|
|
</Stack>
|
|
<Text size="2xl" weight="bold" variant="med">:</Text>
|
|
<Stack align="center" gap={0.5}>
|
|
<Text size="2xl" weight="bold" variant="primary" font="mono">
|
|
{formatTime(countdown.seconds)}
|
|
</Text>
|
|
<Text size="xs" variant="low">Secs</Text>
|
|
</Stack>
|
|
</Stack>
|
|
)}
|
|
</Stack>
|
|
|
|
{/* Actions */}
|
|
<Stack direction="row" gap={3} mt={2}>
|
|
<Link href={`/races/${raceId}`} style={{ flex: 1 }}>
|
|
<Button
|
|
variant="primary"
|
|
style={{ width: '100%' }}
|
|
>
|
|
{isRegistered ? 'View Details' : 'Register'}
|
|
</Button>
|
|
</Link>
|
|
</Stack>
|
|
</Stack>
|
|
</Surface>
|
|
);
|
|
}
|