217 lines
8.4 KiB
TypeScript
217 lines
8.4 KiB
TypeScript
|
|
|
|
import { BillingSummaryPanel } from '@/components/sponsors/BillingSummaryPanel';
|
|
import { SponsorDashboardHeader } from '@/components/sponsors/SponsorDashboardHeader';
|
|
import { PayoutItem, SponsorPayoutQueueTable } from '@/components/sponsors/SponsorPayoutQueueTable';
|
|
import { ViewData } from '@/lib/contracts/view-data/ViewData';
|
|
import { siteConfig } from '@/lib/siteConfig';
|
|
import type { InvoiceDTO, PaymentMethodDTO } from '@/lib/types/tbd/SponsorBillingDTO';
|
|
import { Box } from '@/ui/Box';
|
|
import { Button } from '@/ui/Button';
|
|
import { Card } from '@/ui/Card';
|
|
import { Container } from '@/ui/Container';
|
|
import { Heading } from '@/ui/Heading';
|
|
import { Icon } from '@/ui/Icon';
|
|
import { InfoBanner } from '@/ui/InfoBanner';
|
|
import { Stack } from '@/ui/Stack';
|
|
import { Text } from '@/ui/Text';
|
|
import {
|
|
Building2,
|
|
CreditCard,
|
|
Download,
|
|
ExternalLink,
|
|
LucideIcon,
|
|
Percent,
|
|
Receipt
|
|
} from 'lucide-react';
|
|
|
|
export interface SponsorBillingViewData extends ViewData {
|
|
stats: {
|
|
totalSpent: number;
|
|
pendingAmount: number;
|
|
nextPaymentDate: string;
|
|
nextPaymentAmount: number;
|
|
averageMonthlySpend: number;
|
|
};
|
|
paymentMethods: PaymentMethodDTO[];
|
|
invoices: InvoiceDTO[];
|
|
}
|
|
|
|
interface SponsorBillingTemplateProps {
|
|
viewData: SponsorBillingViewData;
|
|
billingStats: Array<{
|
|
label: string;
|
|
value: string;
|
|
subValue?: string;
|
|
icon: LucideIcon;
|
|
variant: 'success' | 'warning' | 'info' | 'default';
|
|
}>;
|
|
transactions: Array<{
|
|
id: string;
|
|
date: string;
|
|
amount: number | string;
|
|
status: string;
|
|
recipient?: string;
|
|
description?: string;
|
|
invoiceNumber?: string;
|
|
}>;
|
|
onSetDefaultPaymentMethod: (id: string) => void;
|
|
onDownloadInvoice: (id: string) => void;
|
|
}
|
|
|
|
export function SponsorBillingTemplate({
|
|
viewData,
|
|
billingStats,
|
|
transactions,
|
|
onSetDefaultPaymentMethod,
|
|
onDownloadInvoice
|
|
}: SponsorBillingTemplateProps) {
|
|
// Map transactions to PayoutItems for the new table
|
|
const payoutItems: PayoutItem[] = transactions.map(t => ({
|
|
id: t.id,
|
|
date: t.date,
|
|
amount: typeof t.amount === 'number' ? t.amount.toFixed(2) : t.amount,
|
|
status: t.status === 'paid' ? 'completed' : t.status === 'pending' ? 'pending' : 'failed',
|
|
recipient: t.recipient || 'GridPilot Platform',
|
|
description: t.description || `Invoice ${t.invoiceNumber}`
|
|
}));
|
|
|
|
return (
|
|
<Container size="lg" spacing="md">
|
|
<Stack gap={8}>
|
|
<SponsorDashboardHeader
|
|
sponsorName="Sponsor"
|
|
onRefresh={() => console.log('Refresh')}
|
|
/>
|
|
|
|
<BillingSummaryPanel stats={billingStats} />
|
|
|
|
<Box display="grid" gridCols={{ base: 1, lg: 12 }} gap={8}>
|
|
<Box colSpan={{ base: 1, lg: 8 }}>
|
|
<Stack gap={8}>
|
|
{/* Billing History */}
|
|
<Box>
|
|
<Stack direction="row" align="center" justify="between" mb={4}>
|
|
<Heading level={3}>Billing History</Heading>
|
|
<Button variant="secondary" size="sm" icon={<Icon icon={Download} size={4} />} onClick={() => onDownloadInvoice('all')}>
|
|
Export All
|
|
</Button>
|
|
</Stack>
|
|
<Card p={0} overflow="hidden">
|
|
<SponsorPayoutQueueTable payouts={payoutItems} />
|
|
</Card>
|
|
</Box>
|
|
|
|
{/* Platform Fees & VAT */}
|
|
<Box display="grid" gridCols={{ base: 1, md: 2 }} gap={6}>
|
|
<Card overflow="hidden">
|
|
<Box p={4} borderBottom borderColor="border-charcoal-outline" bg="bg-iron-gray/10">
|
|
<Stack direction="row" align="center" gap={2}>
|
|
<Icon icon={Percent} size={4} color="text-primary-blue" />
|
|
<Text size="xs" weight="bold" uppercase letterSpacing="wider" color="text-gray-400">
|
|
Platform Fee
|
|
</Text>
|
|
</Stack>
|
|
</Box>
|
|
<Box p={5}>
|
|
<Text size="3xl" weight="bold" color="text-white" block mb={2}>
|
|
{siteConfig.fees.platformFeePercent}%
|
|
</Text>
|
|
<Text size="sm" color="text-gray-400" block>
|
|
{siteConfig.fees.description}
|
|
</Text>
|
|
</Box>
|
|
</Card>
|
|
|
|
<Card overflow="hidden">
|
|
<Box p={4} borderBottom borderColor="border-charcoal-outline" bg="bg-iron-gray/10">
|
|
<Stack direction="row" align="center" gap={2}>
|
|
<Icon icon={Receipt} size={4} color="text-performance-green" />
|
|
<Text size="xs" weight="bold" uppercase letterSpacing="wider" color="text-gray-400">
|
|
VAT Information
|
|
</Text>
|
|
</Stack>
|
|
</Box>
|
|
<Box p={5}>
|
|
<Text size="sm" color="text-gray-400" block mb={4}>
|
|
{siteConfig.vat.notice}
|
|
</Text>
|
|
<Stack gap={2}>
|
|
<Box display="flex" justifyContent="between">
|
|
<Text size="sm" color="text-gray-500">Standard Rate</Text>
|
|
<Text size="sm" color="text-white" weight="medium">{siteConfig.vat.standardRate}%</Text>
|
|
</Box>
|
|
<Box display="flex" justifyContent="between">
|
|
<Text size="sm" color="text-gray-500">B2B Reverse Charge</Text>
|
|
<Text size="sm" color="text-performance-green" weight="medium">Available</Text>
|
|
</Box>
|
|
</Stack>
|
|
</Box>
|
|
</Card>
|
|
</Box>
|
|
</Stack>
|
|
</Box>
|
|
|
|
<Box colSpan={{ base: 1, lg: 4 }}>
|
|
<Stack gap={6}>
|
|
{/* Payment Methods */}
|
|
<Card>
|
|
<Stack gap={4}>
|
|
<Heading level={3}>Payment Methods</Heading>
|
|
<Stack gap={3}>
|
|
{viewData.paymentMethods.map((method: PaymentMethodDTO) => (
|
|
<Box
|
|
key={method.id}
|
|
p={3}
|
|
rounded="lg"
|
|
border
|
|
borderColor={method.isDefault ? 'border-primary-blue/50' : 'border-charcoal-outline/50'}
|
|
bg={method.isDefault ? 'bg-primary-blue/5' : 'bg-iron-gray/5'}
|
|
>
|
|
<Stack direction="row" align="center" justify="between">
|
|
<Stack direction="row" align="center" gap={3}>
|
|
<Icon icon={method.type === 'sepa' ? Building2 : CreditCard} size={5} color={method.isDefault ? 'text-primary-blue' : 'text-gray-400'} />
|
|
<Box>
|
|
<Text size="sm" weight="medium" color="text-white">
|
|
{method.brand || 'Bank Account'} •••• {method.last4}
|
|
</Text>
|
|
{method.isDefault && (
|
|
<Text size="xs" color="text-primary-blue" weight="medium" block>Default</Text>
|
|
)}
|
|
</Box>
|
|
</Stack>
|
|
{!method.isDefault && (
|
|
<Button variant="ghost" size="sm" onClick={() => onSetDefaultPaymentMethod(method.id)}>
|
|
Set Default
|
|
</Button>
|
|
)}
|
|
</Stack>
|
|
</Box>
|
|
))}
|
|
</Stack>
|
|
<InfoBanner type="info">
|
|
<Text size="xs">All payments are securely processed by our payment provider.</Text>
|
|
</InfoBanner>
|
|
</Stack>
|
|
</Card>
|
|
|
|
{/* Support */}
|
|
<Card>
|
|
<Stack gap={4}>
|
|
<Heading level={3}>Billing Support</Heading>
|
|
<Text size="sm" color="text-gray-400">
|
|
Need help with an invoice or have questions about your plan?
|
|
</Text>
|
|
<Button variant="secondary" fullWidth icon={<Icon icon={ExternalLink} size={4} />}>
|
|
Contact Support
|
|
</Button>
|
|
</Stack>
|
|
</Card>
|
|
</Stack>
|
|
</Box>
|
|
</Box>
|
|
</Stack>
|
|
</Container>
|
|
);
|
|
}
|