Files
gridpilot.gg/apps/website/app/sponsor/campaigns/page.tsx
2025-12-17 15:34:56 +01:00

866 lines
31 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { motion, useReducedMotion, AnimatePresence } from 'framer-motion';
import Link from 'next/link';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import StatusBadge from '@/components/ui/StatusBadge';
import InfoBanner from '@/components/ui/InfoBanner';
import {
Megaphone,
Trophy,
Users,
Eye,
Calendar,
ExternalLink,
Plus,
ChevronRight,
Check,
Clock,
XCircle,
Car,
Flag,
Search,
Filter,
TrendingUp,
BarChart3,
ArrowUpRight,
ArrowDownRight,
AlertCircle,
Send,
ThumbsUp,
ThumbsDown,
RefreshCw,
Handshake
} from 'lucide-react';
// ============================================================================
// Types
// ============================================================================
type SponsorshipType = 'all' | 'leagues' | 'teams' | 'drivers' | 'races' | 'platform';
type SponsorshipStatus = 'all' | 'active' | 'pending_approval' | 'approved' | 'rejected' | 'expired';
interface Sponsorship {
id: string;
type: SponsorshipType;
entityId: string;
entityName: string;
tier?: 'main' | 'secondary';
status: 'active' | 'pending_approval' | 'approved' | 'rejected' | 'expired';
applicationDate?: Date;
approvalDate?: Date;
rejectionReason?: string;
startDate: Date;
endDate: Date;
price: number;
impressions: number;
impressionsChange?: number;
engagement?: number;
details?: string;
// For pending approvals
entityOwner?: string;
applicationMessage?: string;
}
// ============================================================================
// Mock Data - Updated to show application workflow
// ============================================================================
const MOCK_SPONSORSHIPS: Sponsorship[] = [
// Active sponsorships (approved and started)
{
id: 's1',
type: 'leagues',
entityId: 'l1',
entityName: 'GT3 Masters Championship',
tier: 'main',
status: 'active',
applicationDate: new Date('2025-09-15'),
approvalDate: new Date('2025-09-18'),
startDate: new Date('2025-10-01'),
endDate: new Date('2026-02-28'),
price: 1200,
impressions: 45200,
impressionsChange: 12.5,
engagement: 4.2,
details: '48 drivers • 12 races',
entityOwner: 'Championship Admin',
},
{
id: 's2',
type: 'leagues',
entityId: 'l2',
entityName: 'Endurance Pro Series',
tier: 'secondary',
status: 'active',
applicationDate: new Date('2025-10-20'),
approvalDate: new Date('2025-10-25'),
startDate: new Date('2025-11-01'),
endDate: new Date('2026-03-31'),
price: 500,
impressions: 38400,
impressionsChange: 8.3,
engagement: 3.8,
details: '72 drivers • 6 races',
entityOwner: 'Endurance Racing LLC',
},
{
id: 's3',
type: 'teams',
entityId: 't1',
entityName: 'Velocity Racing',
status: 'active',
applicationDate: new Date('2025-08-25'),
approvalDate: new Date('2025-08-26'),
startDate: new Date('2025-09-01'),
endDate: new Date('2026-08-31'),
price: 400,
impressions: 12300,
impressionsChange: 5.2,
engagement: 5.1,
details: '4 drivers • GT3 & LMP',
entityOwner: 'Team Principal',
},
// Pending approval (waiting for entity owner)
{
id: 's9',
type: 'leagues',
entityId: 'l3',
entityName: 'Formula Sim Series',
tier: 'main',
status: 'pending_approval',
applicationDate: new Date('2025-12-14'),
startDate: new Date('2026-01-01'),
endDate: new Date('2026-06-30'),
price: 1500,
impressions: 0,
details: '36 drivers • F3 class',
entityOwner: 'Formula Sim Organization',
applicationMessage: 'We would love to be your main sponsor for the upcoming season.',
},
{
id: 's10',
type: 'teams',
entityId: 't3',
entityName: 'Phoenix Racing Team',
status: 'pending_approval',
applicationDate: new Date('2025-12-12'),
startDate: new Date('2026-01-01'),
endDate: new Date('2026-12-31'),
price: 600,
impressions: 0,
details: '5 drivers • Multi-class',
entityOwner: 'Phoenix Team Manager',
applicationMessage: 'Interested in sponsoring your team for the full 2026 season.',
},
{
id: 's11',
type: 'drivers',
entityId: 'd3',
entityName: 'James Rodriguez',
status: 'pending_approval',
applicationDate: new Date('2025-12-10'),
startDate: new Date('2026-01-01'),
endDate: new Date('2026-12-31'),
price: 250,
impressions: 0,
details: 'Rising rookie • GT3 Masters',
entityOwner: 'James Rodriguez',
applicationMessage: 'Would like to support your racing career.',
},
// Recently approved (not yet started)
{
id: 's12',
type: 'races',
entityId: 'r1',
entityName: 'Spa 24 Hours',
status: 'approved',
applicationDate: new Date('2025-12-01'),
approvalDate: new Date('2025-12-05'),
startDate: new Date('2025-12-20'),
endDate: new Date('2025-12-21'),
price: 300,
impressions: 0,
details: 'Endurance Pro Series • Dec 20-21',
entityOwner: 'Race Director',
},
{
id: 's13',
type: 'drivers',
entityId: 'd4',
entityName: 'Emma Wilson',
status: 'approved',
applicationDate: new Date('2025-12-08'),
approvalDate: new Date('2025-12-10'),
startDate: new Date('2026-01-01'),
endDate: new Date('2026-12-31'),
price: 180,
impressions: 0,
details: 'Touring Car specialist',
entityOwner: 'Emma Wilson',
},
// Rejected applications
{
id: 's14',
type: 'leagues',
entityId: 'l4',
entityName: 'Elite GT Championship',
tier: 'main',
status: 'rejected',
applicationDate: new Date('2025-11-20'),
startDate: new Date('2026-01-01'),
endDate: new Date('2026-06-30'),
price: 2000,
impressions: 0,
details: '24 drivers • Invite-only',
entityOwner: 'Elite Racing Committee',
rejectionReason: 'Main sponsor position already filled for the upcoming season.',
},
{
id: 's15',
type: 'teams',
entityId: 't4',
entityName: 'Apex Motorsport',
status: 'rejected',
applicationDate: new Date('2025-11-15'),
startDate: new Date('2026-01-01'),
endDate: new Date('2026-12-31'),
price: 450,
impressions: 0,
details: '3 drivers • LMP2',
entityOwner: 'Apex Team Owner',
rejectionReason: 'Already have exclusive sponsor agreement in this category.',
},
// Existing active ones
{
id: 's4',
type: 'teams',
entityId: 't2',
entityName: 'Storm Motorsport',
status: 'active',
applicationDate: new Date('2025-10-01'),
approvalDate: new Date('2025-10-05'),
startDate: new Date('2025-10-15'),
endDate: new Date('2026-10-14'),
price: 350,
impressions: 8900,
impressionsChange: -2.1,
engagement: 4.5,
details: '3 drivers • Formula',
entityOwner: 'Storm Racing LLC',
},
{
id: 's5',
type: 'drivers',
entityId: 'd1',
entityName: 'Max Velocity',
status: 'active',
applicationDate: new Date('2025-10-20'),
approvalDate: new Date('2025-10-21'),
startDate: new Date('2025-11-01'),
endDate: new Date('2026-10-31'),
price: 200,
impressions: 8200,
impressionsChange: 15.8,
engagement: 6.2,
details: 'Velocity Racing • P1 in GT3 Masters',
entityOwner: 'Max Velocity',
},
{
id: 's6',
type: 'drivers',
entityId: 'd2',
entityName: 'Sarah Storm',
status: 'active',
applicationDate: new Date('2025-09-25'),
approvalDate: new Date('2025-09-26'),
startDate: new Date('2025-10-01'),
endDate: new Date('2026-09-30'),
price: 150,
impressions: 6100,
impressionsChange: 22.4,
engagement: 5.8,
details: 'Storm Motorsport • Rising star',
entityOwner: 'Sarah Storm',
},
{
id: 's8',
type: 'platform',
entityId: 'p1',
entityName: 'Homepage Banner',
status: 'active',
applicationDate: new Date('2025-11-25'),
approvalDate: new Date('2025-11-25'),
startDate: new Date('2025-12-01'),
endDate: new Date('2025-12-31'),
price: 500,
impressions: 52000,
impressionsChange: 3.4,
engagement: 2.1,
details: 'Header position • All pages',
entityOwner: 'GridPilot',
},
];
// ============================================================================
// Configuration
// ============================================================================
const TYPE_CONFIG = {
leagues: { icon: Trophy, color: 'text-primary-blue', bgColor: 'bg-primary-blue/10', label: 'League' },
teams: { icon: Users, color: 'text-purple-400', bgColor: 'bg-purple-400/10', label: 'Team' },
drivers: { icon: Car, color: 'text-performance-green', bgColor: 'bg-performance-green/10', label: 'Driver' },
races: { icon: Flag, color: 'text-warning-amber', bgColor: 'bg-warning-amber/10', label: 'Race' },
platform: { icon: Megaphone, color: 'text-racing-red', bgColor: 'bg-racing-red/10', label: 'Platform' },
all: { icon: BarChart3, color: 'text-gray-400', bgColor: 'bg-gray-400/10', label: 'All' },
};
const STATUS_CONFIG = {
active: {
icon: Check,
color: 'text-performance-green',
bgColor: 'bg-performance-green/10',
borderColor: 'border-performance-green/30',
label: 'Active'
},
pending_approval: {
icon: Clock,
color: 'text-warning-amber',
bgColor: 'bg-warning-amber/10',
borderColor: 'border-warning-amber/30',
label: 'Awaiting Approval'
},
approved: {
icon: ThumbsUp,
color: 'text-primary-blue',
bgColor: 'bg-primary-blue/10',
borderColor: 'border-primary-blue/30',
label: 'Approved'
},
rejected: {
icon: ThumbsDown,
color: 'text-racing-red',
bgColor: 'bg-racing-red/10',
borderColor: 'border-racing-red/30',
label: 'Declined'
},
expired: {
icon: XCircle,
color: 'text-gray-400',
bgColor: 'bg-gray-400/10',
borderColor: 'border-gray-400/30',
label: 'Expired'
},
};
// ============================================================================
// Components
// ============================================================================
function SponsorshipCard({ sponsorship }: { sponsorship: Sponsorship }) {
const router = useRouter();
const shouldReduceMotion = useReducedMotion();
const typeConfig = TYPE_CONFIG[sponsorship.type as keyof typeof TYPE_CONFIG];
const statusConfig = STATUS_CONFIG[sponsorship.status];
const TypeIcon = typeConfig.icon;
const StatusIcon = statusConfig.icon;
const daysRemaining = Math.ceil((sponsorship.endDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
const isExpiringSoon = daysRemaining > 0 && daysRemaining <= 30;
const isPending = sponsorship.status === 'pending_approval';
const isRejected = sponsorship.status === 'rejected';
const isApproved = sponsorship.status === 'approved';
const getEntityLink = () => {
switch (sponsorship.type) {
case 'leagues': return `/leagues/${sponsorship.entityId}`;
case 'teams': return `/teams/${sponsorship.entityId}`;
case 'drivers': return `/drivers/${sponsorship.entityId}`;
case 'races': return `/races/${sponsorship.entityId}`;
default: return '#';
}
};
return (
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
whileHover={{ y: -2 }}
transition={{ duration: 0.2 }}
>
<Card className={`hover:border-primary-blue/30 transition-all duration-300 ${
isPending ? 'border-warning-amber/30' :
isRejected ? 'border-racing-red/20 opacity-75' :
isApproved ? 'border-primary-blue/30' : ''
}`}>
{/* Header */}
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-lg ${typeConfig.bgColor} flex items-center justify-center`}>
<TypeIcon className={`w-5 h-5 ${typeConfig.color}`} />
</div>
<div>
<div className="flex items-center gap-2 flex-wrap">
<span className={`text-xs font-medium px-2 py-0.5 rounded ${typeConfig.bgColor} ${typeConfig.color}`}>
{typeConfig.label}
</span>
{sponsorship.tier && (
<span className={`text-xs font-medium px-2 py-0.5 rounded ${
sponsorship.tier === 'main'
? 'bg-primary-blue/20 text-primary-blue'
: 'bg-purple-400/20 text-purple-400'
}`}>
{sponsorship.tier === 'main' ? 'Main Sponsor' : 'Secondary'}
</span>
)}
</div>
</div>
</div>
<div className={`flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-medium border ${statusConfig.bgColor} ${statusConfig.color} ${statusConfig.borderColor}`}>
<StatusIcon className="w-3 h-3" />
{statusConfig.label}
</div>
</div>
{/* Entity Name */}
<h3 className="text-lg font-semibold text-white mb-1">{sponsorship.entityName}</h3>
{sponsorship.details && (
<p className="text-sm text-gray-500 mb-3">{sponsorship.details}</p>
)}
{/* Application/Approval Info for non-active states */}
{isPending && (
<div className="mb-4 p-3 rounded-lg bg-warning-amber/5 border border-warning-amber/20">
<div className="flex items-center gap-2 text-warning-amber text-sm mb-2">
<Send className="w-4 h-4" />
<span className="font-medium">Application Pending</span>
</div>
<p className="text-xs text-gray-400 mb-2">
Sent to <span className="text-gray-300">{sponsorship.entityOwner}</span> on{' '}
{sponsorship.applicationDate?.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
</p>
{sponsorship.applicationMessage && (
<p className="text-xs text-gray-500 italic">"{sponsorship.applicationMessage}"</p>
)}
</div>
)}
{isApproved && (
<div className="mb-4 p-3 rounded-lg bg-primary-blue/5 border border-primary-blue/20">
<div className="flex items-center gap-2 text-primary-blue text-sm mb-1">
<ThumbsUp className="w-4 h-4" />
<span className="font-medium">Approved!</span>
</div>
<p className="text-xs text-gray-400">
Approved by <span className="text-gray-300">{sponsorship.entityOwner}</span> on{' '}
{sponsorship.approvalDate?.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
</p>
<p className="text-xs text-gray-500 mt-1">
Starts {sponsorship.startDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
</p>
</div>
)}
{isRejected && (
<div className="mb-4 p-3 rounded-lg bg-racing-red/5 border border-racing-red/20">
<div className="flex items-center gap-2 text-racing-red text-sm mb-1">
<ThumbsDown className="w-4 h-4" />
<span className="font-medium">Application Declined</span>
</div>
{sponsorship.rejectionReason && (
<p className="text-xs text-gray-400 mt-1">
Reason: <span className="text-gray-300">{sponsorship.rejectionReason}</span>
</p>
)}
<Button variant="secondary" className="mt-2 text-xs">
<RefreshCw className="w-3 h-3 mr-1" />
Reapply
</Button>
</div>
)}
{/* Metrics Grid - Only show for active sponsorships */}
{sponsorship.status === 'active' && (
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
<div className="bg-iron-gray/50 rounded-lg p-3">
<div className="flex items-center gap-1 text-gray-400 text-xs mb-1">
<Eye className="w-3 h-3" />
Impressions
</div>
<div className="flex items-center gap-2">
<span className="text-white font-semibold">{sponsorship.impressions.toLocaleString()}</span>
{sponsorship.impressionsChange !== undefined && sponsorship.impressionsChange !== 0 && (
<span className={`text-xs flex items-center ${
sponsorship.impressionsChange > 0 ? 'text-performance-green' : 'text-racing-red'
}`}>
{sponsorship.impressionsChange > 0 ? <ArrowUpRight className="w-3 h-3" /> : <ArrowDownRight className="w-3 h-3" />}
{Math.abs(sponsorship.impressionsChange)}%
</span>
)}
</div>
</div>
{sponsorship.engagement && (
<div className="bg-iron-gray/50 rounded-lg p-3">
<div className="flex items-center gap-1 text-gray-400 text-xs mb-1">
<TrendingUp className="w-3 h-3" />
Engagement
</div>
<div className="text-white font-semibold">{sponsorship.engagement}%</div>
</div>
)}
<div className="bg-iron-gray/50 rounded-lg p-3">
<div className="flex items-center gap-1 text-gray-400 text-xs mb-1">
<Calendar className="w-3 h-3" />
Period
</div>
<div className="text-white font-semibold text-xs">
{sponsorship.startDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} - {sponsorship.endDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}
</div>
</div>
<div className="bg-iron-gray/50 rounded-lg p-3">
<div className="flex items-center gap-1 text-gray-400 text-xs mb-1">
<Trophy className="w-3 h-3" />
Investment
</div>
<div className="text-white font-semibold">${sponsorship.price}</div>
</div>
</div>
)}
{/* Basic info for non-active */}
{sponsorship.status !== 'active' && (
<div className="flex items-center gap-4 mb-4 text-sm">
<div className="flex items-center gap-1 text-gray-400">
<Calendar className="w-3.5 h-3.5" />
{sponsorship.startDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} - {sponsorship.endDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}
</div>
<div className="flex items-center gap-1 text-gray-400">
<Trophy className="w-3.5 h-3.5" />
${sponsorship.price}
</div>
</div>
)}
{/* Footer */}
<div className="flex items-center justify-between pt-3 border-t border-charcoal-outline/50">
<div className="flex items-center gap-2">
{sponsorship.status === 'active' && (
<span className={`text-xs ${isExpiringSoon ? 'text-warning-amber' : 'text-gray-500'}`}>
{daysRemaining > 0 ? `${daysRemaining} days remaining` : 'Ended'}
</span>
)}
{isPending && (
<span className="text-xs text-gray-500">
Waiting for response...
</span>
)}
</div>
<div className="flex items-center gap-2">
{sponsorship.type !== 'platform' && (
<Link href={getEntityLink()}>
<Button variant="secondary" className="text-xs">
<ExternalLink className="w-3 h-3 mr-1" />
View
</Button>
</Link>
)}
{isPending && (
<Button variant="secondary" className="text-xs text-racing-red hover:bg-racing-red/10">
Cancel Application
</Button>
)}
{sponsorship.status === 'active' && (
<Button variant="secondary" className="text-xs">
Details
<ChevronRight className="w-3 h-3 ml-1" />
</Button>
)}
</div>
</div>
</Card>
</motion.div>
);
}
// ============================================================================
// Main Component
// ============================================================================
export default function SponsorCampaignsPage() {
const router = useRouter();
const searchParams = useSearchParams();
const shouldReduceMotion = useReducedMotion();
const initialType = (searchParams.get('type') as SponsorshipType) || 'all';
const [typeFilter, setTypeFilter] = useState<SponsorshipType>(initialType);
const [statusFilter, setStatusFilter] = useState<SponsorshipStatus>('all');
const [searchQuery, setSearchQuery] = useState('');
const [loading, setLoading] = useState(true);
useEffect(() => {
const timer = setTimeout(() => setLoading(false), 300);
return () => clearTimeout(timer);
}, []);
// Filter sponsorships
const filteredSponsorships = MOCK_SPONSORSHIPS.filter(s => {
if (typeFilter !== 'all' && s.type !== typeFilter) return false;
if (statusFilter !== 'all' && s.status !== statusFilter) return false;
if (searchQuery && !s.entityName.toLowerCase().includes(searchQuery.toLowerCase())) return false;
return true;
});
// Calculate stats
const stats = {
total: MOCK_SPONSORSHIPS.length,
active: MOCK_SPONSORSHIPS.filter(s => s.status === 'active').length,
pending: MOCK_SPONSORSHIPS.filter(s => s.status === 'pending_approval').length,
approved: MOCK_SPONSORSHIPS.filter(s => s.status === 'approved').length,
rejected: MOCK_SPONSORSHIPS.filter(s => s.status === 'rejected').length,
totalInvestment: MOCK_SPONSORSHIPS.filter(s => s.status === 'active').reduce((sum, s) => sum + s.price, 0),
totalImpressions: MOCK_SPONSORSHIPS.reduce((sum, s) => sum + s.impressions, 0),
};
// Stats by type
const statsByType = {
leagues: MOCK_SPONSORSHIPS.filter(s => s.type === 'leagues').length,
teams: MOCK_SPONSORSHIPS.filter(s => s.type === 'teams').length,
drivers: MOCK_SPONSORSHIPS.filter(s => s.type === 'drivers').length,
races: MOCK_SPONSORSHIPS.filter(s => s.type === 'races').length,
platform: MOCK_SPONSORSHIPS.filter(s => s.type === 'platform').length,
};
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 sponsorships...</p>
</div>
</div>
);
}
return (
<div className="max-w-7xl mx-auto py-8 px-4">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-8">
<div>
<h1 className="text-2xl font-bold text-white flex items-center gap-3">
<Megaphone className="w-7 h-7 text-primary-blue" />
My Sponsorships
</h1>
<p className="text-gray-400 mt-1">Manage applications and active sponsorship campaigns</p>
</div>
<div className="flex items-center gap-3">
<Link href="/leagues">
<Button variant="primary">
<Plus className="w-4 h-4 mr-2" />
Find Opportunities
</Button>
</Link>
</div>
</div>
{/* Info Banner about how sponsorships work */}
{stats.pending > 0 && (
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="mb-6"
>
<InfoBanner type="info" title="Sponsorship Applications">
<p>
You have <strong className="text-white">{stats.pending} pending application{stats.pending !== 1 ? 's' : ''}</strong> waiting for approval.
League admins, team owners, and drivers review applications before accepting sponsorships.
</p>
</InfoBanner>
</motion.div>
)}
{/* Quick Stats */}
<div className="grid grid-cols-2 md:grid-cols-6 gap-4 mb-8">
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
>
<Card className="p-4">
<div className="text-2xl font-bold text-white">{stats.total}</div>
<div className="text-sm text-gray-400">Total</div>
</Card>
</motion.div>
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.05 }}
>
<Card className="p-4">
<div className="text-2xl font-bold text-performance-green">{stats.active}</div>
<div className="text-sm text-gray-400">Active</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 ${stats.pending > 0 ? 'border-warning-amber/30' : ''}`}>
<div className="text-2xl font-bold text-warning-amber">{stats.pending}</div>
<div className="text-sm text-gray-400">Pending</div>
</Card>
</motion.div>
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.15 }}
>
<Card className="p-4">
<div className="text-2xl font-bold text-primary-blue">{stats.approved}</div>
<div className="text-sm text-gray-400">Approved</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">
<div className="text-2xl font-bold text-white">${stats.totalInvestment.toLocaleString()}</div>
<div className="text-sm text-gray-400">Active Investment</div>
</Card>
</motion.div>
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.25 }}
>
<Card className="p-4">
<div className="text-2xl font-bold text-primary-blue">{(stats.totalImpressions / 1000).toFixed(0)}k</div>
<div className="text-sm text-gray-400">Impressions</div>
</Card>
</motion.div>
</div>
{/* Filters */}
<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
type="text"
placeholder="Search sponsorships..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
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>
{/* Type Filter */}
<div className="flex items-center gap-2 overflow-x-auto pb-2 lg:pb-0">
{(['all', 'leagues', 'teams', 'drivers', 'races', 'platform'] as const).map((type) => {
const config = TYPE_CONFIG[type];
const Icon = config.icon;
const count = type === 'all' ? stats.total : statsByType[type];
return (
<button
key={type}
onClick={() => setTypeFilter(type)}
className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium whitespace-nowrap transition-colors ${
typeFilter === type
? 'bg-primary-blue text-white'
: 'bg-iron-gray/50 text-gray-400 hover:bg-iron-gray'
}`}
>
<Icon className="w-4 h-4" />
{config.label}
<span className={`px-1.5 py-0.5 rounded text-xs ${
typeFilter === type ? 'bg-white/20' : 'bg-charcoal-outline'
}`}>
{count}
</span>
</button>
);
})}
</div>
{/* Status Filter */}
<div className="flex items-center gap-2 overflow-x-auto">
{(['all', 'active', 'pending_approval', 'approved', 'rejected'] as const).map((status) => {
const config = status === 'all'
? { label: 'All', color: 'text-gray-400' }
: STATUS_CONFIG[status];
const count = status === 'all'
? stats.total
: MOCK_SPONSORSHIPS.filter(s => s.status === status).length;
return (
<button
key={status}
onClick={() => setStatusFilter(status)}
className={`px-3 py-2 rounded-lg text-sm font-medium transition-colors whitespace-nowrap ${
statusFilter === status
? 'bg-iron-gray text-white border border-charcoal-outline'
: 'text-gray-500 hover:text-gray-300'
}`}
>
{config.label}
{count > 0 && status !== 'all' && (
<span className={`ml-1.5 px-1.5 py-0.5 rounded text-xs ${
status === 'pending_approval' ? 'bg-warning-amber/20 text-warning-amber' : 'bg-charcoal-outline'
}`}>
{count}
</span>
)}
</button>
);
})}
</div>
</div>
{/* Sponsorship List */}
{filteredSponsorships.length === 0 ? (
<Card className="text-center py-16">
<Megaphone className="w-12 h-12 text-gray-600 mx-auto mb-4" />
<h3 className="text-lg font-semibold text-white mb-2">No sponsorships found</h3>
<p className="text-gray-400 mb-6 max-w-md mx-auto">
{searchQuery || typeFilter !== 'all' || statusFilter !== 'all'
? 'Try adjusting your filters to see more results.'
: 'Start sponsoring leagues, teams, or drivers to grow your brand visibility.'}
</p>
<div className="flex flex-col sm:flex-row gap-3 justify-center">
<Link href="/leagues">
<Button variant="primary">
<Trophy className="w-4 h-4 mr-2" />
Browse Leagues
</Button>
</Link>
<Link href="/teams">
<Button variant="secondary">
<Users className="w-4 h-4 mr-2" />
Browse Teams
</Button>
</Link>
<Link href="/drivers">
<Button variant="secondary">
<Car className="w-4 h-4 mr-2" />
Browse Drivers
</Button>
</Link>
</div>
</Card>
) : (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{filteredSponsorships.map((sponsorship) => (
<SponsorshipCard key={sponsorship.id} sponsorship={sponsorship} />
))}
</div>
)}
</div>
);
}