'use client'; import { useState, useEffect, useRef, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { Trophy, Users, Globe, Award, Search, Plus, ChevronLeft, ChevronRight, Sparkles, Flag, Filter, Gamepad2, Flame, Clock, Zap, Target, Star, TrendingUp, Calendar, Timer, Car, } from 'lucide-react'; import LeagueCard from '@/components/leagues/LeagueCard'; import Button from '@/components/ui/Button'; import Card from '@/components/ui/Card'; import Input from '@/components/ui/Input'; import Heading from '@/components/ui/Heading'; import type { LeagueSummaryDTO } from '@gridpilot/racing/application/dto/LeagueSummaryDTO'; import { getGetAllLeaguesWithCapacityAndScoringUseCase } from '@/lib/di-container'; // ============================================================================ // TYPES // ============================================================================ type CategoryId = | 'all' | 'driver' | 'team' | 'nations' | 'trophy' | 'new' | 'popular' | 'iracing' | 'acc' | 'f1' | 'endurance' | 'sprint' | 'openSlots'; interface Category { id: CategoryId; label: string; icon: React.ElementType; description: string; filter: (league: LeagueSummaryDTO) => boolean; color?: string; } // ============================================================================ // DEMO LEAGUES DATA // ============================================================================ // ============================================================================ // CATEGORIES // ============================================================================ const CATEGORIES: Category[] = [ { id: 'all', label: 'All', icon: Globe, description: 'Browse all available leagues', filter: () => true, }, { id: 'popular', label: 'Popular', icon: Flame, description: 'Most active leagues right now', filter: (league) => { const fillRate = (league.usedDriverSlots ?? 0) / (league.maxDrivers ?? 1); return fillRate > 0.7; }, color: 'text-orange-400', }, { id: 'new', label: 'New', icon: Sparkles, description: 'Fresh leagues looking for members', filter: (league) => { const oneWeekAgo = new Date(); oneWeekAgo.setDate(oneWeekAgo.getDate() - 7); return new Date(league.createdAt) > oneWeekAgo; }, color: 'text-performance-green', }, { id: 'openSlots', label: 'Open Slots', icon: Target, description: 'Leagues with available spots', filter: (league) => { // Check for team slots if it's a team league if (league.maxTeams && league.maxTeams > 0) { const usedTeams = league.usedTeamSlots ?? 0; return usedTeams < league.maxTeams; } // Otherwise check driver slots const used = league.usedDriverSlots ?? 0; const max = league.maxDrivers ?? 0; return max > 0 && used < max; }, color: 'text-neon-aqua', }, { id: 'driver', label: 'Driver', icon: Trophy, description: 'Compete as an individual', filter: (league) => league.scoring?.primaryChampionshipType === 'driver', }, { id: 'team', label: 'Team', icon: Users, description: 'Race together as a team', filter: (league) => league.scoring?.primaryChampionshipType === 'team', }, { id: 'nations', label: 'Nations', icon: Flag, description: 'Represent your country', filter: (league) => league.scoring?.primaryChampionshipType === 'nations', }, { id: 'trophy', label: 'Trophy', icon: Award, description: 'Special championship events', filter: (league) => league.scoring?.primaryChampionshipType === 'trophy', }, { id: 'endurance', label: 'Endurance', icon: Timer, description: 'Long-distance racing', filter: (league) => league.scoring?.scoringPresetId?.includes('endurance') ?? league.timingSummary?.includes('h Race') ?? false, }, { id: 'sprint', label: 'Sprint', icon: Clock, description: 'Quick, intense races', filter: (league) => (league.scoring?.scoringPresetId?.includes('sprint') ?? false) && !(league.scoring?.scoringPresetId?.includes('endurance') ?? false), }, ]; // ============================================================================ // LEAGUE SLIDER COMPONENT // ============================================================================ interface LeagueSliderProps { title: string; icon: React.ElementType; description: string; leagues: LeagueSummaryDTO[]; onLeagueClick: (id: string) => void; autoScroll?: boolean; iconColor?: string; scrollSpeedMultiplier?: number; scrollDirection?: 'left' | 'right'; } function LeagueSlider({ title, icon: Icon, description, leagues, onLeagueClick, autoScroll = true, iconColor = 'text-primary-blue', scrollSpeedMultiplier = 1, scrollDirection = 'right', }: LeagueSliderProps) { const scrollRef = useRef(null); const [canScrollLeft, setCanScrollLeft] = useState(false); const [canScrollRight, setCanScrollRight] = useState(true); const [isHovering, setIsHovering] = useState(false); const animationRef = useRef(null); const scrollPositionRef = useRef(0); const checkScrollButtons = useCallback(() => { if (scrollRef.current) { const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current; setCanScrollLeft(scrollLeft > 0); setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 10); } }, []); const scroll = useCallback((direction: 'left' | 'right') => { if (scrollRef.current) { const cardWidth = 340; const scrollAmount = direction === 'left' ? -cardWidth : cardWidth; // Update the ref so auto-scroll continues from new position scrollPositionRef.current = scrollRef.current.scrollLeft + scrollAmount; scrollRef.current.scrollBy({ left: scrollAmount, behavior: 'smooth' }); } }, []); // Initialize scroll position for left-scrolling sliders useEffect(() => { if (scrollDirection === 'left' && scrollRef.current) { const { scrollWidth, clientWidth } = scrollRef.current; scrollPositionRef.current = scrollWidth - clientWidth; scrollRef.current.scrollLeft = scrollPositionRef.current; } }, [scrollDirection, leagues.length]); // Smooth continuous auto-scroll using requestAnimationFrame with variable speed and direction useEffect(() => { // Allow scroll even with just 2 leagues (minimum threshold = 1) if (!autoScroll || leagues.length <= 1) return; const scrollContainer = scrollRef.current; if (!scrollContainer) return; let lastTimestamp = 0; // Base speed with multiplier for variation between sliders const baseSpeed = 0.025; const scrollSpeed = baseSpeed * scrollSpeedMultiplier; const directionMultiplier = scrollDirection === 'left' ? -1 : 1; const animate = (timestamp: number) => { if (!isHovering && scrollContainer) { const delta = lastTimestamp ? timestamp - lastTimestamp : 0; lastTimestamp = timestamp; scrollPositionRef.current += scrollSpeed * delta * directionMultiplier; const { scrollWidth, clientWidth } = scrollContainer; const maxScroll = scrollWidth - clientWidth; // Handle wrap-around for both directions if (scrollDirection === 'right' && scrollPositionRef.current >= maxScroll) { scrollPositionRef.current = 0; } else if (scrollDirection === 'left' && scrollPositionRef.current <= 0) { scrollPositionRef.current = maxScroll; } scrollContainer.scrollLeft = scrollPositionRef.current; } else { lastTimestamp = timestamp; } animationRef.current = requestAnimationFrame(animate); }; animationRef.current = requestAnimationFrame(animate); return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, [autoScroll, leagues.length, isHovering, scrollSpeedMultiplier, scrollDirection]); // Sync scroll position when user manually scrolls useEffect(() => { const scrollContainer = scrollRef.current; if (!scrollContainer) return; const handleScroll = () => { scrollPositionRef.current = scrollContainer.scrollLeft; checkScrollButtons(); }; scrollContainer.addEventListener('scroll', handleScroll); return () => scrollContainer.removeEventListener('scroll', handleScroll); }, [checkScrollButtons]); if (leagues.length === 0) return null; return (
{/* Section header */}

{title}

{description}

{leagues.length}
{/* Navigation arrows */}
{/* Scrollable container with fade edges */}
{/* Left fade gradient */}
{/* Right fade gradient */}
setIsHovering(true)} onMouseLeave={() => setIsHovering(false)} className="flex gap-4 overflow-x-auto pb-4 px-4" style={{ scrollbarWidth: 'none', msOverflowStyle: 'none', }} > {leagues.map((league) => (
onLeagueClick(league.id)} />
))}
); } // ============================================================================ // MAIN PAGE COMPONENT // ============================================================================ export default function LeaguesPage() { const router = useRouter(); const [realLeagues, setRealLeagues] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [activeCategory, setActiveCategory] = useState('all'); const [showFilters, setShowFilters] = useState(false); useEffect(() => { loadLeagues(); }, []); const loadLeagues = async () => { try { const useCase = getGetAllLeaguesWithCapacityAndScoringUseCase(); await useCase.execute(); const presenter = useCase.presenter as unknown as { getViewModel(): { leagues: LeagueSummaryDTO[] }; }; const viewModel = presenter.getViewModel(); setRealLeagues(viewModel.leagues); } catch (error) { console.error('Failed to load leagues:', error); } finally { setLoading(false); } }; // Use only real leagues from repository const leagues = realLeagues; const handleLeagueClick = (leagueId: string) => { // Navigate to league - all leagues are clickable router.push(`/leagues/${leagueId}`); }; // Filter by search query const searchFilteredLeagues = leagues.filter((league) => { if (!searchQuery) return true; const query = searchQuery.toLowerCase(); return ( league.name.toLowerCase().includes(query) || (league.description ?? '').toLowerCase().includes(query) || (league.scoring?.gameName ?? '').toLowerCase().includes(query) ); }); // Get leagues for active category const activeCategoryData = CATEGORIES.find((c) => c.id === activeCategory); const categoryFilteredLeagues = activeCategoryData ? searchFilteredLeagues.filter(activeCategoryData.filter) : searchFilteredLeagues; // Group leagues by category for slider view const leaguesByCategory = CATEGORIES.reduce( (acc, category) => { acc[category.id] = searchFilteredLeagues.filter(category.filter); return acc; }, {} as Record, ); // Featured categories to show as sliders with different scroll speeds and alternating directions const featuredCategoriesWithSpeed: { id: CategoryId; speed: number; direction: 'left' | 'right' }[] = [ { id: 'popular', speed: 1.0, direction: 'right' }, { id: 'new', speed: 1.3, direction: 'left' }, { id: 'driver', speed: 0.8, direction: 'right' }, { id: 'team', speed: 1.1, direction: 'left' }, { id: 'nations', speed: 0.9, direction: 'right' }, { id: 'endurance', speed: 0.7, direction: 'left' }, { id: 'sprint', speed: 1.2, direction: 'right' }, ]; if (loading) { return (

Loading leagues...

); } return (
{/* Hero Section */}
{/* Background decoration */}
Find Your Grid

From casual sprints to epic endurance battles — discover the perfect league for your racing style.

{/* Stats */}
{leagues.length} active leagues
{leaguesByCategory.new.length} new this week
{leaguesByCategory.openSlots.length} with open slots
{/* CTA */}

Set up your own racing series

{/* Search and Filter Bar */}
{/* Search */}
setSearchQuery(e.target.value)} className="pl-11" />
{/* Filter toggle (mobile) */}
{/* Category Tabs */}
{CATEGORIES.map((category) => { const Icon = category.icon; const count = leaguesByCategory[category.id].length; const isActive = activeCategory === category.id; return ( ); })}
{/* Content */} {leagues.length === 0 ? ( /* Empty State */
No leagues yet

Be the first to create a racing series. Start your own league and invite drivers to compete for glory.

) : activeCategory === 'all' && !searchQuery ? ( /* Slider View - Show featured categories with sliders at different speeds and directions */
{featuredCategoriesWithSpeed .map(({ id, speed, direction }) => { const category = CATEGORIES.find((c) => c.id === id)!; return { category, speed, direction }; }) .filter(({ category }) => leaguesByCategory[category.id].length > 0) .map(({ category, speed, direction }) => ( ))}
) : ( /* Grid View - Filtered by category or search */
{categoryFilteredLeagues.length > 0 ? ( <>

Showing {categoryFilteredLeagues.length}{' '} {categoryFilteredLeagues.length === 1 ? 'league' : 'leagues'} {searchQuery && ( {' '} for "{searchQuery}" )}

{categoryFilteredLeagues.map((league) => ( handleLeagueClick(league.id)} /> ))}
) : (

No leagues found{searchQuery ? ` matching "${searchQuery}"` : ' in this category'}

)}
)}
); }