{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "billing-usage-center",
  "title": "Billing Usage Center",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/billing-usage-center/billing-usage-center.component.ts",
      "content": "// Stacked billing center: a current-plan card with seat meter, monthly usage rows whose\n// bars shift success → warning → destructive as they approach the limit (with overage\n// notes), and an invoice history table with status pills, an unpaid-balance alert, and an\n// empty state. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport { CreditCard, Download, FileText, LucideAngularModule, TriangleAlert } from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiAlertDescriptionComponent, UiAlertTitleComponent, UiAlertComponent } from '@/ui/alert/alert.component'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { UiProgressComponent } from '@/ui/progress/progress.component'\nimport { UiSectionCardComponent } from '@/ui/section-card/section-card.component'\nimport {\n  UiTableBodyComponent,\n  UiTableCellComponent,\n  UiTableComponent,\n  UiTableHeadComponent,\n  UiTableHeaderComponent,\n  UiTableRowComponent,\n} from '@/ui/table/table.component'\n\nexport interface 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\nexport interface 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\nexport type InvoiceStatus = 'paid' | 'open' | 'past-due'\n\nexport interface Invoice {\n  id: string\n  number: string\n  period: string\n  amount: string\n  status: InvoiceStatus\n}\n\nconst defaultPlan: PlanInfo = {\n  name: 'Pro',\n  price: '$49',\n  cadence: 'month',\n  renewalDate: 'September 12, 2026',\n  seatsUsed: 18,\n  seatsTotal: 20,\n}\n\nconst defaultUsage: UsageMetric[] = [\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\nconst defaultInvoices: Invoice[] = [\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// Inlined record (not an indexed VariantProps access): keeps the 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\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-billing-usage-center, [ui-billing-usage-center]',\n  standalone: true,\n  // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiAlertComponent,\n    UiAlertDescriptionComponent,\n    UiAlertTitleComponent,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiProgressComponent,\n    UiSectionCardComponent,\n    UiTableBodyComponent,\n    UiTableCellComponent,\n    UiTableComponent,\n    UiTableHeadComponent,\n    UiTableHeaderComponent,\n    UiTableRowComponent,\n  ],\n  template: `\n    <div data-slot=\"billing-usage-center\" [class]=\"rootClass\">\n      <div ui-section-card 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\"\n              >{{ plan().seatsUsed }} of {{ plan().seatsTotal }}</span\n            >\n          </div>\n          <ui-progress [value]=\"seatsPercent()\" />\n          <p class=\"text-muted-foreground text-xs\">{{ seatsRemaining() }} seats remaining on this plan.</p>\n        </div>\n\n        <div class=\"mt-6 flex flex-wrap justify-end gap-2\">\n          <button ui-button variant=\"outline\" size=\"sm\">\n            <lucide-icon [img]=\"CreditCard\" />\n            Manage payment\n          </button>\n          <button ui-button size=\"sm\">Change plan</button>\n        </div>\n      </div>\n\n      <div ui-section-card title=\"Usage this month\" description=\"Metered against your plan allowances.\">\n        <div class=\"space-y-5\">\n          @for (metric of usage(); track metric.id) {\n            <div 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              <ui-progress [value]=\"Math.min(metric.percent, 100)\" [class]=\"usageBarClass(metric.percent)\" />\n              @if (metric.percent > 100) {\n                <p class=\"text-destructive text-xs\">\n                  {{\n                    metric.overageNote ??\n                      'Over the ' + metric.includedText + ' included in your plan — overage charges apply.'\n                  }}\n                </p>\n              }\n            </div>\n          }\n        </div>\n      </div>\n\n      <div\n        ui-section-card\n        title=\"Invoice history\"\n        [description]=\"invoices().length + ' invoice' + (invoices().length === 1 ? '' : 's')\"\n      >\n        @if (invoices().length > 0) {\n          <div class=\"space-y-4\">\n            @if (unpaidInvoices().length > 0) {\n              <div ui-alert class=\"border-warning/40 bg-warning/5\">\n                <lucide-icon [img]=\"TriangleAlert\" class=\"text-warning size-4\" />\n                <h5 ui-alert-title>Past-due balance</h5>\n                <p ui-alert-description>{{ unpaidNote() }}</p>\n              </div>\n            }\n\n            <div class=\"overflow-x-auto\">\n              <table ui-table>\n                <thead ui-table-header>\n                  <tr ui-table-row>\n                    <th ui-table-head>Invoice</th>\n                    <th ui-table-head>Period</th>\n                    <th ui-table-head class=\"text-right\">Amount</th>\n                    <th ui-table-head>Status</th>\n                    <th ui-table-head class=\"w-12\">\n                      <span class=\"sr-only\">Download</span>\n                    </th>\n                  </tr>\n                </thead>\n                <tbody ui-table-body>\n                  @for (invoice of invoices(); track invoice.id) {\n                    <tr ui-table-row>\n                      <td ui-table-cell class=\"font-medium\">{{ invoice.number }}</td>\n                      <td ui-table-cell class=\"text-muted-foreground\">{{ invoice.period }}</td>\n                      <td ui-table-cell class=\"text-right tabular-nums\">{{ invoice.amount }}</td>\n                      <td ui-table-cell>\n                        <span ui-badge [variant]=\"invoiceVariant(invoice.status)\">{{\n                          invoiceLabel(invoice.status)\n                        }}</span>\n                      </td>\n                      <td ui-table-cell class=\"text-right\">\n                        <button\n                          ui-button\n                          variant=\"ghost\"\n                          size=\"icon-sm\"\n                          [attr.aria-label]=\"'Download invoice ' + invoice.number\"\n                        >\n                          <lucide-icon [img]=\"Download\" />\n                        </button>\n                      </td>\n                    </tr>\n                  }\n                </tbody>\n              </table>\n            </div>\n          </div>\n        } @else {\n          <div 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              <lucide-icon [img]=\"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        }\n      </div>\n    </div>\n  `,\n})\nexport class UiBillingUsageCenterComponent {\n  protected readonly CreditCard = CreditCard\n  protected readonly Download = Download\n  protected readonly FileText = FileText\n  protected readonly Math = Math\n  protected readonly TriangleAlert = TriangleAlert\n\n  readonly plan = signal<PlanInfo>({ ...defaultPlan })\n  readonly usage = signal<UsageMetric[]>(defaultUsage.map((m) => ({ ...m })))\n  readonly invoices = signal<Invoice[]>(defaultInvoices.map((i) => ({ ...i })))\n\n  @Input('plan') set planValue(v: PlanInfo | undefined) {\n    if (v) this.plan.set({ ...v })\n  }\n  @Input('usage') set usageValue(v: UsageMetric[] | undefined) {\n    if (v) this.usage.set(v.map((m) => ({ ...m })))\n  }\n  @Input('invoices') set invoicesValue(v: Invoice[] | undefined) {\n    if (v) this.invoices.set(v.map((i) => ({ ...i })))\n  }\n  @Input('class') className?: string\n\n  readonly seatsPercent = computed(() => {\n    const p = this.plan()\n    return p.seatsTotal > 0 ? (p.seatsUsed / p.seatsTotal) * 100 : 0\n  })\n\n  readonly seatsRemaining = computed(() => Math.max(this.plan().seatsTotal - this.plan().seatsUsed, 0))\n  readonly unpaidInvoices = computed(() => this.invoices().filter((invoice) => invoice.status !== 'paid'))\n  readonly pastDueCount = computed(() => this.invoices().filter((invoice) => invoice.status === 'past-due').length)\n\n  readonly unpaidNote = computed(() => {\n    const count = this.unpaidInvoices().length\n    const past = this.pastDueCount()\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  get rootClass(): string {\n    return cn('mx-auto w-full max-w-3xl space-y-4', this.className)\n  }\n\n  invoiceLabel(status: InvoiceStatus): string {\n    return statusMeta[status].label\n  }\n\n  invoiceVariant(status: InvoiceStatus): 'success' | 'default' | 'destructive' {\n    return statusMeta[status].variant\n  }\n\n  // success below 80%, warning as usage approaches the limit, destructive at/over it.\n  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}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/billing-usage-center/billing-usage-center.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/billing-usage-center/index.ts",
      "content": "export {\n  UiBillingUsageCenterComponent,\n  type Invoice,\n  type InvoiceStatus,\n  type PlanInfo,\n  type UsageMetric,\n} from './billing-usage-center.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/billing-usage-center/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/alert.json",
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/progress.json",
    "https://uipkge.dev/r/angular/section-card.json",
    "https://uipkge.dev/r/angular/table.json"
  ],
  "description": "Stacked billing center: a current-plan card with seat meter, monthly usage rows whose bars shift success to warning to 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.",
  "categories": [
    "finance",
    "dashboard"
  ]
}