// Billing — URL `/dashboard/billing`. Reads the tenant's subscription live // (`billing.subscription`, contributed by @voltro/plugin-billing) and drives the // two write paths the plugin exposes: `billing.startCheckout` (upgrade to Pro → // redirect to the provider's hosted checkout; the `mock` provider returns a // dev URL) and `billing.changeSeats` (raise the seat quantity). No subscription // row yet means the tenant is on the implicit free plan. import type { ReactNode } from 'react' import type { PageMeta } from '@voltro/web' import { T, useT } from '@voltro/i18n' import { getCatalog } from '../../../locales/index' import { useAction, useSubscription } from '../../../lib/api' export const renderMode = 'ssr' as const export const meta = ({ locale }: { readonly locale: string }): PageMeta => ({ title: getCatalog(locale)['meta.billing.title'], }) export default function Billing(): ReactNode { // `billing.*` is contributed by @voltro/plugin-billing and lands in the same // generated map as the app's own procedures, so a plugin route is typed here // exactly like a hand-written one — no local Checkout/Seats input mirrors. const { data } = useSubscription('billing.subscription') const checkout = useAction('billing.startCheckout') const seats = useAction('billing.changeSeats') const sub = data?.subscription ?? null const planLabel = useT('billing.plan.free') const plan = sub?.plan ?? planLabel const quantity = sub?.quantity ?? 1 const busy = checkout.pending || seats.pending const onUpgrade = (): void => { const here = typeof window === 'undefined' ? '/dashboard/billing' : window.location.href void checkout.run({ plan: 'pro', successUrl: here, cancelUrl: here }) .then((r) => { if (r && typeof window !== 'undefined') window.location.assign(r.url) }) .catch(() => { /* surfaced via checkout.error */ }) } const onAddSeat = (): void => { void seats.run({ quantity: quantity + 1 }).catch(() => { /* surfaced via seats.error */ }) } return (

{plan}
{sub?.status ?? '—'}
{quantity}

) }