import { createEffect, createMemo, createResource, createSignal, For, Show } from "solid-js" import { useSearchParams } from "@solidjs/router" import PageContainer from "@/components/PageContainer" import LoadingState from "@/components/LoadingState" import PaymentDialog from "@/components/PaymentDialog" import useMinLoading from "@/components/useMinLoading" import { updateActiveTenant, useTenant } from "@/lib/hooks" import { createPortalSession, getInvoice, listTenantInvoices, type Invoice } from "@/lib/api" import { account } from "@/lib/state" export default function Account() { const [tenant, { refetch: refetchTenant }] = useTenant() const [invoices, { refetch: refetchInvoices }] = createResource(() => listTenantInvoices(account()!.pubkey)) const [nwcUrl, setNwcUrl] = createSignal("") const [saving, setSaving] = createSignal(false) const [error, setError] = createSignal("") const [selectedInvoice, setSelectedInvoice] = createSignal() const [portalLoading, setPortalLoading] = createSignal(false) const invoicesLoading = useMinLoading(() => invoices.loading) // Deep link: /account?invoice= (e.g. from the billing DM) fetches that // invoice and opens the payment dialog. The fetched invoice takes precedence // over a row the user clicked in the list. const [searchParams, setSearchParams] = useSearchParams() const [deepLinkedInvoice] = createResource( () => searchParams.invoice as string | undefined, (id) => getInvoice(id), ) createEffect(() => { if (deepLinkedInvoice.error) setError("Couldn't load that invoice. It may no longer be available.") }) const activeInvoice = () => selectedInvoice() ?? deepLinkedInvoice() // The backend never returns the stored nwc_url (it's private), so the input is // write-only: we can only act on a newly entered URL, not prefill the saved one. const hasBillingChanges = createMemo(() => nwcUrl().trim().length > 0) async function saveBilling() { setError("") setSaving(true) try { const next = nwcUrl().trim() await updateActiveTenant({ nwc_url: next }) setNwcUrl("") await refetchTenant() } catch (e) { setError(e instanceof Error ? e.message : "Failed to update billing") } finally { setSaving(false) } } function handleInvoiceDialogClose() { setSelectedInvoice(undefined) // Clearing the query param drops the deep-linked invoice and closes the dialog. if (searchParams.invoice) setSearchParams({ invoice: undefined }) void refetchInvoices() } async function openPortal() { setPortalLoading(true) try { const { url } = await createPortalSession(account()!.pubkey, window.location.href) window.location.href = url } catch (e) { setError(e instanceof Error ? e.message : "Failed to open billing portal") } finally { setPortalLoading(false) } } function logout() { localStorage.clear() window.location.href = "/" } const invoiceStatusStyles: Record = { draft: "bg-gray-100 text-gray-500 border-gray-200", open: "bg-yellow-50 text-yellow-700 border-yellow-200", paid: "bg-green-50 text-green-700 border-green-200", void: "bg-gray-100 text-gray-500 border-gray-200", uncollectible: "bg-red-50 text-red-700 border-red-200", } return (

My Account

Account Status

tenant

Recurring Billing

Enable automatic payments by providing your Nostr Wallet Connect URL.

A wallet is connected. Enter a new URL to replace it.

setNwcUrl(e.currentTarget.value)} placeholder="nostr+walletconnect://..." class="flex-1 border border-gray-300 rounded-lg px-3 py-2" />

Your account is past due and some relays have been paused. Update your payment method below to restore service.

Lightning auto-payment failed: {tenant()!.nwc_error}

Card auto-payment failed: {tenant()!.stripe_error}

{error()}

Invoice History

0} fallback={

No invoices yet.

}>
    {(invoice) => { const isOpen = () => invoice.status === "open" const statusStyle = () => invoiceStatusStyles[invoice.status] ?? "bg-gray-100 text-gray-500 border-gray-200" const periodLabel = () => { const start = new Date(invoice.period_start * 1000) const end = new Date(invoice.period_end * 1000) return `${start.toLocaleDateString()} – ${end.toLocaleDateString()}` } return (
  • isOpen() && setSelectedInvoice(invoice)} title={isOpen() ? "Click to pay this invoice" : undefined} >
    ${(invoice.amount_due / 100).toFixed(2)}

    {periodLabel()}

    Pay now {invoice.status}
  • ) }}
{(invoice) => ( )}
) }