608 lines
23 KiB
TypeScript
608 lines
23 KiB
TypeScript
'use client';
|
|
|
|
import type { LeagueConfigFormModel } from '@/lib/types/LeagueConfigFormModel';
|
|
import { Heading } from '@/ui/Heading';
|
|
import { Icon } from '@/ui/Icon';
|
|
import { Stack } from '@/ui/Stack';
|
|
import { Text } from '@/ui/Text';
|
|
import { Check, HelpCircle, Trophy, Users, X } from 'lucide-react';
|
|
import type * as React from 'react';
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import { createPortal } from 'react-dom';
|
|
|
|
// Minimum drivers for ranked leagues
|
|
const MIN_RANKED_DRIVERS = 10;
|
|
|
|
// ============================================================================
|
|
// INFO FLYOUT COMPONENT
|
|
// ============================================================================
|
|
|
|
interface InfoFlyoutProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
title: string;
|
|
children: React.ReactNode;
|
|
anchorRef: React.RefObject<HTMLElement>;
|
|
}
|
|
|
|
function InfoFlyout({ isOpen, onClose, title, children, anchorRef }: InfoFlyoutProps) {
|
|
const [position, setPosition] = useState({ top: 0, left: 0 });
|
|
const [mounted, setMounted] = useState(false);
|
|
const flyoutRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
setMounted(true);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (isOpen && anchorRef.current && mounted) {
|
|
const rect = anchorRef.current.getBoundingClientRect();
|
|
const flyoutWidth = Math.min(340, window.innerWidth - 40);
|
|
const flyoutHeight = 350;
|
|
const padding = 16;
|
|
|
|
let left = rect.right + 12;
|
|
let top = rect.top;
|
|
|
|
if (left + flyoutWidth > window.innerWidth - padding) {
|
|
left = rect.left - flyoutWidth - 12;
|
|
}
|
|
if (left < padding) {
|
|
left = Math.max(padding, (window.innerWidth - flyoutWidth) / 2);
|
|
}
|
|
|
|
top = rect.top - flyoutHeight / 3;
|
|
if (top + flyoutHeight > window.innerHeight - padding) {
|
|
top = window.innerHeight - flyoutHeight - padding;
|
|
}
|
|
if (top < padding) top = padding;
|
|
|
|
left = Math.max(padding, Math.min(left, window.innerWidth - flyoutWidth - padding));
|
|
|
|
setPosition({ top, left });
|
|
}
|
|
}, [isOpen, anchorRef, mounted]);
|
|
|
|
useEffect(() => {
|
|
const handleEscape = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') onClose();
|
|
};
|
|
const handleClickOutside = (e: MouseEvent) => {
|
|
if (flyoutRef.current && !flyoutRef.current.contains(e.target as Node)) {
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
if (isOpen) {
|
|
document.addEventListener('keydown', handleEscape);
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
}
|
|
return () => {
|
|
document.removeEventListener('keydown', handleEscape);
|
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
};
|
|
}, [isOpen, onClose]);
|
|
|
|
if (!isOpen || !mounted) return null;
|
|
|
|
return createPortal(
|
|
<Stack
|
|
ref={flyoutRef}
|
|
position="fixed"
|
|
zIndex={50}
|
|
w="340px"
|
|
bg="bg-iron-gray"
|
|
border
|
|
borderColor="border-charcoal-outline"
|
|
rounded="xl"
|
|
shadow="2xl"
|
|
// eslint-disable-next-line gridpilot-rules/component-classification
|
|
style={{ top: position.top, left: position.left, maxHeight: '80vh', overflowY: 'auto' }}
|
|
>
|
|
<Stack
|
|
display="flex"
|
|
alignItems="center"
|
|
justifyContent="between"
|
|
p={4}
|
|
borderBottom
|
|
borderColor="border-charcoal-outline/50"
|
|
position="sticky"
|
|
top="0"
|
|
bg="bg-iron-gray"
|
|
zIndex={10}
|
|
>
|
|
<Stack direction="row" align="center" gap={2}>
|
|
<Icon icon={HelpCircle} size={4} color="text-primary-blue" />
|
|
<Text size="sm" weight="semibold" color="text-white">{title}</Text>
|
|
</Stack>
|
|
<Stack
|
|
as="button"
|
|
type="button"
|
|
onClick={onClose}
|
|
display="flex"
|
|
h="6"
|
|
w="6"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
rounded="md"
|
|
transition
|
|
hoverBg="bg-charcoal-outline"
|
|
>
|
|
<Icon icon={X} size={4} color="text-gray-400" />
|
|
</Stack>
|
|
</Stack>
|
|
<Stack p={4}>
|
|
{children}
|
|
</Stack>
|
|
</Stack>,
|
|
document.body
|
|
);
|
|
}
|
|
|
|
interface LeagueVisibilitySectionProps {
|
|
form: LeagueConfigFormModel;
|
|
onChange?: (form: LeagueConfigFormModel) => void;
|
|
errors?: {
|
|
visibility?: string;
|
|
};
|
|
readOnly?: boolean;
|
|
}
|
|
|
|
export function LeagueVisibilitySection({
|
|
form,
|
|
onChange,
|
|
errors,
|
|
readOnly,
|
|
}: LeagueVisibilitySectionProps) {
|
|
const basics = form.basics;
|
|
const disabled = readOnly || !onChange;
|
|
|
|
// Flyout state
|
|
const [showRankedFlyout, setShowRankedFlyout] = useState(false);
|
|
const [showUnrankedFlyout, setShowUnrankedFlyout] = useState(false);
|
|
const rankedInfoRef = useRef<HTMLButtonElement>(null!);
|
|
const unrankedInfoRef = useRef<HTMLButtonElement>(null!);
|
|
|
|
// Normalize visibility to new terminology
|
|
const isRanked = basics.visibility === 'public';
|
|
|
|
// Auto-update minDrivers when switching to ranked
|
|
const handleVisibilityChange = (visibility: 'public' | 'private' | 'unlisted') => {
|
|
if (!onChange) return;
|
|
|
|
// If switching to public and current maxDrivers is below minimum, update it
|
|
if (visibility === 'public' && (form.structure?.maxDrivers ?? 0) < MIN_RANKED_DRIVERS) {
|
|
onChange({
|
|
...form,
|
|
basics: { ...form.basics, visibility },
|
|
structure: { ...form.structure, maxDrivers: MIN_RANKED_DRIVERS },
|
|
});
|
|
} else {
|
|
onChange({
|
|
...form,
|
|
basics: { ...form.basics, visibility },
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Stack gap={8}>
|
|
{/* Emotional header for the step */}
|
|
<Stack textAlign="center" pb={2}>
|
|
<Heading level={3} mb={2}>
|
|
Choose your league'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>
|
|
</Stack>
|
|
|
|
{/* League Type Selection */}
|
|
<Stack display="grid" responsiveGridCols={{ base: 1, md: 2 }} gap={6}>
|
|
{/* Ranked (Public) Option */}
|
|
<Stack position="relative">
|
|
<Stack
|
|
as="button"
|
|
type="button"
|
|
disabled={disabled}
|
|
onClick={() => handleVisibilityChange('public')}
|
|
display="flex"
|
|
flexDirection="col"
|
|
gap={4}
|
|
p={6}
|
|
textAlign="left"
|
|
rounded="xl"
|
|
border
|
|
borderColor={isRanked ? 'border-primary-blue' : 'border-charcoal-outline'}
|
|
bg={isRanked ? 'bg-primary-blue/15' : 'bg-iron-gray/30'}
|
|
w="full"
|
|
position="relative"
|
|
transition
|
|
shadow={isRanked ? '0_0_30px_rgba(25,140,255,0.25)' : undefined}
|
|
hoverBorderColor={!isRanked && !disabled ? 'border-gray-500' : undefined}
|
|
hoverBg={!isRanked && !disabled ? 'bg-iron-gray/50' : undefined}
|
|
opacity={disabled ? 0.6 : 1}
|
|
cursor={disabled ? 'not-allowed' : 'pointer'}
|
|
group
|
|
>
|
|
{/* Header */}
|
|
<Stack display="flex" alignItems="start" justifyContent="between">
|
|
<Stack direction="row" align="center" gap={3}>
|
|
<Stack
|
|
display="flex"
|
|
h="14"
|
|
w="14"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
rounded="xl"
|
|
bg={isRanked ? 'bg-primary-blue/30' : 'bg-charcoal-outline/50'}
|
|
>
|
|
<Icon icon={Trophy} size={7} color={isRanked ? 'text-primary-blue' : 'text-gray-400'} />
|
|
</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>
|
|
</Stack>
|
|
</Stack>
|
|
{/* Radio indicator */}
|
|
<Stack
|
|
display="flex"
|
|
h="7"
|
|
w="7"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
rounded="full"
|
|
border
|
|
borderColor={isRanked ? 'border-primary-blue' : 'border-gray-500'}
|
|
bg={isRanked ? 'bg-primary-blue' : ''}
|
|
flexShrink={0}
|
|
transition
|
|
>
|
|
{isRanked && <Icon icon={Check} size={4} color="text-white" />}
|
|
</Stack>
|
|
</Stack>
|
|
|
|
{/* Emotional tagline */}
|
|
<Text size="sm" color={isRanked ? 'text-gray-300' : 'text-gray-500'} block>
|
|
Your results matter. Build your reputation in the global standings and climb the ranks.
|
|
</Text>
|
|
|
|
{/* Features */}
|
|
<Stack gap={2.5} py={2}>
|
|
<Stack direction="row" align="center" gap={2}>
|
|
<Icon icon={Check} size={4} color="text-performance-green" />
|
|
<Text size="sm" color="text-gray-400">Discoverable by all drivers</Text>
|
|
</Stack>
|
|
<Stack direction="row" align="center" gap={2}>
|
|
<Icon icon={Check} size={4} color="text-performance-green" />
|
|
<Text size="sm" color="text-gray-400">Affects driver ratings & rankings</Text>
|
|
</Stack>
|
|
<Stack direction="row" align="center" gap={2}>
|
|
<Icon icon={Check} size={4} color="text-performance-green" />
|
|
<Text size="sm" color="text-gray-400">Featured on leaderboards</Text>
|
|
</Stack>
|
|
</Stack>
|
|
|
|
{/* Requirement badge */}
|
|
<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>
|
|
</Stack>
|
|
</Stack>
|
|
|
|
{/* Info button */}
|
|
<Stack
|
|
as="button"
|
|
ref={rankedInfoRef}
|
|
type="button"
|
|
onClick={() => setShowRankedFlyout(true)}
|
|
position="absolute"
|
|
top="3"
|
|
right="3"
|
|
display="flex"
|
|
h="7"
|
|
w="7"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
rounded="full"
|
|
transition
|
|
color="text-gray-500"
|
|
hoverTextColor="text-primary-blue"
|
|
hoverBg="bg-primary-blue/10"
|
|
>
|
|
<Icon icon={HelpCircle} size={4} />
|
|
</Stack>
|
|
</Stack>
|
|
|
|
{/* Ranked Info Flyout */}
|
|
<InfoFlyout
|
|
isOpen={showRankedFlyout}
|
|
onClose={() => setShowRankedFlyout(false)}
|
|
title="Ranked Leagues"
|
|
anchorRef={rankedInfoRef}
|
|
>
|
|
<Stack gap={4}>
|
|
<Text size="xs" color="text-gray-400" block>
|
|
Ranked leagues are competitive series where results matter. Your performance
|
|
affects your driver rating and contributes to global leaderboards.
|
|
</Text>
|
|
|
|
<Stack gap={2}>
|
|
<Text size="xs" weight="bold" color="text-gray-500" transform="uppercase"
|
|
// eslint-disable-next-line gridpilot-rules/component-classification
|
|
className="tracking-wide"
|
|
block
|
|
>
|
|
Requirements
|
|
</Text>
|
|
<Stack gap={1.5}>
|
|
<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>
|
|
</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>
|
|
</Stack>
|
|
</Stack>
|
|
</Stack>
|
|
|
|
<Stack gap={2}>
|
|
<Text size="xs" weight="bold" color="text-gray-500" transform="uppercase"
|
|
// eslint-disable-next-line gridpilot-rules/component-classification
|
|
className="tracking-wide"
|
|
block
|
|
>
|
|
Benefits
|
|
</Text>
|
|
<Stack gap={1.5}>
|
|
<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>
|
|
</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>
|
|
</Stack>
|
|
</Stack>
|
|
</Stack>
|
|
</Stack>
|
|
</InfoFlyout>
|
|
|
|
{/* Unranked (Private) Option */}
|
|
<Stack position="relative">
|
|
<Stack
|
|
as="button"
|
|
type="button"
|
|
disabled={disabled}
|
|
onClick={() => handleVisibilityChange('private')}
|
|
display="flex"
|
|
flexDirection="col"
|
|
gap={4}
|
|
p={6}
|
|
textAlign="left"
|
|
rounded="xl"
|
|
border
|
|
borderColor={!isRanked ? 'border-neon-aqua' : 'border-charcoal-outline'}
|
|
bg={!isRanked ? 'bg-neon-aqua/15' : 'bg-iron-gray/30'}
|
|
w="full"
|
|
position="relative"
|
|
transition
|
|
shadow={!isRanked ? '0_0_30px_rgba(67,201,230,0.2)' : undefined}
|
|
hoverBorderColor={isRanked && !disabled ? 'border-gray-500' : undefined}
|
|
hoverBg={isRanked && !disabled ? 'bg-iron-gray/50' : undefined}
|
|
opacity={disabled ? 0.6 : 1}
|
|
cursor={disabled ? 'not-allowed' : 'pointer'}
|
|
group
|
|
>
|
|
{/* Header */}
|
|
<Stack display="flex" alignItems="start" justifyContent="between">
|
|
<Stack direction="row" align="center" gap={3}>
|
|
<Stack
|
|
display="flex"
|
|
h="14"
|
|
w="14"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
rounded="xl"
|
|
bg={!isRanked ? 'bg-neon-aqua/30' : 'bg-charcoal-outline/50'}
|
|
>
|
|
<Icon icon={Users} size={7} color={!isRanked ? 'text-neon-aqua' : 'text-gray-400'} />
|
|
</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>
|
|
</Stack>
|
|
</Stack>
|
|
{/* Radio indicator */}
|
|
<Stack
|
|
display="flex"
|
|
h="7"
|
|
w="7"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
rounded="full"
|
|
border
|
|
borderColor={!isRanked ? 'border-neon-aqua' : 'border-gray-500'}
|
|
bg={!isRanked ? 'bg-neon-aqua' : ''}
|
|
flexShrink={0}
|
|
transition
|
|
>
|
|
{!isRanked && <Icon icon={Check} size={4} color="text-deep-graphite" />}
|
|
</Stack>
|
|
</Stack>
|
|
|
|
{/* Emotional tagline */}
|
|
<Text size="sm" color={!isRanked ? 'text-gray-300' : 'text-gray-500'} block>
|
|
Pure racing fun. No pressure, no rankings — just you and your crew hitting the track.
|
|
</Text>
|
|
|
|
{/* Features */}
|
|
<Stack gap={2.5} py={2}>
|
|
<Stack direction="row" align="center" gap={2}>
|
|
<Icon icon={Check} size={4} color={!isRanked ? 'text-neon-aqua' : 'text-gray-400'} />
|
|
<Text size="sm" color="text-gray-400">Private, invite-only access</Text>
|
|
</Stack>
|
|
<Stack direction="row" align="center" gap={2}>
|
|
<Icon icon={Check} size={4} color={!isRanked ? 'text-neon-aqua' : 'text-gray-400'} />
|
|
<Text size="sm" color="text-gray-400">Zero impact on your rating</Text>
|
|
</Stack>
|
|
<Stack direction="row" align="center" gap={2}>
|
|
<Icon icon={Check} size={4} color={!isRanked ? 'text-neon-aqua' : 'text-gray-400'} />
|
|
<Text size="sm" color="text-gray-400">Perfect for practice & fun</Text>
|
|
</Stack>
|
|
</Stack>
|
|
|
|
{/* Flexibility badge */}
|
|
<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>
|
|
</Stack>
|
|
</Stack>
|
|
|
|
{/* Info button */}
|
|
<Stack
|
|
as="button"
|
|
ref={unrankedInfoRef}
|
|
type="button"
|
|
onClick={() => setShowUnrankedFlyout(true)}
|
|
position="absolute"
|
|
top="3"
|
|
right="3"
|
|
display="flex"
|
|
h="7"
|
|
w="7"
|
|
alignItems="center"
|
|
justifyContent="center"
|
|
rounded="full"
|
|
transition
|
|
color="text-gray-500"
|
|
hoverTextColor="text-neon-aqua"
|
|
hoverBg="bg-neon-aqua/10"
|
|
>
|
|
<Icon icon={HelpCircle} size={4} />
|
|
</Stack>
|
|
</Stack>
|
|
|
|
{/* Unranked Info Flyout */}
|
|
<InfoFlyout
|
|
isOpen={showUnrankedFlyout}
|
|
onClose={() => setShowUnrankedFlyout(false)}
|
|
title="Unranked Leagues"
|
|
anchorRef={unrankedInfoRef}
|
|
>
|
|
<Stack gap={4}>
|
|
<Text size="xs" color="text-gray-400" block>
|
|
Unranked leagues are casual, private series for racing with friends.
|
|
Results don't affect driver ratings, so you can practice and have fun
|
|
without pressure.
|
|
</Text>
|
|
|
|
<Stack gap={2}>
|
|
<Text size="xs" weight="bold" color="text-gray-500" transform="uppercase"
|
|
// eslint-disable-next-line gridpilot-rules/component-classification
|
|
className="tracking-wide"
|
|
block
|
|
>
|
|
Perfect For
|
|
</Text>
|
|
<Stack gap={1.5}>
|
|
<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>
|
|
</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>
|
|
</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>
|
|
</Stack>
|
|
</Stack>
|
|
</Stack>
|
|
|
|
<Stack gap={2}>
|
|
<Text size="xs" weight="bold" color="text-gray-500" transform="uppercase"
|
|
// eslint-disable-next-line gridpilot-rules/component-classification
|
|
className="tracking-wide"
|
|
block
|
|
>
|
|
Features
|
|
</Text>
|
|
<Stack gap={1.5}>
|
|
<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>
|
|
</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>
|
|
</Stack>
|
|
</Stack>
|
|
</Stack>
|
|
</Stack>
|
|
</InfoFlyout>
|
|
</Stack>
|
|
|
|
{errors?.visibility && (
|
|
<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>
|
|
</Stack>
|
|
)}
|
|
|
|
{/* Contextual info based on selection */}
|
|
<Stack
|
|
rounded="xl"
|
|
p={5}
|
|
border
|
|
transition
|
|
bg={isRanked ? 'bg-primary-blue/5' : 'bg-neon-aqua/5'}
|
|
borderColor={isRanked ? 'border-primary-blue/20' : 'border-neon-aqua/20'}
|
|
>
|
|
<Stack display="flex" alignItems="start" gap={3}>
|
|
{isRanked ? (
|
|
<>
|
|
<Icon icon={Trophy} size={5} color="text-primary-blue" flexShrink={0} mt={0.5} />
|
|
<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>
|
|
</Stack>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Icon icon={Users} size={5} color="text-neon-aqua" flexShrink={0} mt={0.5} />
|
|
<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>
|
|
</Stack>
|
|
</>
|
|
)}
|
|
</Stack>
|
|
</Stack>
|
|
</Stack>
|
|
);
|
|
}
|