import { Callout } from "~/ui/Callout"; import { InlineSelect } from "~/ui/Field"; import { useState } from "react"; import { useNavigate } from "@tanstack/react-router"; import type { getBilling } from "~/fns/billing"; import { formatQuantity, meterDef } from "~/lib/meters"; import type { MeterDef } from "~/lib/meters"; import { Badge } from "~/ui/Badge"; import { EmptyState } from "~/ui/EmptyState"; import { Panel } from "~/ui/Panel"; import { RelativeTime } from "~/ui/RelativeTime"; import { TextLink } from "~/ui/TextLink"; import { Heading } from "~/ui/Heading"; import { ProgressTrack, quotaTone } from "~/ui/ProgressTrack"; import { StripePanels } from "./StripePanels"; import { useFeatureFlag } from "~/hooks/use-feature-flag"; import { UsageChart } from "./UsageChart"; import { SpendingLimit } from "./SpendingLimit"; import { RatesPanel } from "./RatesPanel"; import { InfoTip } from "./InfoTip"; import { ComputeRatesNote } from "./ComputeRatesNote"; import { AiEndpointForm } from "~/components/ai/AiEndpointForm"; type Billing = Awaited>; type Meter = Billing["summary"]["meters"][number]; const CATEGORIES = ["compute", "storage", "network", "ai", "ops"] as const; const CATEGORY_LABELS: Record = { compute: "Compute", storage: "Storage", network: "Network", ai: "AI", ops: "Operations", }; function unitOf(meter: string): MeterDef["unit"] { return meterDef(meter)?.unit ?? "count"; } function dollars(cents: number): string { return `$${(cents / 100).toFixed(2)}`; } function recentPeriods(count = 6): string[] { const now = new Date(); return Array.from({ length: count }, (_, index) => { const date = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - index, 1)); return date.toISOString().slice(0, 7); }); } function MeterRow({ meter }: { meter: Meter }) { const limit = meter.hardCap ?? meter.included; const percent = limit && limit > 0 ? Math.min(100, (meter.quantity / limit) * 100) : 0; const unit = meter.unit as MeterDef["unit"]; return (
  • {meter.label} {meter.hardCap !== null && hard cap} {formatQuantity(unit, meter.quantity)} {limit !== null && limit > 0 && / {formatQuantity(unit, limit)}} {limit === 0 && meter.hardCap === null && ยท pay as you go} {meter.creditCovered > 0 && ( {formatQuantity(unit, meter.creditCovered)} from credit )} {meter.overageCents > 0 && ( {dollars(meter.overageCents)} )}
    {limit !== null && limit > 0 && }
  • ); } export function BillingPage({ billing, org }: { billing: Billing; org: string | null }) { const { summary } = billing; const navigate = useNavigate(); const used = summary.meters.filter((meter) => meter.quantity > 0); const showRates = useFeatureFlag("billing-pricing-table", true); const [chartMeter, setChartMeter] = useState(used[0]?.meter ?? "compute.runner"); return (
    {billing.disabled && ( {billing.disabled.message} )}
    Billing and usage {summary.plan.label} void navigate({ to: ".", search: { period: value } })} options={recentPeriods().map((period) => ({ value: period, label: period }))} />

    Estimated overage this period:{" "} {dollars(summary.totalCents)} {summary.spendingLimitCents !== null && <> of a {dollars(summary.spendingLimitCents)} limit}. Self-hosted runner time is recorded but free. Running editor containers are listed on the{" "} {org ? ( containers page ) : ( containers page )} .

    {CATEGORIES.map((category) => ( {CATEGORY_LABELS[category]} ) : ( CATEGORY_LABELS[category] ) } >
      {summary.meters .filter((meter) => meter.category === category) .map((meter) => ( ))}
    ))} meter.quantity > 0 || meter.meter === chartMeter) .map((meter) => ({ value: meter.meter, label: meter.label }))} /> } > {summary.repos.length === 0 ? ( ) : ( {summary.repos.map((row) => ( ))}
    {row.name ?? row.repoId} {meterDef(row.meter)?.label ?? row.meter} {formatQuantity(unitOf(row.meter), row.quantity)}
    )}
    {showRates && } {billing.account.canEdit && ( )} {summary.alerts.length === 0 ? ( ) : (
      {summary.alerts.map((alert) => (
    • {alert.subject === "spend" ? "Spending limit" : (meterDef(alert.subject)?.label ?? alert.subject)} {alert.threshold}%
    • ))}
    )}
    ); }