'use client' import { useEffect, useState } from 'react' import { useAdminI18n } from '@/components/I18nProvider' import { ADMIN_API_BASE } from '@/lib/api' interface Company { id: string name: string email: string slug: string status: string } interface Invoice { id: string amount: number currency: string status: string paymentProvider: string amanpayTransactionId?: string | null paypalCaptureId?: string | null paidAt: string | null createdAt: string } function buildInvoiceNumber(invoiceId: string, createdAt: string) { const year = new Date(createdAt).getFullYear() const short = invoiceId.slice(-6).toUpperCase() return `INV-${year}-${short}` } interface Subscription { id: string companyId: string company: Company plan: string billingPeriod: string status: string currency: string currentPeriodEnd: string | null cancelAtPeriodEnd: boolean invoices: Invoice[] _count: { invoices: number } createdAt: string } interface Stats { mrr: number activeCount: number trialingCount: number pastDueCount: number cancelledCount: number } const SUB_STATUS_COLORS: Record = { ACTIVE: 'text-emerald-400 bg-emerald-900/30', TRIALING: 'text-sky-400 bg-sky-900/30', PAST_DUE: 'text-orange-400 bg-orange-900/30', CANCELLED: 'text-zinc-400 bg-zinc-800', UNPAID: 'text-red-400 bg-red-900/30', } const INVOICE_STATUS_COLORS: Record = { PAID: 'text-emerald-400 bg-emerald-900/30', PENDING: 'text-orange-400 bg-orange-900/30', FAILED: 'text-red-400 bg-red-900/30', REFUNDED: 'text-zinc-400 bg-zinc-800', } const PLAN_COLORS: Record = { STARTER: 'text-zinc-300', GROWTH: 'text-sky-400', PRO: 'text-violet-400', } function fmt(amount: number, currency: string) { return `${(amount / 100).toFixed(2)} ${currency}` } function fmtDate(iso: string | null) { if (!iso) return '—' return new Date(iso).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) } export default function AdminBillingPage() { const { language } = useAdminI18n() const copy = { en: { title: 'Billing', eyebrow: 'Platform', mrr: 'MRR (MAD)', active: 'Active', trialing: 'Trialing', pastDue: 'Past Due', cancelled: 'Cancelled', company: 'Company', plan: 'Plan', period: 'Period', status: 'Status', currency: 'Currency', nextRenewal: 'Next Renewal', invoices: 'Invoices', actions: 'Actions', viewInvoices: 'Invoices', loading: 'Loading…', empty: 'No subscriptions found', filterAll: 'All', filterActive: 'Active', filterTrialing: 'Trialing', filterPastDue: 'Past Due', filterCancelled: 'Cancelled', closeModal: 'Close', invoicesFor: 'Invoices for', noInvoices: 'No invoices found', amount: 'Amount', invoiceStatus: 'Status', provider: 'Provider', paidAt: 'Paid At', date: 'Date', invoiceNo: 'Invoice #', download: 'PDF', cancelAtEnd: 'Cancels at period end', }, fr: { title: 'Facturation', eyebrow: 'Plateforme', mrr: 'MRR (MAD)', active: 'Actifs', trialing: 'En essai', pastDue: 'En retard', cancelled: 'Annulés', company: 'Entreprise', plan: 'Plan', period: 'Période', status: 'Statut', currency: 'Devise', nextRenewal: 'Prochain renouvellement', invoices: 'Factures', actions: 'Actions', viewInvoices: 'Factures', loading: 'Chargement…', empty: 'Aucun abonnement trouvé', filterAll: 'Tous', filterActive: 'Actifs', filterTrialing: 'Essai', filterPastDue: 'En retard', filterCancelled: 'Annulés', closeModal: 'Fermer', invoicesFor: 'Factures pour', noInvoices: 'Aucune facture trouvée', amount: 'Montant', invoiceStatus: 'Statut', provider: 'Fournisseur', paidAt: 'Payé le', date: 'Date', invoiceNo: 'Facture n°', download: 'PDF', cancelAtEnd: 'Sera annulé en fin de période', }, ar: { title: 'الفوترة', eyebrow: 'المنصة', mrr: 'الإيراد الشهري (MAD)', active: 'نشط', trialing: 'تجريبي', pastDue: 'متأخر', cancelled: 'ملغى', company: 'الشركة', plan: 'الخطة', period: 'الفترة', status: 'الحالة', currency: 'العملة', nextRenewal: 'التجديد القادم', invoices: 'الفواتير', actions: 'الإجراءات', viewInvoices: 'الفواتير', loading: 'جارٍ التحميل…', empty: 'لا توجد اشتراكات', filterAll: 'الكل', filterActive: 'نشط', filterTrialing: 'تجريبي', filterPastDue: 'متأخر', filterCancelled: 'ملغى', closeModal: 'إغلاق', invoicesFor: 'فواتير', noInvoices: 'لا توجد فواتير', amount: 'المبلغ', invoiceStatus: 'الحالة', provider: 'المزود', paidAt: 'تاريخ الدفع', date: 'التاريخ', invoiceNo: 'رقم الفاتورة', download: 'PDF', cancelAtEnd: 'سيُلغى في نهاية الفترة', }, }[language] const [subscriptions, setSubscriptions] = useState([]) const [stats, setStats] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [statusFilter, setStatusFilter] = useState('') const [invoiceModal, setInvoiceModal] = useState<{ companyId: string; companyName: string } | null>(null) const [invoices, setInvoices] = useState([]) const [invoicesLoading, setInvoicesLoading] = useState(false) const [invoicesError, setInvoicesError] = useState(null) async function fetchBilling(status?: string) { setLoading(true) setError(null) const token = localStorage.getItem('admin_token') try { const params = new URLSearchParams({ pageSize: '50' }) if (status) params.set('status', status) const res = await fetch(`${ADMIN_API_BASE}/admin/billing?${params}`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, cache: 'no-store', }) const json = await res.json() if (!res.ok) throw new Error(json?.message ?? 'Failed to fetch') setSubscriptions(json.data ?? []) setStats(json.stats ?? null) } catch (err: any) { setError(err.message) } finally { setLoading(false) } } useEffect(() => { fetchBilling(statusFilter || undefined) }, [statusFilter]) async function downloadInvoicePdf(invoiceId: string, createdAt: string) { const token = localStorage.getItem('admin_token') try { const res = await fetch(`${ADMIN_API_BASE}/admin/billing/invoices/${invoiceId}/pdf`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, }) if (!res.ok) throw new Error('Failed to generate PDF') const blob = await res.blob() const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `${buildInvoiceNumber(invoiceId, createdAt)}.pdf` a.click() URL.revokeObjectURL(url) } catch { // silent — no toast system available here } } async function openInvoices(companyId: string, companyName: string) { setInvoiceModal({ companyId, companyName }) setInvoices([]) setInvoicesError(null) setInvoicesLoading(true) const token = localStorage.getItem('admin_token') try { const res = await fetch(`${ADMIN_API_BASE}/admin/billing/${companyId}/invoices?pageSize=50`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, }) const json = await res.json() if (!res.ok) throw new Error(json?.message ?? `Error ${res.status}`) setInvoices(json.data ?? []) } catch (err: any) { setInvoicesError(err.message) } finally { setInvoicesLoading(false) } } const filters = [ { key: '', label: copy.filterAll }, { key: 'ACTIVE', label: copy.filterActive }, { key: 'TRIALING', label: copy.filterTrialing }, { key: 'PAST_DUE', label: copy.filterPastDue }, { key: 'CANCELLED', label: copy.filterCancelled }, ] return (

{copy.eyebrow}

{copy.title}

{stats && (

{copy.mrr}

{(stats.mrr / 100).toFixed(0)}

{copy.active}

{stats.activeCount}

{copy.trialing}

{stats.trialingCount}

{copy.pastDue}

{stats.pastDueCount}

{copy.cancelled}

{stats.cancelledCount}

)}
{filters.map((f) => ( ))}
{error &&
{error}
}
{loading ? ( ) : subscriptions.length === 0 ? ( ) : subscriptions.map((sub) => ( ))}
{copy.company} {copy.plan} {copy.period} {copy.status} {copy.currency} {copy.nextRenewal} {copy.invoices}
{copy.loading}
{copy.empty}

{sub.company.name}

{sub.company.email}

{sub.plan} {sub.billingPeriod}
{sub.status} {sub.cancelAtPeriodEnd && ( {copy.cancelAtEnd} )}
{sub.currency} {fmtDate(sub.currentPeriodEnd)} {sub._count.invoices}
{invoiceModal && (

{copy.invoicesFor}

{invoiceModal.companyName}

{invoicesLoading ? (
{copy.loading}
) : invoicesError ? (
{invoicesError}
) : invoices.length === 0 ? (
{copy.noInvoices}
) : ( {invoices.map((inv) => ( ))}
{copy.invoiceNo} {copy.date} {copy.amount} {copy.invoiceStatus} {copy.provider} {copy.paidAt}
{buildInvoiceNumber(inv.id, inv.createdAt)} {fmtDate(inv.createdAt)} {fmt(inv.amount, inv.currency)} {inv.status} {inv.paymentProvider} {fmtDate(inv.paidAt)}
)}
)}
) }