auth rework
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useParams, useSearchParams } from 'next/navigation';
|
||||
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 {
|
||||
Trophy,
|
||||
Users,
|
||||
@@ -14,147 +16,246 @@ import {
|
||||
Download,
|
||||
Image as ImageIcon,
|
||||
ExternalLink,
|
||||
ChevronRight
|
||||
ChevronRight,
|
||||
Star,
|
||||
Clock,
|
||||
CheckCircle2,
|
||||
Flag,
|
||||
Car,
|
||||
BarChart3,
|
||||
ArrowUpRight,
|
||||
Megaphone,
|
||||
CreditCard,
|
||||
FileText
|
||||
} from 'lucide-react';
|
||||
|
||||
interface LeagueDriver {
|
||||
id: string;
|
||||
name: string;
|
||||
country: string;
|
||||
position: number;
|
||||
races: number;
|
||||
impressions: number;
|
||||
}
|
||||
|
||||
// Mock data
|
||||
// Mock data for league detail
|
||||
const MOCK_LEAGUE = {
|
||||
id: 'league-1',
|
||||
name: 'GT3 Pro Championship',
|
||||
tier: 'main' as const,
|
||||
name: 'GT3 Masters Championship',
|
||||
game: 'iRacing',
|
||||
tier: 'premium' as const,
|
||||
season: 'Season 3',
|
||||
drivers: 32,
|
||||
description: 'Premier GT3 racing with top-tier drivers competing across the world\'s most iconic circuits. Weekly broadcasts and an active community make this league a premium sponsorship opportunity.',
|
||||
drivers: 48,
|
||||
races: 12,
|
||||
completedRaces: 8,
|
||||
impressions: 45200,
|
||||
totalImpressions: 45200,
|
||||
avgViewsPerRace: 5650,
|
||||
logoPlacement: 'Primary hood placement + League page banner',
|
||||
status: 'active' as const,
|
||||
engagement: 4.2,
|
||||
rating: 4.8,
|
||||
seasonStatus: 'active' as const,
|
||||
seasonDates: { start: '2025-10-01', end: '2026-02-28' },
|
||||
nextRace: { name: 'Spa-Francorchamps', date: '2025-12-20' },
|
||||
sponsorSlots: {
|
||||
main: {
|
||||
available: true,
|
||||
price: 1200,
|
||||
benefits: [
|
||||
'Primary logo placement on all liveries',
|
||||
'League page header banner',
|
||||
'Race results page branding',
|
||||
'Social media feature posts',
|
||||
'Newsletter sponsor spot',
|
||||
]
|
||||
},
|
||||
secondary: {
|
||||
available: 1,
|
||||
total: 2,
|
||||
price: 400,
|
||||
benefits: [
|
||||
'Secondary logo on liveries',
|
||||
'League page sidebar placement',
|
||||
'Race results mention',
|
||||
'Social media mentions',
|
||||
]
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
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_DRIVERS = [
|
||||
{ id: 'd1', name: 'Max Verstappen', country: 'NL', position: 1, races: 8, impressions: 4200, team: 'Red Bull Racing' },
|
||||
{ id: 'd2', name: 'Lewis Hamilton', country: 'GB', position: 2, races: 8, impressions: 3980, team: 'Mercedes AMG' },
|
||||
{ id: 'd3', name: 'Charles Leclerc', country: 'MC', position: 3, races: 8, impressions: 3750, team: 'Ferrari' },
|
||||
{ id: 'd4', name: 'Lando Norris', country: 'GB', position: 4, races: 7, impressions: 3420, team: 'McLaren' },
|
||||
{ id: 'd5', name: 'Carlos Sainz', country: 'ES', position: 5, races: 8, impressions: 3100, team: 'Ferrari' },
|
||||
];
|
||||
|
||||
const MOCK_RACES = [
|
||||
{ id: 'r1', name: 'Spa-Francorchamps', date: '2025-12-01', views: 6200, status: 'completed' },
|
||||
{ id: 'r1', name: 'Spa-Francorchamps', date: '2025-12-20', views: 0, status: 'upcoming' },
|
||||
{ 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' },
|
||||
{ id: 'r3', name: 'Silverstone', date: '2025-11-24', views: 6200, status: 'completed' },
|
||||
{ id: 'r4', name: 'Nürburgring', date: '2025-11-10', views: 5400, status: 'completed' },
|
||||
];
|
||||
|
||||
type TabType = 'overview' | 'drivers' | 'races' | 'sponsor';
|
||||
|
||||
export default function SponsorLeagueDetailPage() {
|
||||
const params = useParams();
|
||||
const [activeTab, setActiveTab] = useState<'overview' | 'drivers' | 'races' | 'assets'>('overview');
|
||||
const searchParams = useSearchParams();
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
const showSponsorAction = searchParams.get('action') === 'sponsor';
|
||||
const [activeTab, setActiveTab] = useState<TabType>(showSponsorAction ? 'sponsor' : 'overview');
|
||||
const [selectedTier, setSelectedTier] = useState<'main' | 'secondary'>('main');
|
||||
|
||||
const league = MOCK_LEAGUE;
|
||||
const tierConfig = {
|
||||
premium: { color: 'text-yellow-400', bgColor: 'bg-yellow-500/10', border: 'border-yellow-500/30' },
|
||||
standard: { color: 'text-primary-blue', bgColor: 'bg-primary-blue/10', border: 'border-primary-blue/30' },
|
||||
starter: { color: 'text-gray-400', bgColor: 'bg-gray-500/10', border: 'border-gray-500/30' },
|
||||
};
|
||||
|
||||
const config = tierConfig[league.tier];
|
||||
|
||||
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>
|
||||
<Link href="/sponsor/dashboard" className="hover:text-white transition-colors">Dashboard</Link>
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
<Link href="/sponsor/leagues" className="hover:text-white">Leagues</Link>
|
||||
<Link href="/sponsor/leagues" className="hover:text-white transition-colors">Leagues</Link>
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
<span className="text-white">{MOCK_LEAGUE.name}</span>
|
||||
<span className="text-white">{league.name}</span>
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between mb-8">
|
||||
<div>
|
||||
<div className="flex flex-col lg:flex-row lg:items-start justify-between gap-6 mb-8">
|
||||
<div className="flex-1">
|
||||
<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 className={`px-3 py-1 rounded-full text-sm font-medium capitalize ${config.bgColor} ${config.color} border ${config.border}`}>
|
||||
⭐ {league.tier}
|
||||
</span>
|
||||
<span className="px-3 py-1 rounded-full text-sm font-medium bg-performance-green/10 text-performance-green">
|
||||
Active Season
|
||||
</span>
|
||||
<div className="flex items-center gap-1 px-2 py-1 rounded bg-iron-gray/50">
|
||||
<Star className="w-4 h-4 text-yellow-400 fill-yellow-400" />
|
||||
<span className="text-sm font-medium text-white">{league.rating}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-400">{MOCK_LEAGUE.season} • {MOCK_LEAGUE.completedRaces}/{MOCK_LEAGUE.races} races completed</p>
|
||||
<h1 className="text-3xl font-bold text-white mb-2">{league.name}</h1>
|
||||
<p className="text-gray-400 mb-4">{league.game} • {league.season} • {league.completedRaces}/{league.races} races completed</p>
|
||||
<p className="text-gray-400 max-w-2xl">{league.description}</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 className="flex flex-col sm:flex-row gap-3">
|
||||
<Link href={`/leagues/${league.id}`}>
|
||||
<Button variant="secondary">
|
||||
<ExternalLink className="w-4 h-4 mr-2" />
|
||||
View League
|
||||
</Button>
|
||||
</Link>
|
||||
{(league.sponsorSlots.main.available || league.sponsorSlots.secondary.available > 0) && (
|
||||
<Button variant="primary" onClick={() => setActiveTab('sponsor')}>
|
||||
<Megaphone className="w-4 h-4 mr-2" />
|
||||
Become a Sponsor
|
||||
</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 className="grid grid-cols-2 md:grid-cols-5 gap-4 mb-8">
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
>
|
||||
<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">{league.totalImpressions.toLocaleString()}</div>
|
||||
<div className="text-xs text-gray-400">Total Views</div>
|
||||
</div>
|
||||
</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>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
>
|
||||
<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">{league.avgViewsPerRace.toLocaleString()}</div>
|
||||
<div className="text-xs text-gray-400">Avg/Race</div>
|
||||
</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" />
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
>
|
||||
<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">{league.drivers}</div>
|
||||
<div className="text-xs text-gray-400">Drivers</div>
|
||||
</div>
|
||||
</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>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
>
|
||||
<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">
|
||||
<BarChart3 className="w-5 h-5 text-warning-amber" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xl font-bold text-white">{league.engagement}%</div>
|
||||
<div className="text-xs text-gray-400">Engagement</div>
|
||||
</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" />
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.4 }}
|
||||
>
|
||||
<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-racing-red/10">
|
||||
<Calendar className="w-5 h-5 text-racing-red" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xl font-bold text-white">{league.races - league.completedRaces}</div>
|
||||
<div className="text-xs text-gray-400">Races Left</div>
|
||||
</div>
|
||||
</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>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-1 mb-6 border-b border-charcoal-outline">
|
||||
{(['overview', 'drivers', 'races', 'assets'] as const).map((tab) => (
|
||||
<div className="flex gap-1 mb-6 border-b border-charcoal-outline overflow-x-auto">
|
||||
{(['overview', 'drivers', 'races', 'sponsor'] 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 ${
|
||||
className={`px-4 py-3 text-sm font-medium capitalize transition-colors border-b-2 -mb-px whitespace-nowrap ${
|
||||
activeTab === tab
|
||||
? 'text-primary-blue border-primary-blue'
|
||||
: 'text-gray-400 border-transparent hover:text-white'
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
{tab === 'sponsor' ? '🎯 Become a Sponsor' : tab}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -162,73 +263,122 @@ export default function SponsorLeagueDetailPage() {
|
||||
{/* 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>
|
||||
<Card className="p-5">
|
||||
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
|
||||
<Trophy className="w-5 h-5 text-primary-blue" />
|
||||
League Information
|
||||
</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 className="flex justify-between py-2 border-b border-charcoal-outline/50">
|
||||
<span className="text-gray-400">Platform</span>
|
||||
<span className="text-white font-medium">{league.game}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-400">Logo Placement</span>
|
||||
<span className="text-white">{MOCK_LEAGUE.logoPlacement}</span>
|
||||
<div className="flex justify-between py-2 border-b border-charcoal-outline/50">
|
||||
<span className="text-gray-400">Season</span>
|
||||
<span className="text-white font-medium">{league.season}</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 className="flex justify-between py-2 border-b border-charcoal-outline/50">
|
||||
<span className="text-gray-400">Duration</span>
|
||||
<span className="text-white font-medium">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 className="flex justify-between py-2 border-b border-charcoal-outline/50">
|
||||
<span className="text-gray-400">Drivers</span>
|
||||
<span className="text-white font-medium">{league.drivers}</span>
|
||||
</div>
|
||||
<div className="flex justify-between py-2">
|
||||
<span className="text-gray-400">Races</span>
|
||||
<span className="text-white font-medium">{league.races}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-4">
|
||||
<h3 className="text-lg font-semibold text-white mb-4">Performance Metrics</h3>
|
||||
<Card className="p-5">
|
||||
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
|
||||
<TrendingUp className="w-5 h-5 text-performance-green" />
|
||||
Sponsorship Value
|
||||
</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 className="flex justify-between py-2 border-b border-charcoal-outline/50">
|
||||
<span className="text-gray-400">Total Season Views</span>
|
||||
<span className="text-white font-medium">{league.totalImpressions.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex justify-between py-2 border-b border-charcoal-outline/50">
|
||||
<span className="text-gray-400">Projected Total</span>
|
||||
<span className="text-white font-medium">{Math.round(league.avgViewsPerRace * league.races).toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between py-2 border-b border-charcoal-outline/50">
|
||||
<span className="text-gray-400">Main Sponsor CPM</span>
|
||||
<span className="text-performance-green font-medium">
|
||||
${((league.sponsorSlots.main.price / (league.avgViewsPerRace * league.races)) * 1000).toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between py-2 border-b border-charcoal-outline/50">
|
||||
<span className="text-gray-400">Engagement Rate</span>
|
||||
<span className="text-white">4.2%</span>
|
||||
<span className="text-white font-medium">{league.engagement}%</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 className="flex justify-between py-2">
|
||||
<span className="text-gray-400">League Rating</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<Star className="w-4 h-4 text-yellow-400 fill-yellow-400" />
|
||||
<span className="text-white font-medium">{league.rating}/5.0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Next Race */}
|
||||
{league.nextRace && (
|
||||
<Card className="p-5 lg:col-span-2">
|
||||
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
|
||||
<Flag className="w-5 h-5 text-warning-amber" />
|
||||
Next Race
|
||||
</h3>
|
||||
<div className="flex items-center justify-between p-4 rounded-lg bg-warning-amber/10 border border-warning-amber/30">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-lg bg-warning-amber/20 flex items-center justify-center">
|
||||
<Flag className="w-6 h-6 text-warning-amber" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-white text-lg">{league.nextRace.name}</p>
|
||||
<p className="text-sm text-gray-400">{league.nextRace.date}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="secondary">
|
||||
View Schedule
|
||||
</Button>
|
||||
</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>
|
||||
<h3 className="text-lg font-semibold text-white">Championship Standings</h3>
|
||||
<p className="text-sm text-gray-400">Top drivers carrying sponsor branding</p>
|
||||
</div>
|
||||
<div className="divide-y divide-charcoal-outline">
|
||||
<div className="divide-y divide-charcoal-outline/50">
|
||||
{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">
|
||||
<div className="w-10 h-10 rounded-full bg-iron-gray flex items-center justify-center text-lg 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 className="text-sm text-gray-500">{driver.team} • {driver.country}</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 className="flex items-center gap-6">
|
||||
<div className="text-right">
|
||||
<div className="font-medium text-white">{driver.races}</div>
|
||||
<div className="text-xs text-gray-500">races</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="font-semibold text-white">{driver.impressions.toLocaleString()}</div>
|
||||
<div className="text-xs text-gray-500">views</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -239,9 +389,10 @@ export default function SponsorLeagueDetailPage() {
|
||||
{activeTab === 'races' && (
|
||||
<Card>
|
||||
<div className="p-4 border-b border-charcoal-outline">
|
||||
<h3 className="text-lg font-semibold text-white">Race Schedule & Performance</h3>
|
||||
<h3 className="text-lg font-semibold text-white">Race Calendar</h3>
|
||||
<p className="text-sm text-gray-400">Season schedule with view statistics</p>
|
||||
</div>
|
||||
<div className="divide-y divide-charcoal-outline">
|
||||
<div className="divide-y divide-charcoal-outline/50">
|
||||
{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">
|
||||
@@ -250,17 +401,19 @@ export default function SponsorLeagueDetailPage() {
|
||||
}`} />
|
||||
<div>
|
||||
<div className="font-medium text-white">{race.name}</div>
|
||||
<div className="text-sm text-gray-400">{race.date}</div>
|
||||
<div className="text-sm text-gray-500">{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="font-semibold 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>
|
||||
<span className="px-3 py-1 rounded-full text-xs font-medium bg-warning-amber/20 text-warning-amber">
|
||||
Upcoming
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -269,38 +422,153 @@ export default function SponsorLeagueDetailPage() {
|
||||
</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" />
|
||||
{activeTab === 'sponsor' && (
|
||||
<div className="space-y-6">
|
||||
{/* Tier Selection */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Main Sponsor */}
|
||||
<Card
|
||||
className={`p-5 cursor-pointer transition-all ${
|
||||
selectedTier === 'main'
|
||||
? 'border-primary-blue ring-2 ring-primary-blue/20'
|
||||
: 'hover:border-charcoal-outline/80'
|
||||
} ${!league.sponsorSlots.main.available ? 'opacity-60' : ''}`}
|
||||
onClick={() => league.sponsorSlots.main.available && setSelectedTier('main')}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Trophy className="w-5 h-5 text-yellow-400" />
|
||||
<h3 className="text-lg font-semibold text-white">Main Sponsor</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-400">Primary branding position</p>
|
||||
</div>
|
||||
{league.sponsorSlots.main.available ? (
|
||||
<span className="px-2 py-1 rounded text-xs font-medium bg-performance-green/20 text-performance-green">
|
||||
Available
|
||||
</span>
|
||||
) : (
|
||||
<span className="px-2 py-1 rounded text-xs font-medium bg-gray-500/20 text-gray-400">
|
||||
Filled
|
||||
</span>
|
||||
)}
|
||||
</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 className="text-3xl font-bold text-white mb-4">
|
||||
${league.sponsorSlots.main.price}
|
||||
<span className="text-sm font-normal text-gray-500">/season</span>
|
||||
</div>
|
||||
|
||||
<ul className="space-y-2 mb-4">
|
||||
{league.sponsorSlots.main.benefits.map((benefit, i) => (
|
||||
<li key={i} className="flex items-center gap-2 text-sm text-gray-300">
|
||||
<CheckCircle2 className="w-4 h-4 text-performance-green flex-shrink-0" />
|
||||
{benefit}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{selectedTier === 'main' && league.sponsorSlots.main.available && (
|
||||
<div className="w-4 h-4 rounded-full bg-primary-blue absolute top-4 right-4 flex items-center justify-center">
|
||||
<CheckCircle2 className="w-3 h-3 text-white" />
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* Secondary Sponsor */}
|
||||
<Card
|
||||
className={`p-5 cursor-pointer transition-all ${
|
||||
selectedTier === 'secondary'
|
||||
? 'border-primary-blue ring-2 ring-primary-blue/20'
|
||||
: 'hover:border-charcoal-outline/80'
|
||||
} ${league.sponsorSlots.secondary.available === 0 ? 'opacity-60' : ''}`}
|
||||
onClick={() => league.sponsorSlots.secondary.available > 0 && setSelectedTier('secondary')}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Star className="w-5 h-5 text-purple-400" />
|
||||
<h3 className="text-lg font-semibold text-white">Secondary Sponsor</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-400">Supporting branding position</p>
|
||||
</div>
|
||||
{league.sponsorSlots.secondary.available > 0 ? (
|
||||
<span className="px-2 py-1 rounded text-xs font-medium bg-performance-green/20 text-performance-green">
|
||||
{league.sponsorSlots.secondary.available}/{league.sponsorSlots.secondary.total} Available
|
||||
</span>
|
||||
) : (
|
||||
<span className="px-2 py-1 rounded text-xs font-medium bg-gray-500/20 text-gray-400">
|
||||
Full
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-3xl font-bold text-white mb-4">
|
||||
${league.sponsorSlots.secondary.price}
|
||||
<span className="text-sm font-normal text-gray-500">/season</span>
|
||||
</div>
|
||||
|
||||
<ul className="space-y-2 mb-4">
|
||||
{league.sponsorSlots.secondary.benefits.map((benefit, i) => (
|
||||
<li key={i} className="flex items-center gap-2 text-sm text-gray-300">
|
||||
<CheckCircle2 className="w-4 h-4 text-performance-green flex-shrink-0" />
|
||||
{benefit}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{selectedTier === 'secondary' && league.sponsorSlots.secondary.available > 0 && (
|
||||
<div className="w-4 h-4 rounded-full bg-primary-blue absolute top-4 right-4 flex items-center justify-center">
|
||||
<CheckCircle2 className="w-3 h-3 text-white" />
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Checkout Summary */}
|
||||
<Card className="p-5">
|
||||
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
|
||||
<CreditCard className="w-5 h-5 text-primary-blue" />
|
||||
Sponsorship Summary
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3 mb-6">
|
||||
<div className="flex justify-between py-2">
|
||||
<span className="text-gray-400">Selected Tier</span>
|
||||
<span className="text-white font-medium capitalize">{selectedTier} Sponsor</span>
|
||||
</div>
|
||||
<div className="flex justify-between py-2">
|
||||
<span className="text-gray-400">Season Price</span>
|
||||
<span className="text-white font-medium">
|
||||
${selectedTier === 'main' ? league.sponsorSlots.main.price : league.sponsorSlots.secondary.price}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between py-2">
|
||||
<span className="text-gray-400">Platform Fee ({siteConfig.fees.platformFeePercent}%)</span>
|
||||
<span className="text-white font-medium">
|
||||
${((selectedTier === 'main' ? league.sponsorSlots.main.price : league.sponsorSlots.secondary.price) * siteConfig.fees.platformFeePercent / 100).toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between py-2 border-t border-charcoal-outline pt-4">
|
||||
<span className="text-white font-semibold">Total (excl. VAT)</span>
|
||||
<span className="text-white font-bold text-xl">
|
||||
${((selectedTier === 'main' ? league.sponsorSlots.main.price : league.sponsorSlots.secondary.price) * (1 + siteConfig.fees.platformFeePercent / 100)).toFixed(2)}
|
||||
</span>
|
||||
</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
|
||||
<p className="text-xs text-gray-500 mb-4">
|
||||
{siteConfig.vat.notice}
|
||||
</p>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Button variant="primary" className="flex-1">
|
||||
<Megaphone className="w-4 h-4 mr-2" />
|
||||
Request Sponsorship
|
||||
</Button>
|
||||
<Button variant="secondary">
|
||||
<FileText className="w-4 h-4 mr-2" />
|
||||
Download Info Pack
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
'use client';
|
||||
|
||||
import { useState } 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 {
|
||||
Trophy,
|
||||
Users,
|
||||
Eye,
|
||||
Search,
|
||||
Filter,
|
||||
Star,
|
||||
ChevronRight
|
||||
ChevronRight,
|
||||
Filter,
|
||||
Car,
|
||||
Flag,
|
||||
TrendingUp,
|
||||
CheckCircle2,
|
||||
Clock,
|
||||
Megaphone,
|
||||
ArrowUpDown
|
||||
} from 'lucide-react';
|
||||
|
||||
interface AvailableLeague {
|
||||
@@ -23,6 +33,9 @@ interface AvailableLeague {
|
||||
secondarySlots: { available: number; total: number; price: number };
|
||||
rating: number;
|
||||
tier: 'premium' | 'standard' | 'starter';
|
||||
nextRace?: string;
|
||||
seasonStatus: 'active' | 'upcoming' | 'completed';
|
||||
description: string;
|
||||
}
|
||||
|
||||
const MOCK_AVAILABLE_LEAGUES: AvailableLeague[] = [
|
||||
@@ -36,6 +49,9 @@ const MOCK_AVAILABLE_LEAGUES: AvailableLeague[] = [
|
||||
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',
|
||||
@@ -47,6 +63,9 @@ const MOCK_AVAILABLE_LEAGUES: AvailableLeague[] = [
|
||||
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',
|
||||
@@ -58,6 +77,9 @@ const MOCK_AVAILABLE_LEAGUES: AvailableLeague[] = [
|
||||
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',
|
||||
@@ -69,6 +91,9 @@ const MOCK_AVAILABLE_LEAGUES: AvailableLeague[] = [
|
||||
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',
|
||||
@@ -80,138 +105,299 @@ const MOCK_AVAILABLE_LEAGUES: AvailableLeague[] = [
|
||||
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.',
|
||||
},
|
||||
];
|
||||
|
||||
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',
|
||||
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 }) {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
const tierConfig = {
|
||||
premium: {
|
||||
bg: 'bg-gradient-to-br from-yellow-500/10 to-amber-500/5',
|
||||
border: 'border-yellow-500/30',
|
||||
badge: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
|
||||
icon: '⭐'
|
||||
},
|
||||
standard: {
|
||||
bg: 'bg-gradient-to-br from-primary-blue/10 to-cyan-500/5',
|
||||
border: 'border-primary-blue/30',
|
||||
badge: 'bg-primary-blue/20 text-primary-blue border-primary-blue/30',
|
||||
icon: '🏆'
|
||||
},
|
||||
starter: {
|
||||
bg: 'bg-gradient-to-br from-gray-500/10 to-slate-500/5',
|
||||
border: 'border-gray-500/30',
|
||||
badge: 'bg-gray-500/20 text-gray-400 border-gray-500/30',
|
||||
icon: '🚀'
|
||||
},
|
||||
};
|
||||
|
||||
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',
|
||||
const statusConfig = {
|
||||
active: { color: 'text-performance-green', bg: 'bg-performance-green/10', label: 'Active Season' },
|
||||
upcoming: { color: 'text-warning-amber', bg: 'bg-warning-amber/10', label: 'Starting Soon' },
|
||||
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);
|
||||
|
||||
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>
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
>
|
||||
<Card className={`overflow-hidden border ${config.border} ${config.bg} hover:border-primary-blue/50 transition-all duration-300 h-full`}>
|
||||
<div className="p-5">
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className={`px-2 py-0.5 rounded text-xs font-medium capitalize border ${config.badge}`}>
|
||||
{config.icon} {league.tier}
|
||||
</span>
|
||||
<span className={`px-2 py-0.5 rounded text-xs font-medium ${status.bg} ${status.color}`}>
|
||||
{status.label}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="font-semibold text-white text-lg">{league.name}</h3>
|
||||
<p className="text-sm text-gray-500">{league.game}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 bg-iron-gray/50 px-2 py-1 rounded">
|
||||
<Star className="w-4 h-4 text-yellow-400 fill-yellow-400" />
|
||||
<span className="text-sm font-medium text-white">{league.rating}</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>
|
||||
{/* Description */}
|
||||
<p className="text-sm text-gray-400 mb-4 line-clamp-2">{league.description}</p>
|
||||
|
||||
<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>
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-3 gap-2 mb-4">
|
||||
<div className="text-center p-2 bg-iron-gray/50 rounded-lg">
|
||||
<div className="text-lg font-bold text-white">{league.drivers}</div>
|
||||
<div className="text-xs text-gray-500">Drivers</div>
|
||||
</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 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-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-xs text-gray-500">CPM</div>
|
||||
</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>
|
||||
{/* Next Race */}
|
||||
{league.nextRace && (
|
||||
<div className="flex items-center gap-2 mb-4 text-sm">
|
||||
<Clock className="w-4 h-4 text-gray-500" />
|
||||
<span className="text-gray-400">Next:</span>
|
||||
<span className="text-white">{league.nextRace}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Sponsorship Slots */}
|
||||
<div className="space-y-2 mb-4">
|
||||
<div className="flex items-center justify-between p-2.5 bg-iron-gray/30 rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-2.5 h-2.5 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-semibold">${league.mainSponsorSlot.price}/season</span>
|
||||
) : (
|
||||
<span className="text-gray-500 flex items-center gap-1">
|
||||
<CheckCircle2 className="w-3 h-3" /> Filled
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-2.5 bg-iron-gray/30 rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-2.5 h-2.5 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-semibold">
|
||||
{league.secondarySlots.available}/{league.secondarySlots.total} @ ${league.secondarySlots.price}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-gray-500 flex items-center gap-1">
|
||||
<CheckCircle2 className="w-3 h-3" /> Full
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2">
|
||||
<Link href={`/sponsor/leagues/${league.id}`} className="flex-1">
|
||||
<Button variant="secondary" className="w-full text-sm">
|
||||
View Details
|
||||
</Button>
|
||||
</Link>
|
||||
{(league.mainSponsorSlot.available || league.secondarySlots.available > 0) && (
|
||||
<Link href={`/sponsor/leagues/${league.id}?action=sponsor`} className="flex-1">
|
||||
<Button variant="primary" className="w-full text-sm">
|
||||
Sponsor
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SponsorLeaguesPage() {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [tierFilter, setTierFilter] = useState<'all' | 'premium' | 'standard' | 'starter'>('all');
|
||||
const [availabilityFilter, setAvailabilityFilter] = useState<'all' | 'main' | 'secondary'>('all');
|
||||
const [tierFilter, setTierFilter] = useState<TierFilter>('all');
|
||||
const [availabilityFilter, setAvailabilityFilter] = useState<AvailabilityFilter>('all');
|
||||
const [sortBy, setSortBy] = useState<SortOption>('rating');
|
||||
|
||||
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;
|
||||
});
|
||||
// Filter and sort leagues
|
||||
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;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case 'rating': return b.rating - a.rating;
|
||||
case 'drivers': return b.drivers - a.drivers;
|
||||
case 'price': return a.mainSponsorSlot.price - b.mainSponsorSlot.price;
|
||||
case 'views': return b.avgViewsPerRace - a.avgViewsPerRace;
|
||||
default: return 0;
|
||||
}
|
||||
});
|
||||
|
||||
// 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),
|
||||
avgCpm: Math.round(
|
||||
MOCK_AVAILABLE_LEAGUES.reduce((sum, l) => sum + (l.mainSponsorSlot.price / l.avgViewsPerRace * 1000), 0) /
|
||||
MOCK_AVAILABLE_LEAGUES.length
|
||||
),
|
||||
};
|
||||
|
||||
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>
|
||||
<Link href="/sponsor/dashboard" className="hover:text-white transition-colors">Dashboard</Link>
|
||||
<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>
|
||||
<h1 className="text-2xl font-bold text-white mb-2 flex items-center gap-3">
|
||||
<Trophy className="w-7 h-7 text-primary-blue" />
|
||||
League Sponsorship Marketplace
|
||||
</h1>
|
||||
<p className="text-gray-400">
|
||||
Discover racing leagues looking for sponsors. All prices shown exclude VAT.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Overview */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 mb-8">
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
>
|
||||
<Card className="p-4 text-center">
|
||||
<div className="text-2xl font-bold text-white">{stats.total}</div>
|
||||
<div className="text-sm text-gray-400">Leagues</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
>
|
||||
<Card className="p-4 text-center">
|
||||
<div className="text-2xl font-bold text-performance-green">{stats.mainAvailable}</div>
|
||||
<div className="text-sm text-gray-400">Main Slots</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
>
|
||||
<Card className="p-4 text-center">
|
||||
<div className="text-2xl font-bold text-primary-blue">{stats.secondaryAvailable}</div>
|
||||
<div className="text-sm text-gray-400">Secondary Slots</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
>
|
||||
<Card className="p-4 text-center">
|
||||
<div className="text-2xl font-bold text-white">{stats.totalDrivers}</div>
|
||||
<div className="text-sm text-gray-400">Total Drivers</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.4 }}
|
||||
>
|
||||
<Card className="p-4 text-center">
|
||||
<div className="text-2xl font-bold text-warning-amber">${stats.avgCpm}</div>
|
||||
<div className="text-sm text-gray-400">Avg CPM</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex flex-col md:flex-row gap-4 mb-6">
|
||||
<div className="flex flex-col lg:flex-row gap-4 mb-6">
|
||||
{/* Search */}
|
||||
<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
|
||||
@@ -219,79 +405,100 @@ export default function SponsorLeaguesPage() {
|
||||
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"
|
||||
className="w-full pl-10 pr-4 py-2.5 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>
|
||||
|
||||
{/* Tier Filter */}
|
||||
<select
|
||||
value={tierFilter}
|
||||
onChange={(e) => setTierFilter(e.target.value as TierFilter)}
|
||||
className="px-4 py-2.5 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>
|
||||
|
||||
{/* Availability Filter */}
|
||||
<select
|
||||
value={availabilityFilter}
|
||||
onChange={(e) => setAvailabilityFilter(e.target.value as AvailabilityFilter)}
|
||||
className="px-4 py-2.5 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>
|
||||
|
||||
{/* Sort */}
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy(e.target.value as SortOption)}
|
||||
className="px-4 py-2.5 rounded-lg border border-charcoal-outline bg-iron-gray text-white focus:border-primary-blue focus:outline-none"
|
||||
>
|
||||
<option value="rating">Sort by Rating</option>
|
||||
<option value="drivers">Sort by Drivers</option>
|
||||
<option value="views">Sort by Views</option>
|
||||
<option value="price">Sort by Price</option>
|
||||
</select>
|
||||
</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>
|
||||
{/* 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
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<Link href="/teams">
|
||||
<Button variant="secondary" className="text-sm">
|
||||
<Users className="w-4 h-4 mr-2" />
|
||||
Browse Teams
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/drivers">
|
||||
<Button variant="secondary" className="text-sm">
|
||||
<Car className="w-4 h-4 mr-2" />
|
||||
Browse Drivers
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</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">
|
||||
{filteredLeagues.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{filteredLeagues.map((league, index) => (
|
||||
<LeagueCard key={league.id} league={league} index={index} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Card className="text-center py-16">
|
||||
<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>
|
||||
<p className="text-gray-400 mb-6">Try adjusting your filters to see more results</p>
|
||||
<Button variant="secondary" onClick={() => {
|
||||
setSearchQuery('');
|
||||
setTierFilter('all');
|
||||
setAvailabilityFilter('all');
|
||||
}}>
|
||||
Clear Filters
|
||||
</Button>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 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>
|
||||
{/* Platform Fee Notice */}
|
||||
<div className="mt-8 rounded-lg bg-iron-gray/50 border border-charcoal-outline p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<Megaphone className="w-5 h-5 text-primary-blue flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm text-gray-300 font-medium mb-1">Platform Fee</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
A {siteConfig.fees.platformFeePercent}% platform fee applies to all sponsorship payments. {siteConfig.fees.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user