website refactor
This commit is contained in:
@@ -8,6 +8,7 @@ export interface PricingTier {
|
||||
id: string;
|
||||
name: string;
|
||||
price: number;
|
||||
priceLabel: string;
|
||||
period: string;
|
||||
description: string;
|
||||
features: string[];
|
||||
@@ -69,7 +70,7 @@ export function PricingTableShell({ title, tiers, onSelect, selectedId }: Pricin
|
||||
{tier.name}
|
||||
</Text>
|
||||
<Stack direction="row" align="baseline" gap={1}>
|
||||
<Text size="3xl" weight="bold" color="text-white">${tier.price}</Text>
|
||||
<Text size="3xl" weight="bold" color="text-white">{tier.priceLabel}</Text>
|
||||
<Text size="sm" color="text-gray-500">/{tier.period}</Text>
|
||||
</Stack>
|
||||
<Text size="sm" color="text-gray-400" block mt={2}>
|
||||
|
||||
@@ -1,62 +1,61 @@
|
||||
export interface SponsorshipSlot {
|
||||
tier: 'main' | 'secondary';
|
||||
available: boolean;
|
||||
price: number;
|
||||
currency?: string;
|
||||
priceLabel: string;
|
||||
benefits: string[];
|
||||
}
|
||||
|
||||
export const SlotTemplates = {
|
||||
league: (mainAvailable: boolean, secondaryAvailable: number, mainPrice: number, secondaryPrice: number): SponsorshipSlot[] => [
|
||||
league: (mainAvailable: boolean, secondaryAvailable: number, mainPriceLabel: string, secondaryPriceLabel: string): SponsorshipSlot[] => [
|
||||
{
|
||||
tier: 'main',
|
||||
available: mainAvailable,
|
||||
price: mainPrice,
|
||||
priceLabel: mainPriceLabel,
|
||||
benefits: ['Hood placement', 'League banner', 'Prominent logo'],
|
||||
},
|
||||
{
|
||||
tier: 'secondary',
|
||||
available: secondaryAvailable > 0,
|
||||
price: secondaryPrice,
|
||||
priceLabel: secondaryPriceLabel,
|
||||
benefits: ['Side logo placement', 'League page listing'],
|
||||
},
|
||||
{
|
||||
tier: 'secondary',
|
||||
available: secondaryAvailable > 1,
|
||||
price: secondaryPrice,
|
||||
priceLabel: secondaryPriceLabel,
|
||||
benefits: ['Side logo placement', 'League page listing'],
|
||||
},
|
||||
],
|
||||
|
||||
race: (mainAvailable: boolean, mainPrice: number): SponsorshipSlot[] => [
|
||||
race: (mainAvailable: boolean, mainPriceLabel: string): SponsorshipSlot[] => [
|
||||
{
|
||||
tier: 'main',
|
||||
available: mainAvailable,
|
||||
price: mainPrice,
|
||||
priceLabel: mainPriceLabel,
|
||||
benefits: ['Race title sponsor', 'Stream overlay', 'Results banner'],
|
||||
},
|
||||
],
|
||||
|
||||
driver: (available: boolean, price: number): SponsorshipSlot[] => [
|
||||
driver: (available: boolean, priceLabel: string): SponsorshipSlot[] => [
|
||||
{
|
||||
tier: 'main',
|
||||
available,
|
||||
price,
|
||||
priceLabel,
|
||||
benefits: ['Suit logo', 'Helmet branding', 'Social mentions'],
|
||||
},
|
||||
],
|
||||
|
||||
team: (mainAvailable: boolean, secondaryAvailable: boolean, mainPrice: number, secondaryPrice: number): SponsorshipSlot[] => [
|
||||
team: (mainAvailable: boolean, secondaryAvailable: boolean, mainPriceLabel: string, secondaryPriceLabel: string): SponsorshipSlot[] => [
|
||||
{
|
||||
tier: 'main',
|
||||
available: mainAvailable,
|
||||
price: mainPrice,
|
||||
priceLabel: mainPriceLabel,
|
||||
benefits: ['Team name suffix', 'Car livery', 'All driver suits'],
|
||||
},
|
||||
{
|
||||
tier: 'secondary',
|
||||
available: secondaryAvailable,
|
||||
price: secondaryPrice,
|
||||
priceLabel: secondaryPriceLabel,
|
||||
benefits: ['Team page logo', 'Minor livery placement'],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -47,11 +47,11 @@ export interface SponsorInsightsProps {
|
||||
slots: SponsorshipSlot[];
|
||||
additionalStats?: {
|
||||
label: string;
|
||||
items: Array<{ label: string; value: string | number }>;
|
||||
items: Array<{ label: string; value: string }>;
|
||||
};
|
||||
trustScore?: number;
|
||||
discordMembers?: number;
|
||||
monthlyActivity?: number;
|
||||
trustScoreLabel?: string;
|
||||
discordMembersLabel?: string;
|
||||
monthlyActivityLabel?: string;
|
||||
ctaLabel?: string;
|
||||
ctaHref?: string;
|
||||
currentSponsorId?: string;
|
||||
@@ -67,9 +67,9 @@ export function SponsorInsightsCard({
|
||||
metrics,
|
||||
slots,
|
||||
additionalStats,
|
||||
trustScore,
|
||||
discordMembers,
|
||||
monthlyActivity,
|
||||
trustScoreLabel,
|
||||
discordMembersLabel,
|
||||
monthlyActivityLabel,
|
||||
ctaLabel,
|
||||
ctaHref,
|
||||
currentSponsorId,
|
||||
@@ -111,22 +111,10 @@ export function SponsorInsightsCard({
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const slot = slotTier === 'main' ? mainSlot : secondarySlots[0];
|
||||
const slotPrice = slot?.price ?? 0;
|
||||
|
||||
const request = {
|
||||
sponsorId: currentSponsorId,
|
||||
entityType: getSponsorableEntityType(entityType),
|
||||
entityId,
|
||||
tier: slotTier,
|
||||
offeredAmount: slotPrice * 100,
|
||||
currency: (slot?.currency as 'USD' | 'EUR' | 'GBP') ?? 'USD',
|
||||
message: `Interested in sponsoring ${entityName} as ${slotTier} sponsor.`,
|
||||
};
|
||||
|
||||
console.log('Sponsorship request:', request);
|
||||
setAppliedTiers(prev => new Set([...prev, slotTier]));
|
||||
// Note: In a real app, we would fetch the raw price from the API or a ViewModel
|
||||
// For now, we assume the parent handles the actual request logic
|
||||
onSponsorshipRequested?.(slotTier);
|
||||
setAppliedTiers(prev => new Set([...prev, slotTier]));
|
||||
|
||||
} catch (err) {
|
||||
console.error('Failed to apply for sponsorship:', err);
|
||||
@@ -134,7 +122,7 @@ export function SponsorInsightsCard({
|
||||
} finally {
|
||||
setApplyingTier(null);
|
||||
}
|
||||
}, [currentSponsorId, ctaHref, entityType, entityId, entityName, onNavigate, mainSlot, secondarySlots, appliedTiers, getSponsorableEntityType, onSponsorshipRequested]);
|
||||
}, [currentSponsorId, ctaHref, entityType, entityId, onNavigate, appliedTiers, onSponsorshipRequested]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -171,27 +159,27 @@ export function SponsorInsightsCard({
|
||||
})}
|
||||
</Stack>
|
||||
|
||||
{(trustScore !== undefined || discordMembers !== undefined || monthlyActivity !== undefined) && (
|
||||
{(trustScoreLabel || discordMembersLabel || monthlyActivityLabel) && (
|
||||
<Stack display="flex" flexWrap="wrap" gap={4} mb={4} pb={4} borderBottom borderColor="border-charcoal-outline/50">
|
||||
{trustScore !== undefined && (
|
||||
{trustScoreLabel && (
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Icon icon={Shield} size={4} color="rgb(16, 185, 129)" />
|
||||
<Text size="sm" color="text-gray-400">Trust Score:</Text>
|
||||
<Text size="sm" weight="semibold" color="text-white">{trustScore}/100</Text>
|
||||
<Text size="sm" weight="semibold" color="text-white">{trustScoreLabel}</Text>
|
||||
</Stack>
|
||||
)}
|
||||
{discordMembers !== undefined && (
|
||||
{discordMembersLabel && (
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Icon icon={MessageCircle} size={4} color="rgb(168, 85, 247)" />
|
||||
<Text size="sm" color="text-gray-400">Discord:</Text>
|
||||
<Text size="sm" weight="semibold" color="text-white">{discordMembers.toLocaleString()}</Text>
|
||||
<Text size="sm" weight="semibold" color="text-white">{discordMembersLabel}</Text>
|
||||
</Stack>
|
||||
)}
|
||||
{monthlyActivity !== undefined && (
|
||||
{monthlyActivityLabel && (
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Icon icon={Activity} size={4} color="rgb(0, 255, 255)" />
|
||||
<Text size="sm" color="text-gray-400">Monthly Activity:</Text>
|
||||
<Text size="sm" weight="semibold" color="text-white">{monthlyActivity}%</Text>
|
||||
<Text size="sm" weight="semibold" color="text-white">{monthlyActivityLabel}</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
@@ -206,7 +194,7 @@ export function SponsorInsightsCard({
|
||||
statusColor={mainSlot.available ? 'text-performance-green' : 'text-gray-500'}
|
||||
benefits={mainSlot.benefits.join(' • ')}
|
||||
available={mainSlot.available}
|
||||
price={`$${mainSlot.price.toLocaleString()}/season`}
|
||||
price={mainSlot.priceLabel}
|
||||
action={
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -240,7 +228,7 @@ export function SponsorInsightsCard({
|
||||
statusColor={availableSecondary > 0 ? 'text-purple-400' : 'text-gray-500'}
|
||||
benefits={secondarySlots[0]?.benefits.join(' • ') || 'Logo placement on page'}
|
||||
available={availableSecondary > 0}
|
||||
price={`$${secondarySlots[0]?.price.toLocaleString()}/season`}
|
||||
price={secondarySlots[0]?.priceLabel}
|
||||
action={
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -275,7 +263,7 @@ export function SponsorInsightsCard({
|
||||
<Stack key={index} direction="row" align="center" gap={2}>
|
||||
<Text size="sm" color="text-gray-500">{item.label}:</Text>
|
||||
<Text size="sm" weight="semibold" color="text-white">
|
||||
{typeof item.value === 'number' ? item.value.toLocaleString() : item.value}
|
||||
{item.value}
|
||||
</Text>
|
||||
</Stack>
|
||||
))}
|
||||
|
||||
@@ -3,10 +3,10 @@ import { ComponentType } from 'react';
|
||||
export interface SponsorMetric {
|
||||
icon: string | ComponentType;
|
||||
label: string;
|
||||
value: string | number;
|
||||
value: string;
|
||||
color?: string;
|
||||
trend?: {
|
||||
value: number;
|
||||
value: string;
|
||||
isPositive: boolean;
|
||||
};
|
||||
}
|
||||
@@ -14,7 +14,6 @@ export interface SponsorMetric {
|
||||
export interface SponsorshipSlot {
|
||||
tier: 'main' | 'secondary';
|
||||
available: boolean;
|
||||
price: number;
|
||||
currency?: string;
|
||||
priceLabel: string;
|
||||
benefits: string[];
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import { LucideIcon } from 'lucide-react';
|
||||
|
||||
interface SponsorMetricCardProps {
|
||||
label: string;
|
||||
value: string | number;
|
||||
value: string;
|
||||
icon: LucideIcon;
|
||||
color?: string;
|
||||
trend?: {
|
||||
value: number;
|
||||
value: string;
|
||||
isPositive: boolean;
|
||||
};
|
||||
}
|
||||
@@ -35,11 +35,11 @@ export function SponsorMetricCard({
|
||||
</Box>
|
||||
<Box display="flex" alignItems="baseline" gap={2}>
|
||||
<Text size="xl" weight="bold" color="text-white">
|
||||
{typeof value === 'number' ? value.toLocaleString() : value}
|
||||
{value}
|
||||
</Text>
|
||||
{trend && (
|
||||
<Text size="xs" color={trend.isPositive ? 'text-performance-green' : 'text-red-400'}>
|
||||
{trend.isPositive ? '+' : ''}{trend.value}%
|
||||
{trend.value}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -8,8 +8,8 @@ import { LucideIcon } from 'lucide-react';
|
||||
interface SponsorshipCategoryCardProps {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
count: number;
|
||||
impressions: number;
|
||||
countLabel: string;
|
||||
impressionsLabel: string;
|
||||
color: string;
|
||||
href: string;
|
||||
}
|
||||
@@ -17,8 +17,8 @@ interface SponsorshipCategoryCardProps {
|
||||
export function SponsorshipCategoryCard({
|
||||
icon,
|
||||
title,
|
||||
count,
|
||||
impressions,
|
||||
countLabel,
|
||||
impressionsLabel,
|
||||
color,
|
||||
href
|
||||
}: SponsorshipCategoryCardProps) {
|
||||
@@ -39,11 +39,11 @@ export function SponsorshipCategoryCard({
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text weight="medium" color="text-white" block>{title}</Text>
|
||||
<Text size="sm" color="text-gray-500">{count} active</Text>
|
||||
<Text size="sm" color="text-gray-500">{countLabel}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack textAlign="right">
|
||||
<Text weight="semibold" color="text-white" block>{impressions.toLocaleString()}</Text>
|
||||
<Text weight="semibold" color="text-white" block>{impressionsLabel}</Text>
|
||||
<Text size="xs" color="text-gray-500">impressions</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -7,9 +7,9 @@ import { AlertTriangle, Check, Clock, Download, Receipt } from 'lucide-react';
|
||||
|
||||
export interface Transaction {
|
||||
id: string;
|
||||
date: string;
|
||||
formattedDate: string;
|
||||
description: string;
|
||||
amount: number;
|
||||
formattedAmount: string;
|
||||
status: 'paid' | 'pending' | 'overdue' | 'failed';
|
||||
invoiceNumber: string;
|
||||
type: string;
|
||||
@@ -103,11 +103,11 @@ export function TransactionTable({ transactions, onDownload }: TransactionTableP
|
||||
</Stack>
|
||||
|
||||
<Stack colSpan={{ base: 1, md: 2 } as any}>
|
||||
<Text size="sm" color="text-gray-400">{new Date(tx.date).toLocaleDateString()}</Text>
|
||||
<Text size="sm" color="text-gray-400">{tx.formattedDate}</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack colSpan={{ base: 1, md: 2 } as any}>
|
||||
<Text weight="semibold" color="text-white">${tx.amount.toFixed(2)}</Text>
|
||||
<Text weight="semibold" color="text-white">{tx.formattedAmount}</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack colSpan={{ base: 1, md: 2 } as any}>
|
||||
|
||||
Reference in New Issue
Block a user