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

28 KiB
Raw Blame History

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

// 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([...])
// Daily usage for charts
const dailyUsage = await prisma.usageLog.groupBy({
  by: ['periodStart'],
  _sum: { requestsCount: true, tokensUsed: true },
  where: { periodStart: { gte: startDate } },
  orderBy: { periodStart: 'asc' },
})
// 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,
})
// 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:

{
  "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)