website refactor

This commit is contained in:
2026-01-20 01:30:17 +01:00
parent 30a31dc44f
commit 3556db494f
6 changed files with 210 additions and 162 deletions

View File

@@ -3,7 +3,7 @@
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 { PageHeader } from '@/ui/PageHeader';
import { Input } from '@/ui/Input';
import { Button } from '@/ui/Button';
import { Group } from '@/ui/Group';
@@ -12,11 +12,16 @@ 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 { ControlBar } from '@/ui/ControlBar';
import { SegmentedControl } from '@/ui/SegmentedControl';
import { MetricCard } from '@/ui/MetricCard';
import { Stack } from '@/ui/Stack';
import { Box } from '@/ui/Box';
import {
Plus,
Search,
Trophy,
Filter,
type LucideIcon,
} from 'lucide-react';
import React from 'react';
@@ -68,29 +73,14 @@ export function LeaguesTemplate({
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" />
<Container size="xl" py={8}>
<Stack gap={8}>
{/* Header Section */}
<PageHeader
icon={Trophy}
title="Leagues"
description="Infrastructure for competitive sim racing."
action={
<Button
onClick={onCreateLeague}
variant="primary"
@@ -99,40 +89,62 @@ export function LeaguesTemplate({
>
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} />}
{/* Stats Overview */}
<Grid cols={{ base: 1, md: 3 }} gap={4}>
<MetricCard
label="Active Leagues"
value={viewData.leagues.length}
icon={Trophy}
intent="telemetry"
/>
<MetricCard
label="Total Drivers"
value={viewData.leagues.reduce((acc, l) => acc + (l.usedDriverSlots ?? 0), 0)}
icon={Trophy}
intent="primary"
/>
<MetricCard
label="Open Slots"
value={viewData.leagues.reduce((acc, l) => acc + Math.max(0, (l.maxDrivers ?? 0) - (l.usedDriverSlots ?? 0)), 0)}
icon={Trophy}
intent="success"
/>
</Grid>
<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>
{/* Control Bar */}
<ControlBar
leftContent={
<Group gap={4} align="center">
<Icon icon={Filter} size={4} intent="low" />
<SegmentedControl
options={categories.map(c => ({
id: c.id,
label: c.label,
icon: <Icon icon={c.icon} size={3} />
}))}
activeId={activeCategory}
onChange={(id) => onCategoryChange(id as CategoryId)}
/>
</Group>
}
>
<Box width="300px">
<Input
type="text"
placeholder="Search infrastructure..."
value={searchQuery}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onSearchChange(e.target.value)}
icon={<Search size={16} />}
size="sm"
/>
</Box>
</ControlBar>
{/* Grid */}
<Group direction="column" fullWidth>
{/* Results */}
<Stack gap={6}>
{filteredLeagues.length > 0 ? (
<Grid cols={{ base: 1, md: 2, lg: 3 }} gap={6}>
{filteredLeagues.map((league) => (
@@ -145,22 +157,23 @@ export function LeaguesTemplate({
</Grid>
) : (
<Section variant="dark" padding="lg">
<Group direction="column" align="center" justify="center" fullWidth>
<Stack align="center" justify="center" gap={4}>
<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>
<Stack align="center" gap={1}>
<Text size="lg" weight="bold">No results found</Text>
<Text variant="low" size="sm">Adjust filters to find matching infrastructure.</Text>
</Stack>
<Button
variant="secondary"
onClick={onClearFilters}
style={{ marginTop: '1.5rem' }}
>
Clear All Filters
Reset Filters
</Button>
</Group>
</Stack>
</Section>
)}
</Group>
</Group>
</Stack>
</Stack>
</Container>
);
}