230 lines
8.1 KiB
TypeScript
230 lines
8.1 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useMemo } from 'react';
|
|
import { Card } from '@/ui/Card';
|
|
import { Button } from '@/ui/Button';
|
|
import { Heading } from '@/ui/Heading';
|
|
import { Box } from '@/ui/Box';
|
|
import { Stack } from '@/ui/Stack';
|
|
import { Text } from '@/ui/Text';
|
|
import { Link } from '@/ui/Link';
|
|
import { Container } from '@/ui/Container';
|
|
import { Grid } from '@/ui/Grid';
|
|
import { GridItem } from '@/ui/GridItem';
|
|
import { Surface } from '@/ui/Surface';
|
|
import { Icon } from '@/ui/Icon';
|
|
import {
|
|
Trophy,
|
|
Users,
|
|
Search,
|
|
ChevronRight,
|
|
Car,
|
|
Megaphone,
|
|
} from 'lucide-react';
|
|
import { siteConfig } from '@/lib/siteConfig';
|
|
import { routes } from '@/lib/routing/RouteConfig';
|
|
import { AvailableLeagueCard } from '@/components/sponsors/AvailableLeagueCard';
|
|
|
|
interface AvailableLeague {
|
|
id: string;
|
|
name: string;
|
|
game: string;
|
|
drivers: number;
|
|
avgViewsPerRace: number;
|
|
mainSponsorSlot: { available: boolean; price: number };
|
|
secondarySlots: { available: number; total: number; price: number };
|
|
rating: number;
|
|
tier: 'premium' | 'standard' | 'starter';
|
|
nextRace?: string;
|
|
seasonStatus: 'active' | 'upcoming' | 'completed';
|
|
description: string;
|
|
formattedAvgViews: string;
|
|
formattedCpm: string;
|
|
cpm: number;
|
|
}
|
|
|
|
type SortOption = 'rating' | 'drivers' | 'price' | 'views';
|
|
type TierFilter = 'all' | 'premium' | 'standard' | 'starter';
|
|
type AvailabilityFilter = 'all' | 'main' | 'secondary';
|
|
|
|
interface SponsorLeaguesTemplateProps {
|
|
viewData: {
|
|
leagues: AvailableLeague[];
|
|
stats: {
|
|
total: number;
|
|
mainAvailable: number;
|
|
secondaryAvailable: number;
|
|
totalDrivers: number;
|
|
avgCpm: number;
|
|
};
|
|
};
|
|
}
|
|
|
|
export function SponsorLeaguesTemplate({ viewData }: SponsorLeaguesTemplateProps) {
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [tierFilter, setTierFilter] = useState<TierFilter>('all');
|
|
const [availabilityFilter, setAvailabilityFilter] = useState<AvailabilityFilter>('all');
|
|
const [sortBy, setSortBy] = useState<SortOption>('rating');
|
|
|
|
// Filter and sort leagues
|
|
const filteredLeagues = useMemo(() => {
|
|
return viewData.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;
|
|
}
|
|
});
|
|
}, [viewData.leagues, searchQuery, tierFilter, availabilityFilter, sortBy]);
|
|
|
|
const stats = viewData.stats;
|
|
|
|
return (
|
|
<Container size="lg" py={8}>
|
|
<Stack gap={8}>
|
|
{/* Breadcrumb */}
|
|
<Box>
|
|
<Stack direction="row" align="center" gap={2}>
|
|
<Link href={routes.sponsor.dashboard}>
|
|
<Text size="sm" color="text-gray-400">Dashboard</Text>
|
|
</Link>
|
|
<Text size="sm" color="text-gray-500">/</Text>
|
|
<Text size="sm" color="text-white">Browse Leagues</Text>
|
|
</Stack>
|
|
</Box>
|
|
|
|
{/* Header */}
|
|
<Box>
|
|
<Heading level={1} icon={<Icon icon={Trophy} size={7} color="#3b82f6" />}>
|
|
League Sponsorship Marketplace
|
|
</Heading>
|
|
<Text color="text-gray-400" block mt={2}>
|
|
Discover racing leagues looking for sponsors. All prices shown exclude VAT.
|
|
</Text>
|
|
</Box>
|
|
|
|
{/* Stats Overview */}
|
|
<Grid cols={5} gap={4}>
|
|
<StatCard label="Leagues" value={stats.total} />
|
|
<StatCard label="Main Slots" value={stats.mainAvailable} color="text-performance-green" />
|
|
<StatCard label="Secondary Slots" value={stats.secondaryAvailable} color="text-primary-blue" />
|
|
<StatCard label="Total Drivers" value={stats.totalDrivers} />
|
|
<StatCard label="Avg CPM" value={`$${stats.avgCpm}`} color="text-warning-amber" />
|
|
</Grid>
|
|
|
|
{/* Filters (Simplified for template) */}
|
|
<Card>
|
|
<Stack gap={4}>
|
|
<Text size="sm" color="text-gray-400">
|
|
Use the search and filter options to find the perfect league for your brand.
|
|
</Text>
|
|
<Grid cols={4} gap={4}>
|
|
<Box>
|
|
<input
|
|
type="text"
|
|
placeholder="Search leagues..."
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
className="w-full px-4 py-2 rounded-lg border border-charcoal-outline bg-iron-gray text-white placeholder-gray-500 focus:border-primary-blue focus:outline-none"
|
|
/>
|
|
</Box>
|
|
{/* Selects would go here, using standard Select UI if available */}
|
|
</Grid>
|
|
</Stack>
|
|
</Card>
|
|
|
|
{/* Results Count */}
|
|
<Stack direction="row" align="center" justify="between">
|
|
<Text size="sm" color="text-gray-400">
|
|
Showing {filteredLeagues.length} of {viewData.leagues.length} leagues
|
|
</Text>
|
|
<Stack direction="row" align="center" gap={3}>
|
|
<Link href="/teams">
|
|
<Button variant="secondary" size="sm" icon={<Icon icon={Users} size={4} />}>
|
|
Browse Teams
|
|
</Button>
|
|
</Link>
|
|
<Link href="/drivers">
|
|
<Button variant="secondary" size="sm" icon={<Icon icon={Car} size={4} />}>
|
|
Browse Drivers
|
|
</Button>
|
|
</Link>
|
|
</Stack>
|
|
</Stack>
|
|
|
|
{/* League Grid */}
|
|
{filteredLeagues.length > 0 ? (
|
|
<Grid cols={3} gap={6}>
|
|
{filteredLeagues.map((league) => (
|
|
<GridItem key={league.id} colSpan={12} mdSpan={6} lgSpan={4}>
|
|
<AvailableLeagueCard league={league as any} />
|
|
</GridItem>
|
|
))}
|
|
</Grid>
|
|
) : (
|
|
<Card>
|
|
<Stack align="center" py={16} gap={4}>
|
|
<Surface variant="muted" rounded="full" padding={4}>
|
|
<Icon icon={Trophy} size={12} color="#525252" />
|
|
</Surface>
|
|
<Box style={{ textAlign: 'center' }}>
|
|
<Heading level={3}>No leagues found</Heading>
|
|
<Text color="text-gray-400" block mt={2}>Try adjusting your filters to see more results</Text>
|
|
</Box>
|
|
<Button variant="secondary" onClick={() => {
|
|
setSearchQuery('');
|
|
setTierFilter('all');
|
|
setAvailabilityFilter('all');
|
|
}}>
|
|
Clear Filters
|
|
</Button>
|
|
</Stack>
|
|
</Card>
|
|
)}
|
|
|
|
{/* Platform Fee Notice */}
|
|
<Surface variant="muted" rounded="lg" border padding={4} style={{ backgroundColor: 'rgba(38, 38, 38, 0.3)' }}>
|
|
<Stack direction="row" align="start" gap={3}>
|
|
<Icon icon={Megaphone} size={5} color="#3b82f6" />
|
|
<Box>
|
|
<Text size="sm" color="text-gray-300" weight="medium" block mb={1}>Platform Fee</Text>
|
|
<Text size="xs" color="text-gray-500">
|
|
A {siteConfig.fees.platformFeePercent}% platform fee applies to all sponsorship payments. {siteConfig.fees.description}
|
|
</Text>
|
|
</Box>
|
|
</Stack>
|
|
</Surface>
|
|
</Stack>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
function StatCard({ label, value, color = 'text-white' }: { label: string, value: string | number, color?: string }) {
|
|
return (
|
|
<Card>
|
|
<Box style={{ textAlign: 'center' }}>
|
|
<Text size="2xl" weight="bold" color={color as any} block mb={1}>{value}</Text>
|
|
<Text size="sm" color="text-gray-400">{label}</Text>
|
|
</Box>
|
|
</Card>
|
|
);
|
|
}
|