This commit is contained in:
2025-12-10 12:38:55 +01:00
parent 0f7fe67d3c
commit fbbcf414a4
87 changed files with 11972 additions and 390 deletions

View File

@@ -0,0 +1,311 @@
'use client';
import { useState } from 'react';
import { useParams } from 'next/navigation';
import Link from 'next/link';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import {
Trophy,
Users,
Calendar,
Eye,
TrendingUp,
Download,
Image as ImageIcon,
ExternalLink,
ChevronRight
} from 'lucide-react';
interface LeagueDriver {
id: string;
name: string;
country: string;
position: number;
races: number;
impressions: number;
}
// Mock data
const MOCK_LEAGUE = {
id: 'league-1',
name: 'GT3 Pro Championship',
tier: 'main' as const,
season: 'Season 3',
drivers: 32,
races: 12,
completedRaces: 8,
impressions: 45200,
avgViewsPerRace: 5650,
logoPlacement: 'Primary hood placement + League page banner',
status: 'active' as const,
};
const MOCK_DRIVERS: LeagueDriver[] = [
{ id: 'd1', name: 'Max Verstappen', country: 'NL', position: 1, races: 8, impressions: 4200 },
{ id: 'd2', name: 'Lewis Hamilton', country: 'GB', position: 2, races: 8, impressions: 3980 },
{ id: 'd3', name: 'Charles Leclerc', country: 'MC', position: 3, races: 8, impressions: 3750 },
{ id: 'd4', name: 'Lando Norris', country: 'GB', position: 4, races: 7, impressions: 3420 },
{ id: 'd5', name: 'Carlos Sainz', country: 'ES', position: 5, races: 8, impressions: 3100 },
];
const MOCK_RACES = [
{ id: 'r1', name: 'Spa-Francorchamps', date: '2025-12-01', views: 6200, status: 'completed' },
{ id: 'r2', name: 'Monza', date: '2025-12-08', views: 5800, status: 'completed' },
{ id: 'r3', name: 'Nürburgring', date: '2025-12-15', views: 0, status: 'upcoming' },
{ id: 'r4', name: 'Suzuka', date: '2025-12-22', views: 0, status: 'upcoming' },
];
export default function SponsorLeagueDetailPage() {
const params = useParams();
const [activeTab, setActiveTab] = useState<'overview' | 'drivers' | 'races' | 'assets'>('overview');
return (
<div className="max-w-7xl mx-auto py-8 px-4">
{/* Breadcrumb */}
<div className="flex items-center gap-2 text-sm text-gray-400 mb-6">
<Link href="/sponsor/dashboard" className="hover:text-white">Dashboard</Link>
<ChevronRight className="w-4 h-4" />
<Link href="/sponsor/leagues" className="hover:text-white">Leagues</Link>
<ChevronRight className="w-4 h-4" />
<span className="text-white">{MOCK_LEAGUE.name}</span>
</div>
{/* Header */}
<div className="flex items-start justify-between mb-8">
<div>
<div className="flex items-center gap-3 mb-2">
<h1 className="text-2xl font-bold text-white">{MOCK_LEAGUE.name}</h1>
<span className="px-2 py-1 rounded text-xs font-medium bg-primary-blue/20 text-primary-blue border border-primary-blue/30">
Main Sponsor
</span>
</div>
<p className="text-gray-400">{MOCK_LEAGUE.season} {MOCK_LEAGUE.completedRaces}/{MOCK_LEAGUE.races} races completed</p>
</div>
<div className="flex items-center gap-2">
<Button variant="secondary">
<ExternalLink className="w-4 h-4 mr-2" />
View League Page
</Button>
<Button variant="primary">
<Download className="w-4 h-4 mr-2" />
Download Report
</Button>
</div>
</div>
{/* Quick Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
<Card className="p-4">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary-blue/10">
<Eye className="w-5 h-5 text-primary-blue" />
</div>
<div>
<div className="text-xl font-bold text-white">{MOCK_LEAGUE.impressions.toLocaleString()}</div>
<div className="text-sm text-gray-400">Total Impressions</div>
</div>
</div>
</Card>
<Card className="p-4">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-performance-green/10">
<TrendingUp className="w-5 h-5 text-performance-green" />
</div>
<div>
<div className="text-xl font-bold text-white">{MOCK_LEAGUE.avgViewsPerRace.toLocaleString()}</div>
<div className="text-sm text-gray-400">Avg Views/Race</div>
</div>
</div>
</Card>
<Card className="p-4">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-purple-500/10">
<Users className="w-5 h-5 text-purple-400" />
</div>
<div>
<div className="text-xl font-bold text-white">{MOCK_LEAGUE.drivers}</div>
<div className="text-sm text-gray-400">Active Drivers</div>
</div>
</div>
</Card>
<Card className="p-4">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-warning-amber/10">
<Calendar className="w-5 h-5 text-warning-amber" />
</div>
<div>
<div className="text-xl font-bold text-white">{MOCK_LEAGUE.races - MOCK_LEAGUE.completedRaces}</div>
<div className="text-sm text-gray-400">Races Remaining</div>
</div>
</div>
</Card>
</div>
{/* Tabs */}
<div className="flex gap-1 mb-6 border-b border-charcoal-outline">
{(['overview', 'drivers', 'races', 'assets'] as const).map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={`px-4 py-2 text-sm font-medium capitalize transition-colors border-b-2 -mb-px ${
activeTab === tab
? 'text-primary-blue border-primary-blue'
: 'text-gray-400 border-transparent hover:text-white'
}`}
>
{tab}
</button>
))}
</div>
{/* Tab Content */}
{activeTab === 'overview' && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card className="p-4">
<h3 className="text-lg font-semibold text-white mb-4">Sponsorship Details</h3>
<div className="space-y-3">
<div className="flex justify-between">
<span className="text-gray-400">Tier</span>
<span className="text-white">Main Sponsor</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Logo Placement</span>
<span className="text-white">{MOCK_LEAGUE.logoPlacement}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Season Duration</span>
<span className="text-white">Oct 2025 - Feb 2026</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Investment</span>
<span className="text-white">$800/season</span>
</div>
</div>
</Card>
<Card className="p-4">
<h3 className="text-lg font-semibold text-white mb-4">Performance Metrics</h3>
<div className="space-y-3">
<div className="flex justify-between">
<span className="text-gray-400">Cost per 1K Impressions</span>
<span className="text-performance-green">$17.70</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Engagement Rate</span>
<span className="text-white">4.2%</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Brand Recall Score</span>
<span className="text-white">78/100</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">ROI Estimate</span>
<span className="text-performance-green">+24%</span>
</div>
</div>
</Card>
</div>
)}
{activeTab === 'drivers' && (
<Card>
<div className="p-4 border-b border-charcoal-outline">
<h3 className="text-lg font-semibold text-white">Drivers Carrying Your Brand</h3>
<p className="text-sm text-gray-400">Top performing drivers with your sponsorship</p>
</div>
<div className="divide-y divide-charcoal-outline">
{MOCK_DRIVERS.map((driver) => (
<div key={driver.id} className="flex items-center justify-between p-4 hover:bg-iron-gray/30 transition-colors">
<div className="flex items-center gap-4">
<div className="w-8 h-8 rounded-full bg-iron-gray flex items-center justify-center text-sm font-bold text-white">
{driver.position}
</div>
<div>
<div className="font-medium text-white">{driver.name}</div>
<div className="text-sm text-gray-400">{driver.country} {driver.races} races</div>
</div>
</div>
<div className="text-right">
<div className="font-medium text-white">{driver.impressions.toLocaleString()}</div>
<div className="text-xs text-gray-500">impressions</div>
</div>
</div>
))}
</div>
</Card>
)}
{activeTab === 'races' && (
<Card>
<div className="p-4 border-b border-charcoal-outline">
<h3 className="text-lg font-semibold text-white">Race Schedule & Performance</h3>
</div>
<div className="divide-y divide-charcoal-outline">
{MOCK_RACES.map((race) => (
<div key={race.id} className="flex items-center justify-between p-4 hover:bg-iron-gray/30 transition-colors">
<div className="flex items-center gap-4">
<div className={`w-3 h-3 rounded-full ${
race.status === 'completed' ? 'bg-performance-green' : 'bg-warning-amber'
}`} />
<div>
<div className="font-medium text-white">{race.name}</div>
<div className="text-sm text-gray-400">{race.date}</div>
</div>
</div>
<div className="flex items-center gap-4">
{race.status === 'completed' ? (
<div className="text-right">
<div className="font-medium text-white">{race.views.toLocaleString()}</div>
<div className="text-xs text-gray-500">views</div>
</div>
) : (
<span className="text-sm text-warning-amber">Upcoming</span>
)}
</div>
</div>
))}
</div>
</Card>
)}
{activeTab === 'assets' && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<Card className="p-4">
<h3 className="text-lg font-semibold text-white mb-4">Your Logo Assets</h3>
<div className="space-y-4">
<div className="aspect-video bg-iron-gray rounded-lg flex items-center justify-center border border-charcoal-outline">
<ImageIcon className="w-12 h-12 text-gray-500" aria-label="Logo placeholder" />
</div>
<div className="flex gap-2">
<Button variant="secondary" className="flex-1">
<Download className="w-4 h-4 mr-2" />
Download Logo Pack
</Button>
<Button variant="secondary">
Update Logo
</Button>
</div>
</div>
</Card>
<Card className="p-4">
<h3 className="text-lg font-semibold text-white mb-4">Livery Preview</h3>
<div className="space-y-4">
<div className="aspect-video bg-iron-gray rounded-lg flex items-center justify-center border border-charcoal-outline">
<Trophy className="w-12 h-12 text-gray-500" />
</div>
<p className="text-sm text-gray-400">
Your logo appears on the primary hood position for all 32 drivers in this league.
</p>
<Button variant="secondary" className="w-full">
<Download className="w-4 h-4 mr-2" />
Download Sample Livery
</Button>
</div>
</Card>
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,298 @@
'use client';
import { useState } from 'react';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import {
Trophy,
Users,
Eye,
Search,
Filter,
Star,
ChevronRight
} from 'lucide-react';
interface AvailableLeague {
id: string;
name: string;
game: string;
drivers: number;
avgViewsPerRace: number;
mainSponsorSlot: { available: boolean; price: number };
secondarySlots: { available: number; total: number; price: number };
rating: number;
tier: 'premium' | 'standard' | 'starter';
}
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',
},
{
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',
},
{
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',
},
{
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',
},
{
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',
},
];
function LeagueCard({ league }: { league: AvailableLeague }) {
const tierColors = {
premium: 'bg-gradient-to-r from-yellow-500/20 to-amber-500/20 border-yellow-500/30',
standard: 'bg-gradient-to-r from-blue-500/20 to-cyan-500/20 border-blue-500/30',
starter: 'bg-gradient-to-r from-gray-500/20 to-slate-500/20 border-gray-500/30',
};
const tierBadgeColors = {
premium: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
standard: 'bg-blue-500/20 text-blue-400 border-blue-500/30',
starter: 'bg-gray-500/20 text-gray-400 border-gray-500/30',
};
return (
<Card className={`overflow-hidden border ${tierColors[league.tier]}`}>
<div className="p-4">
<div className="flex items-start justify-between mb-3">
<div>
<div className="flex items-center gap-2 mb-1">
<h3 className="font-semibold text-white">{league.name}</h3>
<span className={`px-2 py-0.5 rounded text-xs font-medium capitalize border ${tierBadgeColors[league.tier]}`}>
{league.tier}
</span>
</div>
<p className="text-sm text-gray-400">{league.game}</p>
</div>
<div className="flex items-center gap-1">
<Star className="w-4 h-4 text-yellow-400 fill-yellow-400" />
<span className="text-sm text-white">{league.rating}</span>
</div>
</div>
<div className="grid grid-cols-3 gap-3 mb-4">
<div className="text-center p-2 bg-iron-gray/50 rounded">
<div className="text-lg font-bold text-white">{league.drivers}</div>
<div className="text-xs text-gray-500">Drivers</div>
</div>
<div className="text-center p-2 bg-iron-gray/50 rounded">
<div className="text-lg font-bold text-white">{(league.avgViewsPerRace / 1000).toFixed(1)}k</div>
<div className="text-xs text-gray-500">Avg Views</div>
</div>
<div className="text-center p-2 bg-iron-gray/50 rounded">
<div className="text-lg font-bold text-white">${(league.mainSponsorSlot.price / league.avgViewsPerRace * 1000).toFixed(0)}</div>
<div className="text-xs text-gray-500">CPM</div>
</div>
</div>
<div className="space-y-2 mb-4">
<div className="flex items-center justify-between p-2 bg-iron-gray/30 rounded">
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${league.mainSponsorSlot.available ? 'bg-performance-green' : 'bg-racing-red'}`} />
<span className="text-sm text-gray-300">Main Sponsor</span>
</div>
<div className="text-sm">
{league.mainSponsorSlot.available ? (
<span className="text-white font-medium">${league.mainSponsorSlot.price}/season</span>
) : (
<span className="text-gray-500">Taken</span>
)}
</div>
</div>
<div className="flex items-center justify-between p-2 bg-iron-gray/30 rounded">
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${league.secondarySlots.available > 0 ? 'bg-performance-green' : 'bg-racing-red'}`} />
<span className="text-sm text-gray-300">Secondary Slots</span>
</div>
<div className="text-sm">
{league.secondarySlots.available > 0 ? (
<span className="text-white font-medium">{league.secondarySlots.available}/{league.secondarySlots.total} @ ${league.secondarySlots.price}</span>
) : (
<span className="text-gray-500">Full</span>
)}
</div>
</div>
</div>
<div className="flex gap-2">
<Button variant="secondary" className="flex-1">
View Details
</Button>
{(league.mainSponsorSlot.available || league.secondarySlots.available > 0) && (
<Button variant="primary" className="flex-1">
Sponsor
</Button>
)}
</div>
</div>
</Card>
);
}
export default function SponsorLeaguesPage() {
const [searchQuery, setSearchQuery] = useState('');
const [tierFilter, setTierFilter] = useState<'all' | 'premium' | 'standard' | 'starter'>('all');
const [availabilityFilter, setAvailabilityFilter] = useState<'all' | 'main' | 'secondary'>('all');
const filteredLeagues = MOCK_AVAILABLE_LEAGUES.filter(league => {
if (searchQuery && !league.name.toLowerCase().includes(searchQuery.toLowerCase())) {
return false;
}
if (tierFilter !== 'all' && league.tier !== tierFilter) {
return false;
}
if (availabilityFilter === 'main' && !league.mainSponsorSlot.available) {
return false;
}
if (availabilityFilter === 'secondary' && league.secondarySlots.available === 0) {
return false;
}
return true;
});
return (
<div className="max-w-7xl mx-auto py-8 px-4">
{/* Breadcrumb */}
<div className="flex items-center gap-2 text-sm text-gray-400 mb-6">
<a href="/sponsor/dashboard" className="hover:text-white">Dashboard</a>
<ChevronRight className="w-4 h-4" />
<span className="text-white">Browse Leagues</span>
</div>
{/* Header */}
<div className="mb-8">
<h1 className="text-2xl font-bold text-white mb-2">Find Leagues to Sponsor</h1>
<p className="text-gray-400">Discover racing leagues looking for sponsors and grow your brand</p>
</div>
{/* Filters */}
<div className="flex flex-col md:flex-row gap-4 mb-6">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
<input
type="text"
placeholder="Search leagues..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2 rounded-lg border border-charcoal-outline bg-iron-gray text-white placeholder-gray-500 focus:border-primary-blue focus:outline-none"
/>
</div>
<div className="flex gap-2">
<select
value={tierFilter}
onChange={(e) => setTierFilter(e.target.value as typeof tierFilter)}
className="px-3 py-2 rounded-lg border border-charcoal-outline bg-iron-gray text-white focus:border-primary-blue focus:outline-none"
>
<option value="all">All Tiers</option>
<option value="premium">Premium</option>
<option value="standard">Standard</option>
<option value="starter">Starter</option>
</select>
<select
value={availabilityFilter}
onChange={(e) => setAvailabilityFilter(e.target.value as typeof availabilityFilter)}
className="px-3 py-2 rounded-lg border border-charcoal-outline bg-iron-gray text-white focus:border-primary-blue focus:outline-none"
>
<option value="all">All Slots</option>
<option value="main">Main Available</option>
<option value="secondary">Secondary Available</option>
</select>
</div>
</div>
{/* Stats Banner */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
<Card className="p-4 text-center">
<div className="text-2xl font-bold text-white">{MOCK_AVAILABLE_LEAGUES.length}</div>
<div className="text-sm text-gray-400">Available Leagues</div>
</Card>
<Card className="p-4 text-center">
<div className="text-2xl font-bold text-performance-green">
{MOCK_AVAILABLE_LEAGUES.filter(l => l.mainSponsorSlot.available).length}
</div>
<div className="text-sm text-gray-400">Main Slots Open</div>
</Card>
<Card className="p-4 text-center">
<div className="text-2xl font-bold text-primary-blue">
{MOCK_AVAILABLE_LEAGUES.reduce((sum, l) => sum + l.secondarySlots.available, 0)}
</div>
<div className="text-sm text-gray-400">Secondary Slots Open</div>
</Card>
<Card className="p-4 text-center">
<div className="text-2xl font-bold text-white">
{MOCK_AVAILABLE_LEAGUES.reduce((sum, l) => sum + l.drivers, 0)}
</div>
<div className="text-sm text-gray-400">Total Drivers</div>
</Card>
</div>
{/* League Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredLeagues.map((league) => (
<LeagueCard key={league.id} league={league} />
))}
</div>
{filteredLeagues.length === 0 && (
<div className="text-center py-12">
<Trophy className="w-12 h-12 text-gray-500 mx-auto mb-4" />
<h3 className="text-lg font-medium text-white mb-2">No leagues found</h3>
<p className="text-gray-400">Try adjusting your filters to see more results</p>
</div>
)}
{/* Alpha Notice */}
<div className="mt-8 rounded-lg bg-warning-amber/10 border border-warning-amber/30 p-4">
<p className="text-xs text-gray-400">
<strong className="text-warning-amber">Alpha Note:</strong> League sponsorship marketplace is demonstration-only.
Actual sponsorship purchases will be available when the payment system is fully implemented.
</p>
</div>
</div>
);
}