445 lines
18 KiB
TypeScript
445 lines
18 KiB
TypeScript
'use client';
|
|
|
|
import { BillingSummaryPanel } from '@/components/sponsors/BillingSummaryPanel';
|
|
import { PricingTableShell, PricingTier } from '@/components/sponsors/PricingTableShell';
|
|
import { SponsorBrandingPreview } from '@/components/sponsors/SponsorBrandingPreview';
|
|
import { SponsorDashboardHeader } from '@/components/sponsors/SponsorDashboardHeader';
|
|
import { SponsorStatusChip } from '@/components/sponsors/SponsorStatusChip';
|
|
import { routes } from '@/lib/routing/RouteConfig';
|
|
import { siteConfig } from '@/lib/siteConfig';
|
|
import {
|
|
SharedBox,
|
|
SharedButton,
|
|
SharedStack,
|
|
SharedText,
|
|
SharedIcon,
|
|
SharedCard,
|
|
SharedContainer
|
|
} from '@/components/shared/UIComponents';
|
|
import { Heading } from '@/ui/Heading';
|
|
import { Link } from '@/ui/Link';
|
|
import { Grid } from '@/ui/Grid';
|
|
import { GridItem } from '@/ui/GridItem';
|
|
import { Surface } from '@/ui/Surface';
|
|
import {
|
|
BarChart3,
|
|
Calendar,
|
|
CreditCard,
|
|
Eye,
|
|
FileText,
|
|
Flag,
|
|
Megaphone,
|
|
TrendingUp,
|
|
Trophy,
|
|
type LucideIcon
|
|
} from 'lucide-react';
|
|
import { TemplateProps } from '@/lib/contracts/components/ComponentContracts';
|
|
import { ViewData } from '@/lib/contracts/view-data/ViewData';
|
|
|
|
export interface SponsorLeagueDetailViewData extends ViewData {
|
|
league: {
|
|
id: string;
|
|
name: string;
|
|
game: string;
|
|
season: string;
|
|
description: string;
|
|
tier: 'premium' | 'standard' | 'starter';
|
|
rating: number;
|
|
formattedRating: string;
|
|
drivers: number;
|
|
formattedDrivers: string;
|
|
races: number;
|
|
formattedRaces: string;
|
|
completedRaces: number;
|
|
racesLeft: number;
|
|
formattedRacesLeft: string;
|
|
engagement: number;
|
|
formattedEngagement: string;
|
|
totalImpressions: number;
|
|
formattedTotalImpressions: string;
|
|
projectedTotal: number;
|
|
formattedProjectedTotal: string;
|
|
mainSponsorCpm: number;
|
|
formattedMainSponsorCpm: string;
|
|
avgViewsPerRace: number;
|
|
formattedAvgViewsPerRace: string;
|
|
nextRace?: {
|
|
name: string;
|
|
date: string;
|
|
formattedDate: string;
|
|
};
|
|
sponsorSlots: {
|
|
main: {
|
|
available: boolean;
|
|
price: number;
|
|
priceLabel: string;
|
|
benefits: string[];
|
|
};
|
|
secondary: {
|
|
available: number;
|
|
total: number;
|
|
price: number;
|
|
priceLabel: string;
|
|
benefits: string[];
|
|
};
|
|
};
|
|
tierConfig: {
|
|
bgColor: string;
|
|
color: string;
|
|
border: string;
|
|
};
|
|
};
|
|
drivers: Array<{
|
|
id: string;
|
|
position: number;
|
|
positionLabel: string;
|
|
name: string;
|
|
team: string;
|
|
country: string;
|
|
races: number;
|
|
formattedRaces: string;
|
|
impressions: number;
|
|
formattedImpressions: string;
|
|
}>;
|
|
races: Array<{
|
|
id: string;
|
|
name: string;
|
|
date: string;
|
|
formattedDate: string;
|
|
status: 'completed' | 'upcoming';
|
|
views: number;
|
|
formattedViews: string;
|
|
}>;
|
|
}
|
|
|
|
export type SponsorLeagueDetailTab = 'overview' | 'drivers' | 'races' | 'sponsor';
|
|
|
|
interface SponsorLeagueDetailTemplateProps extends TemplateProps<SponsorLeagueDetailViewData> {
|
|
activeTab: SponsorLeagueDetailTab;
|
|
setActiveTab: (tab: SponsorLeagueDetailTab) => void;
|
|
selectedTier: 'main' | 'secondary';
|
|
setSelectedTier: (tier: 'main' | 'secondary') => void;
|
|
}
|
|
|
|
export function SponsorLeagueDetailTemplate({
|
|
viewData,
|
|
activeTab,
|
|
setActiveTab,
|
|
selectedTier,
|
|
setSelectedTier
|
|
}: SponsorLeagueDetailTemplateProps) {
|
|
const league = viewData.league;
|
|
|
|
const billingStats: Array<{
|
|
label: string;
|
|
value: string | number;
|
|
icon: LucideIcon;
|
|
variant: 'info' | 'success' | 'warning' | 'default';
|
|
}> = [
|
|
{
|
|
label: 'Total Views',
|
|
value: league.formattedTotalImpressions,
|
|
icon: Eye,
|
|
variant: 'info',
|
|
},
|
|
{
|
|
label: 'Avg/Race',
|
|
value: league.formattedAvgViewsPerRace,
|
|
icon: TrendingUp,
|
|
variant: 'success',
|
|
},
|
|
{
|
|
label: 'Engagement',
|
|
value: league.formattedEngagement,
|
|
icon: BarChart3,
|
|
variant: 'warning',
|
|
},
|
|
{
|
|
label: 'Races Left',
|
|
value: league.formattedRacesLeft,
|
|
icon: Calendar,
|
|
variant: 'default',
|
|
},
|
|
];
|
|
|
|
const pricingTiers: PricingTier[] = [
|
|
{
|
|
id: 'main',
|
|
name: 'Main Sponsor',
|
|
price: league.sponsorSlots.main.price,
|
|
priceLabel: league.sponsorSlots.main.priceLabel,
|
|
period: 'Season',
|
|
description: 'Exclusive primary branding across all league assets.',
|
|
features: league.sponsorSlots.main.benefits,
|
|
available: league.sponsorSlots.main.available,
|
|
isPopular: true,
|
|
},
|
|
{
|
|
id: 'secondary',
|
|
name: 'Secondary Sponsor',
|
|
price: league.sponsorSlots.secondary.price,
|
|
priceLabel: league.sponsorSlots.secondary.priceLabel,
|
|
period: 'Season',
|
|
description: 'Supporting branding on cars and broadcast overlays.',
|
|
features: league.sponsorSlots.secondary.benefits,
|
|
available: league.sponsorSlots.secondary.available > 0,
|
|
}
|
|
];
|
|
|
|
return (
|
|
<SharedContainer size="lg">
|
|
<SharedBox paddingY={8}>
|
|
<SharedStack gap={8}>
|
|
{/* Breadcrumb */}
|
|
<SharedBox>
|
|
<SharedStack direction="row" align="center" gap={2}>
|
|
<Link href={routes.sponsor.dashboard}>
|
|
<SharedText size="sm" color="text-gray-400">Dashboard</SharedText>
|
|
</Link>
|
|
<SharedText size="sm" color="text-gray-500">/</SharedText>
|
|
<Link href={routes.sponsor.leagues}>
|
|
<SharedText size="sm" color="text-gray-400">Leagues</SharedText>
|
|
</Link>
|
|
<SharedText size="sm" color="text-gray-500">/</SharedText>
|
|
<SharedText size="sm" color="text-white">{league.name}</SharedText>
|
|
</SharedStack>
|
|
</SharedBox>
|
|
|
|
{/* Header */}
|
|
<SponsorDashboardHeader
|
|
sponsorName="Sponsor"
|
|
onRefresh={() => console.log('Refresh')}
|
|
/>
|
|
|
|
{/* Quick Stats */}
|
|
<BillingSummaryPanel stats={billingStats} />
|
|
|
|
{/* Tabs */}
|
|
<SharedBox borderBottom borderColor="border-neutral-800">
|
|
<SharedStack direction="row" gap={6}>
|
|
{(['overview', 'drivers', 'races', 'sponsor'] as const).map((tab) => (
|
|
<SharedBox
|
|
key={tab}
|
|
onClick={() => setActiveTab(tab)}
|
|
style={{ paddingBottom: '0.75rem' }}
|
|
cursor="pointer"
|
|
borderBottom={activeTab === tab}
|
|
borderColor={activeTab === tab ? 'border-primary-blue' : 'border-transparent'}
|
|
color={activeTab === tab ? 'text-primary-blue' : 'text-gray-400'}
|
|
>
|
|
<SharedText size="sm" weight="medium" className="uppercase">
|
|
{tab === 'sponsor' ? '🎯 Become a Sponsor' : tab}
|
|
</SharedText>
|
|
</SharedBox>
|
|
))}
|
|
</SharedStack>
|
|
</SharedBox>
|
|
|
|
{/* Tab Content */}
|
|
{activeTab === 'overview' && (
|
|
<Grid cols={2} gap={6}>
|
|
<SharedCard>
|
|
<SharedBox mb={4}>
|
|
<SharedStack direction="row" align="center" gap={3}>
|
|
<SharedIcon icon={Trophy} size={5} color="#3b82f6" />
|
|
<Heading level={2}>
|
|
League Information
|
|
</Heading>
|
|
</SharedStack>
|
|
</SharedBox>
|
|
<SharedStack 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.formattedDrivers} />
|
|
<InfoRow label="Races" value={league.formattedRaces} last />
|
|
</SharedStack>
|
|
</SharedCard>
|
|
|
|
<SharedCard>
|
|
<SharedBox mb={4}>
|
|
<Heading level={2} icon={<SharedIcon icon={TrendingUp} size={5} color="#10b981" />}>
|
|
Sponsorship Value
|
|
</Heading>
|
|
</SharedBox>
|
|
<SharedStack 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.formattedEngagement} />
|
|
<InfoRow label="League Rating" value={league.formattedRating} last />
|
|
</SharedStack>
|
|
</SharedCard>
|
|
|
|
{league.nextRace && (
|
|
<GridItem colSpan={2}>
|
|
<SharedCard>
|
|
<SharedBox mb={4}>
|
|
<Heading level={2} icon={<SharedIcon icon={Flag} size={5} color="#f59e0b" />}>
|
|
Next Race
|
|
</Heading>
|
|
</SharedBox>
|
|
<Surface variant="muted" rounded="lg" border padding={4} style={{ background: 'rgba(245, 158, 11, 0.05)', borderColor: 'rgba(245, 158, 11, 0.2)' }}>
|
|
<SharedStack direction="row" align="center" justify="between">
|
|
<SharedStack direction="row" align="center" gap={4}>
|
|
<Surface variant="muted" rounded="lg" padding={3} style={{ background: 'rgba(245, 158, 11, 0.1)' }}>
|
|
<SharedIcon icon={Flag} size={6} color="#f59e0b" />
|
|
</Surface>
|
|
<SharedBox>
|
|
<SharedText size="lg" weight="semibold" color="text-white" block>{league.nextRace.name}</SharedText>
|
|
<SharedText size="sm" color="text-gray-400" block mt={1}>{league.nextRace.formattedDate}</SharedText>
|
|
</SharedBox>
|
|
</SharedStack>
|
|
<SharedButton variant="secondary">
|
|
View Schedule
|
|
</SharedButton>
|
|
</SharedStack>
|
|
</Surface>
|
|
</SharedCard>
|
|
</GridItem>
|
|
)}
|
|
</Grid>
|
|
)}
|
|
|
|
{activeTab === 'drivers' && (
|
|
<SharedCard p={0}>
|
|
<SharedBox p={4} borderBottom borderColor="border-neutral-800">
|
|
<Heading level={2}>Championship Standings</Heading>
|
|
<SharedText size="sm" color="text-gray-400" block mt={1}>Top drivers carrying sponsor branding</SharedText>
|
|
</SharedBox>
|
|
<SharedStack gap={0}>
|
|
{viewData.drivers.map((driver, index) => (
|
|
<SharedBox key={driver.id} p={4} borderBottom={index < viewData.drivers.length - 1} borderColor="border-neutral-800/50">
|
|
<SharedStack direction="row" align="center" justify="between">
|
|
<SharedStack 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', background: '#262626' }}>
|
|
<SharedText weight="bold" color="text-white">{driver.positionLabel}</SharedText>
|
|
</Surface>
|
|
<SharedBox>
|
|
<SharedText weight="medium" color="text-white" block>{driver.name}</SharedText>
|
|
<SharedText size="sm" color="text-gray-500" block mt={1}>{driver.team} • {driver.country}</SharedText>
|
|
</SharedBox>
|
|
</SharedStack>
|
|
<SharedStack direction="row" align="center" gap={8}>
|
|
<SharedBox textAlign="right">
|
|
<SharedText weight="medium" color="text-white" block>{driver.formattedRaces}</SharedText>
|
|
<SharedText size="xs" color="text-gray-500">races</SharedText>
|
|
</SharedBox>
|
|
<SharedBox textAlign="right">
|
|
<SharedText weight="semibold" color="text-white" block>{driver.formattedImpressions}</SharedText>
|
|
<SharedText size="xs" color="text-gray-500">views</SharedText>
|
|
</SharedBox>
|
|
</SharedStack>
|
|
</SharedStack>
|
|
</SharedBox>
|
|
))}
|
|
</SharedStack>
|
|
</SharedCard>
|
|
)}
|
|
|
|
{activeTab === 'races' && (
|
|
<SharedCard p={0}>
|
|
<SharedBox p={4} borderBottom borderColor="border-neutral-800">
|
|
<Heading level={2}>Race Calendar</Heading>
|
|
<SharedText size="sm" color="text-gray-400" block mt={1}>Season schedule with view statistics</SharedText>
|
|
</SharedBox>
|
|
<SharedStack gap={0}>
|
|
{viewData.races.map((race, index) => (
|
|
<SharedBox key={race.id} p={4} borderBottom={index < viewData.races.length - 1} borderColor="border-neutral-800/50">
|
|
<SharedStack direction="row" align="center" justify="between">
|
|
<SharedStack direction="row" align="center" gap={4}>
|
|
<SharedBox w="3" h="3" rounded="full" bg={race.status === 'completed' ? 'bg-performance-green' : 'bg-warning-amber'} />
|
|
<SharedBox>
|
|
<SharedText weight="medium" color="text-white" block>{race.name}</SharedText>
|
|
<SharedText size="sm" color="text-gray-500" block mt={1}>{race.formattedDate}</SharedText>
|
|
</SharedBox>
|
|
</SharedStack>
|
|
<SharedBox>
|
|
{race.status === 'completed' ? (
|
|
<SharedBox textAlign="right">
|
|
<SharedText weight="semibold" color="text-white" block>{race.formattedViews}</SharedText>
|
|
<SharedText size="xs" color="text-gray-500">views</SharedText>
|
|
</SharedBox>
|
|
) : (
|
|
<SponsorStatusChip status="pending" label="Upcoming" />
|
|
)}
|
|
</SharedBox>
|
|
</SharedStack>
|
|
</SharedBox>
|
|
))}
|
|
</SharedStack>
|
|
</SharedCard>
|
|
)}
|
|
|
|
{activeTab === 'sponsor' && (
|
|
<Grid cols={12} gap={6}>
|
|
<GridItem colSpan={{ base: 12, lg: 8 }}>
|
|
<PricingTableShell
|
|
title="Sponsorship Tiers"
|
|
tiers={pricingTiers}
|
|
selectedId={selectedTier}
|
|
onSelect={(id) => setSelectedTier(id as 'main' | 'secondary')}
|
|
/>
|
|
</GridItem>
|
|
|
|
<GridItem colSpan={{ base: 12, lg: 4 }}>
|
|
<SharedStack gap={6}>
|
|
<SponsorBrandingPreview
|
|
name="Your Brand"
|
|
/>
|
|
|
|
<SharedCard>
|
|
<SharedBox mb={4}>
|
|
<Heading level={2} icon={<SharedIcon icon={CreditCard} size={5} color="#3b82f6" />}>
|
|
Sponsorship Summary
|
|
</Heading>
|
|
</SharedBox>
|
|
|
|
<SharedStack 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.priceLabel : league.sponsorSlots.secondary.priceLabel} />
|
|
<InfoRow label={`Platform Fee (${siteConfig.fees.platformFeePercent}%)`} value={`$${((selectedTier === 'main' ? league.sponsorSlots.main.price : league.sponsorSlots.secondary.price) * siteConfig.fees.platformFeePercent / 100).toFixed(2)}`} />
|
|
<SharedBox pt={4} borderTop borderColor="border-neutral-800">
|
|
<SharedStack direction="row" align="center" justify="between">
|
|
<SharedText weight="semibold" color="text-white">Total (excl. VAT)</SharedText>
|
|
<SharedText size="xl" weight="bold" color="text-white">
|
|
${((selectedTier === 'main' ? league.sponsorSlots.main.price : league.sponsorSlots.secondary.price) * (1 + siteConfig.fees.platformFeePercent / 100)).toFixed(2)}
|
|
</SharedText>
|
|
</SharedStack>
|
|
</SharedBox>
|
|
</SharedStack>
|
|
|
|
<SharedText size="xs" color="text-gray-500" block mb={4}>
|
|
{siteConfig.vat.notice}
|
|
</SharedText>
|
|
|
|
<SharedStack direction="row" gap={3}>
|
|
<SharedButton variant="primary" fullWidth icon={<SharedIcon icon={Megaphone} size={4} />}>
|
|
Request Sponsorship
|
|
</SharedButton>
|
|
<SharedButton variant="secondary" icon={<SharedIcon icon={FileText} size={4} />}>
|
|
Download Info Pack
|
|
</SharedButton>
|
|
</SharedStack>
|
|
</SharedCard>
|
|
</SharedStack>
|
|
</GridItem>
|
|
</Grid>
|
|
)}
|
|
</SharedStack>
|
|
</SharedBox>
|
|
</SharedContainer>
|
|
);
|
|
}
|
|
|
|
function InfoRow({ label, value, color = 'text-white', last }: { label: string, value: string | number, color?: string, last?: boolean }) {
|
|
return (
|
|
<SharedBox py={2} borderBottom={!last} borderColor="border-neutral-800/50">
|
|
<SharedStack direction="row" align="center" justify="between">
|
|
<SharedText color="text-gray-400">{label}</SharedText>
|
|
<SharedText weight="medium" color={color}>{value}</SharedText>
|
|
</SharedStack>
|
|
</SharedBox>
|
|
);
|
|
}
|