website refactor

This commit is contained in:
2026-01-14 02:02:24 +01:00
parent 8d7c709e0c
commit 4522d41aef
291 changed files with 12763 additions and 9309 deletions

View File

@@ -23,6 +23,7 @@ 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 { LeaguesViewData } from '@/lib/view-data/LeaguesViewData';
import type { LeagueSummaryViewModel } from '@/lib/view-models/LeagueSummaryViewModel';
// ============================================================================
@@ -49,7 +50,7 @@ interface Category {
label: string;
icon: React.ElementType;
description: string;
filter: (league: LeagueSummaryViewModel) => boolean;
filter: (league: LeaguesViewData['leagues'][number]) => boolean;
color?: string;
}
@@ -57,17 +58,15 @@ interface LeagueSliderProps {
title: string;
icon: React.ElementType;
description: string;
leagues: LeagueSummaryViewModel[];
leagues: LeaguesViewData['leagues'];
autoScroll?: boolean;
iconColor?: string;
scrollSpeedMultiplier?: number;
scrollDirection?: 'left' | 'right';
}
import Link from 'next/link';
interface LeaguesTemplateProps {
data: LeagueSummaryViewModel[];
data: LeaguesViewData;
}
// ============================================================================
@@ -367,13 +366,36 @@ function LeagueSlider({
display: none;
}
`}</style>
{leagues.map((league) => (
<div key={league.id} className="flex-shrink-0 w-[320px] h-full">
<Link href={`/leagues/${league.id}`} className="block h-full">
<LeagueCard league={league} />
</Link>
</div>
))}
{leagues.map((league) => {
// Convert ViewData to ViewModel for LeagueCard
const viewModel: LeagueSummaryViewModel = {
id: league.id,
name: league.name,
description: league.description ?? '',
logoUrl: league.logoUrl,
ownerId: league.ownerId,
createdAt: league.createdAt,
maxDrivers: league.maxDrivers,
usedDriverSlots: league.usedDriverSlots,
maxTeams: league.maxTeams ?? 0,
usedTeamSlots: league.usedTeamSlots ?? 0,
structureSummary: league.structureSummary,
timingSummary: league.timingSummary,
category: league.category ?? undefined,
scoring: league.scoring ? {
...league.scoring,
primaryChampionshipType: league.scoring.primaryChampionshipType as 'driver' | 'team' | 'nations' | 'trophy',
} : undefined,
};
return (
<div key={league.id} className="flex-shrink-0 w-[320px] h-full">
<a href={`/leagues/${league.id}`} className="block h-full">
<LeagueCard league={viewModel} />
</a>
</div>
);
})}
</div>
</div>
</div>
@@ -392,7 +414,7 @@ export function LeaguesTemplate({
const [showFilters, setShowFilters] = useState(false);
// Filter by search query
const searchFilteredLeagues = data.filter((league: LeagueSummaryViewModel) => {
const searchFilteredLeagues = data.leagues.filter((league) => {
if (!searchQuery) return true;
const query = searchQuery.toLowerCase();
return (
@@ -412,7 +434,7 @@ export function LeaguesTemplate({
const leaguesByCategory = CATEGORIES.reduce(
(acc, category) => {
// First try to use the dedicated category field, fall back to scoring-based filtering
acc[category.id] = searchFilteredLeagues.filter((league: LeagueSummaryViewModel) => {
acc[category.id] = searchFilteredLeagues.filter((league) => {
// If league has a category field, use it directly
if (league.category) {
return league.category === category.id;
@@ -422,7 +444,7 @@ export function LeaguesTemplate({
});
return acc;
},
{} as Record<CategoryId, LeagueSummaryViewModel[]>,
{} as Record<CategoryId, LeaguesViewData['leagues']>,
);
// Featured categories to show as sliders with different scroll speeds and alternating directions
@@ -463,7 +485,7 @@ export function LeaguesTemplate({
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-performance-green animate-pulse" />
<span className="text-sm text-gray-400">
<span className="text-white font-semibold">{data.length}</span> active leagues
<span className="text-white font-semibold">{data.leagues.length}</span> active leagues
</span>
</div>
<div className="flex items-center gap-2">
@@ -483,10 +505,10 @@ export function LeaguesTemplate({
{/* CTA */}
<div className="flex flex-col gap-4">
<Link href="/leagues/create" className="flex items-center gap-2 px-6 py-3 bg-primary-blue text-white rounded-lg hover:bg-blue-600 transition-colors">
<a href="/leagues/create" className="flex items-center gap-2 px-6 py-3 bg-primary-blue text-white rounded-lg hover:bg-blue-600 transition-colors">
<Plus className="w-5 h-5" />
<span>Create League</span>
</Link>
</a>
<p className="text-xs text-gray-500 text-center">Set up your own racing series</p>
</div>
</div>
@@ -553,7 +575,7 @@ export function LeaguesTemplate({
</div>
{/* Content */}
{data.length === 0 ? (
{data.leagues.length === 0 ? (
/* Empty State */
<Card className="text-center py-16">
<div className="max-w-md mx-auto">
@@ -566,10 +588,10 @@ export function LeaguesTemplate({
<p className="text-gray-400 mb-8">
Be the first to create a racing series. Start your own league and invite drivers to compete for glory.
</p>
<Link href="/leagues/create" className="inline-flex items-center gap-2 px-6 py-3 bg-primary-blue text-white rounded-lg hover:bg-blue-600 transition-colors">
<a href="/leagues/create" className="inline-flex items-center gap-2 px-6 py-3 bg-primary-blue text-white rounded-lg hover:bg-blue-600 transition-colors">
<Sparkles className="w-4 h-4" />
Create Your First League
</Link>
</a>
</div>
</Card>
) : activeCategory === 'all' && !searchQuery ? (
@@ -613,11 +635,34 @@ export function LeaguesTemplate({
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{categoryFilteredLeagues.map((league) => (
<Link key={league.id} href={`/leagues/${league.id}`} className="block h-full">
<LeagueCard league={league} />
</Link>
))}
{categoryFilteredLeagues.map((league) => {
// Convert ViewData to ViewModel for LeagueCard
const viewModel: LeagueSummaryViewModel = {
id: league.id,
name: league.name,
description: league.description ?? '',
logoUrl: league.logoUrl,
ownerId: league.ownerId,
createdAt: league.createdAt,
maxDrivers: league.maxDrivers,
usedDriverSlots: league.usedDriverSlots,
maxTeams: league.maxTeams ?? 0,
usedTeamSlots: league.usedTeamSlots ?? 0,
structureSummary: league.structureSummary,
timingSummary: league.timingSummary,
category: league.category ?? undefined,
scoring: league.scoring ? {
...league.scoring,
primaryChampionshipType: league.scoring.primaryChampionshipType as 'driver' | 'team' | 'nations' | 'trophy',
} : undefined,
};
return (
<a key={league.id} href={`/leagues/${league.id}`} className="block h-full">
<LeagueCard league={viewModel} />
</a>
);
})}
</div>
</>
) : (