'use client'; import { Trophy, Users, Check, HelpCircle, X } from 'lucide-react'; import { useState, useRef, useEffect } from 'react'; import { createPortal } from 'react-dom'; import type { LeagueConfigFormModel } from '@core/racing/application'; // 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; } function InfoFlyout({ isOpen, onClose, title, children, anchorRef }: InfoFlyoutProps) { const [position, setPosition] = useState({ top: 0, left: 0 }); const [mounted, setMounted] = useState(false); const flyoutRef = useRef(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(
{title}
{children}
, 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(null); const unrankedInfoRef = useRef(null); // Normalize visibility to new terminology const isRanked = basics.visibility === 'ranked' || basics.visibility === 'public'; // Auto-update minDrivers when switching to ranked const handleVisibilityChange = (visibility: 'ranked' | 'unranked') => { if (!onChange) return; // If switching to ranked and current maxDrivers is below minimum, update it if (visibility === 'ranked' && form.structure.maxDrivers < MIN_RANKED_DRIVERS) { onChange({ ...form, basics: { ...form.basics, visibility }, structure: { ...form.structure, maxDrivers: MIN_RANKED_DRIVERS }, }); } else { onChange({ ...form, basics: { ...form.basics, visibility }, }); } }; return (
{/* Emotional header for the step */}

Choose your league's destiny

Will you compete for glory on the global leaderboards, or race with friends in a private series?

{/* League Type Selection */}
{/* Ranked (Public) Option */}
{/* Info button */}
{/* Ranked Info Flyout */} setShowRankedFlyout(false)} title="Ranked Leagues" anchorRef={rankedInfoRef} >

Ranked leagues are competitive series where results matter. Your performance affects your driver rating and contributes to global leaderboards.

Requirements
  • Minimum {MIN_RANKED_DRIVERS} drivers for competitive integrity
  • Anyone can discover and join your league
Benefits
  • Results affect driver ratings and rankings
  • Featured in league discovery
{/* Unranked (Private) Option */}
{/* Info button */}
{/* Unranked Info Flyout */} setShowUnrankedFlyout(false)} title="Unranked Leagues" anchorRef={unrankedInfoRef} >

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.

Perfect For
  • Private racing with friends
  • Practice and training sessions
  • Small groups (2+ drivers)
Features
  • Invite-only membership
  • Full stats and standings (internal only)
{errors?.visibility && (

{errors.visibility}

)} {/* Contextual info based on selection */}
{isRanked ? ( <>

Ready to compete

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.

) : ( <>

Private racing awaits

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.

)}
); }