fix data flow issues

This commit is contained in:
2025-12-19 23:18:53 +01:00
parent ec177a75ce
commit 5c74837d73
45 changed files with 2726 additions and 746 deletions

View File

@@ -1,11 +1,14 @@
'use client';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { motion, useReducedMotion } from 'framer-motion';
import Link from 'next/link';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import { siteConfig } from '@/lib/siteConfig';
import { AvailableLeaguesViewModel } from '@/lib/view-models/AvailableLeaguesViewModel';
import { SponsorService } from '@/lib/services/sponsors/SponsorService';
import { ServiceFactory } from '@/lib/services/ServiceFactory';
import {
Trophy,
Users,
@@ -38,98 +41,12 @@ interface AvailableLeague {
description: string;
}
const MOCK_AVAILABLE_LEAGUES: AvailableLeague[] = [
{
id: 'league-1',
name: 'GT3 Masters Championship',
game: 'iRacing',
drivers: 48,
avgViewsPerRace: 8200,
mainSponsorSlot: { available: true, price: 1200 },
secondarySlots: { available: 1, total: 2, price: 400 },
rating: 4.8,
tier: 'premium',
nextRace: 'Dec 20 - Spa',
seasonStatus: 'active',
description: 'Premier GT3 racing with top-tier drivers. Weekly broadcasts and active community.',
},
{
id: 'league-2',
name: 'Endurance Pro Series',
game: 'ACC',
drivers: 72,
avgViewsPerRace: 12500,
mainSponsorSlot: { available: false, price: 1500 },
secondarySlots: { available: 2, total: 2, price: 500 },
rating: 4.9,
tier: 'premium',
nextRace: 'Jan 5 - Nürburgring 24h',
seasonStatus: 'active',
description: 'Multi-class endurance racing. High engagement from dedicated endurance fans.',
},
{
id: 'league-3',
name: 'Formula Sim League',
game: 'iRacing',
drivers: 24,
avgViewsPerRace: 5400,
mainSponsorSlot: { available: true, price: 800 },
secondarySlots: { available: 2, total: 2, price: 300 },
rating: 4.5,
tier: 'standard',
nextRace: 'Dec 22 - Monza',
seasonStatus: 'active',
description: 'Open-wheel racing excellence. Competitive field with consistent racing.',
},
{
id: 'league-4',
name: 'Touring Car Masters',
game: 'rFactor 2',
drivers: 32,
avgViewsPerRace: 3200,
mainSponsorSlot: { available: true, price: 500 },
secondarySlots: { available: 2, total: 2, price: 200 },
rating: 4.2,
tier: 'starter',
nextRace: 'Jan 10 - Brands Hatch',
seasonStatus: 'upcoming',
description: 'Touring car action with close racing. Great for building brand awareness.',
},
{
id: 'league-5',
name: 'LMP Challenge',
game: 'Le Mans Ultimate',
drivers: 36,
avgViewsPerRace: 6800,
mainSponsorSlot: { available: true, price: 900 },
secondarySlots: { available: 1, total: 2, price: 350 },
rating: 4.6,
tier: 'standard',
nextRace: 'Dec 28 - Sebring',
seasonStatus: 'active',
description: 'Prototype racing at its finest. Growing community with passionate fans.',
},
{
id: 'league-6',
name: 'Rally Championship',
game: 'EA WRC',
drivers: 28,
avgViewsPerRace: 4500,
mainSponsorSlot: { available: true, price: 650 },
secondarySlots: { available: 2, total: 2, price: 250 },
rating: 4.4,
tier: 'standard',
nextRace: 'Jan 15 - Monte Carlo',
seasonStatus: 'upcoming',
description: 'Thrilling rally stages. Unique sponsorship exposure in rallying content.',
},
];
type SortOption = 'rating' | 'drivers' | 'price' | 'views';
type TierFilter = 'all' | 'premium' | 'standard' | 'starter';
type AvailabilityFilter = 'all' | 'main' | 'secondary';
function LeagueCard({ league, index }: { league: AvailableLeague; index: number }) {
function LeagueCard({ league, index }: { league: any; index: number }) {
const shouldReduceMotion = useReducedMotion();
const tierConfig = {
@@ -159,9 +76,8 @@ function LeagueCard({ league, index }: { league: AvailableLeague; index: number
completed: { color: 'text-gray-400', bg: 'bg-gray-400/10', label: 'Season Ended' },
};
const config = tierConfig[league.tier];
const status = statusConfig[league.seasonStatus];
const cpm = (league.mainSponsorSlot.price / league.avgViewsPerRace * 1000).toFixed(0);
const config = league.tierConfig;
const status = league.statusConfig;
return (
<motion.div
@@ -201,11 +117,11 @@ function LeagueCard({ league, index }: { league: AvailableLeague; index: number
<div className="text-xs text-gray-500">Drivers</div>
</div>
<div className="text-center p-2 bg-iron-gray/50 rounded-lg">
<div className="text-lg font-bold text-white">{(league.avgViewsPerRace / 1000).toFixed(1)}k</div>
<div className="text-lg font-bold text-white">{league.formattedAvgViews}</div>
<div className="text-xs text-gray-500">Avg Views</div>
</div>
<div className="text-center p-2 bg-iron-gray/50 rounded-lg">
<div className="text-lg font-bold text-performance-green">${cpm}</div>
<div className="text-lg font-bold text-performance-green">{league.formattedCpm}</div>
<div className="text-xs text-gray-500">CPM</div>
</div>
</div>
@@ -282,9 +198,50 @@ export default function SponsorLeaguesPage() {
const [tierFilter, setTierFilter] = useState<TierFilter>('all');
const [availabilityFilter, setAvailabilityFilter] = useState<AvailabilityFilter>('all');
const [sortBy, setSortBy] = useState<SortOption>('rating');
const [data, setData] = useState<AvailableLeaguesViewModel | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const loadLeagues = async () => {
try {
const sponsorService = ServiceFactory.getSponsorService();
const leaguesData = await sponsorService.getAvailableLeagues();
setData(new AvailableLeaguesViewModel(leaguesData));
} catch (err) {
console.error('Error loading leagues:', err);
setError('Failed to load leagues data');
} finally {
setLoading(false);
}
};
loadLeagues();
}, []);
if (loading) {
return (
<div className="max-w-7xl mx-auto py-8 px-4 flex items-center justify-center min-h-[400px]">
<div className="text-center">
<div className="w-8 h-8 border-2 border-primary-blue border-t-transparent rounded-full animate-spin mx-auto mb-4" />
<p className="text-gray-400">Loading leagues...</p>
</div>
</div>
);
}
if (error || !data) {
return (
<div className="max-w-7xl mx-auto py-8 px-4 flex items-center justify-center min-h-[400px]">
<div className="text-center">
<p className="text-gray-400">{error || 'No leagues data available'}</p>
</div>
</div>
);
}
// Filter and sort leagues
const filteredLeagues = MOCK_AVAILABLE_LEAGUES
const filteredLeagues = data.leagues
.filter(league => {
if (searchQuery && !league.name.toLowerCase().includes(searchQuery.toLowerCase())) {
return false;
@@ -312,13 +269,12 @@ export default function SponsorLeaguesPage() {
// Calculate summary stats
const stats = {
total: MOCK_AVAILABLE_LEAGUES.length,
mainAvailable: MOCK_AVAILABLE_LEAGUES.filter(l => l.mainSponsorSlot.available).length,
secondaryAvailable: MOCK_AVAILABLE_LEAGUES.reduce((sum, l) => sum + l.secondarySlots.available, 0),
totalDrivers: MOCK_AVAILABLE_LEAGUES.reduce((sum, l) => sum + l.drivers, 0),
total: data.leagues.length,
mainAvailable: data.leagues.filter(l => l.mainSponsorSlot.available).length,
secondaryAvailable: data.leagues.reduce((sum, l) => sum + l.secondarySlots.available, 0),
totalDrivers: data.leagues.reduce((sum, l) => sum + l.drivers, 0),
avgCpm: Math.round(
MOCK_AVAILABLE_LEAGUES.reduce((sum, l) => sum + (l.mainSponsorSlot.price / l.avgViewsPerRace * 1000), 0) /
MOCK_AVAILABLE_LEAGUES.length
data.leagues.reduce((sum, l) => sum + l.cpm, 0) / data.leagues.length
),
};
@@ -448,7 +404,7 @@ export default function SponsorLeaguesPage() {
{/* Results Count */}
<div className="flex items-center justify-between mb-6">
<p className="text-sm text-gray-400">
Showing {filteredLeagues.length} of {MOCK_AVAILABLE_LEAGUES.length} leagues
Showing {filteredLeagues.length} of {data.leagues.length} leagues
</p>
<div className="flex items-center gap-2">
<Link href="/teams">