645 lines
25 KiB
TypeScript
645 lines
25 KiB
TypeScript
'use client';
|
|
|
|
import { useState } 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 { useSponsorSponsorships } from "@/lib/hooks/sponsor/useSponsorSponsorships";
|
|
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
|
|
// ============================================================================
|
|
|
|
|
|
// ============================================================================
|
|
// 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: any }) {
|
|
const router = useRouter();
|
|
const shouldReduceMotion = useReducedMotion();
|
|
|
|
const typeConfig = TYPE_CONFIG[sponsorship.type as keyof typeof TYPE_CONFIG];
|
|
const statusConfig = STATUS_CONFIG[sponsorship.status as keyof typeof STATUS_CONFIG];
|
|
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.formattedImpressions}</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.formattedPrice}</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.periodDisplay}
|
|
</div>
|
|
<div className="flex items-center gap-1 text-gray-400">
|
|
<Trophy className="w-3.5 h-3.5" />
|
|
{sponsorship.formattedPrice}
|
|
</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 { data: sponsorshipsData, isLoading, error, retry } = useSponsorSponsorships('demo-sponsor-1');
|
|
|
|
if (isLoading) {
|
|
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>
|
|
);
|
|
}
|
|
|
|
if (error || !sponsorshipsData) {
|
|
return (
|
|
<div className="max-w-7xl mx-auto py-8 px-4 flex items-center justify-center min-h-[400px]">
|
|
<div className="text-center">
|
|
<p className="text-gray-400">{error?.getUserMessage() || 'No sponsorships data available'}</p>
|
|
{error && (
|
|
<Button variant="secondary" onClick={retry} className="mt-4">
|
|
Retry
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const data = sponsorshipsData;
|
|
|
|
// Filter sponsorships
|
|
const filteredSponsorships = data.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: data.sponsorships.length,
|
|
active: data.sponsorships.filter(s => s.status === 'active').length,
|
|
pending: data.sponsorships.filter(s => s.status === 'pending_approval').length,
|
|
approved: data.sponsorships.filter(s => s.status === 'approved').length,
|
|
rejected: data.sponsorships.filter(s => s.status === 'rejected').length,
|
|
totalInvestment: data.sponsorships.filter(s => s.status === 'active').reduce((sum, s) => sum + s.price, 0),
|
|
totalImpressions: data.sponsorships.reduce((sum, s) => sum + s.impressions, 0),
|
|
};
|
|
|
|
// Stats by type
|
|
const statsByType = {
|
|
leagues: data.sponsorships.filter(s => s.type === 'leagues').length,
|
|
teams: data.sponsorships.filter(s => s.type === 'teams').length,
|
|
drivers: data.sponsorships.filter(s => s.type === 'drivers').length,
|
|
races: data.sponsorships.filter(s => s.type === 'races').length,
|
|
platform: data.sponsorships.filter(s => s.type === 'platform').length,
|
|
};
|
|
|
|
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
|
|
: data.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>
|
|
);
|
|
} |