class BillingDashboard extends HTMLElement { constructor() { super(); this.currentUsage = null; this.subscription = null; this.pricing = null; this.invoices = []; this.boundHandleClick = this.handleClick.bind(this); this.loading = true; this.error = null; this.stripe = null; } async connectedCallback() { this.addEventListener('click', this.boundHandleClick); this.render(); await this.loadData(); this.render(); this.attachEventListeners(); await this.initStripe(); } disconnectedCallback() { this.removeEventListener('click', this.boundHandleClick); } async loadData() { this.loading = true; this.error = null; try { const [usage, subscription, pricing, invoices] = await Promise.all([ this.fetchCurrentUsage(), this.fetchSubscription(), this.fetchPricing(), this.fetchInvoices() ]); this.currentUsage = usage; this.subscription = subscription; this.pricing = pricing; this.invoices = invoices; this.loading = false; } catch (error) { console.error('Failed to load billing data:', error); this.error = error.message || 'Failed to load billing data'; this.loading = false; } } async initStripe() { if (window.Stripe) { const response = await fetch('/api/billing/stripe-key'); if (response.ok) { const data = await response.json(); this.stripe = window.Stripe(data.publishable_key); } } } async fetchCurrentUsage() { const response = await fetch('/api/billing/usage/current', { headers: {'Authorization': `Bearer ${localStorage.getItem('token')}`} }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } async fetchSubscription() { const response = await fetch('/api/billing/subscription', { headers: {'Authorization': `Bearer ${localStorage.getItem('token')}`} }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } async fetchPricing() { const response = await fetch('/api/billing/pricing'); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } async fetchInvoices() { const response = await fetch('/api/billing/invoices?limit=10', { headers: {'Authorization': `Bearer ${localStorage.getItem('token')}`} }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } formatCurrency(amount) { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 4 }).format(amount); } formatGB(gb) { if (gb >= 1024) { return `${(gb / 1024).toFixed(2)} TB`; } return `${gb.toFixed(2)} GB`; } calculateEstimatedCost() { if (!this.currentUsage || !this.pricing) return 0; const storagePrice = parseFloat(this.pricing.storage_per_gb_month?.value || 0); const bandwidthPrice = parseFloat(this.pricing.bandwidth_egress_per_gb?.value || 0); const freeStorage = parseFloat(this.pricing.free_tier_storage_gb?.value || 0); const freeBandwidth = parseFloat(this.pricing.free_tier_bandwidth_gb?.value || 0); const storageGB = this.currentUsage.storage_gb; const bandwidthGB = this.currentUsage.bandwidth_down_gb_today * 30; const billableStorage = Math.max(0, Math.ceil(storageGB - freeStorage)); const billableBandwidth = Math.max(0, Math.ceil(bandwidthGB - freeBandwidth)); return (billableStorage * storagePrice) + (billableBandwidth * bandwidthPrice); } render() { if (this.loading) { this.innerHTML = '
No invoices yet
'; } return `| Invoice # | Period | Amount | Status | Due Date | Actions |
|---|---|---|---|---|---|
| ${invoice.invoice_number} | ${this.formatDate(invoice.period_start)} - ${this.formatDate(invoice.period_end)} | ${this.formatCurrency(invoice.total)} | ${invoice.status} | ${invoice.due_date ? this.formatDate(invoice.due_date) : '-'} |