- Sidebar: dynamic brand-accent colors, brainstorm section restyled - AI chat general: popup panel with expand/collapse, hides when contextual AI open - AI chat contextual: tabs reordered (Actions first), X close button, height fix - Settings: all tabs restyled, 6 new color presets (sage, terracotta, iron, etc.) - Global color cleanup: emerald/orange hardcoded → brand-accent dynamic - Brainstorm page: orange → brand-accent throughout - PageEntry animation component added to key pages - Floating AI button: bg-brand-accent instead of hardcoded black - i18n: all 15 locales updated with new AI/billing keys - Billing: freemium quota tracking, BYOK, stripe subscription scaffolding - Admin: integrated into new design - AGENTS.md + CLAUDE.md project rules added
10 KiB
Story 3.7: Billing & Subscription UX — Complete User Journey
Epic: 3 — The SaaS Commercial Engine Priority: High Status: ready-for-dev Depends on: 3.1 (quota tracking), 3.6 (Stripe tiers) Blocks: 4.3 (data portability export needs billing status)
Context
The backend billing infrastructure (Stripe checkout, webhook sync, quota tracking via Redis) is fully implemented. The base UI components exist:
billing-plans.tsx— Plan selection cards + Stripe embedded checkoutusage-meter.tsx— Sidebar quota bar with upgrade modal/settings/billingpage — Current plan + upgrade CTA- API routes —
/billing/status,/billing/create-checkout,/billing/portal,/billing/webhook,/usage/current
However, the user experience has significant gaps that block a production launch:
- No detailed usage breakdown — Users see a tiny progress bar in the sidebar but have no dedicated view showing per-feature consumption
- No billing history — Users cannot view past invoices or download receipts
- No inline paywall — When a quota-exceeded error occurs during an AI action, the user gets a generic error toast instead of being guided to upgrade
- No billing cycle overview — Missing period start/end, next billing date, tokens used this cycle
- Upgrade flow not triggered from error states — Quota exhaustion in AI features shows a toast but doesn't offer a path to resolution
User Stories
US-1: Detailed Usage Dashboard
As a user on any tier, I want to see a detailed breakdown of my AI feature usage for the current billing period, So that I understand exactly where my quota is being consumed.
Acceptance Criteria:
- Given I am on the Billing settings page
- When I scroll below the current plan card
- Then I see a "Usage this period" section with a per-feature breakdown:
- Feature name (i18n key)
- Progress bar: used / limit
- Numeric count (e.g., "47 / 100")
- Visual state: normal (violet), warning ≥75% (amber), exhausted 100% (rose)
- And for unlimited features (Enterprise), the bar is full and shows "Unlimited"
- And the data refreshes every 30s via the existing
useQuerywithrefetchInterval - And the section shows the period dates (e.g., "May 1 – May 31, 2026")
US-2: Billing History
As a paying user, I want to see my past invoices and download them, So that I have records for accounting.
Acceptance Criteria:
- Given I have an active or past paid subscription
- When I view the Billing settings page
- Then I see a "Billing History" section below usage
- And it lists invoices sorted by date (newest first) with:
- Date
- Amount (formatted in user currency)
- Status (Paid, Pending, Failed) with color badge
- Download PDF link (via Stripe hosted invoice URL)
- And for BASIC/free users, the section is hidden entirely
- And the "Open Billing Portal" button remains for payment method changes
US-3: Inline Paywall (Quota Exceeded → Upgrade)
As a free user who just exhausted my AI Discovery Pack, I want to see a clear upgrade prompt instead of a cryptic error, So that I can take action immediately.
Acceptance Criteria:
- Given I trigger an AI feature (e.g., auto-tag) and my quota is exhausted
- When the API returns a
QuotaExceededError - Then the UI shows an inline paywall panel (not just a toast) with:
- Clear message: "You've used all your [feature] credits this month"
- Two CTAs:
- "Upgrade to Pro" → links to
/settings/billing - "Use your own API key" → links to
/settings/ai#byok
- "Upgrade to Pro" → links to
- A "Maybe later" dismiss button
- And the paywall replaces the AI action result area (not a full-page overlay)
- And the paywall auto-dismisses if the user navigates away or after 10s
US-4: Billing Cycle Overview
As a paying user, I want to see my current billing period details, So that I know when I'll be charged next and how much I've used.
Acceptance Criteria:
- Given I have an active paid subscription
- When I view the Billing page
- Then the Current Plan card also shows:
- Billing period: "May 1 – May 31, 2026"
- Next billing date (or "Access until [date]" if canceled)
- A small ring/donut chart showing total AI credits used vs total limit (aggregate across features)
US-5: Upgrade Success Confirmation
As a user who just completed checkout, I want to see a clear confirmation that my plan is now active, So that I have confidence the payment worked.
Acceptance Criteria:
- Given I return from Stripe checkout (via
session_idURL param) - When the page loads
- Then I see a success banner at the top: "Welcome to [Plan Name]! Your subscription is now active."
- And the banner auto-dismisses after 5s
- And the usage meter in the sidebar updates immediately
- And the plan cards section is hidden (replaced with current plan + manage)
Technical Design
Architecture
All changes are frontend-only — no new API routes needed. The existing APIs provide all necessary data:
| Data Need | Existing API | Field |
|---|---|---|
| Per-feature quotas | GET /api/usage/current |
quotas[feature].{used,limit,remaining} |
| Billing status | GET /api/billing/status |
tier, status, currentPeriodEnd, cancelAtPeriodEnd |
| Invoices | Stripe Customer Portal | Via /api/billing/portal redirect |
| Checkout | POST /api/billing/create-checkout |
{clientSecret, url} |
Component Structure
settings/billing/page.tsx
├── BillingPlans (existing — current plan card + plan selection)
│ ├── CurrentPlanCard (enhanced with billing cycle dates + mini usage ring)
│ ├── PlanCard[] (existing — unchanged for BASIC)
│ └── ManageBilling (existing — portal button for paid users)
├── UsageBreakdown (NEW — per-feature usage dashboard)
│ ├── PeriodHeader ("Usage for May 1–31, 2026")
│ └── FeatureRow[] (one per feature: label + progress bar + count)
└── BillingHistory (NEW — for paid users, via Stripe portal link)
components/quota-paywall.tsx (NEW — inline paywall)
├── Message ("You've used all X credits")
├── CTAs (Upgrade link + BYOK link)
└── Dismiss button
Files to Create
| File | Description |
|---|---|
components/settings/usage-breakdown.tsx |
Detailed per-feature usage table with progress bars |
components/settings/billing-history.tsx |
Invoice list with download links (via Stripe) |
components/quota-paywall.tsx |
Inline paywall component for quota-exceeded errors |
Files to Modify
| File | Change |
|---|---|
app/(main)/settings/billing/page.tsx |
Add UsageBreakdown + BillingHistory sections |
components/settings/billing-plans.tsx |
Enhance CurrentPlanCard with billing period dates + mini usage ring |
components/usage-meter.tsx |
Add click handler to navigate to /settings/billing (not just on exhausted) |
| Various AI feature components | Catch QuotaExceededError and show QuotaPaywall instead of toast |
Design Tokens (Existing — No New Colors)
| Element | Token/Class |
|---|---|
| Section header | text-[11px] font-bold uppercase tracking-[0.15em] text-muted-foreground |
| Card container | rounded-xl border border-border/40 bg-paper p-6 space-y-4 |
| Progress bar track | h-1.5 rounded-full bg-secondary/40 |
| Progress bar fill (normal) | bg-gradient-to-r from-violet-400 to-purple-400 |
| Progress bar fill (warning ≥75%) | bg-amber-400 |
| Progress bar fill (exhausted) | bg-rose-400 |
| Feature count text | text-[11px] text-muted-ink tabular-nums |
| Badge (active) | bg-emerald-100 text-emerald-700 |
| Badge (past due) | bg-amber-100 text-amber-700 |
| CTA primary | bg-[#D4A373] text-white hover:bg-[#C49363] (highlighted) |
| CTA secondary | border border-border hover:bg-foreground/5 |
| Paywall panel | rounded-xl border border-rose-200 bg-rose-50/50 dark:border-rose-800/40 dark:bg-rose-900/10 p-4 |
i18n Keys to Add (15 locales)
{
"billing": {
"usageThisPeriod": "Usage this period",
"periodRange": "{start} – {end}",
"featureUsedLimit": "{used} / {limit}",
"unlimited": "Unlimited",
"noUsage": "No AI usage yet this period.",
"billingHistory": "Billing History",
"noInvoices": "No invoices yet.",
"viewInvoices": "View all invoices in Stripe portal",
"nextBillingDate": "Next billing date",
"billingPeriod": "Billing period",
"planSince": "Subscribed since {date}"
},
"quotaPaywall": {
"title": "Monthly limit reached",
"description": "You've used all your {feature} credits for this month.",
"upgrade": "Upgrade plan",
"useOwnKey": "Use your own API key",
"later": "Maybe later"
}
}
Out of Scope
- Stripe Customer Portal customization — uses the default hosted portal (already configured)
- Downgrade flow — handled via Stripe portal (out of scope for this story)
- Enterprise self-serve — "Contact Sales" mailto link is sufficient
- Real-time WebSocket quota updates — polling every 30s is sufficient
- Admin billing dashboard — deferred to a later epic
- Currency switching — EUR default, handled by Stripe locale
Testing Notes
- Unit:
billing-sync.test.ts,billing-price-map.test.tsalready exist and pass - Integration: Test checkout flow with Stripe test mode (
sk_test_keys) - Visual: Verify in both light and dark mode
- i18n: All 15 locale files must have the new keys
- Responsive: Plan cards grid must collapse to single column on mobile (<768px)
- Edge cases:
- User with no subscription (BASIC) should see only usage + upgrade cards
- User with canceled subscription should see "Access until [date]" + no upgrade cards
- User with past_due subscription should see warning banner
- Redis down → usage section shows "Unable to load usage" (fail-open on API, graceful on UI)
Estimated Effort
| Task | Hours |
|---|---|
usage-breakdown.tsx component |
2h |
billing-history.tsx component |
1.5h |
quota-paywall.tsx component |
1.5h |
Enhance billing-plans.tsx with cycle info |
1h |
| Wire paywall into AI feature error handlers | 2h |
| i18n keys (15 locales) | 1h |
| Visual testing + dark mode | 1h |
| Total | ~10h |