420 lines
17 KiB
TypeScript
420 lines
17 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState } from 'react';
|
|
import {
|
|
Trophy,
|
|
Users,
|
|
Calendar,
|
|
Eye,
|
|
TrendingUp,
|
|
ExternalLink,
|
|
Star,
|
|
Flag,
|
|
BarChart3,
|
|
Megaphone,
|
|
CreditCard,
|
|
FileText
|
|
} from 'lucide-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 { Badge } from '@/ui/Badge';
|
|
import { SponsorTierCard } from '@/components/sponsors/SponsorTierCard';
|
|
import { siteConfig } from '@/lib/siteConfig';
|
|
import { routes } from '@/lib/routing/RouteConfig';
|
|
|
|
interface SponsorLeagueDetailData {
|
|
league: {
|
|
id: string;
|
|
name: string;
|
|
game: string;
|
|
season: string;
|
|
description: string;
|
|
tier: 'premium' | 'standard' | 'starter';
|
|
rating: number;
|
|
drivers: number;
|
|
races: number;
|
|
completedRaces: number;
|
|
racesLeft: number;
|
|
engagement: number;
|
|
totalImpressions: number;
|
|
formattedTotalImpressions: string;
|
|
projectedTotal: number;
|
|
formattedProjectedTotal: string;
|
|
mainSponsorCpm: number;
|
|
formattedMainSponsorCpm: string;
|
|
avgViewsPerRace: number;
|
|
formattedAvgViewsPerRace: string;
|
|
nextRace?: {
|
|
name: string;
|
|
date: string;
|
|
};
|
|
sponsorSlots: {
|
|
main: {
|
|
available: boolean;
|
|
price: number;
|
|
benefits: string[];
|
|
};
|
|
secondary: {
|
|
available: number;
|
|
total: number;
|
|
price: number;
|
|
benefits: string[];
|
|
};
|
|
};
|
|
tierConfig: {
|
|
bgColor: string;
|
|
color: string;
|
|
border: string;
|
|
};
|
|
};
|
|
drivers: Array<{
|
|
id: string;
|
|
position: number;
|
|
name: string;
|
|
team: string;
|
|
country: string;
|
|
races: number;
|
|
impressions: number;
|
|
formattedImpressions: string;
|
|
}>;
|
|
races: Array<{
|
|
id: string;
|
|
name: string;
|
|
date: string;
|
|
formattedDate: string;
|
|
status: 'completed' | 'upcoming';
|
|
views: number;
|
|
}>;
|
|
}
|
|
|
|
interface SponsorLeagueDetailTemplateProps {
|
|
viewData: SponsorLeagueDetailData;
|
|
}
|
|
|
|
type TabType = 'overview' | 'drivers' | 'races' | 'sponsor';
|
|
|
|
export function SponsorLeagueDetailTemplate({ viewData }: SponsorLeagueDetailTemplateProps) {
|
|
const [activeTab, setActiveTab] = useState<TabType>('overview');
|
|
const [selectedTier, setSelectedTier] = useState<'main' | 'secondary'>('main');
|
|
|
|
const league = viewData.league;
|
|
|
|
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>
|
|
<Link href={routes.sponsor.leagues}>
|
|
<Text size="sm" color="text-gray-400">Leagues</Text>
|
|
</Link>
|
|
<Text size="sm" color="text-gray-500">/</Text>
|
|
<Text size="sm" color="text-white">{league.name}</Text>
|
|
</Stack>
|
|
</Box>
|
|
|
|
{/* Header */}
|
|
<Stack direction="row" align="start" justify="between" wrap gap={6}>
|
|
<Box style={{ flex: 1 }}>
|
|
<Stack direction="row" align="center" gap={3} mb={2}>
|
|
<Badge variant="primary">⭐ {league.tier}</Badge>
|
|
<Badge variant="success">Active Season</Badge>
|
|
<Surface variant="muted" rounded="lg" padding={1} style={{ backgroundColor: 'rgba(38, 38, 38, 0.5)', paddingLeft: '0.5rem', paddingRight: '0.5rem' }}>
|
|
<Stack direction="row" align="center" gap={1}>
|
|
<Icon icon={Star} size={3.5} color="#facc15" />
|
|
<Text size="sm" weight="medium" color="text-white">{league.rating}</Text>
|
|
</Stack>
|
|
</Surface>
|
|
</Stack>
|
|
<Heading level={1}>{league.name}</Heading>
|
|
<Text color="text-gray-400" block mt={2}>
|
|
{league.game} • {league.season} • {league.completedRaces}/{league.races} races completed
|
|
</Text>
|
|
<Text color="text-gray-400" block mt={4} style={{ maxWidth: '42rem' }}>
|
|
{league.description}
|
|
</Text>
|
|
</Box>
|
|
|
|
<Stack direction="row" gap={3}>
|
|
<Link href={`/leagues/${league.id}`}>
|
|
<Button variant="secondary" icon={<Icon icon={ExternalLink} size={4} />}>
|
|
View League
|
|
</Button>
|
|
</Link>
|
|
{(league.sponsorSlots.main.available || league.sponsorSlots.secondary.available > 0) && (
|
|
<Button variant="primary" onClick={() => setActiveTab('sponsor')} icon={<Icon icon={Megaphone} size={4} />}>
|
|
Become a Sponsor
|
|
</Button>
|
|
)}
|
|
</Stack>
|
|
</Stack>
|
|
|
|
{/* Quick Stats */}
|
|
<Grid cols={5} gap={4}>
|
|
<StatCard icon={Eye} label="Total Views" value={league.formattedTotalImpressions} color="#3b82f6" />
|
|
<StatCard icon={TrendingUp} label="Avg/Race" value={league.formattedAvgViewsPerRace} color="#10b981" />
|
|
<StatCard icon={Users} label="Drivers" value={league.drivers} color="#a855f7" />
|
|
<StatCard icon={BarChart3} label="Engagement" value={`${league.engagement}%`} color="#f59e0b" />
|
|
<StatCard icon={Calendar} label="Races Left" value={league.racesLeft} color="#ef4444" />
|
|
</Grid>
|
|
|
|
{/* Tabs */}
|
|
<Box style={{ borderBottom: '1px solid #262626' }}>
|
|
<Stack direction="row" gap={6}>
|
|
{(['overview', 'drivers', 'races', 'sponsor'] as const).map((tab) => (
|
|
<Box
|
|
key={tab}
|
|
onClick={() => setActiveTab(tab)}
|
|
pb={3}
|
|
style={{
|
|
cursor: 'pointer',
|
|
borderBottom: activeTab === tab ? '2px solid #3b82f6' : '2px solid transparent',
|
|
color: activeTab === tab ? '#3b82f6' : '#9ca3af'
|
|
}}
|
|
>
|
|
<Text size="sm" weight="medium" style={{ textTransform: 'capitalize' }}>
|
|
{tab === 'sponsor' ? '🎯 Become a Sponsor' : tab}
|
|
</Text>
|
|
</Box>
|
|
))}
|
|
</Stack>
|
|
</Box>
|
|
|
|
{/* Tab Content */}
|
|
{activeTab === 'overview' && (
|
|
<Grid cols={2} gap={6}>
|
|
<Card>
|
|
<Box mb={4}>
|
|
<Heading level={2} icon={<Icon icon={Trophy} size={5} color="#3b82f6" />}>
|
|
League Information
|
|
</Heading>
|
|
</Box>
|
|
<Stack gap={3}>
|
|
<InfoRow label="Platform" value={league.game} />
|
|
<InfoRow label="Season" value={league.season} />
|
|
<InfoRow label="Duration" value="Oct 2025 - Feb 2026" />
|
|
<InfoRow label="Drivers" value={league.drivers} />
|
|
<InfoRow label="Races" value={league.races} last />
|
|
</Stack>
|
|
</Card>
|
|
|
|
<Card>
|
|
<Box mb={4}>
|
|
<Heading level={2} icon={<Icon icon={TrendingUp} size={5} color="#10b981" />}>
|
|
Sponsorship Value
|
|
</Heading>
|
|
</Box>
|
|
<Stack gap={3}>
|
|
<InfoRow label="Total Season Views" value={league.formattedTotalImpressions} />
|
|
<InfoRow label="Projected Total" value={league.formattedProjectedTotal} />
|
|
<InfoRow label="Main Sponsor CPM" value={league.formattedMainSponsorCpm} color="text-performance-green" />
|
|
<InfoRow label="Engagement Rate" value={`${league.engagement}%`} />
|
|
<InfoRow label="League Rating" value={`${league.rating}/5.0`} last />
|
|
</Stack>
|
|
</Card>
|
|
|
|
{league.nextRace && (
|
|
<GridItem colSpan={2}>
|
|
<Card>
|
|
<Box mb={4}>
|
|
<Heading level={2} icon={<Icon icon={Flag} size={5} color="#f59e0b" />}>
|
|
Next Race
|
|
</Heading>
|
|
</Box>
|
|
<Surface variant="muted" rounded="lg" border padding={4} style={{ backgroundColor: 'rgba(245, 158, 11, 0.05)', borderColor: 'rgba(245, 158, 11, 0.2)' }}>
|
|
<Stack direction="row" align="center" justify="between">
|
|
<Stack direction="row" align="center" gap={4}>
|
|
<Surface variant="muted" rounded="lg" padding={3} style={{ backgroundColor: 'rgba(245, 158, 11, 0.1)' }}>
|
|
<Icon icon={Flag} size={6} color="#f59e0b" />
|
|
</Surface>
|
|
<Box>
|
|
<Text size="lg" weight="semibold" color="text-white" block>{league.nextRace.name}</Text>
|
|
<Text size="sm" color="text-gray-400" block mt={1}>{league.nextRace.date}</Text>
|
|
</Box>
|
|
</Stack>
|
|
<Button variant="secondary">
|
|
View Schedule
|
|
</Button>
|
|
</Stack>
|
|
</Surface>
|
|
</Card>
|
|
</GridItem>
|
|
)}
|
|
</Grid>
|
|
)}
|
|
|
|
{activeTab === 'drivers' && (
|
|
<Card p={0}>
|
|
<Box p={4} style={{ borderBottom: '1px solid #262626' }}>
|
|
<Heading level={2}>Championship Standings</Heading>
|
|
<Text size="sm" color="text-gray-400" block mt={1}>Top drivers carrying sponsor branding</Text>
|
|
</Box>
|
|
<Stack gap={0}>
|
|
{viewData.drivers.map((driver, index) => (
|
|
<Box key={driver.id} p={4} style={{ borderBottom: index < viewData.drivers.length - 1 ? '1px solid rgba(38, 38, 38, 0.5)' : 'none' }}>
|
|
<Stack direction="row" align="center" justify="between">
|
|
<Stack direction="row" align="center" gap={4}>
|
|
<Surface variant="muted" rounded="full" padding={1} style={{ width: '2.5rem', height: '2.5rem', display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: '#262626' }}>
|
|
<Text weight="bold" color="text-white">{driver.position}</Text>
|
|
</Surface>
|
|
<Box>
|
|
<Text weight="medium" color="text-white" block>{driver.name}</Text>
|
|
<Text size="sm" color="text-gray-500" block mt={1}>{driver.team} • {driver.country}</Text>
|
|
</Box>
|
|
</Stack>
|
|
<Stack direction="row" align="center" gap={8}>
|
|
<Box style={{ textAlign: 'right' }}>
|
|
<Text weight="medium" color="text-white" block>{driver.races}</Text>
|
|
<Text size="xs" color="text-gray-500">races</Text>
|
|
</Box>
|
|
<Box style={{ textAlign: 'right' }}>
|
|
<Text weight="semibold" color="text-white" block>{driver.formattedImpressions}</Text>
|
|
<Text size="xs" color="text-gray-500">views</Text>
|
|
</Box>
|
|
</Stack>
|
|
</Stack>
|
|
</Box>
|
|
))}
|
|
</Stack>
|
|
</Card>
|
|
)}
|
|
|
|
{activeTab === 'races' && (
|
|
<Card p={0}>
|
|
<Box p={4} style={{ borderBottom: '1px solid #262626' }}>
|
|
<Heading level={2}>Race Calendar</Heading>
|
|
<Text size="sm" color="text-gray-400" block mt={1}>Season schedule with view statistics</Text>
|
|
</Box>
|
|
<Stack gap={0}>
|
|
{viewData.races.map((race, index) => (
|
|
<Box key={race.id} p={4} style={{ borderBottom: index < viewData.races.length - 1 ? '1px solid rgba(38, 38, 38, 0.5)' : 'none' }}>
|
|
<Stack direction="row" align="center" justify="between">
|
|
<Stack direction="row" align="center" gap={4}>
|
|
<Box style={{ width: '0.75rem', height: '0.75rem', borderRadius: '9999px', backgroundColor: race.status === 'completed' ? '#10b981' : '#f59e0b' }} />
|
|
<Box>
|
|
<Text weight="medium" color="text-white" block>{race.name}</Text>
|
|
<Text size="sm" color="text-gray-500" block mt={1}>{race.formattedDate}</Text>
|
|
</Box>
|
|
</Stack>
|
|
<Box>
|
|
{race.status === 'completed' ? (
|
|
<Box style={{ textAlign: 'right' }}>
|
|
<Text weight="semibold" color="text-white" block>{race.views.toLocaleString()}</Text>
|
|
<Text size="xs" color="text-gray-500">views</Text>
|
|
</Box>
|
|
) : (
|
|
<Badge variant="warning">Upcoming</Badge>
|
|
)}
|
|
</Box>
|
|
</Stack>
|
|
</Box>
|
|
))}
|
|
</Stack>
|
|
</Card>
|
|
)}
|
|
|
|
{activeTab === 'sponsor' && (
|
|
<Stack gap={6}>
|
|
<Grid cols={2} gap={6}>
|
|
<SponsorTierCard
|
|
type="main"
|
|
available={league.sponsorSlots.main.available}
|
|
price={league.sponsorSlots.main.price}
|
|
benefits={league.sponsorSlots.main.benefits}
|
|
isSelected={selectedTier === 'main'}
|
|
onClick={() => setSelectedTier('main')}
|
|
/>
|
|
<SponsorTierCard
|
|
type="secondary"
|
|
available={league.sponsorSlots.secondary.available > 0}
|
|
availableCount={league.sponsorSlots.secondary.available}
|
|
totalCount={league.sponsorSlots.secondary.total}
|
|
price={league.sponsorSlots.secondary.price}
|
|
benefits={league.sponsorSlots.secondary.benefits}
|
|
isSelected={selectedTier === 'secondary'}
|
|
onClick={() => setSelectedTier('secondary')}
|
|
/>
|
|
</Grid>
|
|
|
|
<Card>
|
|
<Box mb={4}>
|
|
<Heading level={2} icon={<Icon icon={CreditCard} size={5} color="#3b82f6" />}>
|
|
Sponsorship Summary
|
|
</Heading>
|
|
</Box>
|
|
|
|
<Stack gap={3} mb={6}>
|
|
<InfoRow label="Selected Tier" value={`${selectedTier.charAt(0).toUpperCase() + selectedTier.slice(1)} Sponsor`} />
|
|
<InfoRow label="Season Price" value={`$${selectedTier === 'main' ? league.sponsorSlots.main.price : league.sponsorSlots.secondary.price}`} />
|
|
<InfoRow label={`Platform Fee (${siteConfig.fees.platformFeePercent}%)`} value={`$${((selectedTier === 'main' ? league.sponsorSlots.main.price : league.sponsorSlots.secondary.price) * siteConfig.fees.platformFeePercent / 100).toFixed(2)}`} />
|
|
<Box pt={4} style={{ borderTop: '1px solid #262626' }}>
|
|
<Stack direction="row" align="center" justify="between">
|
|
<Text weight="semibold" color="text-white">Total (excl. VAT)</Text>
|
|
<Text size="xl" weight="bold" color="text-white">
|
|
${((selectedTier === 'main' ? league.sponsorSlots.main.price : league.sponsorSlots.secondary.price) * (1 + siteConfig.fees.platformFeePercent / 100)).toFixed(2)}
|
|
</Text>
|
|
</Stack>
|
|
</Box>
|
|
</Stack>
|
|
|
|
<Text size="xs" color="text-gray-500" block mb={4}>
|
|
{siteConfig.vat.notice}
|
|
</Text>
|
|
|
|
<Stack direction="row" gap={3}>
|
|
<Button variant="primary" fullWidth icon={<Icon icon={Megaphone} size={4} />}>
|
|
Request Sponsorship
|
|
</Button>
|
|
<Button variant="secondary" icon={<Icon icon={FileText} size={4} />}>
|
|
Download Info Pack
|
|
</Button>
|
|
</Stack>
|
|
</Card>
|
|
</Stack>
|
|
)}
|
|
</Stack>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
function StatCard({ icon, label, value, color }: { icon: any, label: string, value: string | number, color: string }) {
|
|
return (
|
|
<Card>
|
|
<Stack direction="row" align="center" gap={3}>
|
|
<Surface variant="muted" rounded="lg" padding={2} style={{ backgroundColor: `${color}1A` }}>
|
|
<Icon icon={icon} size={5} color={color} />
|
|
</Surface>
|
|
<Box>
|
|
<Text size="xl" weight="bold" color="text-white" block>{value}</Text>
|
|
<Text size="xs" color="text-gray-500" block>{label}</Text>
|
|
</Box>
|
|
</Stack>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
function InfoRow({ label, value, color = 'text-white', last }: { label: string, value: string | number, color?: string, last?: boolean }) {
|
|
return (
|
|
<Box py={2} style={{ borderBottom: last ? 'none' : '1px solid rgba(38, 38, 38, 0.5)' }}>
|
|
<Stack direction="row" align="center" justify="between">
|
|
<Text color="text-gray-400">{label}</Text>
|
|
<Text weight="medium" color={color as any}>{value}</Text>
|
|
</Stack>
|
|
</Box>
|
|
);
|
|
}
|