website refactor

This commit is contained in:
2026-01-19 02:14:53 +01:00
parent 489c5f7858
commit a8731e6937
70 changed files with 2908 additions and 2423 deletions

View File

@@ -7,18 +7,20 @@ import { SponsorDashboardHeader } from '@/components/sponsors/SponsorDashboardHe
import { SponsorStatusChip } from '@/components/sponsors/SponsorStatusChip';
import { routes } from '@/lib/routing/RouteConfig';
import { siteConfig } from '@/lib/siteConfig';
import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';
import { Card } from '@/ui/Card';
import { Container } from '@/ui/Container';
import {
SharedBox,
SharedButton,
SharedStack,
SharedText,
SharedIcon,
SharedCard,
SharedContainer
} from '@/components/shared/UIComponents';
import { Heading } from '@/ui/Heading';
import { Icon } from '@/ui/Icon';
import { Link } from '@/ui/Link';
import { Grid } from '@/ui/Grid';
import { GridItem } from '@/ui/GridItem';
import { Stack } from '@/ui/Stack';
import { Surface } from '@/ui/Surface';
import { Text } from '@/ui/Text';
import {
BarChart3,
Calendar,
@@ -31,8 +33,10 @@ import {
Trophy,
type LucideIcon
} from 'lucide-react';
import { TemplateProps } from '@/lib/contracts/components/ComponentContracts';
import { ViewData } from '@/lib/contracts/view-data/ViewData';
interface SponsorLeagueDetailViewData {
export interface SponsorLeagueDetailViewData extends ViewData {
league: {
id: string;
name: string;
@@ -99,8 +103,7 @@ interface SponsorLeagueDetailViewData {
export type SponsorLeagueDetailTab = 'overview' | 'drivers' | 'races' | 'sponsor';
interface SponsorLeagueDetailTemplateProps {
viewData: SponsorLeagueDetailViewData;
interface SponsorLeagueDetailTemplateProps extends TemplateProps<SponsorLeagueDetailViewData> {
activeTab: SponsorLeagueDetailTab;
setActiveTab: (tab: SponsorLeagueDetailTab) => void;
selectedTier: 'main' | 'secondary';
@@ -171,253 +174,258 @@ export function SponsorLeagueDetailTemplate({
];
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>
<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')}
/>
{/* Header */}
<SponsorDashboardHeader
sponsorName="Sponsor"
onRefresh={() => console.log('Refresh')}
/>
{/* Quick Stats */}
<BillingSummaryPanel stats={billingStats} />
{/* Quick Stats */}
<BillingSummaryPanel stats={billingStats} />
{/* Tabs */}
<Box borderBottom borderColor="border-neutral-800">
<Stack direction="row" gap={6}>
{(['overview', 'drivers', 'races', 'sponsor'] as const).map((tab) => (
<Box
key={tab}
onClick={() => setActiveTab(tab)}
pb={3}
cursor="pointer"
borderBottom={activeTab === tab}
borderColor={activeTab === tab ? 'border-primary-blue' : 'border-transparent'}
color={activeTab === tab ? 'text-primary-blue' : 'text-gray-400'}
>
<Text size="sm" weight="medium" uppercase>
{tab === 'sponsor' ? '🎯 Become a Sponsor' : tab}
</Text>
</Box>
))}
</Stack>
</Box>
{/* 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}>
<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
{/* 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>
</Box>
<Surface variant="muted" rounded="lg" border padding={4} bg="bg-warning-amber/5" borderColor="border-warning-amber/20">
<Stack direction="row" align="center" justify="between">
<Stack direction="row" align="center" gap={4}>
<Surface variant="muted" rounded="lg" padding={3} bg="bg-warning-amber/10">
<Icon icon={Flag} size={6} color="#f59e0b" />
</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.drivers} />
<InfoRow label="Races" value={league.races} 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.engagement}%`} />
<InfoRow label="League Rating" value={`${league.rating}/5.0`} 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.date}</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.position}</SharedText>
</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>
)}
<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.races}</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 === 'drivers' && (
<Card p={0}>
<Box p={4} borderBottom borderColor="border-neutral-800">
<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} borderBottom={index < viewData.drivers.length - 1} borderColor="border-neutral-800/50">
<Stack direction="row" align="center" justify="between">
<Stack direction="row" align="center" gap={4}>
<Surface variant="muted" rounded="full" padding={1} w="10" h="10" display="flex" alignItems="center" justifyContent="center" bg="bg-neutral-800">
<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 textAlign="right">
<Text weight="medium" color="text-white" block>{driver.races}</Text>
<Text size="xs" color="text-gray-500">races</Text>
</Box>
<Box 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' && (
<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.views.toLocaleString()}</SharedText>
<SharedText size="xs" color="text-gray-500">views</SharedText>
</SharedBox>
) : (
<SponsorStatusChip status="pending" label="Upcoming" />
)}
</SharedBox>
</SharedStack>
</SharedBox>
))}
</SharedStack>
</SharedCard>
)}
{activeTab === 'races' && (
<Card p={0}>
<Box p={4} borderBottom borderColor="border-neutral-800">
<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} borderBottom={index < viewData.races.length - 1} borderColor="border-neutral-800/50">
<Stack direction="row" align="center" justify="between">
<Stack direction="row" align="center" gap={4}>
<Box w="3" h="3" rounded="full" bg={race.status === 'completed' ? 'bg-performance-green' : 'bg-warning-amber'} />
<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 textAlign="right">
<Text weight="semibold" color="text-white" block>{race.views.toLocaleString()}</Text>
<Text size="xs" color="text-gray-500">views</Text>
</Box>
) : (
<SponsorStatusChip status="pending" label="Upcoming" />
)}
</Box>
</Stack>
</Box>
))}
</Stack>
</Card>
)}
{activeTab === 'sponsor' && (
<Grid cols={12} gap={6}>
<GridItem colSpan={12} lgSpan={8}>
<PricingTableShell
title="Sponsorship Tiers"
tiers={pricingTiers}
selectedId={selectedTier}
onSelect={(id) => setSelectedTier(id as 'main' | 'secondary')}
/>
</GridItem>
<GridItem colSpan={12} lgSpan={4}>
<Stack gap={6}>
<SponsorBrandingPreview
name="Your Brand"
{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')}
/>
<Card>
<Box mb={4}>
<Heading level={2} icon={<Icon icon={CreditCard} size={5} color="#3b82f6" />}>
Sponsorship Summary
</Heading>
</Box>
</GridItem>
<GridItem colSpan={{ base: 12, lg: 4 }}>
<SharedStack gap={6}>
<SponsorBrandingPreview
name="Your Brand"
/>
<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} borderTop borderColor="border-neutral-800">
<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>
<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.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)}`} />
<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>
<Text size="xs" color="text-gray-500" block mb={4}>
{siteConfig.vat.notice}
</Text>
<SharedText size="xs" color="text-gray-500" block mb={4}>
{siteConfig.vat.notice}
</SharedText>
<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>
</GridItem>
</Grid>
)}
</Stack>
</Container>
<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 (
<Box py={2} borderBottom={!last} borderColor="border-neutral-800/50">
<Stack direction="row" align="center" justify="between">
<Text color="text-gray-400">{label}</Text>
<Text weight="medium" color={color}>{value}</Text>
</Stack>
</Box>
<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>
);
}