website refactor
This commit is contained in:
185
apps/website/components/leagues/NextRaceCountdownWidget.tsx
Normal file
185
apps/website/components/leagues/NextRaceCountdownWidget.tsx
Normal file
@@ -0,0 +1,185 @@
|
||||
'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="muted"
|
||||
rounded="xl"
|
||||
border
|
||||
padding={6}
|
||||
style={{
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
background: 'linear-gradient(to bottom right, #262626, rgba(38, 38, 38, 0.8))',
|
||||
borderColor: 'rgba(59, 130, 246, 0.3)',
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
position="absolute"
|
||||
top="0"
|
||||
right="0"
|
||||
w="40"
|
||||
h="40"
|
||||
style={{
|
||||
background: 'linear-gradient(to bottom left, rgba(59, 130, 246, 0.2), transparent)',
|
||||
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} color="var(--text-gray-500)" />
|
||||
<Text size="sm" color="text-gray-400">
|
||||
{track}
|
||||
</Text>
|
||||
</Stack>
|
||||
)}
|
||||
{car && (
|
||||
<Stack direction="row" align="center" gap={1.5}>
|
||||
<Icon icon={Calendar as LucideIcon} size={4} color="var(--text-gray-500)" />
|
||||
<Text size="sm" color="text-gray-400">
|
||||
{car}
|
||||
</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
{/* Countdown Timer */}
|
||||
<Stack gap={2}>
|
||||
<Text
|
||||
size="xs"
|
||||
color="text-gray-500"
|
||||
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" color="text-primary-blue" font="mono">
|
||||
{formatTime(countdown.days)}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500">Days</Text>
|
||||
</Stack>
|
||||
<Text size="2xl" weight="bold" color="text-gray-600">:</Text>
|
||||
<Stack align="center" gap={0.5}>
|
||||
<Text size="2xl" weight="bold" color="text-primary-blue" font="mono">
|
||||
{formatTime(countdown.hours)}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500">Hours</Text>
|
||||
</Stack>
|
||||
<Text size="2xl" weight="bold" color="text-gray-600">:</Text>
|
||||
<Stack align="center" gap={0.5}>
|
||||
<Text size="2xl" weight="bold" color="text-primary-blue" font="mono">
|
||||
{formatTime(countdown.minutes)}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500">Mins</Text>
|
||||
</Stack>
|
||||
<Text size="2xl" weight="bold" color="text-gray-600">:</Text>
|
||||
<Stack align="center" gap={0.5}>
|
||||
<Text size="2xl" weight="bold" color="text-primary-blue" font="mono">
|
||||
{formatTime(countdown.seconds)}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500">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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user