import { useEffect, useState } from "react" import { Plus, X } from "lucide-react" import { useArcadiaClient } from "@crema/arcadia-core-client" import { useToast } from "@crema/notification-ui" import { TenantSection, Field } from "~/components/tenant-detail/section" import { DataState } from "~/components/data-state" import { Button } from "~/components/ui/button" import { Input } from "~/components/ui/input" import { NativeSelect, NativeSelectOption } from "~/components/ui/native-select" import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card" import { listUsage, updatePlan, TENANT_PLANS, type UsageRow, } from "~/lib/arcadia/tenants" import type { TenantTabProps } from "~/routes/tenants.$id" // One editable limit row. `locked` marks a key that already existed on the // tenant (its name is fixed — you edit the value or remove the row); new rows // added with "Add limit" have an editable key. type LimitRow = { id: number; key: string; value: string; locked: boolean } let rowSeq = 0 const nextRowId = () => ++rowSeq function rowsFromLimits(limits: Record | undefined): LimitRow[] { return Object.entries(limits ?? {}).map(([key, value]) => ({ id: nextRowId(), key, value: String(value ?? ""), locked: true, })) } // Collapse the editable rows into the Record the server wants. // Blank keys are dropped; non-numeric values coerce to 0 (Number("") === 0 too). function buildLimits(rows: LimitRow[]): Record { const out: Record = {} for (const row of rows) { const key = row.key.trim() if (!key) continue out[key] = Number(row.value) } return out } function normaliseLimits(limits: Record | undefined): Record { const out: Record = {} for (const [key, value] of Object.entries(limits ?? {})) out[key] = Number(value) return out } export function PlanTab({ tenant, reload }: TenantTabProps) { const arcadia = useArcadiaClient() const toast = useToast() const currentPlan = tenant.plan?.name ?? "" const [plan, setPlan] = useState(currentPlan || TENANT_PLANS[0]) const [rows, setRows] = useState(() => rowsFromLimits(tenant.plan?.limits)) const [saving, setSaving] = useState(false) const [error, setError] = useState(null) const originalLimits = normaliseLimits(tenant.plan?.limits) const dirty = plan !== currentPlan || JSON.stringify(buildLimits(rows)) !== JSON.stringify(originalLimits) const setRowKey = (id: number, key: string) => setRows((rs) => rs.map((r) => (r.id === id ? { ...r, key } : r))) const setRowValue = (id: number, value: string) => setRows((rs) => rs.map((r) => (r.id === id ? { ...r, value } : r))) const removeRow = (id: number) => setRows((rs) => rs.filter((r) => r.id !== id)) const addRow = () => setRows((rs) => [...rs, { id: nextRowId(), key: "", value: "", locked: false }]) const save = async () => { setSaving(true) setError(null) try { await updatePlan(arcadia, tenant.id, { plan, plan_limits: buildLimits(rows), }) await reload() toast.success("Plan updated") } catch (err) { setError(err) } finally { setSaving(false) } } return (
setPlan(e.target.value)} data-action="tenant-detail-plan-select" > {TENANT_PLANS.map((p) => ( {p} ))}
{rows.length === 0 ? (

No limits set.

) : ( rows.map((row) => (
setRowKey(row.id, e.target.value)} aria-label="Limit key" data-action="tenant-detail-plan-limit-key" /> setRowValue(row.id, e.target.value)} aria-label="Limit value" data-action="tenant-detail-plan-limit-value" />
)) )}
) } // Read-only usage-vs-quota panel. Loads independently so a metering failure // never blanks the plan form above it. function PlanUsage({ tenantId }: { tenantId: string }) { const arcadia = useArcadiaClient() const [rows, setRows] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const load = async () => { setError(null) setLoading(true) try { setRows(await listUsage(arcadia, tenantId)) } catch (err) { setError(err) } finally { setLoading(false) } } useEffect(() => { load() // eslint-disable-next-line react-hooks/exhaustive-deps }, [tenantId]) return ( Usage No metered usage yet. } >
    {rows.map((row) => (
  • {row.category}
  • ))}
) } function UsageMeter({ label, used, limit, }: { label: string used: number limit: number | null }) { return (
{label}
{used} {limit != null ? `/ ${limit}` : "/ no limit"}
) }