website refactor
This commit is contained in:
@@ -2,12 +2,16 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { motion, useReducedMotion, AnimatePresence } from 'framer-motion';
|
||||
import { motion, useReducedMotion } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { InfoBanner } from '@/ui/InfoBanner';
|
||||
import { useSponsorSponsorships } from "@/lib/hooks/sponsor/useSponsorSponsorships";
|
||||
import { useSponsorSponsorships } from "@/hooks/sponsor/useSponsorSponsorships";
|
||||
import {
|
||||
Megaphone,
|
||||
Trophy,
|
||||
@@ -23,17 +27,14 @@ import {
|
||||
Car,
|
||||
Flag,
|
||||
Search,
|
||||
Filter,
|
||||
TrendingUp,
|
||||
BarChart3,
|
||||
ArrowUpRight,
|
||||
ArrowDownRight,
|
||||
AlertCircle,
|
||||
Send,
|
||||
ThumbsUp,
|
||||
ThumbsDown,
|
||||
RefreshCw,
|
||||
Handshake
|
||||
} from 'lucide-react';
|
||||
|
||||
// ============================================================================
|
||||
@@ -98,26 +99,27 @@ const STATUS_CONFIG = {
|
||||
// Components
|
||||
// ============================================================================
|
||||
|
||||
function SponsorshipCard({ sponsorship }: { sponsorship: any }) {
|
||||
function SponsorshipCard({ sponsorship }: { sponsorship: unknown }) {
|
||||
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 s = sponsorship as any; // Temporary cast to avoid breaking logic
|
||||
const typeConfig = TYPE_CONFIG[s.type as keyof typeof TYPE_CONFIG];
|
||||
const statusConfig = STATUS_CONFIG[s.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 daysRemaining = Math.ceil((s.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 isPending = s.status === 'pending_approval';
|
||||
const isRejected = s.status === 'rejected';
|
||||
const isApproved = s.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}`;
|
||||
switch (s.type) {
|
||||
case 'leagues': return `/leagues/${s.entityId}`;
|
||||
case 'teams': return `/teams/${s.entityId}`;
|
||||
case 'drivers': return `/drivers/${s.entityId}`;
|
||||
case 'races': return `/races/${s.entityId}`;
|
||||
default: return '#';
|
||||
}
|
||||
};
|
||||
@@ -135,172 +137,166 @@ function SponsorshipCard({ sponsorship }: { sponsorship: any }) {
|
||||
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`}>
|
||||
<Stack direction="row" align="start" justify="between" mb={4}>
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Box w="10" h="10" rounded="lg" bg={typeConfig.bgColor} display="flex" alignItems="center" justifyContent="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}`}>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box display="flex" alignItems="center" gap={2} flexWrap="wrap">
|
||||
<Text size="xs" weight="medium" px={2} py={0.5} rounded="md" bg={typeConfig.bgColor} color={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>
|
||||
</Text>
|
||||
{s.tier && (
|
||||
<Text size="xs" weight="medium" px={2} py={0.5} rounded="md" bg={s.tier === 'main' ? 'bg-primary-blue/20' : 'bg-purple-400/20'} color={s.tier === 'main' ? 'text-primary-blue' : 'text-purple-400'}>
|
||||
{s.tier === 'main' ? 'Main Sponsor' : 'Secondary'}
|
||||
</Text>
|
||||
)}
|
||||
</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}`}>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box display="flex" alignItems="center" gap={1} px={2.5} py={1} rounded="full" border bg={statusConfig.bgColor} color={statusConfig.color} borderColor={statusConfig.borderColor}>
|
||||
<StatusIcon className="w-3 h-3" />
|
||||
{statusConfig.label}
|
||||
</div>
|
||||
</div>
|
||||
<Text size="xs" weight="medium">{statusConfig.label}</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* 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>
|
||||
<Heading level={3} fontSize="lg" weight="semibold" color="text-white" mb={1}>{s.entityName}</Heading>
|
||||
{s.details && (
|
||||
<Text size="sm" color="text-gray-500" block mb={3}>{s.details}</Text>
|
||||
)}
|
||||
|
||||
{/* 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">
|
||||
<Box mb={4} p={3} rounded="lg" bg="bg-warning-amber/5" border borderColor="border-warning-amber/20">
|
||||
<Stack direction="row" align="center" gap={2} color="text-warning-amber" 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>
|
||||
<Text size="sm" weight="medium">Application Pending</Text>
|
||||
</Stack>
|
||||
<Text size="xs" color="text-gray-400" block mb={2}>
|
||||
Sent to <Text color="text-gray-300">{s.entityOwner}</Text> on{' '}
|
||||
{s.applicationDate?.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
|
||||
</Text>
|
||||
{s.applicationMessage && (
|
||||
<Text size="xs" color="text-gray-500" italic block>"{s.applicationMessage}"</Text>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{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">
|
||||
<Box mb={4} p={3} rounded="lg" bg="bg-primary-blue/5" border borderColor="border-primary-blue/20">
|
||||
<Stack direction="row" align="center" gap={2} color="text-primary-blue" 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>
|
||||
<Text size="sm" weight="medium">Approved!</Text>
|
||||
</Stack>
|
||||
<Text size="xs" color="text-gray-400" block>
|
||||
Approved by <Text color="text-gray-300">{s.entityOwner}</Text> on{' '}
|
||||
{s.approvalDate?.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500" block mt={1}>
|
||||
Starts {s.startDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{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">
|
||||
<Box mb={4} p={3} rounded="lg" bg="bg-racing-red/5" border borderColor="border-racing-red/20">
|
||||
<Stack direction="row" align="center" gap={2} color="text-racing-red" 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>
|
||||
<Text size="sm" weight="medium">Application Declined</Text>
|
||||
</Stack>
|
||||
{s.rejectionReason && (
|
||||
<Text size="xs" color="text-gray-400" block mt={1}>
|
||||
Reason: <Text color="text-gray-300">{s.rejectionReason}</Text>
|
||||
</Text>
|
||||
)}
|
||||
<Button variant="secondary" className="mt-2 text-xs">
|
||||
<RefreshCw className="w-3 h-3 mr-1" />
|
||||
Reapply
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* 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">
|
||||
{s.status === 'active' && (
|
||||
<Box display="grid" gridCols={{ base: 2, md: 4 }} gap={3} mb={4}>
|
||||
<Box bg="bg-iron-gray/50" rounded="lg" p={3}>
|
||||
<Box display="flex" alignItems="center" gap={1} color="text-gray-400" 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>
|
||||
<Text size="xs">Impressions</Text>
|
||||
</Box>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Text color="text-white" weight="semibold">{s.formattedImpressions}</Text>
|
||||
{s.impressionsChange !== undefined && s.impressionsChange !== 0 && (
|
||||
<Text size="xs" display="flex" alignItems="center" color={s.impressionsChange > 0 ? 'text-performance-green' : 'text-racing-red'}>
|
||||
{s.impressionsChange > 0 ? <ArrowUpRight className="w-3 h-3" /> : <ArrowDownRight className="w-3 h-3" />}
|
||||
{Math.abs(s.impressionsChange)}%
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{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">
|
||||
{s.engagement && (
|
||||
<Box bg="bg-iron-gray/50" rounded="lg" p={3}>
|
||||
<Box display="flex" alignItems="center" gap={1} color="text-gray-400" mb={1}>
|
||||
<TrendingUp className="w-3 h-3" />
|
||||
Engagement
|
||||
</div>
|
||||
<div className="text-white font-semibold">{sponsorship.engagement}%</div>
|
||||
</div>
|
||||
<Text size="xs">Engagement</Text>
|
||||
</Box>
|
||||
<Text color="text-white" weight="semibold">{s.engagement}%</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<div className="bg-iron-gray/50 rounded-lg p-3">
|
||||
<div className="flex items-center gap-1 text-gray-400 text-xs mb-1">
|
||||
<Box bg="bg-iron-gray/50" rounded="lg" p={3}>
|
||||
<Box display="flex" alignItems="center" gap={1} color="text-gray-400" 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>
|
||||
<Text size="xs">Period</Text>
|
||||
</Box>
|
||||
<Text color="text-white" weight="semibold" size="xs">
|
||||
{s.startDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} - {s.endDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<div className="bg-iron-gray/50 rounded-lg p-3">
|
||||
<div className="flex items-center gap-1 text-gray-400 text-xs mb-1">
|
||||
<Box bg="bg-iron-gray/50" rounded="lg" p={3}>
|
||||
<Box display="flex" alignItems="center" gap={1} color="text-gray-400" mb={1}>
|
||||
<Trophy className="w-3 h-3" />
|
||||
Investment
|
||||
</div>
|
||||
<div className="text-white font-semibold">{sponsorship.formattedPrice}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Text size="xs">Investment</Text>
|
||||
</Box>
|
||||
<Text color="text-white" weight="semibold">{s.formattedPrice}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* 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">
|
||||
{s.status !== 'active' && (
|
||||
<Stack direction="row" align="center" gap={4} mb={4}>
|
||||
<Box display="flex" alignItems="center" gap={1} color="text-gray-400">
|
||||
<Calendar className="w-3.5 h-3.5" />
|
||||
{sponsorship.periodDisplay}
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-gray-400">
|
||||
<Text size="sm">{s.periodDisplay}</Text>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={1} color="text-gray-400">
|
||||
<Trophy className="w-3.5 h-3.5" />
|
||||
{sponsorship.formattedPrice}
|
||||
</div>
|
||||
</div>
|
||||
<Text size="sm">{s.formattedPrice}</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{/* 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'}`}>
|
||||
<Box display="flex" alignItems="center" justifyContent="between" pt={3} borderTop borderColor="border-charcoal-outline/50">
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
{s.status === 'active' && (
|
||||
<Text size="xs" color={isExpiringSoon ? 'text-warning-amber' : 'text-gray-500'}>
|
||||
{daysRemaining > 0 ? `${daysRemaining} days remaining` : 'Ended'}
|
||||
</span>
|
||||
</Text>
|
||||
)}
|
||||
{isPending && (
|
||||
<span className="text-xs text-gray-500">
|
||||
<Text size="xs" color="text-gray-500">
|
||||
Waiting for response...
|
||||
</span>
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{sponsorship.type !== 'platform' && (
|
||||
</Box>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
{s.type !== 'platform' && (
|
||||
<Link href={getEntityLink()}>
|
||||
<Button variant="secondary" className="text-xs">
|
||||
<ExternalLink className="w-3 h-3 mr-1" />
|
||||
@@ -313,14 +309,14 @@ function SponsorshipCard({ sponsorship }: { sponsorship: any }) {
|
||||
Cancel Application
|
||||
</Button>
|
||||
)}
|
||||
{sponsorship.status === 'active' && (
|
||||
{s.status === 'active' && (
|
||||
<Button variant="secondary" className="text-xs">
|
||||
Details
|
||||
<ChevronRight className="w-3 h-3 ml-1" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
@@ -370,53 +366,65 @@ export default function SponsorCampaignsPage() {
|
||||
const data = sponsorshipsData;
|
||||
|
||||
// Filter sponsorships
|
||||
const filteredSponsorships = data.sponsorships.filter((s: any) => {
|
||||
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;
|
||||
const filteredSponsorships = data.sponsorships.filter((s: unknown) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const sponsorship = s as any;
|
||||
if (typeFilter !== 'all' && sponsorship.type !== typeFilter) return false;
|
||||
if (statusFilter !== 'all' && sponsorship.status !== statusFilter) return false;
|
||||
if (searchQuery && !sponsorship.entityName.toLowerCase().includes(searchQuery.toLowerCase())) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
// Calculate stats
|
||||
const stats = {
|
||||
total: data.sponsorships.length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
active: data.sponsorships.filter((s: any) => s.status === 'active').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
pending: data.sponsorships.filter((s: any) => s.status === 'pending_approval').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
approved: data.sponsorships.filter((s: any) => s.status === 'approved').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
rejected: data.sponsorships.filter((s: any) => s.status === 'rejected').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
totalInvestment: data.sponsorships.filter((s: any) => s.status === 'active').reduce((sum: number, s: any) => sum + s.price, 0),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
totalImpressions: data.sponsorships.reduce((sum: number, s: any) => sum + s.impressions, 0),
|
||||
};
|
||||
|
||||
// Stats by type
|
||||
const statsByType = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
leagues: data.sponsorships.filter((s: any) => s.type === 'leagues').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
teams: data.sponsorships.filter((s: any) => s.type === 'teams').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
drivers: data.sponsorships.filter((s: any) => s.type === 'drivers').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
races: data.sponsorships.filter((s: any) => s.type === 'races').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
platform: data.sponsorships.filter((s: any) => s.type === 'platform').length,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto py-8 px-4">
|
||||
<Box maxWidth="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" />
|
||||
<Stack direction={{ base: 'col', md: 'row' }} align="center" justify="between" gap={4} mb={8}>
|
||||
<Box>
|
||||
<Heading level={1} fontSize="2xl" weight="bold" color="text-white" icon={<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">
|
||||
</Heading>
|
||||
<Text color="text-gray-400" mt={1} block>Manage applications and active sponsorship campaigns</Text>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Link href="/leagues">
|
||||
<Button variant="primary">
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Find Opportunities
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* Info Banner about how sponsorships work */}
|
||||
{stats.pending > 0 && (
|
||||
@@ -426,23 +434,23 @@ export default function SponsorCampaignsPage() {
|
||||
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.
|
||||
<Text size="sm">
|
||||
You have <Text weight="bold" color="text-white">{stats.pending} pending application{stats.pending !== 1 ? 's' : ''}</Text> waiting for approval.
|
||||
League admins, team owners, and drivers review applications before accepting sponsorships.
|
||||
</p>
|
||||
</Text>
|
||||
</InfoBanner>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Quick Stats */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-6 gap-4 mb-8">
|
||||
<Box display="grid" gridCols={{ base: 2, md: 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>
|
||||
<Text size="2xl" weight="bold" color="text-white" block>{stats.total}</Text>
|
||||
<Text size="sm" color="text-gray-400">Total</Text>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
@@ -451,8 +459,8 @@ export default function SponsorCampaignsPage() {
|
||||
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>
|
||||
<Text size="2xl" weight="bold" color="text-performance-green" block>{stats.active}</Text>
|
||||
<Text size="sm" color="text-gray-400">Active</Text>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
@@ -461,8 +469,8 @@ export default function SponsorCampaignsPage() {
|
||||
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>
|
||||
<Text size="2xl" weight="bold" color="text-warning-amber" block>{stats.pending}</Text>
|
||||
<Text size="sm" color="text-gray-400">Pending</Text>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
@@ -471,8 +479,8 @@ export default function SponsorCampaignsPage() {
|
||||
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>
|
||||
<Text size="2xl" weight="bold" color="text-primary-blue" block>{stats.approved}</Text>
|
||||
<Text size="sm" color="text-gray-400">Approved</Text>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
@@ -481,8 +489,8 @@ export default function SponsorCampaignsPage() {
|
||||
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>
|
||||
<Text size="2xl" weight="bold" color="text-white" block>${stats.totalInvestment.toLocaleString()}</Text>
|
||||
<Text size="sm" color="text-gray-400">Active Investment</Text>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
@@ -491,16 +499,16 @@ export default function SponsorCampaignsPage() {
|
||||
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>
|
||||
<Text size="2xl" weight="bold" color="text-primary-blue" block>{(stats.totalImpressions / 1000).toFixed(0)}k</Text>
|
||||
<Text size="sm" color="text-gray-400">Impressions</Text>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex flex-col lg:flex-row gap-4 mb-6">
|
||||
<Stack direction={{ base: 'col', lg: 'row' }} gap={4} mb={6}>
|
||||
{/* Search */}
|
||||
<div className="relative flex-1">
|
||||
<Box position="relative" flexGrow={1}>
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
|
||||
<input
|
||||
type="text"
|
||||
@@ -509,10 +517,10 @@ export default function SponsorCampaignsPage() {
|
||||
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>
|
||||
</Box>
|
||||
|
||||
{/* Type Filter */}
|
||||
<div className="flex items-center gap-2 overflow-x-auto pb-2 lg:pb-0">
|
||||
<Box display="flex" alignItems="center" gap={2} overflow="auto" pb={{ base: 2, lg: 0 }}>
|
||||
{(['all', 'leagues', 'teams', 'drivers', 'races', 'platform'] as const).map((type) => {
|
||||
const config = TYPE_CONFIG[type];
|
||||
const Icon = config.icon;
|
||||
@@ -525,22 +533,20 @@ export default function SponsorCampaignsPage() {
|
||||
typeFilter === type
|
||||
? 'bg-primary-blue text-white'
|
||||
: 'bg-iron-gray/50 text-gray-400 hover:bg-iron-gray'
|
||||
}`}
|
||||
} border-0 cursor-pointer`}
|
||||
>
|
||||
<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'
|
||||
}`}>
|
||||
<Text size="xs" px={1.5} py={0.5} rounded="sm" bg={typeFilter === type ? 'bg-white/20' : 'bg-charcoal-outline'}>
|
||||
{count}
|
||||
</span>
|
||||
</Text>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
{/* Status Filter */}
|
||||
<div className="flex items-center gap-2 overflow-x-auto">
|
||||
<Box display="flex" alignItems="center" gap={2} overflow="auto">
|
||||
{(['all', 'active', 'pending_approval', 'approved', 'rejected'] as const).map((status) => {
|
||||
const config = status === 'all'
|
||||
? { label: 'All', color: 'text-gray-400' }
|
||||
@@ -556,33 +562,31 @@ export default function SponsorCampaignsPage() {
|
||||
statusFilter === status
|
||||
? 'bg-iron-gray text-white border border-charcoal-outline'
|
||||
: 'text-gray-500 hover:text-gray-300'
|
||||
}`}
|
||||
} border-0 cursor-pointer`}
|
||||
>
|
||||
{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'
|
||||
}`}>
|
||||
<Text size="xs" ml={1.5} px={1.5} py={0.5} rounded="sm" bg={status === 'pending_approval' ? 'bg-warning-amber/20' : 'bg-charcoal-outline'} color={status === 'pending_approval' ? 'text-warning-amber' : ''}>
|
||||
{count}
|
||||
</span>
|
||||
</Text>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* 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">
|
||||
<Heading level={3} fontSize="lg" weight="semibold" color="text-white" mb={2}>No sponsorships found</Heading>
|
||||
<Text color="text-gray-400" mb={6} maxWidth="md" mx="auto" block>
|
||||
{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">
|
||||
</Text>
|
||||
<Stack direction={{ base: 'col', md: 'row' }} gap={3} justify="center">
|
||||
<Link href="/leagues">
|
||||
<Button variant="primary">
|
||||
<Trophy className="w-4 h-4 mr-2" />
|
||||
@@ -601,15 +605,15 @@ export default function SponsorCampaignsPage() {
|
||||
Browse Drivers
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</Stack>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<Box display="grid" gridCols={{ base: 1, lg: 2 }} gap={4}>
|
||||
{filteredSponsorships.map((sponsorship: any) => (
|
||||
<SponsorshipCard key={sponsorship.id} sponsorship={sponsorship} />
|
||||
))}
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user