Files
Momento/docs/story-3.8-admin-console.md
Antigravity 96e7902f01
Some checks failed
CI / Lint, Unit Tests & Build (push) Failing after 1m22s
CI / Deploy production (on server) (push) Has been skipped
feat: publication IA (magazine/brief/essay) + fixes critique
Publication IA:
- 4 templates (magazine, brief, essay, simple) avec CSS riche
- Rewrite IA (article/exercises/tutorial/reference/mixed)
- Modération avec timeout 12s + fallback safe
- Quotas publish_enhance par tier (basic=2, pro=15, business=100)
- Détection contenu stale (hash)
- Migration DB publishedContent/publishedTemplate/publishedSourceHash

Fixes:
- cheerio v1.2: Element -> AnyNode (domhandler), decodeEntities cast
- _isShared ajouté au type Note (champ virtuel serveur)
- callout colors PDF export: extraction fonction pure testable
- admin/published: guard note.userId null
- Cmd+S fonctionne en mode dialog (pas seulement fullPage)

i18n:
- 23 clés publish* traduites dans les 15 locales
- Extension Web Clipper: 13 locales mise à jour

Tests:
- callout-colors.test.ts (6 tests)
- note-visible-in-view.test.ts (5 tests)
- entitlements.test.ts + byok-entitlements.test.ts: mock usageLog + unstubAllEnvs
- 199/199 tests passent

Tracker: user-stories.md sync avec sprint-status.yaml
2026-06-28 07:32:57 +00:00

593 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Story 3.8: Admin Console — Complete Management Dashboard
> **Epic:** 3 — The SaaS Commercial Engine
> **Priority:** High
> **Status:** ready-for-dev
> **Depends on:** 3.1 (quota tracking), 3.6 (Stripe tiers), 3.7 (billing UX)
> **Blocks:** —
> **Related:** FR13 (real-time quota indicator), FR21 (audit logging), FR18 (fallback rules)
---
## Problem Statement
The admin console exists but is a **hollow shell**:
- **Dashboard**: 3 of 4 metrics are hardcoded strings ("24", "1,234", "856"). Trends are fake. "Recent Activity" is a placeholder div.
- **Users page**: No pagination, no search, no edit, no subscription info. Shows only name/email/role/date. Cannot see what plan a user is on, how much they consume, or manage their subscription.
- **No billing/subscription visibility**: The `Subscription` model has real data (tier, status, Stripe IDs, period dates) but there is zero admin UI for it.
- **No usage analytics**: `UsageLog` collects per-user, per-feature `requestsCount` and `tokensUsed` but nobody can see it. No charts, no breakdown, no per-user drill-down.
- **FeatureFlag model unused**: Schema exists (`key`, `enabled`, `tiers`) with zero code references and zero admin UI.
- **No i18n**: Dashboard, users, AI pages have hardcoded English/French text. The `admin.*` locale block (260+ keys) only exists in `en.json`.
- **Design inconsistency**: Admin pages use raw Tailwind (`text-gray-900`, `bg-white`) instead of the design tokens (`text-foreground`, `bg-paper`, `font-memento-serif`). The sidebar is properly styled but the content area looks like a different app.
This story transforms the admin console into a **production-ready management dashboard** with real data, real metrics, and real controls.
---
## User Stories
### US-1: Real Dashboard Metrics
**As an** admin,
**I want** to see real, live metrics on the dashboard,
**So that** I understand the health and growth of the platform.
**Acceptance Criteria:**
- **Given** I navigate to `/admin`
- **When** the page loads
- **Then** I see 6 metric cards with real data:
| Metric | Source | Computation |
|--------|--------|-------------|
| Total Users | `User.count()` | Current count |
| Total Notes | `Note.count()` | Current count |
| AI Requests (30d) | `UsageLog.sum(requestsCount)` where `periodStart` in last 30 days | Aggregate |
| Active Subscribers | `Subscription.count()` where `status = ACTIVE` and `tier != BASIC` | Count |
| Tokens Used (30d) | `UsageLog.sum(tokensUsed)` where `periodStart` in last 30 days | Aggregate, formatted (e.g., "1.2M") |
| Revenue (30d) | Stripe API or `Subscription` count × price | If Stripe not connected, show subscriber count × plan price as estimate |
- **And** each card shows a trend arrow comparing to the previous 30-day period (computed from `UsageLog` and `User.createdAt`)
- **And** data is fetched server-side (no client loading state for metrics)
- **And** the page uses the `font-memento-serif` heading + `text-foreground` / `bg-paper` design tokens (NOT raw gray/white)
### US-2: Dashboard Charts
**As an** admin,
**I want** to see usage trends over time,
**So that** I can spot patterns and growth trajectories.
**Acceptance Criteria:**
- **Given** I am on the dashboard
- **When** I scroll below the metric cards
- **Then** I see a 7-day area chart showing daily AI requests (sourced from `UsageLog` grouped by `periodStart` day)
- **And** I see a user growth line chart (users created per day, last 30 days)
- **And** charts use Recharts (already a dependency — verify) with the Memento color palette:
- Line/area: `#ACB995` (sage) or `#D4A373` (ochre)
- Grid: `border-border/40`
- Labels: `text-[11px] text-muted-foreground`
- Background: transparent (inherits `bg-paper`)
### US-3: Recent Activity Feed
**As an** admin,
**I want** to see recent platform activity,
**So that** I can monitor what's happening in real-time.
**Acceptance Criteria:**
- **Given** I am on the dashboard
- **When** I view the "Recent Activity" section
- **Then** I see the last 20 significant events:
- New user registration (icon: `UserPlus`, text: "[Name] signed up", relative time)
- Subscription created/upgraded (icon: `Crown`, text: "[Name] upgraded to [Tier]", relative time)
- Subscription canceled (icon: `AlertTriangle`, text: "[Name] canceled [Tier]", relative time)
- High usage alert (icon: `Zap`, text: "[Name] used 90%+ of quota", relative time)
- **And** each event shows relative time ("2 minutes ago", "3 hours ago")
- **And** events are sourced from `UsageLog` (high usage), `Subscription.createdAt` (new subs), `Subscription.canceledAt` (cancellations), `User.createdAt` (signups)
### US-4: Enhanced User Management
**As an** admin,
**I want** to see and manage comprehensive user data,
**So that** I can support users and manage the platform.
**Acceptance Criteria:**
- **Given** I navigate to `/admin/users`
- **When** the user table loads
- **Then** each row shows:
- Name + avatar initial
- Email
- Role badge (USER / ADMIN)
- **Subscription tier** badge (Free / Pro / Business / Enterprise) with color:
- Free: `bg-secondary text-secondary-foreground`
- Pro: `bg-violet-100 text-violet-700`
- Business: `bg-amber-100 text-amber-700`
- Enterprise: `bg-emerald-100 text-emerald-700`
- **AI usage this period** (sum of `requestsCount` from current period `UsageLog`, formatted as number)
- Created date
- Actions (role toggle, delete, **new: view details**)
- **And** there is a **search bar** at the top that filters by name or email (client-side for now, debounced 300ms)
- **And** there is **pagination** (25 users per page) with page controls
- **And** there is a **subscription filter** dropdown: All / Free / Pro / Business / Enterprise
- **And** clicking a user row opens a **User Detail Drawer** (slide-in from right, 400px)
### US-5: User Detail Drawer
**As an** admin,
**I want** to see detailed information about a specific user,
**So that** I can troubleshoot issues and manage their account.
**Acceptance Criteria:**
- **Given** I click on a user row in the user table
- **When** the drawer opens
- **Then** I see:
**Profile Section:**
- Avatar initial + name + email
- Role badge
- Member since date
- Last active (from most recent `UsageLog.createdAt` or `session.expires`)
**Subscription Section:**
- Current plan badge (tier)
- Status badge (ACTIVE / CANCELED / PAST_DUE / TRIALING)
- Billing period: "[start] [end]"
- Stripe customer ID (clickable link to Stripe dashboard if available)
- "Manage in Stripe" button (opens portal for that customer)
**Usage Section:**
- Per-feature usage table for current period:
- Feature name
- Requests used / limit
- Tokens used
- Progress bar (same visual as UsageBreakdown from Story 3.7)
- "View full history" link (future scope, just a placeholder for now)
**Account Actions:**
- Edit name (inline edit, save via server action)
- Reset password (sends reset email)
- Change role dropdown
- Impersonate user (future scope — button disabled with tooltip "Coming soon")
- Delete user (red, with double-confirm for users with notes)
- **And** the drawer uses the same design tokens as the settings panels (`rounded-xl border border-border/40 bg-paper`)
- **And** closing the drawer refreshes the user list
### US-6: Subscriptions Overview Page
**As an** admin,
**I want** to see all subscriptions in one place,
**So that** I can monitor revenue and plan distribution.
**Acceptance Criteria:**
- **Given** I navigate to `/admin/subscriptions` (new sidebar nav item)
- **When** the page loads
- **Then** I see:
**Summary Cards (top):**
- Total subscribers (count of non-BASIC active subscriptions)
- Monthly recurring revenue estimate (subscriber count × plan price)
- Churn rate (canceled in last 30d / total subscribers 30d ago)
- Most popular plan (tier with highest count)
**Subscriber Table:**
- Columns: User name, Email, Tier, Status, Period end, MRR, Actions
- Filterable by tier and status
- Sortable by period end (default: expiring soonest first)
- Paginated (25 per page)
- Actions: View user drawer, Open Stripe portal, Cancel subscription (with confirm)
**Tier Distribution:**
- Simple horizontal bar chart showing count per tier
- Colors: Free (gray), Pro (violet), Business (amber), Enterprise (emerald)
- **And** the sidebar nav includes "Subscriptions" with a `CreditCard` icon between "Users" and "AI Management"
### US-7: Usage Analytics Page
**As an** admin,
**I want** to see platform-wide AI usage analytics,
**So that** I can optimize costs and understand feature adoption.
**Acceptance Criteria:**
- **Given** I navigate to `/admin/usage` (new sidebar nav item)
- **When** the page loads
- **Then** I see:
**Period Selector:** Last 7 days / 30 days / 90 days (pill toggle, default 30d)
**Aggregate Cards:**
- Total AI requests (period)
- Total tokens consumed (period, formatted: "1.2M")
- Average requests per user (period)
- Cost estimate (tokens × $0.002/1K tokens for gpt-4o-mini as rough estimate)
**Feature Breakdown:**
- Bar chart: requests per feature (semantic_search, auto_tag, auto_title, reformulate, chat, brainstorm_*)
- Sorted by volume descending
- Each bar shows count + percentage of total
**Top Users Table:**
- Columns: Rank, User name, Requests, Tokens, Tier
- Top 25 users by request count
- Clickable rows → user detail drawer
**Daily Trend Chart:**
- Area chart: daily request count over selected period
- Tooltip shows date + count
- **And** all data is sourced from `UsageLog` aggregated via Prisma `groupBy`
### US-8: Feature Flags Management
**As an** admin,
**I want** to toggle features on/off per tier,
**So that** I can roll out features gradually.
**Acceptance Criteria:**
- **Given** I navigate to `/admin/settings` (existing page)
- **When** I scroll to a new "Feature Flags" section
- **Then** I see a table of feature flags from the `FeatureFlag` model:
- Key (string, editable)
- Enabled toggle (switch)
- Tiers multiselect (checkboxes: BASIC, PRO, BUSINESS, ENTERPRISE)
- Last updated date
- **And** I can create a new feature flag via "Add Flag" button
- **And** I can delete a feature flag
- **And** changes take effect immediately (writes to DB, `revalidatePath('/admin/settings')`)
- **And** the section uses the existing `SettingsSection` / `SettingToggle` component patterns
---
## Technical Design
### New Files to Create
| File | Description |
|------|-------------|
| `app/(admin)/admin/subscriptions/page.tsx` | Subscriptions overview page |
| `app/(admin)/admin/subscriptions/subscription-table.tsx` | Subscriber table client component |
| `app/(admin)/admin/usage/page.tsx` | Usage analytics page |
| `app/(admin)/admin/usage/usage-client.tsx` | Usage analytics client component (charts) |
| `components/admin/user-detail-drawer.tsx` | Slide-in user detail panel |
| `components/admin/charts/area-chart.tsx` | Recharts wrapper with Memento theme |
| `components/admin/charts/bar-chart.tsx` | Recharts wrapper with Memento theme |
| `app/actions/admin-subscriptions.ts` | Server actions: getSubscriptions, cancelSubscription, getSubscriptionSummary |
| `app/actions/admin-usage.ts` | Server actions: getUsageAggregate, getUsageByFeature, getTopUsers, getDailyUsage |
| `app/actions/admin-feature-flags.ts` | Server actions: getFeatureFlags, createFeatureFlag, updateFeatureFlag, deleteFeatureFlag |
### Files to Modify
| File | Change |
|------|--------|
| `app/(admin)/admin/page.tsx` | Replace mock metrics with real DB queries, add chart components, add activity feed, apply design tokens |
| `app/(admin)/admin/users/page.tsx` | Add search bar, subscription filter, apply design tokens, pass subscription data to UserList |
| `app/(admin)/admin/user-list.tsx` | Add subscription tier column, usage column, row click handler → drawer, pagination, apply design tokens |
| `components/admin-sidebar.tsx` | Add "Subscriptions" and "Usage" nav items |
| `components/admin-metrics.tsx` | Apply design tokens (`bg-paper` instead of `bg-white`, `text-foreground` instead of `text-gray-900`) |
| `app/(admin)/admin/settings/page.tsx` | Add Feature Flags section |
| `app/(admin)/admin/settings/admin-settings-form.tsx` | Add Feature Flags management UI |
| `app/actions/admin.ts` | Update `getUsers()` to include subscription and usage data, add pagination params |
### Data Aggregation Queries
```typescript
// Dashboard metrics
const [
totalUsers,
totalNotes,
aiRequests30d, // UsageLog.sum({ requestsCount }, { periodStart: { gte: 30dAgo } })
activeSubscribers, // Subscription.count({ status: ACTIVE, tier: { not: BASIC } })
tokensUsed30d, // UsageLog.sum({ tokensUsed }, { periodStart: { gte: 30dAgo } })
prevMonthUsers, // User.count({ createdAt: { lt: 30dAgo } })
prevMonthRequests, // UsageLog.sum for previous period
] = await Promise.all([...])
```
```typescript
// Daily usage for charts
const dailyUsage = await prisma.usageLog.groupBy({
by: ['periodStart'],
_sum: { requestsCount: true, tokensUsed: true },
where: { periodStart: { gte: startDate } },
orderBy: { periodStart: 'asc' },
})
```
```typescript
// Top users
const topUsers = await prisma.usageLog.groupBy({
by: ['userId'],
_sum: { requestsCount: true, tokensUsed: true },
where: { periodStart: { gte: startDate } },
orderBy: { _sum: { requestsCount: 'desc' } },
take: 25,
})
```
```typescript
// Users with subscription + usage (enhanced getUsers)
const users = await prisma.user.findMany({
skip: (page - 1) * 25,
take: 25,
include: {
subscription: { select: { tier: true, status: true, currentPeriodEnd: true } },
usageLogs: {
where: { periodStart: { gte: currentPeriodStart } },
select: { requestsCount: true, tokensUsed: true },
},
},
orderBy: { createdAt: 'desc' },
})
```
### Dependencies to Verify
| Package | Status | Action |
|---------|--------|--------|
| `recharts` | Verify in package.json | Install if missing (`npm i recharts`) |
| `date-fns` | Already installed | No action |
| `@tanstack/react-query` | Already installed | No action |
### Design Tokens (Existing — Consistent with Main App)
All admin pages MUST use these tokens instead of raw Tailwind colors:
| Element | Current (WRONG) | Correct Token |
|---------|-----------------|---------------|
| Page background | `bg-white` / `bg-gray-50` | `bg-paper` (`#F2F0E9`) |
| Text primary | `text-gray-900` | `text-foreground` / `text-ink` |
| Text secondary | `text-gray-600` | `text-muted-foreground` / `text-muted-ink` |
| Cards | `bg-white border-gray-200` | `bg-paper border-border/40` (or `bg-card`) |
| Headings | `text-3xl font-bold text-gray-900` | `font-memento-serif text-3xl font-medium text-foreground` |
| Subtitles | `text-gray-600 mt-1` | `text-[11px] text-muted-foreground uppercase tracking-[0.2em]` |
| Dark mode | Manual `dark:` classes | Automatic via CSS variables |
| Sidebar active | Custom classes | `memento-active-nav` class (already defined) |
### Sidebar Navigation (Updated)
```
ADMIN_NAV_ITEMS = [
{ titleKey: 'admin.sidebar.dashboard', href: '/admin', icon: LayoutDashboard },
{ titleKey: 'admin.sidebar.users', href: '/admin/users', icon: Users },
{ titleKey: 'admin.sidebar.subscriptions', href: '/admin/subscriptions', icon: CreditCard },
{ titleKey: 'admin.sidebar.usageAnalytics', href: '/admin/usage', icon: BarChart3 },
{ titleKey: 'admin.sidebar.aiManagement', href: '/admin/ai', icon: Brain },
{ titleKey: 'admin.sidebar.settings', href: '/admin/settings', icon: Settings },
]
```
### i18n Keys to Add (15 locales)
All new keys under `admin.*` namespace. The full `admin` block (260+ keys) needs to be propagated to all 15 locale files. New keys for this story:
```json
{
"admin": {
"sidebar.subscriptions": "Subscriptions",
"sidebar.usageAnalytics": "Usage Analytics",
"dashboard.totalNotes": "Total Notes",
"dashboard.aiRequests": "AI Requests",
"dashboard.activeSubscribers": "Active Subscribers",
"dashboard.tokensUsed": "Tokens Used",
"dashboard.revenueEstimate": "Revenue (est.)",
"dashboard.recentActivity": "Recent Activity",
"dashboard.noActivity": "No recent activity.",
"dashboard.newSignup": "{name} signed up",
"dashboard.upgraded": "{name} upgraded to {tier}",
"dashboard.canceled": "{name} canceled {tier}",
"dashboard.highUsage": "{name} reached {percent}% of quota",
"dashboard.userGrowth": "User Growth",
"dashboard.dailyRequests": "Daily AI Requests",
"subscriptions.title": "Subscriptions",
"subscriptions.description": "Monitor and manage subscription plans",
"subscriptions.totalSubscribers": "Total Subscribers",
"subscriptions.monthlyRevenue": "Monthly Revenue (est.)",
"subscriptions.churnRate": "Churn Rate",
"subscriptions.popularPlan": "Most Popular",
"subscriptions.table.user": "User",
"subscriptions.table.tier": "Plan",
"subscriptions.table.status": "Status",
"subscriptions.table.periodEnd": "Period End",
"subscriptions.table.mrr": "MRR",
"subscriptions.table.actions": "Actions",
"subscriptions.manageStripe": "Manage in Stripe",
"subscriptions.cancelConfirm": "Cancel subscription for {name}?",
"subscriptions.tierDistribution": "Plan Distribution",
"usage.title": "Usage Analytics",
"usage.description": "Platform-wide AI feature consumption",
"usage.totalRequests": "Total Requests",
"usage.totalTokens": "Total Tokens",
"usage.avgPerUser": "Avg per User",
"usage.costEstimate": "Est. Cost",
"usage.featureBreakdown": "Feature Breakdown",
"usage.topUsers": "Top Users",
"usage.rank": "#",
"usage.requests": "Requests",
"usage.tokens": "Tokens",
"usage.dailyTrend": "Daily Trend",
"usage.last7d": "Last 7 days",
"usage.last30d": "Last 30 days",
"usage.last90d": "Last 90 days",
"users.search": "Search users...",
"users.filterTier": "Filter by plan",
"users.allTiers": "All plans",
"users.usageThisPeriod": "AI usage",
"users.detail.title": "User Details",
"users.detail.memberSince": "Member since",
"users.detail.lastActive": "Last active",
"users.detail.subscription": "Subscription",
"users.detail.billingPeriod": "Billing period",
"users.detail.stripeId": "Stripe ID",
"users.detail.manageStripe": "Manage in Stripe",
"users.detail.usagePeriod": "Usage this period",
"users.detail.editName": "Edit name",
"users.detail.resetPassword": "Send reset email",
"users.detail.impersonate": "Impersonate",
"users.detail.impersonateSoon": "Coming soon",
"users.detail.deleteWithNotes": "This user has {count} notes. Confirm deletion?",
"settings.featureFlags": "Feature Flags",
"settings.featureFlagsDescription": "Toggle features on/off per subscription tier",
"settings.addFlag": "Add Flag",
"settings.flagKey": "Key",
"settings.flagEnabled": "Enabled",
"settings.flagTiers": "Available for",
"settings.deleteFlag": "Delete flag",
"settings.confirmDeleteFlag": "Delete feature flag \"{key}\"?"
}
}
```
---
## Pages Architecture
### `/admin` — Dashboard (Rewrite)
```
┌─────────────────────────────────────────────┐
│ Dashboard │ ← font-memento-serif
│ Overview of your platform │ ← text-muted-foreground
├─────────┬─────────┬─────────┬──────────────┤
│ Users │ Notes │ AI Req │ Subscribers │ ← 4 real metrics
│ 142 ↑12│ 3,241 ↑8│ 12.4K ↑24│ 23 ↑15% │
├─────────┴─────────┴─────────┴──────────────┤
│ ┌───────────────────┐ ┌───────────────────┐│
│ │ Daily AI Requests │ │ User Growth ││ ← Recharts
│ │ ▁▂▃▅▇█▇▅▃▂▁▁▂▃ │ │ ▁▂▃▃▄▅▅▆▇▇█ ││
│ └───────────────────┘ └───────────────────┘│
├─────────────────────────────────────────────┤
│ Recent Activity │
│ ● Sepehr upgraded to Pro — 2 min ago │
│ ● Jane signed up — 15 min ago │
│ ● ... │
└─────────────────────────────────────────────┘
```
### `/admin/users` — Enhanced User Management (Rewrite)
```
┌─────────────────────────────────────────────┐
│ Users [+ Add] │
│ Manage users and permissions │
├─────────────────────────────────────────────┤
│ 🔍 Search... [Plan ▼] [All plans] │
├──────┬────────┬──────┬──────┬──────┬───────┤
│ Name │ Email │ Role │ Plan │ Usage│ Date │
│ John │ j@e.co │ USER │ Pro │ 847 │ May 2 │ ← row click → drawer
│ Jane │ j@e.co │ ADMIN│ Free │ 23 │ Apr 28│
├──────┴────────┴──────┴──────┴──────┴───────┤
│ ← 1 2 3 → 25 per page│
└─────────────────────────────────────────────┘
```
### `/admin/subscriptions` — New Page
```
┌─────────────────────────────────────────────┐
│ Subscriptions │
│ Monitor and manage subscription plans │
├─────────┬─────────┬─────────┬──────────────┤
│ 23 Subs │ €228/mo │ 4.2% Churn│ Pro (most) │ ← summary cards
├─────────┴─────────┴─────────┴──────────────┤
│ Tier Distribution │
│ Free ████████████████ 119 │
│ Pro █████████ 15 │
│ Biz ██████ 6 │ ← horizontal bars
│ Ent ███ 2 │
├─────────────────────────────────────────────┤
│ [Tier ▼] [Status ▼] │
├──────┬──────┬──────┬──────┬────────────────┤
│ User │ Tier │Status│ Until│ Actions │
│ ... │ ... │ ... │ ... │ [Stripe] [Cancel]│
└──────┴──────┴──────┴──────┴────────────────┘
```
### `/admin/usage` — New Page
```
┌─────────────────────────────────────────────┐
│ Usage Analytics │
│ Platform-wide AI feature consumption │
├─────────────────────────────────────────────┤
│ [7d] [30d] [90d] │ ← period toggle
├──────────┬──────────┬──────────┬────────────┤
│ 12.4K │ 1.2M │ 87 │ $2.40 │ ← aggregate cards
│ Requests │ Tokens │ Avg/user │ Est. cost │
├──────────┴──────────┴──────────┴────────────┤
│ Feature Breakdown │
│ semantic_search ████████████████ 4,200 │
│ auto_tag ██████████ 2,800 │ ← horizontal bars
│ auto_title █████ 1,400 │
│ chat ███ 900 │
├─────────────────────────────────────────────┤
│ Daily Trend │
│ ▁▂▃▅▇█▇▅▃▂▁▁▂▃ │ ← area chart
├─────────────────────────────────────────────┤
│ Top 25 Users │
│ # │ User │ Requests │ Tokens │ Tier │
│ 1 │ Sepehr │ 3,200 │ 420K │ Pro │
│ 2 │ ... │ ... │ ... │ ... │
└─────────────────────────────────────────────┘
```
---
## Implementation Order
| Step | Task | Est. |
|------|------|------|
| 1 | Fix design tokens on all existing admin pages (replace `bg-white`/`text-gray-900` with tokens) | 1.5h |
| 2 | Rewrite dashboard with real metrics (6 cards) | 2h |
| 3 | Add dashboard charts (daily requests + user growth) | 2h |
| 4 | Add recent activity feed | 1.5h |
| 5 | Enhance `getUsers()` with subscription + usage data + pagination | 1h |
| 6 | Add search bar + tier filter + subscription column + pagination to users page | 2h |
| 7 | Build user detail drawer | 3h |
| 8 | Create subscriptions page (summary + table + tier distribution) | 3h |
| 9 | Create usage analytics page (aggregates + charts + top users) | 3h |
| 10 | Add feature flags section to settings page | 1.5h |
| 11 | Update admin sidebar with 2 new nav items | 0.5h |
| 12 | i18n: propagate full `admin.*` block to all 15 locales | 2h |
| **Total** | | **~23h** |
---
## Out of Scope
- **Impersonate user** — requires careful security design, deferred
- **Admin API key management** — viewing/rotating BYOK keys, deferred
- **Export admin data as CSV** — deferred to a follow-up
- **Real-time WebSocket updates** — polling/refresh is sufficient
- **Email notifications for admin events** — deferred
- **Admin audit log** (who did what) — deferred to Epic 4 (FR21)
- **Backup/restore UI** — the `dump-db.sh` script is sufficient for now
- **Advanced charting** (heatmaps, cohort analysis) — deferred
- **Bulk user operations** (mass delete, mass role change) — deferred
- **SSO/SAML management** — Epic 4 scope
---
## Edge Cases & Error Handling
- **No UsageLog data yet** (fresh install) → charts show empty state, metrics show 0
- **Redis down** → usage section shows "Unable to load" gracefully (fail-open)
- **Stripe not configured** → subscription page shows "Connect Stripe" CTA instead of subscriber table
- **User has no subscription** → show "Free" badge, no subscription section in drawer
- **User deletion with notes** → double-confirm dialog showing note count
- **Self-management protection** → admin cannot delete self, change own role, or cancel own subscription via admin UI
- **Pagination with filters** → filter + search are client-side on the fetched page (acceptable for <10K users); server-side filtering if user base grows
- **Dark mode** → all new components must work in both modes via CSS variables
---
## Testing Notes
- **Visual**: Verify all pages in both light and dark mode
- **Responsive**: Dashboard charts should stack on mobile; tables should scroll horizontally
- **i18n**: Switch language and verify all admin keys render correctly
- **Data**: Seed test data with `UsageLog` entries across multiple users and periods
- **Access**: Verify non-admin users are blocked at middleware level
- **Performance**: Dashboard with 10K+ `UsageLog` entries should load in <2s (verify query performance)