import type { getBilling } from "~/fns/billing"; import { formatQuantity, METERS } from "~/lib/meters"; import type { MeterDef } from "~/lib/meters"; import { PRICING_BASIS, rateLabel } from "~/lib/pricing-display"; import { Panel } from "~/ui/Panel"; import { ComputeRatesNote } from "./ComputeRatesNote"; import { InfoTip } from "./InfoTip"; type Billing = Awaited>; function allowance(unit: MeterDef["unit"], value: number | undefined): string { if (value === undefined) return "unlimited"; if (value === 0) return "none"; return formatQuantity(unit, value); } /** Every priced meter with what each plan includes and the rate past it, all derived from src/lib/pricing.ts. */ export function RatesPanel({ billing }: { billing: Billing }) { const { free, pro } = billing.plans; const priced = METERS.filter((meter) => rateLabel(meter.id) !== null); return ( Pricing Cloudflare's cost x {PRICING_BASIS.markup}, metered exactly } >
{priced.map((meter) => ( ))}
Usage {free.label} includes {pro.label} includes (${(pro.priceCents / 100).toFixed(0)}/month) Then
{meter.label} {meter.id === "compute.runner" && ( )}
{allowance(meter.unit, free.included[meter.id])} {allowance(meter.unit, pro.included[meter.id])} {rateLabel(meter.id)}

The free plan stops at its allowances instead of billing. Bandwidth, git operations, self-hosted runners and calls with your own AI key are free.

); }