87 lines · 3.9 KB
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<ReturnType<typeof getBilling>>;

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 (
      <p className="p-4 text-sm text-kumo-subtle">
        The {summary.plan.label} plan never bills overage: usage stops at the plan's limits instead.
      </p>
    );
  }
  return (
    <div className="space-y-4 p-4">
      <div className="space-y-1.5 text-sm">
        <div className="flex items-baseline justify-between gap-3">
          <span className="text-kumo-default">
            {limit === null ? "No limit set" : limit === 0 ? "Overage is blocked" : `${dollars(limit)} per month`}
          </span>
          <span className="font-mono text-kumo-subtle">{dollars(summary.totalCents)} used this period</span>