Source:
ocean/docs/adr/0020-billing-ux-and-seat-management.md| ✏️ Edit on GitHub
ADR-0020: Billing UX (shadcn + TanStack) and Team Seat Management
Date: 2025-08-13
Status
Accepted
Context
We need a clear, modern billing UI with plan selection and seat control for Team:
- Use shadcn components for consistent look-and-feel
- Use TanStack Query for data flow
- Allow Owners/Admins to adjust Team seat count (subscription item quantity)
Decision
- UI Standards
- Use a two-column layout on the billing page with shadcn
Card/Separator. - Components:
CurrentPlan,AvailablePlans,PaymentMethods,BillingHistory. - Plan selection via RadioGroup; Subscribe/Update via primary CTA.
- Seat Management (Team)
- Show seat count input when Team plan is active; call
updateSubscription({ priceId: TEAM_PRICE_ID, quantity }). - Validate min=1, show optimistic UI, and toast on success/failure.
- Feature Flags
- Hide advanced sections (history, portal controls) behind
advanced-billingor similar flags. - Keep core upgrade visible to encourage conversion.
Implementation Details
- Route:
src/routes/dashboard.billing.tsx(grid layout, hooks, toasts) - Components:
src/components/billing/*(plan cards, payments, history) - Hooks:
src/hooks/billing/*using GraphQL operations - GraphQL:
createSubscription,updateSubscription,cancelSubscription,resumeSubscription
Consequences
Positive
- Predictable, modern UX; minimal layout shift
- Simple seat management tied directly to Stripe subscription quantity
Negative
- Seat changes depend on Stripe round-trip; transient states need handling
Alternatives Considered
- Separate seat management page
- Rejected: adds friction; inline control on billing page is sufficient
- Custom tables/forms instead of shadcn/TanStack
- Rejected: inconsistent UX and more custom code
References
- ADR-0011: Design System Enhancements
- ADR-0012: GraphQL-Based Billing Architecture
- ADR-0016: Billing Resolver Alignment and UI Standards