All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s
- 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
244 lines
10 KiB
Markdown
244 lines
10 KiB
Markdown
# 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 checkout
|
||
- `usage-meter.tsx` — Sidebar quota bar with upgrade modal
|
||
- `/settings/billing` page — 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:
|
||
|
||
1. **No detailed usage breakdown** — Users see a tiny progress bar in the sidebar but have no dedicated view showing per-feature consumption
|
||
2. **No billing history** — Users cannot view past invoices or download receipts
|
||
3. **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
|
||
4. **No billing cycle overview** — Missing period start/end, next billing date, tokens used this cycle
|
||
5. **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 `useQuery` with `refetchInterval`
|
||
- **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:
|
||
1. "Upgrade to Pro" → links to `/settings/billing`
|
||
2. "Use your own API key" → links to `/settings/ai#byok`
|
||
- 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_id` URL 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)
|
||
|
||
```json
|
||
{
|
||
"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.ts` already 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** |
|