diff --git a/components/settings/unified/tabs/WorkspaceBillingTab.tsx b/components/settings/unified/tabs/WorkspaceBillingTab.tsx index 93f5ab7..1785d90 100644 --- a/components/settings/unified/tabs/WorkspaceBillingTab.tsx +++ b/components/settings/unified/tabs/WorkspaceBillingTab.tsx @@ -3,9 +3,9 @@ import { useState, useEffect } from 'react' import Link from 'next/link' import { Button, toast, Spinner, Modal } from '@ciphera-net/ui' -import { CreditCard, ArrowSquareOut } from '@phosphor-icons/react' +import { CreditCard, ArrowSquareOut, DownloadSimple } from '@phosphor-icons/react' import { useSubscription } from '@/lib/swr/dashboard' -import { updatePaymentMethod, cancelSubscription, resumeSubscription, getOrders, type Order } from '@/lib/api/billing' +import { updatePaymentMethod, cancelSubscription, resumeSubscription, getInvoices, downloadInvoicePDF, type Invoice } from '@/lib/api/billing' import { formatDateLong, formatDate } from '@/lib/utils/formatDate' import { getAuthErrorMessage } from '@ciphera-net/ui' @@ -13,16 +13,12 @@ export default function WorkspaceBillingTab() { const { data: subscription, isLoading, mutate } = useSubscription() const [cancelling, setCancelling] = useState(false) const [showCancelConfirm, setShowCancelConfirm] = useState(false) - const [orders, setOrders] = useState([]) + const [invoices, setInvoices] = useState([]) useEffect(() => { - getOrders().then(setOrders).catch(() => {}) + getInvoices().then(setInvoices).catch(() => {}) }, []) - const formatAmount = (amount: string, currency: string) => { - return new Intl.NumberFormat('en-GB', { style: 'currency', currency: currency || 'EUR' }).format(parseFloat(amount)) - } - const handleManageBilling = async () => { try { const { url } = await updatePaymentMethod() @@ -197,19 +193,34 @@ export default function WorkspaceBillingTab() { {/* Recent Invoices */} - {orders.length > 0 && ( + {invoices.length > 0 && (

Recent Invoices

- {orders.map(order => ( -
+ {invoices.map(invoice => ( +
- {formatDate(new Date(order.created_at))} - {formatAmount(order.amount, order.currency)} + {invoice.invoice_number ?? '—'} + {formatDate(new Date(invoice.created_at))} + + {new Intl.NumberFormat('en-GB', { style: 'currency', currency: invoice.currency || 'EUR' }).format(invoice.total_cents / 100)} + + + (incl. {new Intl.NumberFormat('en-GB', { style: 'currency', currency: invoice.currency || 'EUR' }).format(invoice.vat_cents / 100)} VAT) + +
+
+ + {invoice.status === 'sent' ? 'Paid' : invoice.status} + +
- - {order.status === 'paid' ? 'Paid' : order.status} -
))}
diff --git a/lib/api/billing.ts b/lib/api/billing.ts index 48f3ed7..f915673 100644 --- a/lib/api/billing.ts +++ b/lib/api/billing.ts @@ -91,16 +91,36 @@ export async function updatePaymentMethod(): Promise<{ url: string }> { }) } -export interface Order { +export interface Invoice { id: string - amount: string + invoice_number: string | null + amount_cents: number + vat_cents: number + total_cents: number currency: string + description: string status: string created_at: string } -export async function getOrders(): Promise { - return apiRequest('/api/billing/invoices') +export async function getInvoices(): Promise { + const res = await apiRequest<{ invoices: Invoice[] }>('/api/billing/invoices') + return res.invoices ?? [] +} + +export async function downloadInvoicePDF(invoiceId: string): Promise { + const { API_URL } = await import('./client') + const res = await fetch(API_URL + '/api/billing/invoices/' + invoiceId + '/pdf', { + credentials: 'include', + }) + if (!res.ok) throw new Error('Failed to download invoice PDF') + const blob = await res.blob() + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = 'invoice.pdf' + a.click() + URL.revokeObjectURL(url) } export interface VATResult {