Skip to content

Epic 11 — Admin Panel


BE-ADMIN-001 — Full admin user management API

  • [x] Implemented

Files:

  • Edit: src/graphql/schema/index.ts — add admin queries/mutations
  • Create: src/graphql/resolvers/admin.ts
  • Create: src/services/admin/userManagement.ts

Queries: adminUsers(page, filter): UserPage!, adminUser(id): User!, adminStats: PlatformStats!

Mutations: suspendUser(id, reason), banUser(id, reason), restoreUser(id), verifyUser(id), resetUserPassword(id)

Note: the originally-planned impersonateUser mutation has moved to its own epic — see Epic 24 — Admin Impersonation. The earlier shape (impersonateUser(id): AuthPayload!) was insufficient because it returned a normal access token that would collide with the admin's own session and left no audit trail.

Add to User model: status UserStatus @default(ACTIVE) (enum: ACTIVE SUSPENDED BANNED), verifiedAt DateTime?, suspendedAt DateTime?, banReason String?


FE-ADMIN-001 — Admin user management page

  • [x] Implemented

Files:

  • Edit: apps/app/src/pages/admin/AdminPage.tsx — add tabs for Users, Jobs, Reports
  • Create: apps/app/src/pages/admin/AdminUsersPage.tsx
  • Create: apps/app/src/pages/admin/AdminUserDetailPage.tsx
  • Create: apps/app/src/hooks/useAdminUsers.ts

Description: Admin-only (role: ADMIN). User list with search and filter by role/status. Each row: avatar, email, role, status, created date, action menu (Suspend / Ban / Verify / Reset Password). User detail shows full profile data, activity log, and moderation history. Uses DS components. Matches "ADMIN-Desktop" frames in Figma.


BE-ADMIN-003 — Admin missing spec features (merge, logs, analytics, config, concierge)

  • [x] Implemented

Files:

  • Edit: prisma/schema.prisma — add UserActivityLog, SystemConfig models
  • Run: pnpm db:migrate
  • Edit: src/graphql/schema/index.ts — extend admin queries/mutations
  • Edit: src/graphql/resolvers/admin.ts
  • Create: src/services/admin/activityLog.ts
  • Create: src/services/admin/systemConfig.ts

1. Merge duplicate accounts:

graphql
mergeUserAccounts(primaryId: ID!, duplicateId: ID!): User!

Migrates all profile data, applications, messages, and CasTars from duplicateId to primaryId, then deletes the duplicate. Requires confirmation step — admin must type the duplicate email to confirm.

2. User activity & login logs:

prisma
model UserActivityLog {
  id        String   @id @default(cuid())
  userId    String
  action    String   // "LOGIN" | "PASSWORD_CHANGE" | "ROLE_CHANGE" | "SUSPENSION" | "BAN" | "PROFILE_UPDATE"
  detail    String?
  ip        String?
  userAgent String?
  createdAt DateTime @default(now())
  @@map("user_activity_logs")
}

Write a log entry on: login, password reset, role change, suspend/ban/restore. Query: userActivityLog(userId, first, after): ActivityLogConnection!

3. Platform analytics:

graphql
platformAnalytics(range: DateRange!): PlatformAnalytics!

type PlatformAnalytics {
  newUsers:       Int!
  activeUsers:    Int!
  jobsPosted:     Int!
  applicationsSubmitted: Int!
  hiresCompleted: Int!
  casTarsPurchased: Int!   # revenue proxy
  topCategories:  [CategoryStat!]!
}

4. System configuration:

prisma
model SystemConfig {
  key       String @id   // "MAINTENANCE_MODE" | "MAX_APPLICATIONS_PER_JOB" | "CASTARS_EARN_MULTIPLIER" etc.
  value     String
  updatedBy String
  updatedAt DateTime @updatedAt
  @@map("system_config")
}

getSystemConfig(key) / setSystemConfig(key, value) — admin only.

5. Concierge tab — surfaced in the FE admin panel; resolvers already defined in Epic 23 (updateConciergeStatus, fulfillConciergeRequest).


BE-ADMIN-002 — Reporting & appeals

  • [x] Implemented

Files:

  • Edit: prisma/schema.prisma — add Report model
  • Edit: src/graphql/schema/index.ts
  • Create: src/graphql/resolvers/reports.ts

Schema:

prisma
model Report {
  id           String   @id @default(cuid())
  reporterId   String
  targetType   String   // USER | JOB | GROUP
  targetId     String
  reason       String
  description  String?
  status       ReportStatus @default(PENDING)
  resolvedBy   String?
  resolvedAt   DateTime?
  createdAt    DateTime @default(now())
  @@map("reports")
}
enum ReportStatus { PENDING REVIEWED RESOLVED DISMISSED }

Mutations: reportContent(targetType, targetId, reason, description?), resolveReport(id, action) (admin)


FE-ADMIN-002 — Admin extended UI (logs, analytics, merge, config, concierge)

  • [x] Implemented

Files:

  • Edit: apps/app/src/pages/admin/AdminPage.tsx — add tabs: Analytics, Config, Concierge
  • Edit: apps/app/src/pages/admin/AdminUserDetailPage.tsx — add activity log timeline, merge account action
  • Create: apps/app/src/pages/admin/AdminAnalyticsPage.tsx
  • Create: apps/app/src/pages/admin/AdminConfigPage.tsx
  • Create: apps/app/src/pages/admin/AdminConciergePage.tsx
  • Create: apps/app/src/hooks/useAdminAnalytics.ts

New tabs/pages:

  • User detail — Activity Log: chronological timeline of login, role change, suspension events per user. IP and user agent shown. Used for fraud investigation.
  • User detail — Merge Accounts: "Merge with…" button opens a search modal to pick the duplicate account. Shows a diff of both profiles before confirming. Requires typing the duplicate email.
  • Analytics tab: date-range picker + stat cards (new users, active users, jobs posted, applications, hires, CasTars revenue). Bar chart for top talent categories (use recharts).
  • Config tab: key-value table of SystemConfig entries. Each row has an inline edit field. Save triggers setSystemConfig. Sensitive keys (e.g. MAINTENANCE_MODE) have a confirmation dialog.
  • Concierge tab: table of all ConciergeRequest entries with status filter. Detail drawer shows description, requester, cost, admin notes field, and result submission form. "Fulfil" action opens a form to add candidate links and notes.