diff --git a/apps/website/client-wrapper/LeaguesPageClient.tsx b/apps/website/app/leagues/LeaguesPageClient.tsx
similarity index 86%
rename from apps/website/client-wrapper/LeaguesPageClient.tsx
rename to apps/website/app/leagues/LeaguesPageClient.tsx
index 82f5379ae..d61799af8 100644
--- a/apps/website/client-wrapper/LeaguesPageClient.tsx
+++ b/apps/website/app/leagues/LeaguesPageClient.tsx
@@ -24,37 +24,35 @@ const CATEGORIES: Category[] = [
id: 'all',
label: 'All',
icon: Globe,
- description: 'Browse all available leagues',
+ description: 'All available competition infrastructure.',
filter: () => true,
},
{
id: 'popular',
label: 'Popular',
icon: Flame,
- description: 'Most active leagues right now',
+ description: 'High utilization infrastructure.',
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',
+ description: 'Recently deployed infrastructure.',
filter: (league) => {
const oneWeekAgo = new Date();
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
return new Date(league.createdAt) > oneWeekAgo;
},
- color: 'text-green-500',
},
{
id: 'openSlots',
- label: 'Open Slots',
+ label: 'Open',
icon: Target,
- description: 'Leagues with available spots',
+ description: 'Infrastructure with available capacity.',
filter: (league) => {
if (league.maxTeams && league.maxTeams > 0) {
const usedTeams = league.usedTeamSlots ?? 0;
@@ -64,41 +62,40 @@ const CATEGORIES: Category[] = [
const max = league.maxDrivers ?? 0;
return max > 0 && used < max;
},
- color: 'text-cyan-400',
},
{
id: 'driver',
label: 'Driver',
icon: Trophy,
- description: 'Compete as an individual',
+ description: 'Individual competition format.',
filter: (league) => league.scoring?.primaryChampionshipType === 'driver',
},
{
id: 'team',
label: 'Team',
icon: Users,
- description: 'Race together as a team',
+ description: 'Team-based competition format.',
filter: (league) => league.scoring?.primaryChampionshipType === 'team',
},
{
id: 'nations',
label: 'Nations',
icon: Flag,
- description: 'Represent your country',
+ description: 'National representation format.',
filter: (league) => league.scoring?.primaryChampionshipType === 'nations',
},
{
id: 'trophy',
label: 'Trophy',
icon: Award,
- description: 'Special championship events',
+ description: 'Special event infrastructure.',
filter: (league) => league.scoring?.primaryChampionshipType === 'trophy',
},
{
id: 'endurance',
label: 'Endurance',
icon: Timer,
- description: 'Long-distance racing',
+ description: 'Long-duration competition.',
filter: (league) =>
league.scoring?.scoringPresetId?.includes('endurance') ??
league.timingSummary?.includes('h Race') ??
@@ -108,7 +105,7 @@ const CATEGORIES: Category[] = [
id: 'sprint',
label: 'Sprint',
icon: Clock,
- description: 'Quick, intense races',
+ description: 'Short-duration competition.',
filter: (league) =>
(league.scoring?.scoringPresetId?.includes('sprint') ?? false) &&
!(league.scoring?.scoringPresetId?.includes('endurance') ?? false),
diff --git a/apps/website/app/leagues/page.tsx b/apps/website/app/leagues/page.tsx
index b53b5bea7..b06a7e449 100644
--- a/apps/website/app/leagues/page.tsx
+++ b/apps/website/app/leagues/page.tsx
@@ -1,5 +1,5 @@
import { notFound } from 'next/navigation';
-import { LeaguesPageClient } from '@/client-wrapper/LeaguesPageClient';
+import { LeaguesPageClient } from './LeaguesPageClient';
import { LeaguesPageQuery } from '@/lib/page-queries/LeaguesPageQuery';
export default async function Page() {
diff --git a/apps/website/components/leagues/LeagueCard.tsx b/apps/website/components/leagues/LeagueCard.tsx
index f853600fc..067956764 100644
--- a/apps/website/components/leagues/LeagueCard.tsx
+++ b/apps/website/components/leagues/LeagueCard.tsx
@@ -8,8 +8,9 @@ import { Text } from '@/ui/Text';
import { Box } from '@/ui/Box';
import { Group } from '@/ui/Group';
import { Surface } from '@/ui/Surface';
+import { Stack } from '@/ui/Stack';
import { LeagueCard as UILeagueCard, LeagueCardStats, LeagueCardFooter } from '@/ui/LeagueCard';
-import { Calendar as LucideCalendar } from 'lucide-react';
+import { Calendar, Users, Activity } from 'lucide-react';
import React, { ReactNode } from 'react';
interface LeagueCardProps {
@@ -54,80 +55,81 @@ export function LeagueCard({
coverUrl={coverUrl}
logo={
{logoUrl ? (
) : (
-
+
)}
}
badges={
-
+
{badges}
{championshipBadge}
-
+
}
>
-
-
-
- {name}
-
-
-
-
- {description || 'No description available'}
-
-
- = 90 ? 'warning' : fillPercentage >= 70 ? 'primary' : 'success'}
- />
-
- {hasOpenSlots && (
-
-
-
- {openSlotsCount} OPEN
-
-
- )}
-
-
- {timingSummary && (
-
-
-
- {timingSummary.split('•')[1]?.trim() || timingSummary}
-
+
+
+
+
+ {name}
- )}
-
+
+ {description || 'No infrastructure description provided.'}
+
+
+
+
+ = 90 ? 'warning' : fillPercentage >= 70 ? 'primary' : 'success'}
+ />
+
+ {hasOpenSlots && (
+
+
+
+ {openSlotsCount} slots available
+
+
+ )}
+
+
+
+
+ {timingSummary && (
+
+
+
+ {timingSummary.split('•')[1]?.trim() || timingSummary}
+
+
+ )}
+
+
+
+ {usedSlots} active participants
+
+
+
+
+
);
}
diff --git a/apps/website/components/leagues/LeagueCardWrapper.tsx b/apps/website/components/leagues/LeagueCardWrapper.tsx
index f51bd8022..a2d435b0a 100644
--- a/apps/website/components/leagues/LeagueCardWrapper.tsx
+++ b/apps/website/components/leagues/LeagueCardWrapper.tsx
@@ -5,6 +5,8 @@ import {
Flag,
Award,
Sparkles,
+ Gamepad2,
+ Layers,
} from 'lucide-react';
import type { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel';
import { getMediaUrl } from '@/lib/utilities/media';
@@ -152,24 +154,24 @@ export function LeagueCard({ league, onClick }: LeagueCardProps) {
badges={
<>
{isNew && (
-
+
NEW
)}
{league.scoring?.gameName && (
-
+
{league.scoring.gameName}
)}
{league.category && (
-
+
{categoryLabel}
)}
>
}
championshipBadge={
-
+
{championshipLabel}
}
diff --git a/apps/website/templates/LeaguesTemplate.tsx b/apps/website/templates/LeaguesTemplate.tsx
index 5de2d90d4..83118d228 100644
--- a/apps/website/templates/LeaguesTemplate.tsx
+++ b/apps/website/templates/LeaguesTemplate.tsx
@@ -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 (
-
-
- {/* Hero */}
-
-
-
-
- Competition Hub
-
-
- Find Your Grid
-
-
- From casual sprints to epic endurance battles — discover the perfect league for your racing style.
-
-
-
-
-
- {viewData.leagues.length}
- Active Leagues
-
-
+
+
+ {/* Header Section */}
+
Create League
-
-
+ }
+ />
- {/* Search & Filters */}
-
- ) => onSearchChange(e.target.value)}
- icon={}
+ {/* Stats Overview */}
+
+
+ acc + (l.usedDriverSlots ?? 0), 0)}
+ icon={Trophy}
+ intent="primary"
+ />
+ acc + Math.max(0, (l.maxDrivers ?? 0) - (l.usedDriverSlots ?? 0)), 0)}
+ icon={Trophy}
+ intent="success"
+ />
+
-
- {categories.map((category) => {
- const isActive = activeCategory === category.id;
- const CategoryIcon = category.icon;
- return (
-
- );
- })}
-
-
+ {/* Control Bar */}
+
+
+ ({
+ id: c.id,
+ label: c.label,
+ icon:
+ }))}
+ activeId={activeCategory}
+ onChange={(id) => onCategoryChange(id as CategoryId)}
+ />
+
+ }
+ >
+
+ ) => onSearchChange(e.target.value)}
+ icon={}
+ size="sm"
+ />
+
+
- {/* Grid */}
-
+ {/* Results */}
+
{filteredLeagues.length > 0 ? (
{filteredLeagues.map((league) => (
@@ -145,22 +157,23 @@ export function LeaguesTemplate({
) : (
-
+
- No Leagues Found
- Try adjusting your search or filters
+
+ No results found
+ Adjust filters to find matching infrastructure.
+
-
+
)}
-
-
+
+
);
}
diff --git a/apps/website/ui/LeagueCard.tsx b/apps/website/ui/LeagueCard.tsx
index 071be6783..0f8f10b7d 100644
--- a/apps/website/ui/LeagueCard.tsx
+++ b/apps/website/ui/LeagueCard.tsx
@@ -5,6 +5,8 @@ import { Card } from './Card';
import { Icon } from './Icon';
import { Image } from './Image';
import { Text } from './Text';
+import { Stack } from './Stack';
+import { Group } from './Group';
export interface LeagueCardProps {
children: ReactNode;
@@ -17,23 +19,37 @@ export interface LeagueCardProps {
export const LeagueCard = ({ children, onClick, coverUrl, logo, badges }: LeagueCardProps) => {
return (
-
-
-
-
- {badges}
+
+
+
+
+
+ {badges}
+
{logo && (
-
+
{logo}
)}
-
+
{children}
@@ -44,7 +60,7 @@ export interface LeagueCardStatsProps {
label: string;
value: string;
percentage: number;
- intent?: 'primary' | 'success' | 'warning';
+ intent?: 'primary' | 'success' | 'warning' | 'telemetry';
}
export const LeagueCardStats = ({ label, value, percentage, intent = 'primary' }: LeagueCardStatsProps) => {
@@ -52,17 +68,27 @@ export const LeagueCardStats = ({ label, value, percentage, intent = 'primary' }
primary: 'var(--ui-color-intent-primary)',
success: 'var(--ui-color-intent-success)',
warning: 'var(--ui-color-intent-warning)',
+ telemetry: 'var(--ui-color-intent-telemetry)',
};
return (
-
-
- {label}
- {value}
-
-
-
-
+
+
+
+ {label}
+ {value}
+
+
+
+
+
);
};
@@ -72,11 +98,19 @@ export interface LeagueCardFooterProps {
}
export const LeagueCardFooter = ({ children }: LeagueCardFooterProps) => (
-
- {children}
-
- VIEW
-
-
+
+
+
+ {children}
+
+
+ ACCESS
+
+
+
);