website refactor

This commit is contained in:
2026-01-18 16:18:18 +01:00
parent 0b301feb61
commit 13567d51af
329 changed files with 4701 additions and 4750 deletions

View File

@@ -5,10 +5,9 @@ import { useState, useRef, useEffect } from 'react';
import type * as React from 'react';
import { createPortal } from 'react-dom';
import type { LeagueConfigFormModel } from '@/lib/types/LeagueConfigFormModel';
import { Box } from '@/ui/Box';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
import { Stack } from '@/ui/Stack';
import { Icon } from '@/ui/Icon';
// Minimum drivers for ranked leagues
@@ -87,7 +86,7 @@ function InfoFlyout({ isOpen, onClose, title, children, anchorRef }: InfoFlyoutP
if (!isOpen || !mounted) return null;
return createPortal(
<Box
<Stack
ref={flyoutRef}
position="fixed"
zIndex={50}
@@ -100,7 +99,7 @@ function InfoFlyout({ isOpen, onClose, title, children, anchorRef }: InfoFlyoutP
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ top: position.top, left: position.left, maxHeight: '80vh', overflowY: 'auto' }}
>
<Box
<Stack
display="flex"
alignItems="center"
justifyContent="between"
@@ -116,7 +115,7 @@ function InfoFlyout({ isOpen, onClose, title, children, anchorRef }: InfoFlyoutP
<Icon icon={HelpCircle} size={4} color="text-primary-blue" />
<Text size="sm" weight="semibold" color="text-white">{title}</Text>
</Stack>
<Box
<Stack
as="button"
type="button"
onClick={onClose}
@@ -130,12 +129,12 @@ function InfoFlyout({ isOpen, onClose, title, children, anchorRef }: InfoFlyoutP
hoverBg="bg-charcoal-outline"
>
<Icon icon={X} size={4} color="text-gray-400" />
</Box>
</Box>
<Box p={4}>
</Stack>
</Stack>
<Stack p={4}>
{children}
</Box>
</Box>,
</Stack>
</Stack>,
document.body
);
}
@@ -189,20 +188,20 @@ export function LeagueVisibilitySection({
return (
<Stack gap={8}>
{/* Emotional header for the step */}
<Box textAlign="center" pb={2}>
<Stack textAlign="center" pb={2}>
<Heading level={3} mb={2}>
Choose your league&apos;s destiny
</Heading>
<Text size="sm" color="text-gray-400" maxWidth="lg" mx="auto" block>
Will you compete for glory on the global leaderboards, or race with friends in a private series?
</Text>
</Box>
</Stack>
{/* League Type Selection */}
<Box display="grid" responsiveGridCols={{ base: 1, md: 2 }} gap={6}>
<Stack display="grid" responsiveGridCols={{ base: 1, md: 2 }} gap={6}>
{/* Ranked (Public) Option */}
<Box position="relative">
<Box
<Stack position="relative">
<Stack
as="button"
type="button"
disabled={disabled}
@@ -227,9 +226,9 @@ export function LeagueVisibilitySection({
group
>
{/* Header */}
<Box display="flex" alignItems="start" justifyContent="between">
<Stack display="flex" alignItems="start" justifyContent="between">
<Stack direction="row" align="center" gap={3}>
<Box
<Stack
display="flex"
h="14"
w="14"
@@ -239,18 +238,18 @@ export function LeagueVisibilitySection({
bg={isRanked ? 'bg-primary-blue/30' : 'bg-charcoal-outline/50'}
>
<Icon icon={Trophy} size={7} color={isRanked ? 'text-primary-blue' : 'text-gray-400'} />
</Box>
<Box>
</Stack>
<Stack>
<Text weight="bold" size="xl" color={isRanked ? 'text-white' : 'text-gray-300'} block>
Ranked
</Text>
<Text size="sm" color={isRanked ? 'text-primary-blue' : 'text-gray-500'} block>
Compete for glory
</Text>
</Box>
</Stack>
</Stack>
{/* Radio indicator */}
<Box
<Stack
display="flex"
h="7"
w="7"
@@ -264,8 +263,8 @@ export function LeagueVisibilitySection({
transition
>
{isRanked && <Icon icon={Check} size={4} color="text-white" />}
</Box>
</Box>
</Stack>
</Stack>
{/* Emotional tagline */}
<Text size="sm" color={isRanked ? 'text-gray-300' : 'text-gray-500'} block>
@@ -289,16 +288,16 @@ export function LeagueVisibilitySection({
</Stack>
{/* Requirement badge */}
<Box display="flex" alignItems="center" gap={2} mt="auto" px={3} py={2} rounded="lg" bg="bg-warning-amber/10" border borderColor="border-warning-amber/20" w="fit">
<Stack display="flex" alignItems="center" gap={2} mt="auto" px={3} py={2} rounded="lg" bg="bg-warning-amber/10" border borderColor="border-warning-amber/20" w="fit">
<Icon icon={Users} size={4} color="text-warning-amber" />
<Text size="xs" color="text-warning-amber" weight="medium">
Requires {MIN_RANKED_DRIVERS}+ drivers for competitive integrity
</Text>
</Box>
</Box>
</Stack>
</Stack>
{/* Info button */}
<Box
<Stack
as="button"
ref={rankedInfoRef}
type="button"
@@ -318,8 +317,8 @@ export function LeagueVisibilitySection({
hoverBg="bg-primary-blue/10"
>
<Icon icon={HelpCircle} size={4} />
</Box>
</Box>
</Stack>
</Stack>
{/* Ranked Info Flyout */}
<InfoFlyout
@@ -343,16 +342,16 @@ export function LeagueVisibilitySection({
Requirements
</Text>
<Stack gap={1.5}>
<Box display="flex" alignItems="start" gap={2}>
<Stack display="flex" alignItems="start" gap={2}>
<Icon icon={Users} size={3.5} color="text-warning-amber" flexShrink={0} mt={0.5} />
<Text size="xs" color="text-gray-400">
<Text weight="bold" color="text-white">Minimum {MIN_RANKED_DRIVERS} drivers</Text> for competitive integrity
</Text>
</Box>
<Box display="flex" alignItems="start" gap={2}>
</Stack>
<Stack display="flex" alignItems="start" gap={2}>
<Icon icon={Check} size={3.5} color="text-performance-green" flexShrink={0} mt={0.5} />
<Text size="xs" color="text-gray-400">Anyone can discover and join your league</Text>
</Box>
</Stack>
</Stack>
</Stack>
@@ -365,22 +364,22 @@ export function LeagueVisibilitySection({
Benefits
</Text>
<Stack gap={1.5}>
<Box display="flex" alignItems="start" gap={2}>
<Stack display="flex" alignItems="start" gap={2}>
<Icon icon={Trophy} size={3.5} color="text-primary-blue" flexShrink={0} mt={0.5} />
<Text size="xs" color="text-gray-400">Results affect driver ratings and rankings</Text>
</Box>
<Box display="flex" alignItems="start" gap={2}>
</Stack>
<Stack display="flex" alignItems="start" gap={2}>
<Icon icon={Check} size={3.5} color="text-performance-green" flexShrink={0} mt={0.5} />
<Text size="xs" color="text-gray-400">Featured in league discovery</Text>
</Box>
</Stack>
</Stack>
</Stack>
</Stack>
</InfoFlyout>
{/* Unranked (Private) Option */}
<Box position="relative">
<Box
<Stack position="relative">
<Stack
as="button"
type="button"
disabled={disabled}
@@ -405,9 +404,9 @@ export function LeagueVisibilitySection({
group
>
{/* Header */}
<Box display="flex" alignItems="start" justifyContent="between">
<Stack display="flex" alignItems="start" justifyContent="between">
<Stack direction="row" align="center" gap={3}>
<Box
<Stack
display="flex"
h="14"
w="14"
@@ -417,18 +416,18 @@ export function LeagueVisibilitySection({
bg={!isRanked ? 'bg-neon-aqua/30' : 'bg-charcoal-outline/50'}
>
<Icon icon={Users} size={7} color={!isRanked ? 'text-neon-aqua' : 'text-gray-400'} />
</Box>
<Box>
</Stack>
<Stack>
<Text weight="bold" size="xl" color={!isRanked ? 'text-white' : 'text-gray-300'} block>
Unranked
</Text>
<Text size="sm" color={!isRanked ? 'text-neon-aqua' : 'text-gray-500'} block>
Race with friends
</Text>
</Box>
</Stack>
</Stack>
{/* Radio indicator */}
<Box
<Stack
display="flex"
h="7"
w="7"
@@ -442,8 +441,8 @@ export function LeagueVisibilitySection({
transition
>
{!isRanked && <Icon icon={Check} size={4} color="text-deep-graphite" />}
</Box>
</Box>
</Stack>
</Stack>
{/* Emotional tagline */}
<Text size="sm" color={!isRanked ? 'text-gray-300' : 'text-gray-500'} block>
@@ -467,16 +466,16 @@ export function LeagueVisibilitySection({
</Stack>
{/* Flexibility badge */}
<Box display="flex" alignItems="center" gap={2} mt="auto" px={3} py={2} rounded="lg" bg="bg-neon-aqua/10" border borderColor="border-neon-aqua/20" w="fit">
<Stack display="flex" alignItems="center" gap={2} mt="auto" px={3} py={2} rounded="lg" bg="bg-neon-aqua/10" border borderColor="border-neon-aqua/20" w="fit">
<Icon icon={Users} size={4} color={!isRanked ? 'text-neon-aqua' : 'text-gray-400'} />
<Text size="xs" color={!isRanked ? 'text-neon-aqua' : 'text-gray-400'} weight="medium">
Any size even 2 friends
</Text>
</Box>
</Box>
</Stack>
</Stack>
{/* Info button */}
<Box
<Stack
as="button"
ref={unrankedInfoRef}
type="button"
@@ -496,8 +495,8 @@ export function LeagueVisibilitySection({
hoverBg="bg-neon-aqua/10"
>
<Icon icon={HelpCircle} size={4} />
</Box>
</Box>
</Stack>
</Stack>
{/* Unranked Info Flyout */}
<InfoFlyout
@@ -522,18 +521,18 @@ export function LeagueVisibilitySection({
Perfect For
</Text>
<Stack gap={1.5}>
<Box display="flex" alignItems="start" gap={2}>
<Stack display="flex" alignItems="start" gap={2}>
<Icon icon={Check} size={3.5} color="text-neon-aqua" flexShrink={0} mt={0.5} />
<Text size="xs" color="text-gray-400">Private racing with friends</Text>
</Box>
<Box display="flex" alignItems="start" gap={2}>
</Stack>
<Stack display="flex" alignItems="start" gap={2}>
<Icon icon={Check} size={3.5} color="text-neon-aqua" flexShrink={0} mt={0.5} />
<Text size="xs" color="text-gray-400">Practice and training sessions</Text>
</Box>
<Box display="flex" alignItems="start" gap={2}>
</Stack>
<Stack display="flex" alignItems="start" gap={2}>
<Icon icon={Check} size={3.5} color="text-neon-aqua" flexShrink={0} mt={0.5} />
<Text size="xs" color="text-gray-400">Small groups (2+ drivers)</Text>
</Box>
</Stack>
</Stack>
</Stack>
@@ -546,29 +545,29 @@ export function LeagueVisibilitySection({
Features
</Text>
<Stack gap={1.5}>
<Box display="flex" alignItems="start" gap={2}>
<Stack display="flex" alignItems="start" gap={2}>
<Icon icon={Users} size={3.5} color="text-neon-aqua" flexShrink={0} mt={0.5} />
<Text size="xs" color="text-gray-400">Invite-only membership</Text>
</Box>
<Box display="flex" alignItems="start" gap={2}>
</Stack>
<Stack display="flex" alignItems="start" gap={2}>
<Icon icon={Check} size={3.5} color="text-neon-aqua" flexShrink={0} mt={0.5} />
<Text size="xs" color="text-gray-400">Full stats and standings (internal only)</Text>
</Box>
</Stack>
</Stack>
</Stack>
</Stack>
</InfoFlyout>
</Box>
</Stack>
{errors?.visibility && (
<Box display="flex" alignItems="center" gap={2} p={3} rounded="lg" bg="bg-warning-amber/10" border borderColor="border-warning-amber/20">
<Stack display="flex" alignItems="center" gap={2} p={3} rounded="lg" bg="bg-warning-amber/10" border borderColor="border-warning-amber/20">
<Icon icon={HelpCircle} size={4} color="text-warning-amber" flexShrink={0} />
<Text size="xs" color="text-warning-amber">{errors.visibility}</Text>
</Box>
</Stack>
)}
{/* Contextual info based on selection */}
<Box
<Stack
rounded="xl"
p={5}
border
@@ -576,33 +575,33 @@ export function LeagueVisibilitySection({
bg={isRanked ? 'bg-primary-blue/5' : 'bg-neon-aqua/5'}
borderColor={isRanked ? 'border-primary-blue/20' : 'border-neon-aqua/20'}
>
<Box display="flex" alignItems="start" gap={3}>
<Stack display="flex" alignItems="start" gap={3}>
{isRanked ? (
<>
<Icon icon={Trophy} size={5} color="text-primary-blue" flexShrink={0} mt={0.5} />
<Box>
<Stack>
<Text size="sm" weight="medium" color="text-white" block mb={1}>Ready to compete</Text>
<Text size="xs" color="text-gray-400" block>
Your league will be visible to all GridPilot drivers. Results will affect driver ratings
and contribute to the global leaderboards. Make sure you have at least {MIN_RANKED_DRIVERS} drivers
to ensure competitive integrity.
</Text>
</Box>
</Stack>
</>
) : (
<>
<Icon icon={Users} size={5} color="text-neon-aqua" flexShrink={0} mt={0.5} />
<Box>
<Stack>
<Text size="sm" weight="medium" color="text-white" block mb={1}>Private racing awaits</Text>
<Text size="xs" color="text-gray-400" block>
Your league will be invite-only. Perfect for racing with friends, practice sessions,
or any time you want to have fun without affecting your official ratings.
</Text>
</Box>
</Stack>
</>
)}
</Box>
</Box>
</Stack>
</Stack>
</Stack>
);
}