website refactor
This commit is contained in:
@@ -5,12 +5,16 @@ import { motion, useReducedMotion } from 'framer-motion';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { StatCard } from '@/ui/StatCard';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { SectionHeader } from '@/ui/SectionHeader';
|
||||
import { StatusBadge } from '@/ui/StatusBadge';
|
||||
import { InfoBanner } from '@/ui/InfoBanner';
|
||||
import { PageHeader } from '@/ui/PageHeader';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { siteConfig } from '@/lib/siteConfig';
|
||||
import { useSponsorBilling } from "@/lib/hooks/sponsor/useSponsorBilling";
|
||||
import { useSponsorBilling } from "@/hooks/sponsor/useSponsorBilling";
|
||||
import {
|
||||
CreditCard,
|
||||
DollarSign,
|
||||
@@ -20,7 +24,6 @@ import {
|
||||
Check,
|
||||
AlertTriangle,
|
||||
FileText,
|
||||
ArrowRight,
|
||||
TrendingUp,
|
||||
Receipt,
|
||||
Building2,
|
||||
@@ -29,62 +32,21 @@ import {
|
||||
ChevronRight,
|
||||
Info,
|
||||
ExternalLink,
|
||||
Percent
|
||||
Percent,
|
||||
Loader2
|
||||
} from 'lucide-react';
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
interface PaymentMethod {
|
||||
id: string;
|
||||
type: 'card' | 'bank' | 'sepa';
|
||||
last4: string;
|
||||
brand?: string;
|
||||
isDefault: boolean;
|
||||
expiryMonth?: number;
|
||||
expiryYear?: number;
|
||||
bankName?: string;
|
||||
}
|
||||
|
||||
interface Invoice {
|
||||
id: string;
|
||||
invoiceNumber: string;
|
||||
date: Date;
|
||||
dueDate: Date;
|
||||
amount: number;
|
||||
vatAmount: number;
|
||||
totalAmount: number;
|
||||
status: 'paid' | 'pending' | 'overdue' | 'failed';
|
||||
description: string;
|
||||
sponsorshipType: 'league' | 'team' | 'driver' | 'race' | 'platform';
|
||||
pdfUrl: string;
|
||||
}
|
||||
|
||||
interface BillingStats {
|
||||
totalSpent: number;
|
||||
pendingAmount: number;
|
||||
nextPaymentDate: Date;
|
||||
nextPaymentAmount: number;
|
||||
activeSponsorships: number;
|
||||
averageMonthlySpend: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mock Data
|
||||
// ============================================================================
|
||||
|
||||
import type { PaymentMethodDTO, InvoiceDTO } from '@/lib/types/tbd/SponsorBillingDTO';
|
||||
|
||||
// ============================================================================
|
||||
// Components
|
||||
// ============================================================================
|
||||
|
||||
function PaymentMethodCard({
|
||||
function PaymentMethodCardComponent({
|
||||
method,
|
||||
onSetDefault,
|
||||
onRemove
|
||||
}: {
|
||||
method: any;
|
||||
method: PaymentMethodDTO;
|
||||
onSetDefault: () => void;
|
||||
onRemove: () => void;
|
||||
}) {
|
||||
@@ -95,68 +57,81 @@ function PaymentMethodCard({
|
||||
return CreditCard;
|
||||
};
|
||||
|
||||
const Icon = getIcon();
|
||||
const MethodIcon = getIcon();
|
||||
|
||||
const getLabel = () => {
|
||||
if (method.type === 'sepa' && method.bankName) {
|
||||
return `${method.bankName} •••• ${method.last4}`;
|
||||
}
|
||||
return `${method.brand} •••• ${method.last4}`;
|
||||
};
|
||||
const displayLabel = method.type === 'sepa' && method.bankName
|
||||
? `${method.bankName} •••• ${method.last4}`
|
||||
: `${method.brand} •••• ${method.last4}`;
|
||||
|
||||
const expiryDisplay = method.expiryMonth && method.expiryYear
|
||||
? `${method.expiryMonth}/${method.expiryYear}`
|
||||
: null;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
<Box
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: shouldReduceMotion ? 0 : 0.2 }}
|
||||
className={`p-4 rounded-xl border transition-all ${
|
||||
method.isDefault
|
||||
? 'border-primary-blue/50 bg-gradient-to-r from-primary-blue/10 to-transparent shadow-[0_0_20px_rgba(25,140,255,0.1)]'
|
||||
: 'border-charcoal-outline bg-iron-gray/30 hover:border-charcoal-outline/80'
|
||||
}`}
|
||||
p={4}
|
||||
rounded="xl"
|
||||
border
|
||||
borderColor={method.isDefault ? 'border-primary-blue/50' : 'border-charcoal-outline'}
|
||||
bg={method.isDefault ? 'bg-gradient-to-r from-primary-blue/10 to-transparent' : 'bg-iron-gray/30'}
|
||||
shadow={method.isDefault ? '0_0_20px_rgba(25,140,255,0.1)' : undefined}
|
||||
hoverBorderColor={!method.isDefault ? 'border-charcoal-outline/80' : undefined}
|
||||
transition-all
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${
|
||||
method.isDefault ? 'bg-primary-blue/20' : 'bg-iron-gray'
|
||||
}`}>
|
||||
<Icon className={`w-6 h-6 ${method.isDefault ? 'text-primary-blue' : 'text-gray-400'}`} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-white">{method.displayLabel}</span>
|
||||
<Box display="flex" alignItems="center" justifyContent="between">
|
||||
<Box display="flex" alignItems="center" gap={4}>
|
||||
<Box
|
||||
w="12"
|
||||
h="12"
|
||||
rounded="xl"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
bg={method.isDefault ? 'bg-primary-blue/20' : 'bg-iron-gray'}
|
||||
>
|
||||
<Icon icon={MethodIcon} size={6} color={method.isDefault ? 'text-primary-blue' : 'text-gray-400'} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Text weight="medium" color="text-white">{displayLabel}</Text>
|
||||
{method.isDefault && (
|
||||
<span className="px-2 py-0.5 rounded-full text-xs bg-primary-blue/20 text-primary-blue font-medium">
|
||||
Default
|
||||
</span>
|
||||
<Box px={2} py={0.5} rounded="full" bg="bg-primary-blue/20">
|
||||
<Text size="xs" color="text-primary-blue" weight="medium">
|
||||
Default
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
{method.expiryDisplay && (
|
||||
<span className="text-sm text-gray-500">
|
||||
Expires {method.expiryDisplay}
|
||||
</span>
|
||||
</Box>
|
||||
{expiryDisplay && (
|
||||
<Text size="sm" color="text-gray-500" block>
|
||||
Expires {expiryDisplay}
|
||||
</Text>
|
||||
)}
|
||||
{method.type === 'sepa' && (
|
||||
<span className="text-sm text-gray-500">SEPA Direct Debit</span>
|
||||
<Text size="sm" color="text-gray-500" block>SEPA Direct Debit</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
</Box>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
{!method.isDefault && (
|
||||
<Button variant="secondary" onClick={onSetDefault} className="text-xs">
|
||||
<Button variant="secondary" onClick={onSetDefault} size="sm">
|
||||
Set Default
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="secondary" onClick={onRemove} className="text-xs text-gray-400 hover:text-racing-red">
|
||||
<Button variant="secondary" onClick={onRemove} size="sm" color="text-gray-400" hoverTextColor="text-racing-red">
|
||||
Remove
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function InvoiceRow({ invoice, index }: { invoice: any; index: number }) {
|
||||
function InvoiceRowComponent({ invoice, index }: { invoice: InvoiceDTO; index: number }) {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
const statusConfig = {
|
||||
@@ -202,54 +177,64 @@ function InvoiceRow({ invoice, index }: { invoice: any; index: number }) {
|
||||
const StatusIcon = status.icon;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
<Box
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: shouldReduceMotion ? 0 : 0.2, delay: shouldReduceMotion ? 0 : index * 0.05 }}
|
||||
className="flex items-center justify-between p-4 border-b border-charcoal-outline/50 last:border-b-0 hover:bg-iron-gray/20 transition-colors group"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="between"
|
||||
p={4}
|
||||
borderBottom
|
||||
borderColor="border-charcoal-outline/50"
|
||||
hoverBg="bg-iron-gray/20"
|
||||
transition-colors
|
||||
group
|
||||
>
|
||||
<div className="flex items-center gap-4 flex-1">
|
||||
<div className="w-10 h-10 rounded-lg bg-iron-gray flex items-center justify-center">
|
||||
<Receipt className="w-5 h-5 text-gray-400" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-0.5">
|
||||
<span className="font-medium text-white truncate">{invoice.description}</span>
|
||||
<span className="px-2 py-0.5 rounded text-xs bg-iron-gray text-gray-400 flex-shrink-0">
|
||||
{typeLabels[invoice.sponsorshipType as keyof typeof typeLabels]}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-sm text-gray-500">
|
||||
<span>{invoice.invoiceNumber}</span>
|
||||
<span>•</span>
|
||||
<span>
|
||||
{invoice.formattedDate}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Box display="flex" alignItems="center" gap={4} flexGrow={1}>
|
||||
<Box w="10" h="10" rounded="lg" bg="bg-iron-gray" display="flex" alignItems="center" justifyContent="center">
|
||||
<Icon icon={Receipt} size={5} color="text-gray-400" />
|
||||
</Box>
|
||||
<Box flexGrow={1} minWidth="0">
|
||||
<Box display="flex" alignItems="center" gap={2} mb={0.5}>
|
||||
<Text weight="medium" color="text-white" truncate>{invoice.description}</Text>
|
||||
<Box px={2} py={0.5} rounded bg="bg-iron-gray">
|
||||
<Text size="xs" color="text-gray-400">
|
||||
{typeLabels[invoice.sponsorshipType as keyof typeof typeLabels]}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Text size="sm" color="text-gray-500">{invoice.invoiceNumber}</Text>
|
||||
<Text size="sm" color="text-gray-500">•</Text>
|
||||
<Text size="sm" color="text-gray-500">
|
||||
{new globalThis.Date(invoice.date).toLocaleDateString()}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="text-right">
|
||||
<div className="font-semibold text-white">
|
||||
{invoice.formattedTotalAmount}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
incl. {invoice.formattedVatAmount} VAT
|
||||
</div>
|
||||
</div>
|
||||
<Box display="flex" alignItems="center" gap={6}>
|
||||
<Box textAlign="right">
|
||||
<Text weight="semibold" color="text-white" block>
|
||||
${invoice.totalAmount.toFixed(2)}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500" block>
|
||||
incl. ${invoice.vatAmount.toFixed(2)} VAT
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<div className={`flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${status.bg} ${status.color} border ${status.border}`}>
|
||||
<StatusIcon className="w-3 h-3" />
|
||||
{status.label}
|
||||
</div>
|
||||
<Box display="flex" alignItems="center" gap={1.5} px={2.5} py={1} rounded="full" bg={status.bg} border borderColor={status.border}>
|
||||
<Icon icon={StatusIcon} size={3} color={status.color} />
|
||||
<Text size="xs" weight="medium" color={status.color}>{status.label}</Text>
|
||||
</Box>
|
||||
|
||||
<Button variant="secondary" className="text-xs opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Download className="w-3 h-3 mr-1" />
|
||||
<Button variant="secondary" size="sm" opacity={0} groupHoverTextColor="opacity-100" transition-opacity icon={<Icon icon={Download} size={3} />}>
|
||||
PDF
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -265,27 +250,27 @@ export default function SponsorBillingPage() {
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto py-8 px-4 flex items-center justify-center min-h-[400px]">
|
||||
<div className="text-center">
|
||||
<div className="w-8 h-8 border-2 border-primary-blue border-t-transparent rounded-full animate-spin mx-auto mb-4" />
|
||||
<p className="text-gray-400">Loading billing data...</p>
|
||||
</div>
|
||||
</div>
|
||||
<Box maxWidth="5xl" mx="auto" py={8} px={4} display="flex" alignItems="center" justifyContent="center" minHeight="400px">
|
||||
<Box textAlign="center">
|
||||
<Box w="8" h="8" border borderTop={false} borderColor="border-primary-blue" rounded="full" animate="spin" mx="auto" mb={4} />
|
||||
<Text color="text-gray-400">Loading billing data...</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !billingData) {
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto py-8 px-4 flex items-center justify-center min-h-[400px]">
|
||||
<div className="text-center">
|
||||
<p className="text-gray-400">{error?.getUserMessage() || 'No billing data available'}</p>
|
||||
<Box maxWidth="5xl" mx="auto" py={8} px={4} display="flex" alignItems="center" justifyContent="center" minHeight="400px">
|
||||
<Box textAlign="center">
|
||||
<Text color="text-gray-400" block>{error?.message || 'No billing data available'}</Text>
|
||||
{error && (
|
||||
<Button variant="secondary" onClick={retry} className="mt-4">
|
||||
<Button variant="secondary" onClick={retry} mt={4}>
|
||||
Retry
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -297,7 +282,7 @@ export default function SponsorBillingPage() {
|
||||
};
|
||||
|
||||
const handleRemoveMethod = (methodId: string) => {
|
||||
if (confirm('Remove this payment method?')) {
|
||||
if (window.confirm('Remove this payment method?')) {
|
||||
// In a real app, this would call an API
|
||||
console.log('Removing payment method:', methodId);
|
||||
}
|
||||
@@ -319,14 +304,19 @@ export default function SponsorBillingPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="max-w-5xl mx-auto py-8 px-4"
|
||||
<Box
|
||||
maxWidth="5xl"
|
||||
mx="auto"
|
||||
py={8}
|
||||
px={4}
|
||||
as={motion.div}
|
||||
// @ts-ignore
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
>
|
||||
{/* Header */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<PageHeader
|
||||
icon={Wallet}
|
||||
title="Billing & Payments"
|
||||
@@ -334,190 +324,178 @@ export default function SponsorBillingPage() {
|
||||
iconGradient="from-warning-amber/20 to-warning-amber/5"
|
||||
iconBorder="border-warning-amber/30"
|
||||
/>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<motion.div variants={itemVariants} className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||
<Box as={motion.div} variants={itemVariants} display="grid" gridCols={{ base: 1, md: 2, lg: 4 }} gap={4} mb={8}>
|
||||
<StatCard
|
||||
icon={DollarSign}
|
||||
label="Total Spent"
|
||||
value={data.stats.formattedTotalSpent}
|
||||
value={`$${data.stats.totalSpent.toFixed(2)}`}
|
||||
subValue="All time"
|
||||
color="text-performance-green"
|
||||
bgColor="bg-performance-green/10"
|
||||
variant="green"
|
||||
/>
|
||||
<StatCard
|
||||
icon={AlertTriangle}
|
||||
label="Pending Payments"
|
||||
value={data.stats.formattedPendingAmount}
|
||||
value={`$${data.stats.pendingAmount.toFixed(2)}`}
|
||||
subValue={`${data.invoices.filter((i: { status: string }) => i.status === 'pending' || i.status === 'overdue').length} invoices`}
|
||||
color="text-warning-amber"
|
||||
bgColor="bg-warning-amber/10"
|
||||
variant="orange"
|
||||
/>
|
||||
<StatCard
|
||||
icon={Calendar}
|
||||
label="Next Payment"
|
||||
value={data.stats.formattedNextPaymentDate}
|
||||
subValue={data.stats.formattedNextPaymentAmount}
|
||||
color="text-primary-blue"
|
||||
bgColor="bg-primary-blue/10"
|
||||
value={new globalThis.Date(data.stats.nextPaymentDate).toLocaleDateString()}
|
||||
subValue={`$${data.stats.nextPaymentAmount.toFixed(2)}`}
|
||||
variant="blue"
|
||||
/>
|
||||
<StatCard
|
||||
icon={TrendingUp}
|
||||
label="Monthly Average"
|
||||
value={data.stats.formattedAverageMonthlySpend}
|
||||
value={`$${data.stats.averageMonthlySpend.toFixed(2)}`}
|
||||
subValue="Last 6 months"
|
||||
color="text-gray-400"
|
||||
bgColor="bg-iron-gray"
|
||||
variant="blue"
|
||||
/>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Payment Methods */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Card className="mb-8 overflow-hidden">
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Card mb={8} overflow="hidden">
|
||||
<SectionHeader
|
||||
icon={CreditCard}
|
||||
title="Payment Methods"
|
||||
action={
|
||||
<Button variant="secondary" className="text-sm">
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
<Button variant="secondary" size="sm" icon={<Icon icon={Plus} size={4} />}>
|
||||
Add Payment Method
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<div className="p-5 space-y-3">
|
||||
{data.paymentMethods.map((method: { id: string; type: string; last4: string; brand: string; default: boolean }) => (
|
||||
<PaymentMethodCard
|
||||
<Box p={5} display="flex" flexDirection="col" gap={3}>
|
||||
{data.paymentMethods.map((method: PaymentMethodDTO) => (
|
||||
<PaymentMethodCardComponent
|
||||
key={method.id}
|
||||
method={method}
|
||||
onSetDefault={() => handleSetDefault(method.id)}
|
||||
onRemove={() => handleRemoveMethod(method.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="px-5 pb-5">
|
||||
</Box>
|
||||
<Box px={5} pb={5}>
|
||||
<InfoBanner type="info">
|
||||
<p className="mb-1">We support Visa, Mastercard, American Express, and SEPA Direct Debit.</p>
|
||||
<p>All payment information is securely processed and stored by our payment provider.</p>
|
||||
<Text block mb={1}>We support Visa, Mastercard, American Express, and SEPA Direct Debit.</Text>
|
||||
<Text block>All payment information is securely processed and stored by our payment provider.</Text>
|
||||
</InfoBanner>
|
||||
</div>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Billing History */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Card className="mb-8 overflow-hidden">
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Card mb={8} overflow="hidden">
|
||||
<SectionHeader
|
||||
icon={FileText}
|
||||
title="Billing History"
|
||||
color="text-warning-amber"
|
||||
action={
|
||||
<Button variant="secondary" className="text-sm">
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
<Button variant="secondary" size="sm" icon={<Icon icon={Download} size={4} />}>
|
||||
Export All
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
{data.invoices.slice(0, showAllInvoices ? data.invoices.length : 4).map((invoice: { id: string; date: string; amount: number; status: string }, index: number) => (
|
||||
<InvoiceRow key={invoice.id} invoice={invoice} index={index} />
|
||||
<Box>
|
||||
{data.invoices.slice(0, showAllInvoices ? data.invoices.length : 4).map((invoice: InvoiceDTO, index: number) => (
|
||||
<InvoiceRowComponent key={invoice.id} invoice={invoice} index={index} />
|
||||
))}
|
||||
</div>
|
||||
</Box>
|
||||
{data.invoices.length > 4 && (
|
||||
<div className="p-4 border-t border-charcoal-outline">
|
||||
<Box p={4} borderTop borderColor="border-charcoal-outline">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="w-full"
|
||||
fullWidth
|
||||
onClick={() => setShowAllInvoices(!showAllInvoices)}
|
||||
icon={<Icon icon={ChevronRight} size={4} className={showAllInvoices ? 'rotate-90' : ''} />}
|
||||
flexDirection="row-reverse"
|
||||
>
|
||||
{showAllInvoices ? 'Show Less' : `View All ${data.invoices.length} Invoices`}
|
||||
<ChevronRight className={`w-4 h-4 ml-2 transition-transform ${showAllInvoices ? 'rotate-90' : ''}`} />
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Platform Fee & VAT Information */}
|
||||
<motion.div variants={itemVariants} className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Box as={motion.div} variants={itemVariants} display="grid" gridCols={{ base: 1, md: 2 }} gap={6}>
|
||||
{/* Platform Fee */}
|
||||
<Card className="overflow-hidden">
|
||||
<div className="p-5 border-b border-charcoal-outline bg-gradient-to-r from-iron-gray/30 to-transparent">
|
||||
<h3 className="font-semibold text-white flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-iron-gray/50">
|
||||
<Percent className="w-4 h-4 text-primary-blue" />
|
||||
</div>
|
||||
<Card overflow="hidden">
|
||||
<Box p={5} borderBottom borderColor="border-charcoal-outline" bg="bg-gradient-to-r from-iron-gray/30 to-transparent">
|
||||
<Heading level={3} fontSize="base" weight="semibold" color="text-white" icon={<Box p={2} rounded="lg" bg="bg-iron-gray/50"><Icon icon={Percent} size={4} color="text-primary-blue" /></Box>}>
|
||||
Platform Fee
|
||||
</h3>
|
||||
</div>
|
||||
<div className="p-5">
|
||||
<div className="text-3xl font-bold text-white mb-2">
|
||||
</Heading>
|
||||
</Box>
|
||||
<Box p={5}>
|
||||
<Text size="3xl" weight="bold" color="text-white" block mb={2}>
|
||||
{siteConfig.fees.platformFeePercent}%
|
||||
</div>
|
||||
<p className="text-sm text-gray-400 mb-4">
|
||||
</Text>
|
||||
<Text size="sm" color="text-gray-400" block mb={4}>
|
||||
{siteConfig.fees.description}
|
||||
</p>
|
||||
<div className="text-xs text-gray-500 space-y-1">
|
||||
<p>• Applied to all sponsorship payments</p>
|
||||
<p>• Covers platform maintenance and analytics</p>
|
||||
<p>• Ensures quality sponsorship placements</p>
|
||||
</div>
|
||||
</div>
|
||||
</Text>
|
||||
<Box display="flex" flexDirection="col" gap={1}>
|
||||
<Text size="xs" color="text-gray-500" block>• Applied to all sponsorship payments</Text>
|
||||
<Text size="xs" color="text-gray-500" block>• Covers platform maintenance and analytics</Text>
|
||||
<Text size="xs" color="text-gray-500" block>• Ensures quality sponsorship placements</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
{/* VAT Information */}
|
||||
<Card className="overflow-hidden">
|
||||
<div className="p-5 border-b border-charcoal-outline bg-gradient-to-r from-iron-gray/30 to-transparent">
|
||||
<h3 className="font-semibold text-white flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-iron-gray/50">
|
||||
<Receipt className="w-4 h-4 text-performance-green" />
|
||||
</div>
|
||||
<Card overflow="hidden">
|
||||
<Box p={5} borderBottom borderColor="border-charcoal-outline" bg="bg-gradient-to-r from-iron-gray/30 to-transparent">
|
||||
<Heading level={3} fontSize="base" weight="semibold" color="text-white" icon={<Box p={2} rounded="lg" bg="bg-iron-gray/50"><Icon icon={Receipt} size={4} color="text-performance-green" /></Box>}>
|
||||
VAT Information
|
||||
</h3>
|
||||
</div>
|
||||
<div className="p-5">
|
||||
<p className="text-sm text-gray-400 mb-4">
|
||||
</Heading>
|
||||
</Box>
|
||||
<Box p={5}>
|
||||
<Text size="sm" color="text-gray-400" block mb={4}>
|
||||
{siteConfig.vat.notice}
|
||||
</p>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex justify-between items-center py-2 border-b border-charcoal-outline/50">
|
||||
<span className="text-gray-500">Standard VAT Rate</span>
|
||||
<span className="text-white font-medium">{siteConfig.vat.standardRate}%</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center py-2">
|
||||
<span className="text-gray-500">B2B Reverse Charge</span>
|
||||
<span className="text-performance-green font-medium">Available</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-4">
|
||||
</Text>
|
||||
<Box display="flex" flexDirection="col" gap={3}>
|
||||
<Box display="flex" justifyContent="between" alignItems="center" py={2} borderBottom borderColor="border-charcoal-outline/50">
|
||||
<Text color="text-gray-500">Standard VAT Rate</Text>
|
||||
<Text color="text-white" weight="medium">{siteConfig.vat.standardRate}%</Text>
|
||||
</Box>
|
||||
<Box display="flex" justifyContent="between" alignItems="center" py={2}>
|
||||
<Text color="text-gray-500">B2B Reverse Charge</Text>
|
||||
<Text color="text-performance-green" weight="medium">Available</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Text size="xs" color="text-gray-500" block mt={4}>
|
||||
Enter your VAT ID in Settings to enable reverse charge for B2B transactions.
|
||||
</p>
|
||||
</div>
|
||||
</Text>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Billing Support */}
|
||||
<motion.div variants={itemVariants} className="mt-6">
|
||||
<Card className="p-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 rounded-xl bg-iron-gray">
|
||||
<Info className="w-5 h-5 text-gray-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-white">Need help with billing?</h3>
|
||||
<p className="text-sm text-gray-500">
|
||||
<Box as={motion.div} variants={itemVariants} mt={6}>
|
||||
<Card p={5}>
|
||||
<Box display="flex" alignItems="center" justifyContent="between">
|
||||
<Stack direction="row" align="center" gap={4}>
|
||||
<Box p={3} rounded="xl" bg="bg-iron-gray">
|
||||
<Icon icon={Info} size={5} color="text-gray-400" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Heading level={3} fontSize="base" weight="medium" color="text-white">Need help with billing?</Heading>
|
||||
<Text size="sm" color="text-gray-500" block>
|
||||
Contact our billing support for questions about invoices, payments, or refunds.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="secondary">
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Button variant="secondary" icon={<Icon icon={ExternalLink} size={4} />}>
|
||||
Contact Support
|
||||
<ExternalLink className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { motion, useReducedMotion, AnimatePresence } from 'framer-motion';
|
||||
import { motion, useReducedMotion } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { InfoBanner } from '@/ui/InfoBanner';
|
||||
import { useSponsorSponsorships } from "@/lib/hooks/sponsor/useSponsorSponsorships";
|
||||
import { useSponsorSponsorships } from "@/hooks/sponsor/useSponsorSponsorships";
|
||||
import {
|
||||
Megaphone,
|
||||
Trophy,
|
||||
@@ -23,17 +27,14 @@ import {
|
||||
Car,
|
||||
Flag,
|
||||
Search,
|
||||
Filter,
|
||||
TrendingUp,
|
||||
BarChart3,
|
||||
ArrowUpRight,
|
||||
ArrowDownRight,
|
||||
AlertCircle,
|
||||
Send,
|
||||
ThumbsUp,
|
||||
ThumbsDown,
|
||||
RefreshCw,
|
||||
Handshake
|
||||
} from 'lucide-react';
|
||||
|
||||
// ============================================================================
|
||||
@@ -98,26 +99,27 @@ const STATUS_CONFIG = {
|
||||
// Components
|
||||
// ============================================================================
|
||||
|
||||
function SponsorshipCard({ sponsorship }: { sponsorship: any }) {
|
||||
function SponsorshipCard({ sponsorship }: { sponsorship: unknown }) {
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
|
||||
const typeConfig = TYPE_CONFIG[sponsorship.type as keyof typeof TYPE_CONFIG];
|
||||
const statusConfig = STATUS_CONFIG[sponsorship.status as keyof typeof STATUS_CONFIG];
|
||||
const s = sponsorship as any; // Temporary cast to avoid breaking logic
|
||||
const typeConfig = TYPE_CONFIG[s.type as keyof typeof TYPE_CONFIG];
|
||||
const statusConfig = STATUS_CONFIG[s.status as keyof typeof STATUS_CONFIG];
|
||||
const TypeIcon = typeConfig.icon;
|
||||
const StatusIcon = statusConfig.icon;
|
||||
|
||||
const daysRemaining = Math.ceil((sponsorship.endDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
|
||||
const daysRemaining = Math.ceil((s.endDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
|
||||
const isExpiringSoon = daysRemaining > 0 && daysRemaining <= 30;
|
||||
const isPending = sponsorship.status === 'pending_approval';
|
||||
const isRejected = sponsorship.status === 'rejected';
|
||||
const isApproved = sponsorship.status === 'approved';
|
||||
const isPending = s.status === 'pending_approval';
|
||||
const isRejected = s.status === 'rejected';
|
||||
const isApproved = s.status === 'approved';
|
||||
|
||||
const getEntityLink = () => {
|
||||
switch (sponsorship.type) {
|
||||
case 'leagues': return `/leagues/${sponsorship.entityId}`;
|
||||
case 'teams': return `/teams/${sponsorship.entityId}`;
|
||||
case 'drivers': return `/drivers/${sponsorship.entityId}`;
|
||||
case 'races': return `/races/${sponsorship.entityId}`;
|
||||
switch (s.type) {
|
||||
case 'leagues': return `/leagues/${s.entityId}`;
|
||||
case 'teams': return `/teams/${s.entityId}`;
|
||||
case 'drivers': return `/drivers/${s.entityId}`;
|
||||
case 'races': return `/races/${s.entityId}`;
|
||||
default: return '#';
|
||||
}
|
||||
};
|
||||
@@ -135,172 +137,166 @@ function SponsorshipCard({ sponsorship }: { sponsorship: any }) {
|
||||
isApproved ? 'border-primary-blue/30' : ''
|
||||
}`}>
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-lg ${typeConfig.bgColor} flex items-center justify-center`}>
|
||||
<Stack direction="row" align="start" justify="between" mb={4}>
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Box w="10" h="10" rounded="lg" bg={typeConfig.bgColor} display="flex" alignItems="center" justifyContent="center">
|
||||
<TypeIcon className={`w-5 h-5 ${typeConfig.color}`} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className={`text-xs font-medium px-2 py-0.5 rounded ${typeConfig.bgColor} ${typeConfig.color}`}>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box display="flex" alignItems="center" gap={2} flexWrap="wrap">
|
||||
<Text size="xs" weight="medium" px={2} py={0.5} rounded="md" bg={typeConfig.bgColor} color={typeConfig.color}>
|
||||
{typeConfig.label}
|
||||
</span>
|
||||
{sponsorship.tier && (
|
||||
<span className={`text-xs font-medium px-2 py-0.5 rounded ${
|
||||
sponsorship.tier === 'main'
|
||||
? 'bg-primary-blue/20 text-primary-blue'
|
||||
: 'bg-purple-400/20 text-purple-400'
|
||||
}`}>
|
||||
{sponsorship.tier === 'main' ? 'Main Sponsor' : 'Secondary'}
|
||||
</span>
|
||||
</Text>
|
||||
{s.tier && (
|
||||
<Text size="xs" weight="medium" px={2} py={0.5} rounded="md" bg={s.tier === 'main' ? 'bg-primary-blue/20' : 'bg-purple-400/20'} color={s.tier === 'main' ? 'text-primary-blue' : 'text-purple-400'}>
|
||||
{s.tier === 'main' ? 'Main Sponsor' : 'Secondary'}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-medium border ${statusConfig.bgColor} ${statusConfig.color} ${statusConfig.borderColor}`}>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box display="flex" alignItems="center" gap={1} px={2.5} py={1} rounded="full" border bg={statusConfig.bgColor} color={statusConfig.color} borderColor={statusConfig.borderColor}>
|
||||
<StatusIcon className="w-3 h-3" />
|
||||
{statusConfig.label}
|
||||
</div>
|
||||
</div>
|
||||
<Text size="xs" weight="medium">{statusConfig.label}</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* Entity Name */}
|
||||
<h3 className="text-lg font-semibold text-white mb-1">{sponsorship.entityName}</h3>
|
||||
{sponsorship.details && (
|
||||
<p className="text-sm text-gray-500 mb-3">{sponsorship.details}</p>
|
||||
<Heading level={3} fontSize="lg" weight="semibold" color="text-white" mb={1}>{s.entityName}</Heading>
|
||||
{s.details && (
|
||||
<Text size="sm" color="text-gray-500" block mb={3}>{s.details}</Text>
|
||||
)}
|
||||
|
||||
{/* Application/Approval Info for non-active states */}
|
||||
{isPending && (
|
||||
<div className="mb-4 p-3 rounded-lg bg-warning-amber/5 border border-warning-amber/20">
|
||||
<div className="flex items-center gap-2 text-warning-amber text-sm mb-2">
|
||||
<Box mb={4} p={3} rounded="lg" bg="bg-warning-amber/5" border borderColor="border-warning-amber/20">
|
||||
<Stack direction="row" align="center" gap={2} color="text-warning-amber" mb={2}>
|
||||
<Send className="w-4 h-4" />
|
||||
<span className="font-medium">Application Pending</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-400 mb-2">
|
||||
Sent to <span className="text-gray-300">{sponsorship.entityOwner}</span> on{' '}
|
||||
{sponsorship.applicationDate?.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
|
||||
</p>
|
||||
{sponsorship.applicationMessage && (
|
||||
<p className="text-xs text-gray-500 italic">"{sponsorship.applicationMessage}"</p>
|
||||
<Text size="sm" weight="medium">Application Pending</Text>
|
||||
</Stack>
|
||||
<Text size="xs" color="text-gray-400" block mb={2}>
|
||||
Sent to <Text color="text-gray-300">{s.entityOwner}</Text> on{' '}
|
||||
{s.applicationDate?.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
|
||||
</Text>
|
||||
{s.applicationMessage && (
|
||||
<Text size="xs" color="text-gray-500" italic block>"{s.applicationMessage}"</Text>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{isApproved && (
|
||||
<div className="mb-4 p-3 rounded-lg bg-primary-blue/5 border border-primary-blue/20">
|
||||
<div className="flex items-center gap-2 text-primary-blue text-sm mb-1">
|
||||
<Box mb={4} p={3} rounded="lg" bg="bg-primary-blue/5" border borderColor="border-primary-blue/20">
|
||||
<Stack direction="row" align="center" gap={2} color="text-primary-blue" mb={1}>
|
||||
<ThumbsUp className="w-4 h-4" />
|
||||
<span className="font-medium">Approved!</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-400">
|
||||
Approved by <span className="text-gray-300">{sponsorship.entityOwner}</span> on{' '}
|
||||
{sponsorship.approvalDate?.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
Starts {sponsorship.startDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
|
||||
</p>
|
||||
</div>
|
||||
<Text size="sm" weight="medium">Approved!</Text>
|
||||
</Stack>
|
||||
<Text size="xs" color="text-gray-400" block>
|
||||
Approved by <Text color="text-gray-300">{s.entityOwner}</Text> on{' '}
|
||||
{s.approvalDate?.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500" block mt={1}>
|
||||
Starts {s.startDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{isRejected && (
|
||||
<div className="mb-4 p-3 rounded-lg bg-racing-red/5 border border-racing-red/20">
|
||||
<div className="flex items-center gap-2 text-racing-red text-sm mb-1">
|
||||
<Box mb={4} p={3} rounded="lg" bg="bg-racing-red/5" border borderColor="border-racing-red/20">
|
||||
<Stack direction="row" align="center" gap={2} color="text-racing-red" mb={1}>
|
||||
<ThumbsDown className="w-4 h-4" />
|
||||
<span className="font-medium">Application Declined</span>
|
||||
</div>
|
||||
{sponsorship.rejectionReason && (
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
Reason: <span className="text-gray-300">{sponsorship.rejectionReason}</span>
|
||||
</p>
|
||||
<Text size="sm" weight="medium">Application Declined</Text>
|
||||
</Stack>
|
||||
{s.rejectionReason && (
|
||||
<Text size="xs" color="text-gray-400" block mt={1}>
|
||||
Reason: <Text color="text-gray-300">{s.rejectionReason}</Text>
|
||||
</Text>
|
||||
)}
|
||||
<Button variant="secondary" className="mt-2 text-xs">
|
||||
<RefreshCw className="w-3 h-3 mr-1" />
|
||||
Reapply
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Metrics Grid - Only show for active sponsorships */}
|
||||
{sponsorship.status === 'active' && (
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
|
||||
<div className="bg-iron-gray/50 rounded-lg p-3">
|
||||
<div className="flex items-center gap-1 text-gray-400 text-xs mb-1">
|
||||
{s.status === 'active' && (
|
||||
<Box display="grid" gridCols={{ base: 2, md: 4 }} gap={3} mb={4}>
|
||||
<Box bg="bg-iron-gray/50" rounded="lg" p={3}>
|
||||
<Box display="flex" alignItems="center" gap={1} color="text-gray-400" mb={1}>
|
||||
<Eye className="w-3 h-3" />
|
||||
Impressions
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-white font-semibold">{sponsorship.formattedImpressions}</span>
|
||||
{sponsorship.impressionsChange !== undefined && sponsorship.impressionsChange !== 0 && (
|
||||
<span className={`text-xs flex items-center ${
|
||||
sponsorship.impressionsChange > 0 ? 'text-performance-green' : 'text-racing-red'
|
||||
}`}>
|
||||
{sponsorship.impressionsChange > 0 ? <ArrowUpRight className="w-3 h-3" /> : <ArrowDownRight className="w-3 h-3" />}
|
||||
{Math.abs(sponsorship.impressionsChange)}%
|
||||
</span>
|
||||
<Text size="xs">Impressions</Text>
|
||||
</Box>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Text color="text-white" weight="semibold">{s.formattedImpressions}</Text>
|
||||
{s.impressionsChange !== undefined && s.impressionsChange !== 0 && (
|
||||
<Text size="xs" display="flex" alignItems="center" color={s.impressionsChange > 0 ? 'text-performance-green' : 'text-racing-red'}>
|
||||
{s.impressionsChange > 0 ? <ArrowUpRight className="w-3 h-3" /> : <ArrowDownRight className="w-3 h-3" />}
|
||||
{Math.abs(s.impressionsChange)}%
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{sponsorship.engagement && (
|
||||
<div className="bg-iron-gray/50 rounded-lg p-3">
|
||||
<div className="flex items-center gap-1 text-gray-400 text-xs mb-1">
|
||||
{s.engagement && (
|
||||
<Box bg="bg-iron-gray/50" rounded="lg" p={3}>
|
||||
<Box display="flex" alignItems="center" gap={1} color="text-gray-400" mb={1}>
|
||||
<TrendingUp className="w-3 h-3" />
|
||||
Engagement
|
||||
</div>
|
||||
<div className="text-white font-semibold">{sponsorship.engagement}%</div>
|
||||
</div>
|
||||
<Text size="xs">Engagement</Text>
|
||||
</Box>
|
||||
<Text color="text-white" weight="semibold">{s.engagement}%</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<div className="bg-iron-gray/50 rounded-lg p-3">
|
||||
<div className="flex items-center gap-1 text-gray-400 text-xs mb-1">
|
||||
<Box bg="bg-iron-gray/50" rounded="lg" p={3}>
|
||||
<Box display="flex" alignItems="center" gap={1} color="text-gray-400" mb={1}>
|
||||
<Calendar className="w-3 h-3" />
|
||||
Period
|
||||
</div>
|
||||
<div className="text-white font-semibold text-xs">
|
||||
{sponsorship.startDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} - {sponsorship.endDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}
|
||||
</div>
|
||||
</div>
|
||||
<Text size="xs">Period</Text>
|
||||
</Box>
|
||||
<Text color="text-white" weight="semibold" size="xs">
|
||||
{s.startDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} - {s.endDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<div className="bg-iron-gray/50 rounded-lg p-3">
|
||||
<div className="flex items-center gap-1 text-gray-400 text-xs mb-1">
|
||||
<Box bg="bg-iron-gray/50" rounded="lg" p={3}>
|
||||
<Box display="flex" alignItems="center" gap={1} color="text-gray-400" mb={1}>
|
||||
<Trophy className="w-3 h-3" />
|
||||
Investment
|
||||
</div>
|
||||
<div className="text-white font-semibold">{sponsorship.formattedPrice}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Text size="xs">Investment</Text>
|
||||
</Box>
|
||||
<Text color="text-white" weight="semibold">{s.formattedPrice}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Basic info for non-active */}
|
||||
{sponsorship.status !== 'active' && (
|
||||
<div className="flex items-center gap-4 mb-4 text-sm">
|
||||
<div className="flex items-center gap-1 text-gray-400">
|
||||
{s.status !== 'active' && (
|
||||
<Stack direction="row" align="center" gap={4} mb={4}>
|
||||
<Box display="flex" alignItems="center" gap={1} color="text-gray-400">
|
||||
<Calendar className="w-3.5 h-3.5" />
|
||||
{sponsorship.periodDisplay}
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-gray-400">
|
||||
<Text size="sm">{s.periodDisplay}</Text>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={1} color="text-gray-400">
|
||||
<Trophy className="w-3.5 h-3.5" />
|
||||
{sponsorship.formattedPrice}
|
||||
</div>
|
||||
</div>
|
||||
<Text size="sm">{s.formattedPrice}</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex items-center justify-between pt-3 border-t border-charcoal-outline/50">
|
||||
<div className="flex items-center gap-2">
|
||||
{sponsorship.status === 'active' && (
|
||||
<span className={`text-xs ${isExpiringSoon ? 'text-warning-amber' : 'text-gray-500'}`}>
|
||||
<Box display="flex" alignItems="center" justifyContent="between" pt={3} borderTop borderColor="border-charcoal-outline/50">
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
{s.status === 'active' && (
|
||||
<Text size="xs" color={isExpiringSoon ? 'text-warning-amber' : 'text-gray-500'}>
|
||||
{daysRemaining > 0 ? `${daysRemaining} days remaining` : 'Ended'}
|
||||
</span>
|
||||
</Text>
|
||||
)}
|
||||
{isPending && (
|
||||
<span className="text-xs text-gray-500">
|
||||
<Text size="xs" color="text-gray-500">
|
||||
Waiting for response...
|
||||
</span>
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{sponsorship.type !== 'platform' && (
|
||||
</Box>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
{s.type !== 'platform' && (
|
||||
<Link href={getEntityLink()}>
|
||||
<Button variant="secondary" className="text-xs">
|
||||
<ExternalLink className="w-3 h-3 mr-1" />
|
||||
@@ -313,14 +309,14 @@ function SponsorshipCard({ sponsorship }: { sponsorship: any }) {
|
||||
Cancel Application
|
||||
</Button>
|
||||
)}
|
||||
{sponsorship.status === 'active' && (
|
||||
{s.status === 'active' && (
|
||||
<Button variant="secondary" className="text-xs">
|
||||
Details
|
||||
<ChevronRight className="w-3 h-3 ml-1" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
@@ -370,53 +366,65 @@ export default function SponsorCampaignsPage() {
|
||||
const data = sponsorshipsData;
|
||||
|
||||
// Filter sponsorships
|
||||
const filteredSponsorships = data.sponsorships.filter((s: any) => {
|
||||
if (typeFilter !== 'all' && s.type !== typeFilter) return false;
|
||||
if (statusFilter !== 'all' && s.status !== statusFilter) return false;
|
||||
if (searchQuery && !s.entityName.toLowerCase().includes(searchQuery.toLowerCase())) return false;
|
||||
const filteredSponsorships = data.sponsorships.filter((s: unknown) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const sponsorship = s as any;
|
||||
if (typeFilter !== 'all' && sponsorship.type !== typeFilter) return false;
|
||||
if (statusFilter !== 'all' && sponsorship.status !== statusFilter) return false;
|
||||
if (searchQuery && !sponsorship.entityName.toLowerCase().includes(searchQuery.toLowerCase())) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
// Calculate stats
|
||||
const stats = {
|
||||
total: data.sponsorships.length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
active: data.sponsorships.filter((s: any) => s.status === 'active').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
pending: data.sponsorships.filter((s: any) => s.status === 'pending_approval').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
approved: data.sponsorships.filter((s: any) => s.status === 'approved').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
rejected: data.sponsorships.filter((s: any) => s.status === 'rejected').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
totalInvestment: data.sponsorships.filter((s: any) => s.status === 'active').reduce((sum: number, s: any) => sum + s.price, 0),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
totalImpressions: data.sponsorships.reduce((sum: number, s: any) => sum + s.impressions, 0),
|
||||
};
|
||||
|
||||
// Stats by type
|
||||
const statsByType = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
leagues: data.sponsorships.filter((s: any) => s.type === 'leagues').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
teams: data.sponsorships.filter((s: any) => s.type === 'teams').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
drivers: data.sponsorships.filter((s: any) => s.type === 'drivers').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
races: data.sponsorships.filter((s: any) => s.type === 'races').length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
platform: data.sponsorships.filter((s: any) => s.type === 'platform').length,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto py-8 px-4">
|
||||
<Box maxWidth="7xl" mx="auto" py={8} px={4}>
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-8">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white flex items-center gap-3">
|
||||
<Megaphone className="w-7 h-7 text-primary-blue" />
|
||||
<Stack direction={{ base: 'col', md: 'row' }} align="center" justify="between" gap={4} mb={8}>
|
||||
<Box>
|
||||
<Heading level={1} fontSize="2xl" weight="bold" color="text-white" icon={<Megaphone className="w-7 h-7 text-primary-blue" />}>
|
||||
My Sponsorships
|
||||
</h1>
|
||||
<p className="text-gray-400 mt-1">Manage applications and active sponsorship campaigns</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
</Heading>
|
||||
<Text color="text-gray-400" mt={1} block>Manage applications and active sponsorship campaigns</Text>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Link href="/leagues">
|
||||
<Button variant="primary">
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Find Opportunities
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* Info Banner about how sponsorships work */}
|
||||
{stats.pending > 0 && (
|
||||
@@ -426,23 +434,23 @@ export default function SponsorCampaignsPage() {
|
||||
className="mb-6"
|
||||
>
|
||||
<InfoBanner type="info" title="Sponsorship Applications">
|
||||
<p>
|
||||
You have <strong className="text-white">{stats.pending} pending application{stats.pending !== 1 ? 's' : ''}</strong> waiting for approval.
|
||||
<Text size="sm">
|
||||
You have <Text weight="bold" color="text-white">{stats.pending} pending application{stats.pending !== 1 ? 's' : ''}</Text> waiting for approval.
|
||||
League admins, team owners, and drivers review applications before accepting sponsorships.
|
||||
</p>
|
||||
</Text>
|
||||
</InfoBanner>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Quick Stats */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-6 gap-4 mb-8">
|
||||
<Box display="grid" gridCols={{ base: 2, md: 6 }} gap={4} mb={8}>
|
||||
<motion.div
|
||||
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
>
|
||||
<Card className="p-4">
|
||||
<div className="text-2xl font-bold text-white">{stats.total}</div>
|
||||
<div className="text-sm text-gray-400">Total</div>
|
||||
<Text size="2xl" weight="bold" color="text-white" block>{stats.total}</Text>
|
||||
<Text size="sm" color="text-gray-400">Total</Text>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
@@ -451,8 +459,8 @@ export default function SponsorCampaignsPage() {
|
||||
transition={{ delay: 0.05 }}
|
||||
>
|
||||
<Card className="p-4">
|
||||
<div className="text-2xl font-bold text-performance-green">{stats.active}</div>
|
||||
<div className="text-sm text-gray-400">Active</div>
|
||||
<Text size="2xl" weight="bold" color="text-performance-green" block>{stats.active}</Text>
|
||||
<Text size="sm" color="text-gray-400">Active</Text>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
@@ -461,8 +469,8 @@ export default function SponsorCampaignsPage() {
|
||||
transition={{ delay: 0.1 }}
|
||||
>
|
||||
<Card className={`p-4 ${stats.pending > 0 ? 'border-warning-amber/30' : ''}`}>
|
||||
<div className="text-2xl font-bold text-warning-amber">{stats.pending}</div>
|
||||
<div className="text-sm text-gray-400">Pending</div>
|
||||
<Text size="2xl" weight="bold" color="text-warning-amber" block>{stats.pending}</Text>
|
||||
<Text size="sm" color="text-gray-400">Pending</Text>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
@@ -471,8 +479,8 @@ export default function SponsorCampaignsPage() {
|
||||
transition={{ delay: 0.15 }}
|
||||
>
|
||||
<Card className="p-4">
|
||||
<div className="text-2xl font-bold text-primary-blue">{stats.approved}</div>
|
||||
<div className="text-sm text-gray-400">Approved</div>
|
||||
<Text size="2xl" weight="bold" color="text-primary-blue" block>{stats.approved}</Text>
|
||||
<Text size="sm" color="text-gray-400">Approved</Text>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
@@ -481,8 +489,8 @@ export default function SponsorCampaignsPage() {
|
||||
transition={{ delay: 0.2 }}
|
||||
>
|
||||
<Card className="p-4">
|
||||
<div className="text-2xl font-bold text-white">${stats.totalInvestment.toLocaleString()}</div>
|
||||
<div className="text-sm text-gray-400">Active Investment</div>
|
||||
<Text size="2xl" weight="bold" color="text-white" block>${stats.totalInvestment.toLocaleString()}</Text>
|
||||
<Text size="sm" color="text-gray-400">Active Investment</Text>
|
||||
</Card>
|
||||
</motion.div>
|
||||
<motion.div
|
||||
@@ -491,16 +499,16 @@ export default function SponsorCampaignsPage() {
|
||||
transition={{ delay: 0.25 }}
|
||||
>
|
||||
<Card className="p-4">
|
||||
<div className="text-2xl font-bold text-primary-blue">{(stats.totalImpressions / 1000).toFixed(0)}k</div>
|
||||
<div className="text-sm text-gray-400">Impressions</div>
|
||||
<Text size="2xl" weight="bold" color="text-primary-blue" block>{(stats.totalImpressions / 1000).toFixed(0)}k</Text>
|
||||
<Text size="sm" color="text-gray-400">Impressions</Text>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
{/* Filters */}
|
||||
<div className="flex flex-col lg:flex-row gap-4 mb-6">
|
||||
<Stack direction={{ base: 'col', lg: 'row' }} gap={4} mb={6}>
|
||||
{/* Search */}
|
||||
<div className="relative flex-1">
|
||||
<Box position="relative" flexGrow={1}>
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
|
||||
<input
|
||||
type="text"
|
||||
@@ -509,10 +517,10 @@ export default function SponsorCampaignsPage() {
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-charcoal-outline bg-iron-gray text-white placeholder-gray-500 focus:border-primary-blue focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
{/* Type Filter */}
|
||||
<div className="flex items-center gap-2 overflow-x-auto pb-2 lg:pb-0">
|
||||
<Box display="flex" alignItems="center" gap={2} overflow="auto" pb={{ base: 2, lg: 0 }}>
|
||||
{(['all', 'leagues', 'teams', 'drivers', 'races', 'platform'] as const).map((type) => {
|
||||
const config = TYPE_CONFIG[type];
|
||||
const Icon = config.icon;
|
||||
@@ -525,22 +533,20 @@ export default function SponsorCampaignsPage() {
|
||||
typeFilter === type
|
||||
? 'bg-primary-blue text-white'
|
||||
: 'bg-iron-gray/50 text-gray-400 hover:bg-iron-gray'
|
||||
}`}
|
||||
} border-0 cursor-pointer`}
|
||||
>
|
||||
<Icon className="w-4 h-4" />
|
||||
{config.label}
|
||||
<span className={`px-1.5 py-0.5 rounded text-xs ${
|
||||
typeFilter === type ? 'bg-white/20' : 'bg-charcoal-outline'
|
||||
}`}>
|
||||
<Text size="xs" px={1.5} py={0.5} rounded="sm" bg={typeFilter === type ? 'bg-white/20' : 'bg-charcoal-outline'}>
|
||||
{count}
|
||||
</span>
|
||||
</Text>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
{/* Status Filter */}
|
||||
<div className="flex items-center gap-2 overflow-x-auto">
|
||||
<Box display="flex" alignItems="center" gap={2} overflow="auto">
|
||||
{(['all', 'active', 'pending_approval', 'approved', 'rejected'] as const).map((status) => {
|
||||
const config = status === 'all'
|
||||
? { label: 'All', color: 'text-gray-400' }
|
||||
@@ -556,33 +562,31 @@ export default function SponsorCampaignsPage() {
|
||||
statusFilter === status
|
||||
? 'bg-iron-gray text-white border border-charcoal-outline'
|
||||
: 'text-gray-500 hover:text-gray-300'
|
||||
}`}
|
||||
} border-0 cursor-pointer`}
|
||||
>
|
||||
{config.label}
|
||||
{count > 0 && status !== 'all' && (
|
||||
<span className={`ml-1.5 px-1.5 py-0.5 rounded text-xs ${
|
||||
status === 'pending_approval' ? 'bg-warning-amber/20 text-warning-amber' : 'bg-charcoal-outline'
|
||||
}`}>
|
||||
<Text size="xs" ml={1.5} px={1.5} py={0.5} rounded="sm" bg={status === 'pending_approval' ? 'bg-warning-amber/20' : 'bg-charcoal-outline'} color={status === 'pending_approval' ? 'text-warning-amber' : ''}>
|
||||
{count}
|
||||
</span>
|
||||
</Text>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* Sponsorship List */}
|
||||
{filteredSponsorships.length === 0 ? (
|
||||
<Card className="text-center py-16">
|
||||
<Megaphone className="w-12 h-12 text-gray-600 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-white mb-2">No sponsorships found</h3>
|
||||
<p className="text-gray-400 mb-6 max-w-md mx-auto">
|
||||
<Heading level={3} fontSize="lg" weight="semibold" color="text-white" mb={2}>No sponsorships found</Heading>
|
||||
<Text color="text-gray-400" mb={6} maxWidth="md" mx="auto" block>
|
||||
{searchQuery || typeFilter !== 'all' || statusFilter !== 'all'
|
||||
? 'Try adjusting your filters to see more results.'
|
||||
: 'Start sponsoring leagues, teams, or drivers to grow your brand visibility.'}
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
||||
</Text>
|
||||
<Stack direction={{ base: 'col', md: 'row' }} gap={3} justify="center">
|
||||
<Link href="/leagues">
|
||||
<Button variant="primary">
|
||||
<Trophy className="w-4 h-4 mr-2" />
|
||||
@@ -601,15 +605,15 @@ export default function SponsorCampaignsPage() {
|
||||
Browse Drivers
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</Stack>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<Box display="grid" gridCols={{ base: 1, lg: 2 }} gap={4}>
|
||||
{filteredSponsorships.map((sponsorship: any) => (
|
||||
<SponsorshipCard key={sponsorship.id} sponsorship={sponsorship} />
|
||||
))}
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -3,15 +3,18 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { SponsorLeaguesTemplate, type SortOption, type TierFilter, type AvailabilityFilter } from '@/templates/SponsorLeaguesTemplate';
|
||||
|
||||
export default function SponsorLeaguesPageClient({ data }: { data: any }) {
|
||||
export default function SponsorLeaguesPageClient({ data }: { data: unknown }) {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [tierFilter, setTierFilter] = useState<TierFilter>('all');
|
||||
const [availabilityFilter, setAvailabilityFilter] = useState<AvailabilityFilter>('all');
|
||||
const [sortBy, setSortBy] = useState<SortOption>('rating');
|
||||
const [tierFilter] = useState<TierFilter>('all');
|
||||
const [availabilityFilter] = useState<AvailabilityFilter>('all');
|
||||
const [sortBy] = useState<SortOption>('rating');
|
||||
|
||||
const filteredLeagues = useMemo(() => {
|
||||
if (!data?.leagues) return [];
|
||||
return data.leagues
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const d = data as any;
|
||||
if (!d?.leagues) return [];
|
||||
return d.leagues
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.filter((league: any) => {
|
||||
if (searchQuery && !league.name.toLowerCase().includes(searchQuery.toLowerCase())) {
|
||||
return false;
|
||||
@@ -27,6 +30,7 @@ export default function SponsorLeaguesPageClient({ data }: { data: any }) {
|
||||
}
|
||||
return true;
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.sort((a: any, b: any) => {
|
||||
switch (sortBy) {
|
||||
case 'rating': return b.rating - a.rating;
|
||||
@@ -36,20 +40,15 @@ export default function SponsorLeaguesPageClient({ data }: { data: any }) {
|
||||
default: return 0;
|
||||
}
|
||||
});
|
||||
}, [data?.leagues, searchQuery, tierFilter, availabilityFilter, sortBy]);
|
||||
}, [data, searchQuery, tierFilter, availabilityFilter, sortBy]);
|
||||
|
||||
return (
|
||||
<SponsorLeaguesTemplate
|
||||
viewData={data}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
viewData={data as any}
|
||||
filteredLeagues={filteredLeagues}
|
||||
searchQuery={searchQuery}
|
||||
setSearchQuery={setSearchQuery}
|
||||
tierFilter={tierFilter}
|
||||
setTierFilter={setTierFilter}
|
||||
availabilityFilter={availabilityFilter}
|
||||
setAvailabilityFilter={setAvailabilityFilter}
|
||||
sortBy={sortBy}
|
||||
setSortBy={setSortBy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,10 +29,14 @@ export default async function Page() {
|
||||
// Calculate summary stats (business logic moved from view model)
|
||||
const stats = {
|
||||
total: leaguesData.length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mainAvailable: leaguesData.filter((l: any) => l.mainSponsorSlot.available).length,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
secondaryAvailable: leaguesData.reduce((sum: number, l: any) => sum + l.secondarySlots.available, 0),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
totalDrivers: leaguesData.reduce((sum: number, l: any) => sum + l.drivers, 0),
|
||||
avgCpm: Math.round(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
leaguesData.reduce((sum: number, l: any) => sum + l.cpm, 0) / leaguesData.length
|
||||
),
|
||||
};
|
||||
|
||||
@@ -6,9 +6,14 @@ import { Card } from '@/ui/Card';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Toggle } from '@/ui/Toggle';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { SectionHeader } from '@/ui/SectionHeader';
|
||||
import { FormField } from '@/ui/FormField';
|
||||
import { PageHeader } from '@/ui/PageHeader';
|
||||
import { Image } from '@/ui/Image';
|
||||
import {
|
||||
Settings,
|
||||
Building2,
|
||||
@@ -201,33 +206,38 @@ export default function SponsorSettingsPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="max-w-4xl mx-auto py-8 px-4"
|
||||
<Box
|
||||
maxWidth="4xl"
|
||||
mx="auto"
|
||||
py={8}
|
||||
px={4}
|
||||
as={motion.div}
|
||||
// @ts-ignore
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
>
|
||||
{/* Header */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<PageHeader
|
||||
icon={Settings}
|
||||
title="Sponsor Settings"
|
||||
description="Manage your company profile, notifications, and security preferences"
|
||||
action={<SavedIndicator visible={saved} />}
|
||||
/>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Company Profile */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Card className="mb-6 overflow-hidden">
|
||||
<SectionHeader
|
||||
icon={Building2}
|
||||
title="Company Profile"
|
||||
description="Your public-facing company information"
|
||||
/>
|
||||
<div className="p-6 space-y-6">
|
||||
<Box p={6} className="space-y-6">
|
||||
{/* Company Basic Info */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Box display="grid" gridCols={{ base: 1, md: 2 }} gap={6}>
|
||||
<FormField label="Company Name" icon={Building2} required>
|
||||
<Input
|
||||
type="text"
|
||||
@@ -238,24 +248,32 @@ export default function SponsorSettingsPage() {
|
||||
</FormField>
|
||||
|
||||
<FormField label="Industry">
|
||||
<select
|
||||
<Box as="select"
|
||||
value={profile.industry}
|
||||
onChange={(e) => setProfile({ ...profile, industry: e.target.value })}
|
||||
className="w-full px-3 py-2 bg-iron-gray border border-charcoal-outline rounded-lg text-white focus:outline-none focus:border-primary-blue"
|
||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => setProfile({ ...profile, industry: e.target.value })}
|
||||
w="full"
|
||||
px={3}
|
||||
py={2}
|
||||
bg="bg-iron-gray"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
rounded="lg"
|
||||
color="text-white"
|
||||
className="focus:outline-none focus:border-primary-blue"
|
||||
>
|
||||
{INDUSTRY_OPTIONS.map(industry => (
|
||||
<option key={industry} value={industry}>{industry}</option>
|
||||
))}
|
||||
</select>
|
||||
</Box>
|
||||
</FormField>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
{/* Contact Information */}
|
||||
<div className="pt-4 border-t border-charcoal-outline/50">
|
||||
<h3 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4">
|
||||
<Box pt={4} borderTop borderColor="border-charcoal-outline/50">
|
||||
<Heading level={3} fontSize="sm" weight="semibold" color="text-gray-400" mb={4}>
|
||||
Contact Information
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
</Heading>
|
||||
<Box display="grid" gridCols={{ base: 1, md: 2 }} gap={6}>
|
||||
<FormField label="Contact Name" icon={User} required>
|
||||
<Input
|
||||
type="text"
|
||||
@@ -291,16 +309,16 @@ export default function SponsorSettingsPage() {
|
||||
placeholder="https://company.com"
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Address */}
|
||||
<div className="pt-4 border-t border-charcoal-outline/50">
|
||||
<h3 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4">
|
||||
<Box pt={4} borderTop borderColor="border-charcoal-outline/50">
|
||||
<Heading level={3} fontSize="sm" weight="semibold" color="text-gray-400" mb={4}>
|
||||
Business Address
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="md:col-span-2">
|
||||
</Heading>
|
||||
<Box display="grid" gridCols={{ base: 1, md: 2 }} gap={6}>
|
||||
<Box colSpan={{ base: 1, md: 2 }}>
|
||||
<FormField label="Street Address" icon={MapPin}>
|
||||
<Input
|
||||
type="text"
|
||||
@@ -312,7 +330,7 @@ export default function SponsorSettingsPage() {
|
||||
placeholder="123 Main Street"
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<FormField label="City">
|
||||
<Input
|
||||
@@ -358,31 +376,39 @@ export default function SponsorSettingsPage() {
|
||||
placeholder="XX12-3456789"
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Description */}
|
||||
<div className="pt-4 border-t border-charcoal-outline/50">
|
||||
<Box pt={4} borderTop borderColor="border-charcoal-outline/50">
|
||||
<FormField label="Company Description">
|
||||
<textarea
|
||||
<Box as="textarea"
|
||||
value={profile.description}
|
||||
onChange={(e) => setProfile({ ...profile, description: e.target.value })}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setProfile({ ...profile, description: e.target.value })}
|
||||
placeholder="Tell potential sponsorship partners about your company, products, and what you're looking for in sponsorship opportunities..."
|
||||
rows={4}
|
||||
className="w-full px-4 py-3 bg-iron-gray border border-charcoal-outline rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-primary-blue resize-none"
|
||||
w="full"
|
||||
px={4}
|
||||
py={3}
|
||||
bg="bg-iron-gray"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
rounded="lg"
|
||||
color="text-white"
|
||||
className="placeholder-gray-500 focus:outline-none focus:border-primary-blue resize-none"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
<Text size="xs" color="text-gray-500" block mt={1}>
|
||||
This description appears on your public sponsor profile.
|
||||
</p>
|
||||
</Text>
|
||||
</FormField>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
{/* Social Links */}
|
||||
<div className="pt-4 border-t border-charcoal-outline/50">
|
||||
<h3 className="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-4">
|
||||
<Box pt={4} borderTop borderColor="border-charcoal-outline/50">
|
||||
<Heading level={3} fontSize="sm" weight="semibold" color="text-gray-400" mb={4}>
|
||||
Social Media
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
</Heading>
|
||||
<Box display="grid" gridCols={{ base: 1, md: 3 }} gap={6}>
|
||||
<FormField label="Twitter / X" icon={LinkIcon}>
|
||||
<Input
|
||||
type="text"
|
||||
@@ -418,49 +444,49 @@ export default function SponsorSettingsPage() {
|
||||
placeholder="@username"
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Logo Upload */}
|
||||
<div className="pt-4 border-t border-charcoal-outline/50">
|
||||
<Box pt={4} borderTop borderColor="border-charcoal-outline/50">
|
||||
<FormField label="Company Logo" icon={ImageIcon}>
|
||||
<div className="flex items-start gap-6">
|
||||
<div className="w-24 h-24 rounded-xl bg-gradient-to-br from-iron-gray to-deep-graphite border-2 border-dashed border-charcoal-outline flex items-center justify-center overflow-hidden">
|
||||
<Stack direction="row" align="start" gap={6}>
|
||||
<Box w="24" h="24" rounded="xl" bg="bg-gradient-to-br from-iron-gray to-deep-graphite" border borderColor="border-charcoal-outline" borderStyle="dashed" display="flex" alignItems="center" justifyContent="center" overflow="hidden">
|
||||
{profile.logoUrl ? (
|
||||
<img src={profile.logoUrl} alt="Company logo" className="w-full h-full object-cover" />
|
||||
<Image src={profile.logoUrl} alt="Company logo" width={96} height={96} objectFit="cover" />
|
||||
) : (
|
||||
<Building2 className="w-10 h-10 text-gray-600" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3">
|
||||
<label className="cursor-pointer">
|
||||
</Box>
|
||||
<Box flexGrow={1}>
|
||||
<Stack direction="row" align="center" gap={3}>
|
||||
<Text as="label" cursor="pointer">
|
||||
<input
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/svg+xml"
|
||||
className="hidden"
|
||||
/>
|
||||
<div className="px-4 py-2 rounded-lg bg-iron-gray border border-charcoal-outline text-gray-300 hover:bg-charcoal-outline transition-colors flex items-center gap-2">
|
||||
<Box px={4} py={2} rounded="lg" bg="bg-iron-gray" border borderColor="border-charcoal-outline" color="text-gray-300" transition className="hover:bg-charcoal-outline" display="flex" alignItems="center" gap={2}>
|
||||
<Upload className="w-4 h-4" />
|
||||
Upload Logo
|
||||
</div>
|
||||
</label>
|
||||
<Text>Upload Logo</Text>
|
||||
</Box>
|
||||
</Text>
|
||||
{profile.logoUrl && (
|
||||
<Button variant="secondary" className="text-sm text-gray-400">
|
||||
Remove
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
</Stack>
|
||||
<Text size="xs" color="text-gray-500" block mt={2}>
|
||||
PNG, JPEG, or SVG. Max 2MB. Recommended size: 400x400px.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</FormField>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
{/* Save Button */}
|
||||
<div className="pt-6 border-t border-charcoal-outline flex items-center justify-end gap-4">
|
||||
<Box pt={6} borderTop borderColor="border-charcoal-outline" display="flex" alignItems="center" justifyContent="end" gap={4}>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleSaveProfile}
|
||||
@@ -468,24 +494,24 @@ export default function SponsorSettingsPage() {
|
||||
className="min-w-[160px]"
|
||||
>
|
||||
{saving ? (
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
||||
Saving...
|
||||
</span>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Box w="4" h="4" border borderColor="border-white/30" borderTopColor="border-t-white" rounded="full" animate="spin" />
|
||||
<Text>Saving...</Text>
|
||||
</Stack>
|
||||
) : (
|
||||
<span className="flex items-center gap-2">
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Save className="w-4 h-4" />
|
||||
Save Profile
|
||||
</span>
|
||||
<Text>Save Profile</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Notification Preferences */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Card className="mb-6 overflow-hidden">
|
||||
<SectionHeader
|
||||
icon={Bell}
|
||||
@@ -493,8 +519,8 @@ export default function SponsorSettingsPage() {
|
||||
description="Control which emails you receive from GridPilot"
|
||||
color="text-warning-amber"
|
||||
/>
|
||||
<div className="p-6">
|
||||
<div className="space-y-1">
|
||||
<Box p={6}>
|
||||
<Box className="space-y-1">
|
||||
<Toggle
|
||||
checked={notifications.emailNewSponsorships}
|
||||
onChange={(checked) => setNotifications({ ...notifications, emailNewSponsorships: checked })}
|
||||
@@ -531,13 +557,13 @@ export default function SponsorSettingsPage() {
|
||||
label="Contract Expiry Reminders"
|
||||
description="Receive reminders before your sponsorship contracts expire"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Privacy & Visibility */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Card className="mb-6 overflow-hidden">
|
||||
<SectionHeader
|
||||
icon={Eye}
|
||||
@@ -545,8 +571,8 @@ export default function SponsorSettingsPage() {
|
||||
description="Control how your profile appears to others"
|
||||
color="text-performance-green"
|
||||
/>
|
||||
<div className="p-6">
|
||||
<div className="space-y-1">
|
||||
<Box p={6}>
|
||||
<Box className="space-y-1">
|
||||
<Toggle
|
||||
checked={privacy.publicProfile}
|
||||
onChange={(checked) => setPrivacy({ ...privacy, publicProfile: checked })}
|
||||
@@ -571,13 +597,13 @@ export default function SponsorSettingsPage() {
|
||||
label="Allow Direct Contact"
|
||||
description="Enable leagues and teams to send you sponsorship proposals"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Security */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Card className="mb-6 overflow-hidden">
|
||||
<SectionHeader
|
||||
icon={Shield}
|
||||
@@ -585,80 +611,77 @@ export default function SponsorSettingsPage() {
|
||||
description="Protect your sponsor account"
|
||||
color="text-primary-blue"
|
||||
/>
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="flex items-center justify-between py-3 border-b border-charcoal-outline/50">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-2 rounded-lg bg-iron-gray">
|
||||
<Box p={6} className="space-y-4">
|
||||
<Box display="flex" alignItems="center" justifyContent="between" py={3} borderBottom borderColor="border-charcoal-outline/50">
|
||||
<Stack direction="row" align="center" gap={4}>
|
||||
<Box p={2} rounded="lg" bg="bg-iron-gray">
|
||||
<Key className="w-5 h-5 text-gray-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-200 font-medium">Password</p>
|
||||
<p className="text-sm text-gray-500">Last changed 3 months ago</p>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text color="text-gray-200" weight="medium" block>Password</Text>
|
||||
<Text size="sm" color="text-gray-500" block>Last changed 3 months ago</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Button variant="secondary">
|
||||
Change Password
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div className="flex items-center justify-between py-3 border-b border-charcoal-outline/50">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-2 rounded-lg bg-iron-gray">
|
||||
<Box display="flex" alignItems="center" justifyContent="between" py={3} borderBottom borderColor="border-charcoal-outline/50">
|
||||
<Stack direction="row" align="center" gap={4}>
|
||||
<Box p={2} rounded="lg" bg="bg-iron-gray">
|
||||
<Smartphone className="w-5 h-5 text-gray-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-200 font-medium">Two-Factor Authentication</p>
|
||||
<p className="text-sm text-gray-500">Add an extra layer of security to your account</p>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text color="text-gray-200" weight="medium" block>Two-Factor Authentication</Text>
|
||||
<Text size="sm" color="text-gray-500" block>Add an extra layer of security to your account</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Button variant="secondary">
|
||||
Enable 2FA
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div className="flex items-center justify-between py-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-2 rounded-lg bg-iron-gray">
|
||||
<Box display="flex" alignItems="center" justifyContent="between" py={3}>
|
||||
<Stack direction="row" align="center" gap={4}>
|
||||
<Box p={2} rounded="lg" bg="bg-iron-gray">
|
||||
<Lock className="w-5 h-5 text-gray-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-200 font-medium">Active Sessions</p>
|
||||
<p className="text-sm text-gray-500">Manage devices where you're logged in</p>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text color="text-gray-200" weight="medium" block>Active Sessions</Text>
|
||||
<Text size="sm" color="text-gray-500" block>Manage devices where you're logged in</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Button variant="secondary">
|
||||
View Sessions
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Danger Zone */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<Box as={motion.div} variants={itemVariants}>
|
||||
<Card className="border-racing-red/30 overflow-hidden">
|
||||
<div className="p-5 border-b border-racing-red/30 bg-gradient-to-r from-racing-red/10 to-transparent">
|
||||
<h2 className="text-lg font-semibold text-racing-red flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-racing-red/10">
|
||||
<AlertCircle className="w-5 h-5 text-racing-red" />
|
||||
</div>
|
||||
<Box p={5} borderBottom borderColor="border-racing-red/30" bg="bg-gradient-to-r from-racing-red/10 to-transparent">
|
||||
<Heading level={2} fontSize="lg" weight="semibold" color="text-racing-red" icon={<Box p={2} rounded="lg" bg="bg-racing-red/10"><AlertCircle className="w-5 h-5 text-racing-red" /></Box>}>
|
||||
Danger Zone
|
||||
</h2>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-2 rounded-lg bg-racing-red/10">
|
||||
</Heading>
|
||||
</Box>
|
||||
<Box p={6}>
|
||||
<Box display="flex" alignItems="center" justifyContent="between">
|
||||
<Stack direction="row" align="center" gap={4}>
|
||||
<Box p={2} rounded="lg" bg="bg-racing-red/10">
|
||||
<Trash2 className="w-5 h-5 text-racing-red" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-200 font-medium">Delete Sponsor Account</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
</Box>
|
||||
<Box>
|
||||
<Text color="text-gray-200" weight="medium" block>Delete Sponsor Account</Text>
|
||||
<Text size="sm" color="text-gray-500" block>
|
||||
Permanently delete your account and all associated sponsorship data.
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleDeleteAccount}
|
||||
@@ -666,10 +689,10 @@ export default function SponsorSettingsPage() {
|
||||
>
|
||||
Delete Account
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,10 @@ import { motion, useReducedMotion } from 'framer-motion';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Input } from '@/ui/Input';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { SponsorHero } from '@/components/sponsors/SponsorHero';
|
||||
import { SponsorWorkflowMockup } from '@/components/sponsors/SponsorWorkflowMockup';
|
||||
import { SponsorBenefitCard } from '@/components/sponsors/SponsorBenefitCard';
|
||||
@@ -25,7 +29,6 @@ import {
|
||||
BarChart3,
|
||||
Shield,
|
||||
CheckCircle2,
|
||||
Star,
|
||||
Megaphone
|
||||
} from 'lucide-react';
|
||||
|
||||
@@ -238,13 +241,13 @@ export default function SponsorSignupPage() {
|
||||
// Landing page for sponsors
|
||||
if (mode === 'landing') {
|
||||
return (
|
||||
<div className="min-h-screen bg-deep-graphite">
|
||||
<Box minHeight="screen" bg="bg-deep-graphite">
|
||||
{/* Hero Section */}
|
||||
<SponsorHero
|
||||
title="Connect Your Brand with Sim Racing"
|
||||
subtitle="Reach passionate racing communities through league, team, driver, and race sponsorships. Real exposure, measurable results."
|
||||
>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Stack direction={{ base: 'col', md: 'row' }} gap={4} justify="center">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => setMode('signup')}
|
||||
@@ -261,12 +264,12 @@ export default function SponsorSignupPage() {
|
||||
Sign In
|
||||
<ArrowRight className="w-5 h-5 ml-2" />
|
||||
</Button>
|
||||
</div>
|
||||
</Stack>
|
||||
</SponsorHero>
|
||||
|
||||
{/* Platform Stats */}
|
||||
<div className="max-w-6xl mx-auto px-4 -mt-8 relative z-10">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<Box maxWidth="6xl" mx="auto" px={4} mt={-8} position="relative" zIndex={10}>
|
||||
<Box display="grid" gridCols={{ base: 2, md: 4 }} gap={4}>
|
||||
{PLATFORM_STATS.map((stat, index) => (
|
||||
<motion.div
|
||||
key={stat.label}
|
||||
@@ -275,27 +278,27 @@ export default function SponsorSignupPage() {
|
||||
transition={{ delay: index * 0.1 }}
|
||||
>
|
||||
<Card className="text-center p-4 bg-iron-gray/80 backdrop-blur-sm">
|
||||
<div className="text-2xl sm:text-3xl font-bold text-white mb-1">{stat.value}</div>
|
||||
<div className="text-xs sm:text-sm text-gray-400">{stat.label}</div>
|
||||
<Text size={{ base: '2xl', sm: '3xl' }} weight="bold" color="text-white" block mb={1}>{stat.value}</Text>
|
||||
<Text size={{ base: 'xs', sm: 'sm' }} color="text-gray-400">{stat.label}</Text>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Sponsorship Types Section */}
|
||||
<section className="max-w-6xl mx-auto px-4 py-16 sm:py-24">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-white mb-4">
|
||||
<Box as="section" maxWidth="6xl" mx="auto" px={4} py={{ base: 16, md: 24 }}>
|
||||
<Box textAlign="center" mb={12}>
|
||||
<Heading level={2} fontSize={{ base: '3xl', md: '4xl' }} weight="bold" color="text-white" mb={4}>
|
||||
Sponsorship Opportunities
|
||||
</h2>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
</Heading>
|
||||
<Text color="text-gray-400" maxWidth="2xl" mx="auto" block>
|
||||
Choose how you want to connect with the sim racing community.
|
||||
Multiple sponsorship tiers and types to fit every budget and goal.
|
||||
</p>
|
||||
</div>
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<Box display="grid" gridCols={{ base: 1, md: 2, lg: 3 }} gap={6}>
|
||||
{SPONSORSHIP_TYPES.map((type, index) => (
|
||||
<motion.div
|
||||
key={type.id}
|
||||
@@ -304,60 +307,60 @@ export default function SponsorSignupPage() {
|
||||
transition={{ delay: index * 0.1 }}
|
||||
>
|
||||
<Card className="h-full hover:border-primary-blue/50 transition-all duration-300 group">
|
||||
<div className="flex items-start gap-4 mb-4">
|
||||
<div className={`w-12 h-12 rounded-xl bg-iron-gray border border-charcoal-outline flex items-center justify-center group-hover:border-primary-blue/50 transition-colors`}>
|
||||
<Stack direction="row" align="start" gap={4} mb={4}>
|
||||
<Box w="12" h="12" rounded="xl" bg="bg-iron-gray" border borderColor="border-charcoal-outline" display="flex" alignItems="center" justifyContent="center" group hoverBorderColor="primary-blue/50" transition>
|
||||
<type.icon className={`w-6 h-6 ${type.color}`} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-white">{type.title}</h3>
|
||||
<p className="text-sm text-primary-blue font-medium">{type.priceRange}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<Box>
|
||||
<Heading level={3} fontSize="lg" weight="semibold" color="text-white">{type.title}</Heading>
|
||||
<Text size="sm" color="text-primary-blue" weight="medium" block>{type.priceRange}</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<p className="text-sm text-gray-400 mb-4">{type.description}</p>
|
||||
<Text size="sm" color="text-gray-400" block mb={4}>{type.description}</Text>
|
||||
|
||||
<ul className="space-y-2">
|
||||
<Box as="ul" className="space-y-2">
|
||||
{type.benefits.map((benefit, i) => (
|
||||
<li key={i} className="flex items-center gap-2 text-sm text-gray-300">
|
||||
<Box as="li" key={i} display="flex" alignItems="center" gap={2}>
|
||||
<CheckCircle2 className="w-4 h-4 text-performance-green flex-shrink-0" />
|
||||
{benefit}
|
||||
</li>
|
||||
<Text size="sm" color="text-gray-300">{benefit}</Text>
|
||||
</Box>
|
||||
))}
|
||||
</ul>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Workflow Mockup Section */}
|
||||
<section className="bg-iron-gray/30 py-16 sm:py-24">
|
||||
<div className="max-w-6xl mx-auto px-4">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-white mb-4">
|
||||
<Box as="section" bg="bg-iron-gray/30" py={{ base: 16, md: 24 }}>
|
||||
<Box maxWidth="6xl" mx="auto" px={4}>
|
||||
<Box textAlign="center" mb={12}>
|
||||
<Heading level={2} fontSize={{ base: '3xl', md: '4xl' }} weight="bold" color="text-white" mb={4}>
|
||||
How It Works
|
||||
</h2>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
</Heading>
|
||||
<Text color="text-gray-400" maxWidth="2xl" mx="auto" block>
|
||||
From discovery to results tracking — a seamless sponsorship experience.
|
||||
</p>
|
||||
</div>
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<SponsorWorkflowMockup />
|
||||
</div>
|
||||
</section>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Benefits Grid */}
|
||||
<section className="max-w-6xl mx-auto px-4 py-16 sm:py-24">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-white mb-4">
|
||||
<Box as="section" maxWidth="6xl" mx="auto" px={4} py={{ base: 16, md: 24 }}>
|
||||
<Box textAlign="center" mb={12}>
|
||||
<Heading level={2} fontSize={{ base: '3xl', md: '4xl' }} weight="bold" color="text-white" mb={4}>
|
||||
Why Sponsor on GridPilot?
|
||||
</h2>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||
</Heading>
|
||||
<Text color="text-gray-400" maxWidth="2xl" mx="auto" block>
|
||||
Professional tools and genuine community engagement make your sponsorship worthwhile.
|
||||
</p>
|
||||
</div>
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<Box display="grid" gridCols={{ base: 1, md: 2, lg: 3 }} gap={6}>
|
||||
<SponsorBenefitCard
|
||||
icon={Eye}
|
||||
title="Real Visibility"
|
||||
@@ -395,18 +398,18 @@ export default function SponsorSignupPage() {
|
||||
description="Scale your sponsorships as you see results. Start small or go big from day one."
|
||||
delay={0.5}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="max-w-4xl mx-auto px-4 py-16 sm:py-24 text-center">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold text-white mb-4">
|
||||
<Box as="section" maxWidth="4xl" mx="auto" px={4} py={{ base: 16, md: 24 }} textAlign="center">
|
||||
<Heading level={2} fontSize={{ base: '3xl', md: '4xl' }} weight="bold" color="text-white" mb={4}>
|
||||
Ready to Grow Your Brand?
|
||||
</h2>
|
||||
<p className="text-gray-400 max-w-2xl mx-auto mb-8">
|
||||
</Heading>
|
||||
<Text color="text-gray-400" maxWidth="2xl" mx="auto" block mb={8}>
|
||||
Join sponsors connecting with sim racing communities worldwide.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
</Text>
|
||||
<Stack direction={{ base: 'col', md: 'row' }} gap={4} justify="center">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => setMode('signup')}
|
||||
@@ -424,45 +427,45 @@ export default function SponsorSignupPage() {
|
||||
<Mail className="w-5 h-5 mr-2" />
|
||||
Contact Sales
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Login form
|
||||
if (mode === 'login') {
|
||||
return (
|
||||
<div className="min-h-screen bg-deep-graphite flex items-center justify-center px-4 py-12">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="mb-8">
|
||||
<Box minHeight="screen" bg="bg-deep-graphite" display="flex" alignItems="center" justifyContent="center" px={4} py={12}>
|
||||
<Box fullWidth maxWidth="md">
|
||||
<Box mb={8}>
|
||||
<button
|
||||
onClick={() => setMode('landing')}
|
||||
className="text-sm text-gray-400 hover:text-white mb-6 flex items-center gap-2"
|
||||
className="text-sm text-gray-400 hover:text-white mb-6 flex items-center gap-2 bg-transparent border-0 cursor-pointer"
|
||||
>
|
||||
<ArrowRight className="w-4 h-4 rotate-180" />
|
||||
Back to overview
|
||||
</button>
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-primary-blue/10 border border-primary-blue/20">
|
||||
<Stack direction="row" align="center" gap={4} mb={2}>
|
||||
<Box display="flex" h="14" w="14" alignItems="center" justifyContent="center" rounded="2xl" bg="bg-primary-blue/10" border borderColor="border-primary-blue/20">
|
||||
<Building2 className="w-7 h-7 text-primary-blue" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">Sponsor Sign In</h1>
|
||||
<p className="text-sm text-gray-400">Access your sponsor dashboard</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<Box>
|
||||
<Heading level={1} fontSize="2xl" weight="bold" color="text-white">Sponsor Sign In</Heading>
|
||||
<Text size="sm" color="text-gray-400">Access your sponsor dashboard</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Card className="p-6">
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Box as="form" onSubmit={handleSubmit} className="space-y-5">
|
||||
<Box>
|
||||
<Text as="label" block size="sm" weight="medium" color="text-gray-300" mb={2}>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Mail className="w-4 h-4 text-gray-500" />
|
||||
Email Address
|
||||
</div>
|
||||
</label>
|
||||
</Stack>
|
||||
</Text>
|
||||
<Input
|
||||
type="email"
|
||||
value={formData.contactEmail}
|
||||
@@ -471,12 +474,12 @@ export default function SponsorSignupPage() {
|
||||
variant={errors.contactEmail ? 'error' : 'default'}
|
||||
errorMessage={errors.contactEmail}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<Box>
|
||||
<Text as="label" block size="sm" weight="medium" color="text-gray-300" mb={2}>
|
||||
Password
|
||||
</label>
|
||||
</Text>
|
||||
<Input
|
||||
type="password"
|
||||
value={formData.password}
|
||||
@@ -485,17 +488,17 @@ export default function SponsorSignupPage() {
|
||||
variant={errors.password ? 'error' : 'default'}
|
||||
errorMessage={errors.password}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<label className="flex items-center gap-2 text-gray-400">
|
||||
<Stack direction="row" align="center" justify="between">
|
||||
<Text as="label" display="flex" alignItems="center" gap={2} color="text-gray-400">
|
||||
<input type="checkbox" className="rounded border-charcoal-outline bg-iron-gray" />
|
||||
Remember me
|
||||
</label>
|
||||
<button type="button" className="text-primary-blue hover:underline">
|
||||
</Text>
|
||||
<button type="button" className="text-primary-blue hover:underline bg-transparent border-0 cursor-pointer">
|
||||
Forgot password?
|
||||
</button>
|
||||
</div>
|
||||
</Stack>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
@@ -505,63 +508,62 @@ export default function SponsorSignupPage() {
|
||||
>
|
||||
{submitting ? 'Signing in...' : 'Sign In'}
|
||||
</Button>
|
||||
</form>
|
||||
</Box>
|
||||
|
||||
<div className="mt-6 pt-6 border-t border-charcoal-outline">
|
||||
<p className="text-sm text-gray-400 text-center mb-4">
|
||||
<Box mt={6} pt={6} borderTop borderColor="border-charcoal-outline">
|
||||
<Text size="sm" color="text-gray-400" align="center" block mb={4}>
|
||||
Don't have an account?{' '}
|
||||
<button
|
||||
onClick={() => setMode('signup')}
|
||||
className="text-primary-blue hover:underline"
|
||||
className="text-primary-blue hover:underline bg-transparent border-0 cursor-pointer"
|
||||
>
|
||||
Create one
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</Text>
|
||||
</Box>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Signup form
|
||||
return (
|
||||
<div className="min-h-screen bg-deep-graphite py-12 px-4">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<Box minHeight="screen" bg="bg-deep-graphite" py={12} px={4}>
|
||||
<Box maxWidth="2xl" mx="auto">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<Box mb={8}>
|
||||
<button
|
||||
onClick={() => setMode('landing')}
|
||||
className="text-sm text-gray-400 hover:text-white mb-6 flex items-center gap-2"
|
||||
className="text-sm text-gray-400 hover:text-white mb-6 flex items-center gap-2 bg-transparent border-0 cursor-pointer"
|
||||
>
|
||||
<ArrowRight className="w-4 h-4 rotate-180" />
|
||||
Back to overview
|
||||
</button>
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-primary-blue/10 border border-primary-blue/20">
|
||||
<Stack direction="row" align="center" gap={4} mb={2}>
|
||||
<Box display="flex" h="14" w="14" alignItems="center" justifyContent="center" rounded="2xl" bg="bg-primary-blue/10" border borderColor="border-primary-blue/20">
|
||||
<Building2 className="w-7 h-7 text-primary-blue" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">Create Sponsor Account</h1>
|
||||
<p className="text-sm text-gray-400">Register your company to sponsor racing entities</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<Box>
|
||||
<Heading level={1} fontSize="2xl" weight="bold" color="text-white">Create Sponsor Account</Heading>
|
||||
<Text size="sm" color="text-gray-400">Register your company to sponsor racing entities</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<Card className="p-6">
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<Box as="form" onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Company Information */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
|
||||
<Building2 className="w-5 h-5 text-primary-blue" />
|
||||
<Box>
|
||||
<Heading level={3} fontSize="lg" weight="semibold" color="text-white" mb={4} icon={<Building2 className="w-5 h-5 text-primary-blue" />}>
|
||||
Company Information
|
||||
</h3>
|
||||
</Heading>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<Box display="grid" gridCols={{ base: 1, md: 2 }} gap={4}>
|
||||
<Box colSpan={{ base: 1, md: 2 }}>
|
||||
<Text as="label" block size="sm" weight="medium" color="text-gray-300" mb={2}>
|
||||
Company Name *
|
||||
</label>
|
||||
</Text>
|
||||
<Input
|
||||
type="text"
|
||||
value={formData.companyName}
|
||||
@@ -570,15 +572,15 @@ export default function SponsorSignupPage() {
|
||||
variant={errors.companyName ? 'error' : 'default'}
|
||||
errorMessage={errors.companyName}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Box>
|
||||
<Text as="label" block size="sm" weight="medium" color="text-gray-300" mb={2}>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Mail className="w-4 h-4 text-gray-500" />
|
||||
Contact Email *
|
||||
</div>
|
||||
</label>
|
||||
</Stack>
|
||||
</Text>
|
||||
<Input
|
||||
type="email"
|
||||
value={formData.contactEmail}
|
||||
@@ -587,36 +589,35 @@ export default function SponsorSignupPage() {
|
||||
variant={errors.contactEmail ? 'error' : 'default'}
|
||||
errorMessage={errors.contactEmail}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Box>
|
||||
<Text as="label" block size="sm" weight="medium" color="text-gray-300" mb={2}>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Globe className="w-4 h-4 text-gray-500" />
|
||||
Website URL
|
||||
</div>
|
||||
</label>
|
||||
</Stack>
|
||||
</Text>
|
||||
<Input
|
||||
type="url"
|
||||
value={formData.websiteUrl}
|
||||
onChange={(e) => setFormData({ ...formData, websiteUrl: e.target.value })}
|
||||
placeholder="https://company.com"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Sponsorship Interests */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
|
||||
<Target className="w-5 h-5 text-purple-400" />
|
||||
<Box>
|
||||
<Heading level={3} fontSize="lg" weight="semibold" color="text-white" mb={4} icon={<Target className="w-5 h-5 text-purple-400" />}>
|
||||
Sponsorship Interests
|
||||
</h3>
|
||||
<p className="text-sm text-gray-400 mb-4">
|
||||
</Heading>
|
||||
<Text size="sm" color="text-gray-400" block mb={4}>
|
||||
Select the types of sponsorships you're interested in (optional)
|
||||
</p>
|
||||
</Text>
|
||||
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||||
<Box display="grid" gridCols={{ base: 2, sm: 3 }} gap={3}>
|
||||
{SPONSORSHIP_TYPES.map((type) => {
|
||||
const isSelected = formData.interests.includes(type.id);
|
||||
return (
|
||||
@@ -633,28 +634,28 @@ export default function SponsorSignupPage() {
|
||||
`}
|
||||
>
|
||||
<type.icon className={`w-5 h-5 ${isSelected ? type.color : 'text-gray-500'} mb-2`} />
|
||||
<p className={`text-sm font-medium ${isSelected ? 'text-white' : 'text-gray-400'}`}>
|
||||
<Text size="sm" weight="medium" color={isSelected ? 'text-white' : 'text-gray-400'} block>
|
||||
{type.title.replace(' Sponsorship', '').replace(' Advertising', '')}
|
||||
</p>
|
||||
</Text>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Company Logo */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Box>
|
||||
<Text as="label" block size="sm" weight="medium" color="text-gray-300" mb={2}>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Upload className="w-4 h-4 text-gray-500" />
|
||||
Company Logo (optional)
|
||||
</div>
|
||||
</label>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-16 h-16 rounded-lg bg-iron-gray border border-charcoal-outline flex items-center justify-center flex-shrink-0">
|
||||
</Stack>
|
||||
</Text>
|
||||
<Stack direction="row" align="center" gap={4}>
|
||||
<Box w="16" h="16" rounded="lg" bg="bg-iron-gray" border borderColor="border-charcoal-outline" display="flex" alignItems="center" justifyContent="center" flexShrink={0}>
|
||||
<Building2 className="w-6 h-6 text-gray-500" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
</Box>
|
||||
<Box flexGrow={1}>
|
||||
<input
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/svg+xml"
|
||||
@@ -664,25 +665,24 @@ export default function SponsorSignupPage() {
|
||||
}}
|
||||
className="block w-full text-sm text-gray-400 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-primary-blue/10 file:text-primary-blue hover:file:bg-primary-blue/20"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
<Text size="xs" color="text-gray-500" block mt={1}>
|
||||
PNG, JPEG, or SVG. Recommended: 500x500px transparent background.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* Account Security */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
|
||||
<Shield className="w-5 h-5 text-performance-green" />
|
||||
<Box>
|
||||
<Heading level={3} fontSize="lg" weight="semibold" color="text-white" mb={4} icon={<Shield className="w-5 h-5 text-performance-green" />}>
|
||||
Account Security
|
||||
</h3>
|
||||
</Heading>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<Box display="grid" gridCols={{ base: 1, md: 2 }} gap={4}>
|
||||
<Box>
|
||||
<Text as="label" block size="sm" weight="medium" color="text-gray-300" mb={2}>
|
||||
Password *
|
||||
</label>
|
||||
</Text>
|
||||
<Input
|
||||
type="password"
|
||||
value={formData.password}
|
||||
@@ -691,12 +691,12 @@ export default function SponsorSignupPage() {
|
||||
variant={errors.password ? 'error' : 'default'}
|
||||
errorMessage={errors.password}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<Box>
|
||||
<Text as="label" block size="sm" weight="medium" color="text-gray-300" mb={2}>
|
||||
Confirm Password *
|
||||
</label>
|
||||
</Text>
|
||||
<Input
|
||||
type="password"
|
||||
value={formData.confirmPassword}
|
||||
@@ -705,49 +705,49 @@ export default function SponsorSignupPage() {
|
||||
variant={errors.confirmPassword ? 'error' : 'default'}
|
||||
errorMessage={errors.confirmPassword}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Legal Agreements */}
|
||||
<div className="space-y-3 pt-4 border-t border-charcoal-outline">
|
||||
<label className="flex items-start gap-3 cursor-pointer">
|
||||
<Stack gap={3} pt={4} borderTop borderColor="border-charcoal-outline">
|
||||
<Text as="label" display="flex" alignItems="start" gap={3} cursor="pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.acceptTerms}
|
||||
onChange={(e) => setFormData({ ...formData, acceptTerms: e.target.checked })}
|
||||
className="mt-1 rounded border-charcoal-outline bg-iron-gray text-primary-blue focus:ring-primary-blue"
|
||||
/>
|
||||
<span className="text-sm text-gray-400">
|
||||
<Text size="sm" color="text-gray-400">
|
||||
I accept the{' '}
|
||||
<a href="/legal/terms" className="text-primary-blue hover:underline">Terms of Service</a>
|
||||
{' '}and{' '}
|
||||
<a href="/legal/privacy" className="text-primary-blue hover:underline">Privacy Policy</a>
|
||||
{' '}*
|
||||
</span>
|
||||
</label>
|
||||
</Text>
|
||||
</Text>
|
||||
{errors.acceptTerms && (
|
||||
<p className="text-sm text-warning-amber">{errors.acceptTerms}</p>
|
||||
<Text size="sm" color="text-warning-amber">{errors.acceptTerms}</Text>
|
||||
)}
|
||||
|
||||
<label className="flex items-start gap-3 cursor-pointer">
|
||||
<Text as="label" display="flex" alignItems="start" gap={3} cursor="pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.acceptVat}
|
||||
onChange={(e) => setFormData({ ...formData, acceptVat: e.target.checked })}
|
||||
className="mt-1 rounded border-charcoal-outline bg-iron-gray text-primary-blue focus:ring-primary-blue"
|
||||
/>
|
||||
<span className="text-sm text-gray-400">
|
||||
<Text size="sm" color="text-gray-400">
|
||||
{siteConfig.vat.notice} A {siteConfig.fees.platformFeePercent}% platform fee applies to all sponsorships. *
|
||||
</span>
|
||||
</label>
|
||||
</Text>
|
||||
</Text>
|
||||
{errors.acceptVat && (
|
||||
<p className="text-sm text-warning-amber">{errors.acceptVat}</p>
|
||||
<Text size="sm" color="text-warning-amber">{errors.acceptVat}</Text>
|
||||
)}
|
||||
</div>
|
||||
</Stack>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-3 pt-4">
|
||||
<Stack direction="row" gap={3} pt={4}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
@@ -764,22 +764,22 @@ export default function SponsorSignupPage() {
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
<div className="mt-6 pt-6 border-t border-charcoal-outline text-center">
|
||||
<p className="text-sm text-gray-400">
|
||||
<Box mt={6} pt={6} borderTop="1px solid" borderColor="border-charcoal-outline" textAlign="center">
|
||||
<Text size="sm" color="text-gray-400">
|
||||
Already have an account?{' '}
|
||||
<button
|
||||
onClick={() => setMode('login')}
|
||||
className="text-primary-blue hover:underline"
|
||||
className="text-primary-blue hover:underline bg-transparent border-0 cursor-pointer"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</Text>
|
||||
</Box>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user