feat(admin): show upgrade state, customer tenure, and storage fill bar - #958
Conversation
- Plan card: badge for Pro (Stripe-backed vs admin-comped), free gets no badge; surfaces cancelAtPeriodEnd as a clear cancel notice - Adds paidSince to WorkspaceRecord, stamped once on the first free -> paid transition (Stripe webhook bridge and admin PATCH plan path), never overwritten on a later transition. Exposed on the admin plan response and rendered as 'Customer since <date> (N mo)' - Limits card: compact inline percent-full bar for storage (and uploads/period) with warning color at >=80% and >=100%; omitted for unlimited caps
|
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (2)
🚫 Excluded labels (none allowed) (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
uploads-api | 9fd6268 | Commit Preview URL Branch Preview URL |
Sep 11 2026, 09:06 PM |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
uploads-web | 9fd6268 | Commit Preview URL Branch Preview URL |
Sep 11 2026, 09:06 PM |
The cookie-authed admin panel plan view returns paidSince (#958) but the bearer-token GET /admin/workspaces/:name did not, so ops scripts and CI saw null for a field the panel renders. Add it next to plan.
What
Improves the operator admin panel (per workspace) so an operator can see, at a glance:
Upgraded state. A Pro workspace now gets a badge: "Pro · Stripe" for a Stripe-backed subscription, "Pro · comped" for an admin-set plan with no backing subscription. Free workspaces get no badge. The existing
planSourcesemantics (stripe/admin/none, fromworkspace-plan.ts'splanSourceFor) are unchanged — the badge is just a clearer rendering of the same signal, replacing the old plain "Source: …" text line.cancelAtPeriodEndnow renders as an explicit "cancels on<date>" notice (users cancel via the Stripe-hosted portal, so this is otherwise invisible to the operator).Customer tenure. Adds a
paidSincefield to the workspace KV record, stamped once — by the Stripe webhook bridge's plan sync (POST /internal/billing/plan) and by the admin panel'sPATCH /admin-ui/workspaces/:name/plan— the first time a workspace's plan transitions from free to paid. It is never overwritten on a later transition (a downgrade-then-re-upgrade keeps the original date). Rendered as "Customer since<date>(N mo)".Why a new field instead of deriving it from the auth worker's subscription data: I checked the
@better-auth/stripesubscriptiontable (apps/auth/src/schema.ts) and the auth worker's internal/orgs/:slug/subscriptionroute. That table has nocreated_at/first-subscribed column — onlyperiod_start/period_end/trial_start, which roll forward every renewal and can reset if a subscription is ever recreated (e.g. a lapsed-then-resubscribed customer). None of those are a stable "first paid at" answer.paidSinceon the workspace record (written throughmutateWorkspaceRecord, same as every other workspace-record mutation in this repo) is the smallest addition that gives an honest, durable answer.One existing paid workspace in production predates this change and has no
paidSincestamped. A backfill (setpaidSinceto the earliest known date for that workspace, or leave it null and let the UI simply omit the tenure line) is a follow-up — I did not run anything against production for this PR.Storage percent-full bar. The limits card's "X of Y stored" line now includes a compact inline progress bar with the percent as text — plain HTML/CSS, no framework JS (same astro-inline-script pattern as the rest of the file). Warning color at ≥80%, over-cap color at ≥100%. Omitted entirely when the cap is unlimited (
null). Did the same for uploads-per-period since it was a one-line addition on the same helper.Files changed
apps/api/src/workspace.ts—paidSince?: stringfield + doc commentapps/api/src/routes/internal-billing.ts— stampspaidSincein the Stripe-driven plan-sync routeapps/api/src/routes/admin-ui.ts— stampspaidSincein the adminPATCH .../planroute; importsgetPlanapps/api/src/workspace-plan.ts—planResponse()now includespaidSince: record.paidSince ?? nullapps/web/src/pages/admin/index.astro— plan badge, cancel notice, tenure line, storage/uploads percent barapps/web/src/styles/admin-workspaces.css— badge + bar styles (design tokens only, no new colors)apps/api/src/routes/internal-billing.test.ts,apps/api/src/routes/admin-ui.test.ts,apps/api/src/workspace-plan.test.ts— coverage for the new field and the stamp-once behaviorConstraints followed
.envedits, nothing run against production./meresponses are unaffected (this touches the admin-ui plan route and the sharedplanResponsehelper, but nothing here changes the fields/me/workspaces/:name/billingasserts on in its tests).Testing
pnpm test:api— 186 files / 2808 tests passedpnpm test(whole suite) — 378 files / 6001 tests passed, 1 file / 2 tests skipped (pre-existing)pnpm typecheck— 0 errors across all packages (pre-existing warnings/hints only, unrelated to this change)pnpm lint/pnpm format— clean; only pre-existing warnings elsewhere in the repoNo screenshot attached — reproducing the admin panel needs a signed-in prod session. Summary of the visual change: the "Plan" card heading now carries an inline pill badge when the workspace is on Pro (green for Stripe-backed, accent-colored for comped); a red "cancels on
<date>" note appears inline in the subscription line whencancelAtPeriodEndis true; a new muted line reads "Customer since<date>(N mo)" whenpaidSinceis set; and the "Limits" card's storage line now has a small rounded progress bar next to the byte count, turning orange at 80% and red at 100%+.