+ {hint} +
+ ) : null} +
+ {children}
+
+ );
+}
diff --git a/frontend/src/components/cluster-onboarding/billing-panel.tsx b/frontend/src/components/cluster-onboarding/billing-panel.tsx
new file mode 100644
index 0000000000..183cabd827
--- /dev/null
+++ b/frontend/src/components/cluster-onboarding/billing-panel.tsx
@@ -0,0 +1,546 @@
+import {
+ faArrowUpRight,
+ faBucket,
+ faHdd,
+ faInfoCircle,
+ faServer,
+ faSignalStream,
+ Icon,
+ type IconProp,
+} from "@rivet-gg/icons";
+import { endOfMonth, startOfMonth } from "date-fns";
+import { useState } from "react";
+import { PlanBadge } from "@/app/billing/billing-plan-badge";
+import { SettingsCard } from "@/app/settings-pages/settings-card";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { WithTooltip } from "@/components/ui/tooltip";
+import { cn } from "@/components/lib/utils";
+import { formatCurrency } from "@/components/lib/formatter";
+import { TwinklingSparkles } from "@/components/twinkling-sparkles";
+import { BILLING, formatVcpu, type TierId, TIERS } from "./catalog";
+import { Highlight } from "./highlight";
+import {
+ type ClusterSelection,
+ selectTier,
+ formatMemory,
+ formatUsd,
+ type Quote,
+ quote,
+} from "./model";
+
+interface ClusterBillingPanelProps {
+ selection: ClusterSelection;
+ onTierChange: (tier: TierId) => void;
+}
+
+/**
+ * Same layout as the real `BillingPanel` (current plan + current bill, a
+ * choose-a-plan dialog, and usage rows), but priced per cluster: the plan
+ * fee covers a fixed control plane, runner compute is priced per node for
+ * the chosen cloud, and storage, object storage, and egress are included up
+ * to the plan's allowance.
+ */
+export function ClusterBillingPanel({
+ selection,
+ onTierChange,
+}: ClusterBillingPanelProps) {
+ const q = quote(selection);
+ const usage = mockUsage(q);
+ const [plansOpen, setPlansOpen] = useState(false);
+
+ const periodStart = startOfMonth(new Date());
+ const periodEnd = endOfMonth(new Date());
+ const overageUsd = usage.reduce((sum, u) => sum + u.overageUsd, 0);
+
+ return (
+ + Billed monthly for this cluster. Scale actor nodes + in Settings or change plan any time; changes are + prorated. +
++ Current billing period usage vs. what the cluster + includes. +
++ Updated just now +
++ {q.tier.description} +
++ {formatCurrency(subscriptionUsd)} subscription +{" "} + {formatCurrency(overageUsd)} usage +
++ {description} +
+ ) : null} +{tier.description}
++ {description} +
+ ) : null} ++ {description} +
+ ) : null} +No clusters yet
++ Create one to get an endpoint and an admin token. +
++ {tagline} +
+