167 lines
5.5 KiB
TypeScript
167 lines
5.5 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 { Heading } from '@/ui/Heading';
|
|
import { Input } from '@/ui/Input';
|
|
import { Button } from '@/ui/Button';
|
|
import { Group } from '@/ui/Group';
|
|
import { Grid } from '@/ui/Grid';
|
|
import { Container } from '@/ui/Container';
|
|
import { Text } from '@/ui/Text';
|
|
import { Icon } from '@/ui/Icon';
|
|
import { Section } from '@/ui/Section';
|
|
import { StatusDot } from '@/ui/StatusDot';
|
|
import {
|
|
Plus,
|
|
Search,
|
|
Trophy,
|
|
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 (
|
|
<Container size="xl" py={12}>
|
|
<Group direction="column" gap={16} fullWidth>
|
|
{/* Hero */}
|
|
<Group direction={{ base: 'column', md: 'row' } as any} align={{ base: 'start', md: 'end' } as any} justify="between" gap={8} fullWidth>
|
|
<Group direction="column" gap={4}>
|
|
<Group direction="row" align="center" gap={3}>
|
|
<Icon icon={Trophy} size={6} intent="primary" />
|
|
<Text size="xs" weight="bold" uppercase letterSpacing="widest" color="text-primary-accent">Competition Hub</Text>
|
|
</Group>
|
|
<Heading level={1} size="5xl" weight="bold">
|
|
Find Your <Text as="span" color="text-primary-accent">Grid</Text>
|
|
</Heading>
|
|
<Text variant="low" maxWidth="md">
|
|
From casual sprints to epic endurance battles — discover the perfect league for your racing style.
|
|
</Text>
|
|
</Group>
|
|
|
|
<Group direction="row" align="center" gap={4}>
|
|
<Group direction="column" align="end">
|
|
<Text size="2xl" weight="bold" font="mono">{viewData.leagues.length}</Text>
|
|
<Text weight="bold" variant="low" uppercase letterSpacing="widest" size="xs">Active Leagues</Text>
|
|
</Group>
|
|
<StatusDot intent="telemetry" size="lg" />
|
|
<Button
|
|
onClick={onCreateLeague}
|
|
variant="primary"
|
|
size="lg"
|
|
icon={<Plus size={16} />}
|
|
>
|
|
Create League
|
|
</Button>
|
|
</Group>
|
|
</Group>
|
|
|
|
{/* Search & Filters */}
|
|
<Group direction="column" gap={8} fullWidth>
|
|
<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} />}
|
|
/>
|
|
|
|
<Group direction="row" wrap gap={2} fullWidth>
|
|
{categories.map((category) => {
|
|
const isActive = activeCategory === category.id;
|
|
const CategoryIcon = category.icon;
|
|
return (
|
|
<Button
|
|
key={category.id}
|
|
onClick={() => onCategoryChange(category.id)}
|
|
variant={isActive ? 'primary' : 'secondary'}
|
|
size="sm"
|
|
icon={<CategoryIcon size={14} />}
|
|
>
|
|
{category.label}
|
|
</Button>
|
|
);
|
|
})}
|
|
</Group>
|
|
</Group>
|
|
|
|
{/* Grid */}
|
|
<Group direction="column" fullWidth>
|
|
{filteredLeagues.length > 0 ? (
|
|
<Grid cols={{ 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)}
|
|
/>
|
|
))}
|
|
</Grid>
|
|
) : (
|
|
<Section variant="dark" padding="lg">
|
|
<Group direction="column" align="center" justify="center" fullWidth>
|
|
<Icon icon={Search} size={12} intent="low" />
|
|
<Heading level={3} weight="bold">No Leagues Found</Heading>
|
|
<Text variant="low" size="sm">Try adjusting your search or filters</Text>
|
|
<Button
|
|
variant="secondary"
|
|
onClick={onClearFilters}
|
|
style={{ marginTop: '1.5rem' }}
|
|
>
|
|
Clear All Filters
|
|
</Button>
|
|
</Group>
|
|
</Section>
|
|
)}
|
|
</Group>
|
|
</Group>
|
|
</Container>
|
|
);
|
|
}
|