import { useState } from "react"; import type { getBilling } from "~/fns/billing"; import { updateSpendingLimit } from "~/fns/billing"; import { useAction } from "~/hooks/use-action"; import { Button } from "~/ui/Button"; import { ErrorBanner } from "~/ui/Callout"; import { ProgressTrack, quotaTone } from "~/ui/ProgressTrack"; import { MAX_SPENDING_LIMIT_CENTS } from "~/lib/plans"; type Billing = Awaited>; function dollars(cents: number): string { return `$${(cents / 100).toFixed(2)}`; } /** The monthly cap on overage: current status, a progress bar, and one aligned input row to change it. */ export function SpendingLimit({ billing, org }: { billing: Billing; org: string | null }) { const { summary, account } = billing; const limit = summary.spendingLimitCents; const [value, setValue] = useState(limit === null ? "" : String(limit / 100)); const save = useAction(updateSpendingLimit); const parsed = value.trim() === "" ? null : Math.round(Number(value) * 100); const invalid = parsed !== null && (!Number.isFinite(parsed) || parsed < 0 || parsed > MAX_SPENDING_LIMIT_CENTS); const unchanged = parsed === limit; const percent = limit && limit > 0 ? Math.min(100, (summary.totalCents / limit) * 100) : 0; if (!summary.plan.billsOverage) { return (

The {summary.plan.label} plan never bills overage: usage stops at the plan's limits instead.

); } return (
{limit === null ? "No limit set" : limit === 0 ? "Overage is blocked" : `${dollars(limit)} per month`} {dollars(summary.totalCents)} used this period
{limit !== null && limit > 0 && }
{account.canEdit ? (
{ event.preventDefault(); if (!invalid) void save.run({ data: { org, cents: parsed } }); }} >
$ setValue(event.target.value)} className="h-9 w-full rounded-md border border-kumo-hairline bg-kumo-canvas pl-6 pr-2 font-mono text-sm text-kumo-strong focus:border-accent-500 focus:outline-none aria-[invalid=true]:border-danger-500" />

{invalid ? `Enter an amount from $0 to ${dollars(MAX_SPENDING_LIMIT_CENTS)}.` : `Raise or lower it any time, up to ${dollars(MAX_SPENDING_LIMIT_CENTS)}. Leave empty for the plan's default limit, or enter 0 to never pay overage. Alerts go out at 50%, 80% and 100%.`}

) : (

Only account owners can change the spending limit.

)}
); }