- 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
28 KiB
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
Subscriptionmodel has real data (tier, status, Stripe IDs, period dates) but there is zero admin UI for it. - No usage analytics:
UsageLogcollects per-user, per-featurerequestsCountandtokensUsedbut 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 inen.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
UsageLogandUser.createdAt) - And data is fetched server-side (no client loading state for metrics)
- And the page uses the
font-memento-serifheading +text-foreground/bg-paperdesign 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
UsageLoggrouped byperiodStartday) - 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 Momento color palette:
- Line/area:
#ACB995(sage) or#D4A373(ochre) - Grid:
border-border/40 - Labels:
text-[11px] text-muted-foreground - Background: transparent (inherits
bg-paper)
- Line/area:
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)
- New user registration (icon:
- 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
- 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
- Free:
- AI usage this period (sum of
requestsCountfrom current periodUsageLog, 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.createdAtorsession.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
CreditCardicon 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
UsageLogaggregated via PrismagroupBy
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
FeatureFlagmodel:- 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/SettingTogglecomponent 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 Momento theme |
components/admin/charts/bar-chart.tsx |
Recharts wrapper with Momento 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.shscript 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
UsageLogentries across multiple users and periods - Access: Verify non-admin users are blocked at middleware level
- Performance: Dashboard with 10K+
UsageLogentries should load in <2s (verify query performance)