Skip to content

feat(admin): show upgrade state, customer tenure, and storage fill bar - #958

Merged
Zach Dunn (zachdunn) merged 1 commit into
mainfrom
claude/paid-user-db-check-5b9ffe
Sep 11, 2026
Merged

feat(admin): show upgrade state, customer tenure, and storage fill bar#958
Zach Dunn (zachdunn) merged 1 commit into
mainfrom
claude/paid-user-db-check-5b9ffe

Conversation

@zachdunn

Copy link
Copy Markdown
Member

What

Improves the operator admin panel (per workspace) so an operator can see, at a glance:

  1. 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 planSource semantics (stripe/admin/none, from workspace-plan.ts's planSourceFor) are unchanged — the badge is just a clearer rendering of the same signal, replacing the old plain "Source: …" text line. cancelAtPeriodEnd now renders as an explicit "cancels on <date>" notice (users cancel via the Stripe-hosted portal, so this is otherwise invisible to the operator).

  2. Customer tenure. Adds a paidSince field to the workspace KV record, stamped once — by the Stripe webhook bridge's plan sync (POST /internal/billing/plan) and by the admin panel's PATCH /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/stripe subscription table (apps/auth/src/schema.ts) and the auth worker's internal /orgs/:slug/subscription route. That table has no created_at/first-subscribed column — only period_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. paidSince on the workspace record (written through mutateWorkspaceRecord, 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 paidSince stamped. A backfill (set paidSince to 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.

  3. 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.tspaidSince?: string field + doc comment
  • apps/api/src/routes/internal-billing.ts — stamps paidSince in the Stripe-driven plan-sync route
  • apps/api/src/routes/admin-ui.ts — stamps paidSince in the admin PATCH .../plan route; imports getPlan
  • apps/api/src/workspace-plan.tsplanResponse() now includes paidSince: record.paidSince ?? null
  • apps/web/src/pages/admin/index.astro — plan badge, cancel notice, tenure line, storage/uploads percent bar
  • apps/web/src/styles/admin-workspaces.css — badge + bar styles (design tokens only, no new colors)
  • Tests: 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 behavior

Constraints followed

  • No .env edits, nothing run against production.
  • Additive API changes only — /me responses are unaffected (this touches the admin-ui plan route and the shared planResponse helper, but nothing here changes the fields /me/workspaces/:name/billing asserts on in its tests).
  • All fixtures use fictional data ("acme" workspace); no real customer name, email, workspace slug, Stripe id, or revenue/count figures appear anywhere in this diff.

Testing

  • pnpm test:api — 186 files / 2808 tests passed
  • pnpm 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 repo

No 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 when cancelAtPeriodEnd is true; a new muted line reads "Customer since <date> (N mo)" when paidSince is 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%+.

- 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
@changeset-bot

changeset-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 9fd6268

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (2)
  • coderabbit:review
  • review
🚫 Excluded labels (none allowed) (1)
  • wip

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 99619a8a-5f2c-4ec6-b100-650c7bc187e2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@zachdunn
Zach Dunn (zachdunn) merged commit e868c68 into main Sep 11, 2026
5 checks passed
@zachdunn
Zach Dunn (zachdunn) deleted the claude/paid-user-db-check-5b9ffe branch September 11, 2026 21:14
Zach Dunn (zachdunn) added a commit that referenced this pull request Sep 11, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant