{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "billing-usage-center",
  "title": "Billing Usage Center",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/billing-usage-center/BillingUsageCenter.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { computed } from 'vue'\nimport { CreditCard, Download, FileText, TriangleAlert } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Progress } from '@/components/ui/progress'\nimport { SectionCard } from '@/components/ui/section-card'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'\n\ninterface PlanInfo {\n  name: string\n  price: string\n  /** Billing cadence shown after the price, e.g. 'month' or 'year'. */\n  cadence: string\n  renewalDate: string\n  seatsUsed: number\n  seatsTotal: number\n}\n\ninterface UsageMetric {\n  id: string\n  label: string\n  usedText: string\n  includedText: string\n  /** Percent of the included allowance. Above 100 means overage. */\n  percent: number\n  /** Shown when percent exceeds 100; falls back to a generic overage note. */\n  overageNote?: string\n}\n\ntype InvoiceStatus = 'paid' | 'open' | 'past-due'\n\ninterface Invoice {\n  id: string\n  number: string\n  period: string\n  amount: string\n  status: InvoiceStatus\n}\n\nconst props = withDefaults(\n  defineProps<{\n    plan?: PlanInfo\n    usage?: UsageMetric[]\n    invoices?: Invoice[]\n    class?: HTMLAttributes['class']\n  }>(),\n  {\n    plan: () => ({\n      name: 'Pro',\n      price: '$49',\n      cadence: 'month',\n      renewalDate: 'September 12, 2026',\n      seatsUsed: 18,\n      seatsTotal: 20,\n    }),\n    usage: () => [\n      { id: 'storage', label: 'Storage', usedText: '8.2 GB', includedText: '10 GB', percent: 82 },\n      { id: 'bandwidth', label: 'Bandwidth', usedText: '412 GB', includedText: '1 TB', percent: 41 },\n      { id: 'api-calls', label: 'API calls', usedText: '640K', includedText: '1M', percent: 64 },\n    ],\n    invoices: () => [\n      { id: 'inv-003', number: 'INV-2026-003', period: 'Jul 1 – Jul 31, 2026', amount: '$49.00', status: 'paid' },\n      { id: 'inv-002', number: 'INV-2026-002', period: 'Jun 1 – Jun 30, 2026', amount: '$49.00', status: 'paid' },\n      { id: 'inv-001', number: 'INV-2026-001', period: 'May 1 – May 31, 2026', amount: '$49.00', status: 'paid' },\n    ],\n  },\n)\n\n// Inlined record (not an indexed VariantProps access): keeps the SFC compiler happy.\nconst statusMeta: Record<InvoiceStatus, { label: string; variant: 'success' | 'default' | 'destructive' }> = {\n  paid: { label: 'Paid', variant: 'success' },\n  open: { label: 'Open', variant: 'default' },\n  'past-due': { label: 'Past due', variant: 'destructive' },\n}\n\nconst seatsPercent = computed(() =>\n  props.plan.seatsTotal > 0 ? (props.plan.seatsUsed / props.plan.seatsTotal) * 100 : 0,\n)\n\nconst unpaidInvoices = computed(() => props.invoices.filter((invoice) => invoice.status !== 'paid'))\nconst pastDueCount = computed(() => props.invoices.filter((invoice) => invoice.status === 'past-due').length)\n\nconst unpaidNote = computed(() => {\n  const count = unpaidInvoices.value.length\n  const past = pastDueCount.value\n  const plural = count === 1 ? '' : 's'\n  const pastPart = past > 0 ? `, ${past} of them past due` : ''\n  return `You have ${count} unpaid invoice${plural}${pastPart}. Update your payment method to avoid service interruption.`\n})\n\n// success below 80%, warning as usage approaches the limit, destructive at/over it.\nfunction usageBarClass(percent: number): string {\n  if (percent >= 100) return '[&_[data-slot=progress-indicator]]:bg-destructive'\n  if (percent >= 80) return '[&_[data-slot=progress-indicator]]:bg-warning'\n  return '[&_[data-slot=progress-indicator]]:bg-success'\n}\n</script>\n\n<template>\n  <div data-slot=\"billing-usage-center\" :class=\"cn('mx-auto w-full max-w-3xl space-y-4', props.class)\">\n    <SectionCard title=\"Current plan\" :description=\"`Renews on ${plan.renewalDate}`\">\n      <div class=\"flex items-start justify-between gap-4\">\n        <div>\n          <p class=\"text-2xl font-bold tracking-tight\">{{ plan.name }}</p>\n          <p class=\"text-muted-foreground mt-1 text-sm\">\n            {{ plan.price }}\n            <span class=\"text-xs\">/ {{ plan.cadence }}</span>\n          </p>\n        </div>\n      </div>\n\n      <div class=\"mt-6 space-y-1.5\">\n        <div class=\"flex flex-wrap items-baseline justify-between gap-4 text-sm\">\n          <span class=\"font-medium\">Seats used</span>\n          <span class=\"text-muted-foreground text-xs tabular-nums\">{{ plan.seatsUsed }} of {{ plan.seatsTotal }}</span>\n        </div>\n        <Progress :model-value=\"seatsPercent\" />\n        <p class=\"text-muted-foreground text-xs\">\n          {{ Math.max(plan.seatsTotal - plan.seatsUsed, 0) }} seats remaining on this plan.\n        </p>\n      </div>\n\n      <div class=\"mt-6 flex flex-wrap justify-end gap-2\">\n        <Button variant=\"outline\" size=\"sm\">\n          <CreditCard aria-hidden=\"true\" />\n          Manage payment\n        </Button>\n        <Button size=\"sm\">Change plan</Button>\n      </div>\n    </SectionCard>\n\n    <SectionCard title=\"Usage this month\" description=\"Metered against your plan allowances.\">\n      <div class=\"space-y-5\">\n        <div v-for=\"metric in usage\" :key=\"metric.id\" class=\"space-y-1.5\">\n          <div class=\"flex flex-wrap items-baseline justify-between gap-4 text-sm\">\n            <span class=\"font-medium\">{{ metric.label }}</span>\n            <span class=\"text-muted-foreground text-xs tabular-nums\">\n              {{ metric.usedText }} of {{ metric.includedText }}\n            </span>\n          </div>\n          <Progress :model-value=\"Math.min(metric.percent, 100)\" :class=\"usageBarClass(metric.percent)\" />\n          <p v-if=\"metric.percent > 100\" class=\"text-destructive text-xs\">\n            {{ metric.overageNote ?? `Over the ${metric.includedText} included in your plan — overage charges apply.` }}\n          </p>\n        </div>\n      </div>\n    </SectionCard>\n\n    <SectionCard title=\"Invoice history\" :description=\"`${invoices.length} invoice${invoices.length === 1 ? '' : 's'}`\">\n      <div v-if=\"invoices.length > 0\" class=\"space-y-4\">\n        <Alert v-if=\"unpaidInvoices.length > 0\" class=\"border-warning/40 bg-warning/5\">\n          <TriangleAlert class=\"text-warning size-4\" aria-hidden=\"true\" />\n          <AlertTitle>Past-due balance</AlertTitle>\n          <AlertDescription>{{ unpaidNote }}</AlertDescription>\n        </Alert>\n\n        <div class=\"overflow-x-auto\">\n          <Table>\n            <TableHeader>\n              <TableRow>\n                <TableHead>Invoice</TableHead>\n                <TableHead>Period</TableHead>\n                <TableHead class=\"text-right\">Amount</TableHead>\n                <TableHead>Status</TableHead>\n                <TableHead class=\"w-12\">\n                  <span class=\"sr-only\">Download</span>\n                </TableHead>\n              </TableRow>\n            </TableHeader>\n            <TableBody>\n              <TableRow v-for=\"invoice in invoices\" :key=\"invoice.id\">\n                <TableCell class=\"font-medium\">{{ invoice.number }}</TableCell>\n                <TableCell class=\"text-muted-foreground\">{{ invoice.period }}</TableCell>\n                <TableCell class=\"text-right tabular-nums\">{{ invoice.amount }}</TableCell>\n                <TableCell>\n                  <Badge :variant=\"statusMeta[invoice.status].variant\">{{ statusMeta[invoice.status].label }}</Badge>\n                </TableCell>\n                <TableCell class=\"text-right\">\n                  <Button variant=\"ghost\" size=\"icon-sm\" :aria-label=\"`Download invoice ${invoice.number}`\">\n                    <Download aria-hidden=\"true\" />\n                  </Button>\n                </TableCell>\n              </TableRow>\n            </TableBody>\n          </Table>\n        </div>\n      </div>\n\n      <div v-else class=\"flex flex-col items-center justify-center px-6 py-10 text-center\">\n        <div class=\"bg-muted mb-3 rounded-full p-3\">\n          <FileText class=\"text-muted-foreground size-5\" />\n        </div>\n        <p class=\"text-sm font-medium\">No invoices yet</p>\n        <p class=\"text-muted-foreground mt-0.5 text-xs\">Invoices appear here after your first billing cycle.</p>\n      </div>\n    </SectionCard>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/BillingUsageCenter.vue"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/alert.json",
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/progress.json",
    "https://uipkge.dev/r/vue/section-card.json",
    "https://uipkge.dev/r/vue/table.json"
  ],
  "description": "Stacked billing center: a current-plan card with seat meter, monthly usage rows whose bars shift success → warning → destructive as they approach the limit (with overage notes), and an invoice history table with status pills, an unpaid-balance alert, and an empty state. Plan, usage, and invoices are all overridable.",
  "categories": [
    "finance",
    "dashboard"
  ]
}