Skip to content

First-run tour

The admin follows a sidebar-groups + focus-content layout. Everything you can do is in the sidebar (permission-aware, hidden if your role can’t do it). The URL always reflects state so links are shareable.

The nav is organized around what you’re trying to do, not how the DB is shaped:

GroupWhat lives here
Dashboard (/)KPI tiles + trend graphs. First thing you see after login.
Catalog (/series, /catalog/*)Series list, wizard, episode editor, genres, tropes, cast.
Media (/media/*)Uploads, library, retry/reprocess.
Curation (/curation/*)For You home, collections, preview-reel ordering, free-today rotation.
Users (/users/*)Search, User 360, segments.
Moderation (/moderation/*)Comment queue, reports queue.
Monetization (/monetization/*, /pricing/*)Pricing overrides, coin packages, subscriptions, promos.
Payments (/payments/*)Transactions, refunds, reconciliation.
Analytics (/analytics/*)Overview, revenue, content, funnel, economy.
Engagement (/engagement/*, /push)Push composer, streaks & rewards config.
System (/system/*, /audit)Admin users, roles, approvals, audit log, feature flags, settings.

Press ⌘K (macOS) or Ctrl+K (Windows/Linux) from anywhere. The palette lets you:

  • Jump to a route: start typing “series” or “refunds” and hit Enter.
  • Jump to a user: paste an email, phone number (E.164), or user ID.
  • Jump to a series: paste a series ID or slug.
  • Jump to a transaction: paste a transaction ID.

Palette entries respect your permissions: you won’t see “Refunds” if you don’t have payments:read.

Every list surface uses TanStack Table with:

  • URL-state filters: every filter, sort, and page cursor lives in the query string. Copy the URL to share the exact view with a teammate.
  • Server-side pagination via opaque cursors (?cursor=…). No offset pagination.
  • Bulk actions where relevant (multi-select in comments queue, schedules, users).
  • CSV export on transactions and audit log.

Any destructive or high-impact action pops a typed-confirmation modal:

  • Type the resource name (e.g. the series title) to unlock the Confirm button.
  • Add an audit note: the reason string that gets attached to the AuditLog entry and, if applicable, the AdminApproval record.

Actions that hit the two-person rule (see below) go to a queue instead of executing immediately.

Some actions can’t be done alone. They’re written into an admin_approvals row with status=pending, and a different admin with the right permission must approve them in System → Approvals.

What routes through approvals:

ActionThresholdWho approves
Refund> $50 (5000 cents)payments:refund (finance / superadmin)
Coin grant> 2000 coinswallet:adjust (finance / superadmin, not the requester)
Series deletionAlwaysSecond catalog:delete holder
Role changesAlwaysSecond roles:manage (superadmin)
GDPR anonymizeAlwaysSecond users:anonymize

Approval decisions themselves are audited (AuditLog entry with the payload diff). If the same admin somehow tries to approve their own request, the API returns approver_must_differ (403).

Every mutating admin action writes to AuditLog with:

  • admin_id: who did it
  • action: the operation (user.ban, refund.execute, series.create, …)
  • ref_type / ref_id: the target (user:abc, series:xyz)
  • before / after JSON: full diff for reconstruction
  • ip / user_agent: provenance
  • created_at

Read it at System → Audit log. Analyst and finance have audit:read; support and moderator do not.

Any content field with a localized version has three tabs at edit time:

  • EN: English (default)
  • AR: Arabic (RTL preview)
  • ID: Bahasa Indonesia

The admin UI itself stays in English. Only content fields (series/episode titles, synopses, collection labels, push copy, etc.) localize.