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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user