Files
gridpilot.gg/apps/website/templates/LeaguesTemplate.tsx
2026-01-19 02:14:53 +01:00

184 lines
6.3 KiB
TypeScript

'use client';
import { LeagueCard } from '@/components/leagues/LeagueCardWrapper';
import type { LeaguesViewData } from '@/lib/view-data/LeaguesViewData';
import { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel';
import {
SharedBox,
SharedButton,
SharedStack,
SharedText,
SharedIcon,
SharedContainer
} from '@/components/shared/UIComponents';
import { Heading } from '@/ui/Heading';
import { Input } from '@/ui/Input';
import {
Award,
Clock,
Flag,
Flame,
Globe,
Plus,
Search,
Sparkles,
Target,
Timer,
Trophy,
Users,
type LucideIcon,
} from 'lucide-react';
import React from 'react';
import { TemplateProps } from '@/lib/contracts/components/ComponentContracts';
export type CategoryId =
| 'all'
| 'driver'
| 'team'
| 'nations'
| 'trophy'
| 'new'
| 'popular'
| 'openSlots'
| 'endurance'
| 'sprint';
export interface Category {
id: CategoryId;
label: string;
icon: LucideIcon;
description: string;
filter: (league: LeaguesViewData['leagues'][number]) => boolean;
color?: string;
}
interface LeaguesTemplateProps extends TemplateProps<LeaguesViewData> {
searchQuery: string;
onSearchChange: (query: string) => void;
activeCategory: CategoryId;
onCategoryChange: (id: CategoryId) => void;
filteredLeagues: LeaguesViewData['leagues'];
categories: Category[];
onCreateLeague: () => void;
onLeagueClick: (id: string) => void;
onClearFilters: () => void;
}
export function LeaguesTemplate({
viewData,
searchQuery,
onSearchChange,
activeCategory,
onCategoryChange,
filteredLeagues,
categories,
onCreateLeague,
onLeagueClick,
onClearFilters,
}: LeaguesTemplateProps) {
return (
<SharedBox minHeight="screen" bg="zinc-950" color="text-zinc-200">
<SharedBox maxWidth="7xl" mx="auto" px={{ base: 4, sm: 6, lg: 8 }} py={12}>
{/* Hero */}
<SharedBox as="header" display="flex" flexDirection={{ base: 'col', md: 'row' }} alignItems={{ base: 'start', md: 'end' }} justifyContent="between" gap={8} mb={16}>
<SharedStack gap={4}>
<SharedBox display="flex" alignItems="center" gap={3} color="text-blue-500">
<Trophy size={24} />
<SharedText fontSize="xs" weight="bold" uppercase letterSpacing="widest">Competition Hub</SharedText>
</SharedBox>
<Heading level={1} fontSize="5xl" weight="bold" color="text-white">
Find Your <SharedText as="span" color="text-blue-500">Grid</SharedText>
</Heading>
<SharedText color="text-zinc-400" maxWidth="md" leading="relaxed">
From casual sprints to epic endurance battles discover the perfect league for your racing style.
</SharedText>
</SharedStack>
<SharedBox display="flex" alignItems="center" gap={4}>
<SharedBox display="flex" flexDirection="col" alignItems="end">
<SharedText fontSize="2xl" weight="bold" color="text-white" font="mono">{viewData.leagues.length}</SharedText>
<SharedText weight="bold" color="text-zinc-500" uppercase letterSpacing="widest" fontSize="10px">Active Leagues</SharedText>
</SharedBox>
<SharedBox w="px" h="8" bg="zinc-800" />
<SharedButton
onClick={onCreateLeague}
variant="primary"
size="lg"
>
<SharedStack direction="row" align="center" gap={2}>
<Plus size={16} />
Create League
</SharedStack>
</SharedButton>
</SharedBox>
</SharedBox>
{/* Search & Filters */}
<SharedBox as="section" display="flex" flexDirection="col" gap={8} mb={12}>
<Input
type="text"
placeholder="Search leagues by name, description, or game..."
value={searchQuery}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onSearchChange(e.target.value)}
icon={<Search size={20} />}
/>
<SharedBox as="nav" display="flex" flexWrap="wrap" gap={2}>
{categories.map((category) => {
const isActive = activeCategory === category.id;
const CategoryIcon = category.icon;
return (
<SharedButton
key={category.id}
onClick={() => onCategoryChange(category.id)}
variant={isActive ? 'primary' : 'secondary'}
size="sm"
>
<SharedStack direction="row" align="center" gap={2}>
<SharedBox
color={!isActive && category.color ? category.color : undefined}
>
<CategoryIcon size={14} />
</SharedBox>
<SharedText>{category.label}</SharedText>
</SharedStack>
</SharedButton>
);
})}
</SharedBox>
</SharedBox>
{/* Grid */}
<SharedBox as="main">
{filteredLeagues.length > 0 ? (
<SharedBox display="grid" responsiveGridCols={{ base: 1, md: 2, lg: 3 }} gap={6}>
{filteredLeagues.map((league) => (
<LeagueCard
key={league.id}
league={league as unknown as LeagueSummaryViewModel}
onClick={() => onLeagueClick(league.id)}
/>
))}
</SharedBox>
) : (
<SharedBox display="flex" flexDirection="col" alignItems="center" justifyContent="center" py={24} border borderStyle="dashed" borderColor="zinc-800" bg="zinc-900/20">
<SharedBox color="text-zinc-800" mb={4}>
<Search size={48} />
</SharedBox>
<Heading level={3} fontSize="xl" weight="bold" color="text-zinc-500">No Leagues Found</Heading>
<SharedText color="text-zinc-600" size="sm" mt={2}>Try adjusting your search or filters</SharedText>
<SharedButton
variant="ghost"
style={{ marginTop: '1.5rem' }}
onClick={onClearFilters}
>
Clear All Filters
</SharedButton>
</SharedBox>
)}
</SharedBox>
</SharedBox>
</SharedBox>
);
}