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
593 lines
28 KiB
Markdown
593 lines
28 KiB
Markdown
# 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)
|