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 = '
Loading billing data...
'; return; } if (this.error) { this.innerHTML = `
Error: ${this.error}
`; return; } const estimatedCost = this.calculateEstimatedCost(); const storageUsed = this.currentUsage?.storage_gb || 0; const freeStorage = parseFloat(this.pricing?.free_tier_storage_gb?.value || 15); const storagePercentage = Math.min(100, (storageUsed / freeStorage) * 100); this.innerHTML = `

Billing & Usage

${this.subscription?.billing_type === 'pay_as_you_go' ? 'Pay As You Go' : this.subscription?.plan_name || 'Free'}

Current Usage

Storage ${this.formatGB(storageUsed)}
${this.formatGB(freeStorage)} included free
Bandwidth (Today) ${this.formatGB(this.currentUsage?.bandwidth_down_gb_today || 0)}

Estimated Monthly Cost

${this.formatCurrency(estimatedCost)}
Storage ${this.formatCurrency(Math.max(0, Math.ceil(storageUsed - freeStorage)) * parseFloat(this.pricing?.storage_per_gb_month?.value || 0))}
Bandwidth ${this.formatCurrency(0)}

Current Pricing

Storage ${this.formatCurrency(parseFloat(this.pricing?.storage_per_gb_month?.value || 0))}/GB/month
Bandwidth ${this.formatCurrency(parseFloat(this.pricing?.bandwidth_egress_per_gb?.value || 0))}/GB
Free Tier ${this.formatGB(freeStorage)} storage, ${this.formatGB(parseFloat(this.pricing?.free_tier_bandwidth_gb?.value || 15))} bandwidth/month

Recent Invoices

${this.renderInvoicesTable()}

Payment Methods

`; } renderInvoicesTable() { if (!this.invoices || this.invoices.length === 0) { return '

No invoices yet

'; } return ` ${this.invoices.map(invoice => ` `).join('')}
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) : '-'}
`; } formatDate(dateString) { return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); } handleClick(e) { const target = e.target; if (target.id === 'addPaymentMethod') { this.showPaymentMethodModal(); return; } if (target.dataset.invoiceId) { const invoiceId = target.dataset.invoiceId; this.showInvoiceDetail(invoiceId); } } attachEventListeners() { } async showPaymentMethodModal() { if (!this.stripe) { alert('Payment processing not available'); return; } try { const response = await fetch('/api/billing/payment-methods/setup-intent', { method: 'POST', headers: {'Authorization': `Bearer ${localStorage.getItem('token')}`} }); if (!response.ok) { const error = await response.json(); alert(`Failed to initialize payment: ${error.detail}`); return; } const { client_secret } = await response.json(); const modal = document.createElement('div'); modal.className = 'modal'; modal.innerHTML = ` `; document.body.appendChild(modal); const elements = this.stripe.elements({ clientSecret: client_secret }); const paymentElement = elements.create('payment'); paymentElement.mount('#payment-element'); modal.querySelector('#submitPayment').addEventListener('click', async () => { const submitButton = modal.querySelector('#submitPayment'); submitButton.disabled = true; submitButton.textContent = 'Processing...'; const { error } = await this.stripe.confirmSetup({ elements, confirmParams: { return_url: window.location.href, }, redirect: 'if_required' }); if (error) { alert(`Payment failed: ${error.message}`); submitButton.disabled = false; submitButton.textContent = 'Add Card'; } else { alert('Payment method added successfully'); modal.remove(); await this.loadData(); this.render(); } }); modal.querySelector('#cancelPayment').addEventListener('click', () => { modal.remove(); }); } catch (error) { alert(`Error: ${error.message}`); } } async showInvoiceDetail(invoiceId) { const response = await fetch(`/api/billing/invoices/${invoiceId}`, { headers: {'Authorization': `Bearer ${localStorage.getItem('token')}`} }); const invoice = await response.json(); const modal = document.createElement('div'); modal.className = 'modal'; modal.innerHTML = ` `; document.body.appendChild(modal); } } customElements.define('billing-dashboard', BillingDashboard); export default BillingDashboard;