feat: design system overhaul — sidebar, AI chats, settings, brainstorm, color cleanup
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
This commit is contained in:
Antigravity
2026-05-16 12:59:30 +00:00
parent 1fcea6ed7d
commit bd495be965
2284 changed files with 395285 additions and 2327 deletions

View File

@@ -0,0 +1,243 @@
# 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 131, 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** |