Admin Guide
Approval queues, user management, club management, platform config, scheduled changes, admin roles, drill library, content moderation, financials, audit log, cron jobs, invoicing, feature flags, chatbot KB management
Last updated April 20, 2026
Admin Guide#
This guide is for Prodemy platform admins. Unlike files 01–09 which are user-friendly, this one is allowed to reference feature IDs (FEAT-XXX) and API routes — admins are technical enough to work with them and sometimes need to find specific features by ID.
Source of truth for everything architectural:
CLAUDE.mdat the repo root — single source of truth, updated daily.project-state.jsonat repo root — next feature numbers, in-progress features, platform rules.docs/— feature implementation plans, bug logs.
Admin dashboard#
Dashboard at /dashboard/admin. Sidebar: Overview, Approvals, Users, Clubs, Coaches, Content, Financials, Audit log, Settings, Notifications, Chatbot KB.
Summary cards#
Total users, active clubs, monthly platform revenue, pending approvals count. Engagement snapshot below — DAU, drills completed, videos uploaded, lane bookings. User breakdown by role.
Approvals queue (unified)#
Four tabs:
- Club registrations — new clubs awaiting approval. Shows owner details, locations, lanes, Stripe Connect status. Approve / reject.
- Drill submissions — club-submitted custom drills for the master library. Approve / reject / edit.
- Reported posts — flagged community posts (already auto-hidden). Restore (false flag) or permanently remove (with optional author warning or suspension).
- Flagged coach reviews — reviews coaches have disputed. Keep or remove.
Each tab shows a pending count badge in the sidebar.
User management#
/dashboard/admin/users. Searchable table with role badges, status, actions.
Per-user actions#
- Edit profile — update any user field for support cases.
- Suspend — disables login, hides profile. User sees "Account suspended." Reversible.
- Delete with cool-off — 7 days for users, 14 days for clubs. All affected parties notified. Admin can cancel during cool-off.
- Impersonate — log in as any user for troubleshooting (read-only, logged to audit trail).
- Verify coach — platform-level verification separate from club verification.
- Change role — opens a role change modal. Shows current role badge, role selector, red
SOLE_CLUB_OWNERblock if applicable (with eligible transfer targets from theClubCoachjoin table).
Role change restrictions#
Role changes fire USER_ROLE_CHANGED notification, log USER_ROLE_CHANGED audit action, and create an ActionSnapshot for revert.
Blocked changes:
- Parent/Student → Coach/Club Owner (role conflict).
- Coach/Club Owner → Parent/Student (role conflict).
- Club Owner who is the sole owner of an active club — returns
400 SOLE_CLUB_OWNERwith eligible target coaches.
Delete cascade rules#
- User delete — profile hidden, login disabled, posts hidden, drill history anonymized. If parent deleted, children reassigned or suspended.
- Club delete (14-day cool-off) — memberships terminated, bookings cancelled with refunds, tournaments cancelled, coaches unlinked.
- Coach delete (7-day cool-off) — sessions cancelled with refunds, students notified, reviews archived.
All deletes trigger email + in-app + (when live) push notifications to entity + all affected parties + initiating admin.
Club management#
/dashboard/admin/clubs. Master registry — every club on the platform.
Per-club actions#
- Approve — transitions a PENDING club to ACTIVE. Creates
ClubFeeAgreement(FEAT-103), setsClub.approvedAt(used for ANNIVERSARY billing day, capped at 28), creates platform-side Stripe Customer onClubOwnerProfile.stripeCustomerIdif FIXED_MONTHLY or HYBRID. - Reject — rejects a PENDING club registration with a reason.
- Suspend — sets
Club.status = SUSPENDED. Suspended clubs block new enrollments, bookings, and tournament registrations viaassertClubNotSuspended()calls at each route. Existing members keep access. Admin can reactivate. - Reactivate — returns a SUSPENDED club to ACTIVE.
- Edit — update club fields for support cases.
- Feature flags — modify via
ClubFeatureGrantrecords. Requires SUPER_ADMIN.
Suspension vs reactivation#
Requires SUPER_ADMIN role. Suspend reason is required — empty reason returns 400 with validation error. Reactivate returns a suspended club to ACTIVE immediately; renewal cron automatically skips suspended clubs so reactivation doesn't trigger retroactive billing.
Club Fee Agreement (FEAT-103)#
Created at club approval. Stores the per-club platform fee model:
feeModel:PERCENTAGE,FIXED_MONTHLY, orHYBRID.commissionRate: percentage taken from each transaction (PERCENTAGE and HYBRID).hybridCommissionRate: percentage for HYBRID mode (as of FEAT-104 Phase 5A).fixedMonthlyFee: flat monthly fee for FIXED_MONTHLY and HYBRID.prorateFirstInvoice: boolean toggle at approval time.
Club.feeModel is a denormalised nullable field mirroring feeAgreement.feeModel for fast list queries. Don't edit it directly — always update through ClubFeeAgreement.
For Stripe application_fee_amount logic, always call resolveStripeApplicationFeeRate(agreement) from @/lib/club-fee. Returns commissionRate for PERCENTAGE, hybridCommissionRate for HYBRID, null for FIXED_MONTHLY. Never pass application_fee_amount: 0 — omit the field entirely when null.
Legacy clubs without a feeAgreement fall back to PlatformConfig.defaultCommissionRate.
Platform config#
/dashboard/admin/payment-config. Platform-wide defaults.
Configurable settings#
- Default commission rate.
- Default Stripe fee handling (
PASS_TO_PARENTis the default, per FEAT-077 Phase 4). - Default grace period (days).
- Default auto-lock threshold (days).
- Minimum refund window (hours — platform minimum).
- Student plan prices (
platformSubPriceBasic,platformSubPricePro). - Feature flags (platform-level).
Scheduled changes#
Commission rate and Stripe fee handling changes are never applied immediately. They create ScheduledChange records with an effectiveDate. The cron at /api/cron/apply-scheduled-changes applies them when effectiveDate ≤ now.
The admin UI shows an amber banner + date picker when either scheduled field has drifted from saved values. handleSaveDefaults sends only changed scheduled fields plus effectiveDate. Surfaces SUPER_ADMIN_REQUIRED 403 if the admin isn't SUPER_ADMIN.
Admin roles#
ADMINrole — regular admin. All operations except managing other admins, critical config, and explicitly gated SUPER_ADMIN fields.ADMIN+adminRole: "SUPER_ADMIN"— can do everything.
SUPER_ADMIN required for:
- Commission / pricing changes.
- Feature flag modifications (
ClubFeatureGrant). - Club suspend / reactivate.
- Revert actions (14-day revert window on audit log entries).
- Role changes on protected fields.
Use isSuperAdmin(user) from @/lib/admin-auth for gating.
Drill library management#
/dashboard/admin/drills. Master library of 1,200+ drills across 8 activities (FEAT-076).
Drill categories per activity#
Cricket has 5 categories: BATTING, BOWLING, FIELDING (WK merged in), FITNESS, MENTAL. CHALLENGE was archived in FEAT-076. Legacy WICKET_KEEPING and CHALLENGE code lives in src/lib/legacy/cricket-drill-legacy.ts.
Other activities have activity-specific categories set via the ActivityType enum and customCategory field on Drill.
CRUD actions#
- Add drill — inline form below the table. Title, description, category, skill level, duration, video URL.
- Edit — inline form pre-populated. PUT
/api/admin/drills/[id], partial update pattern. - Archive (soft delete) — DELETE
/api/admin/drills/[id], sets status toarchived. Master drills are protected from hard delete.
Bulk operations#
- Bulk upload (FEAT-025) — GET
/api/admin/drills/bulk-uploadreturnsdrill_template.xlsx. POST with multipart form data. Validates category (BATTING/BOWLING/FIELDING/FITNESS/MENTAL — WICKET_KEEPING and CHALLENGE rejected as of FEAT-076), skill level, title. Creates withsource: MASTER. Returns{ created, total, errors[] }. - Bulk edit (FEAT-026) — PUT
/api/admin/drills/bulk. Update category, skill level, or status across multiple drills. - Bulk delete — DELETE
/api/admin/drills/bulk. Archives multiple drills at once.
Multi-select via select-all checkbox and per-row checkboxes. Bulk action bar shows selected count.
Club submission review#
Club owners can submit custom drills to the master library. These appear in the Approvals queue → Drill submissions tab. Admin approves (promotes to MASTER), rejects (keeps as club-only), or edits then approves.
Platform analytics and financials#
/dashboard/admin/financials. Platform-level revenue, commission, ARPU.
Financial dashboard#
- Monthly revenue by category (class, tournament, workshop, camp, coaching session, lane, membership, subscription).
- Platform commission earned per category.
- Revenue share summary per club (which uses
PERCENTAGEvsFIXED_MONTHLYvsHYBRID). - Transaction volume and ARPU.
- "Open Stripe Dashboard" button for direct access.
- Export as CSV or PDF.
Engagement analytics#
- Drill completion rate.
- Verification turnaround (median hours from submission to verify).
- Video upload rate.
- DAU / WAU / MAU.
- Retention cohorts.
- Churn analysis.
Audit log#
Immutable log of every admin action. ClubActivityLog and ActionSnapshot have no DELETE or UPDATE endpoints — entries are permanent. 2-year retention minimum. 14-day admin revert window on ActionSnapshot entries.
Fields#
AuditLog (admin-only): adminUserId, actionType (AuditActionType Prisma enum), targetEntityType, targetEntityId, details (JSON, before vs after for config changes), ipAddress, createdAt.
ClubActivityLog (club-scoped): clubId (nullable for platform-level), userId, category (MEMBERSHIP / COACHES / CLASSES / TOURNAMENTS / WORKSHOPS / FINANCIAL / BROADCASTS / FACILITIES / SETTINGS / ACCOUNT), action, options (JSON).
ActionSnapshot (for revert): actionType (plain String, not enum — don't confuse with AuditLog.actionType), entityType, entityId, beforeState, afterState, performedBy, performedByRole, clubId?, revertable, metadata.
New AuditActionType values (Phase 8)#
PLATFORM_CONFIG_CHANGE, USER_ROLE_CHANGED. Both pushes follow the two-step pattern: prisma db push --skip-generate then prisma generate.
Searchable#
Filter by date range, admin user, action type, target entity. Sortable. Exportable as CSV.
Revert window#
ActionSnapshot entries with revertable: true can be reverted via the admin UI within 14 days of the action. Revert creates a new ActionSnapshot showing the reversal. This is how "undo class ending," "undo tournament cancel," and "undo fee change" work.
Cron jobs#
All crons at /api/cron/* protected by CRON_SECRET header. Scheduled in vercel.json.
Active crons (13 total as of 2026-04-13)#
generate-club-invoices— daily 00:05 UTC. FIXED_MONTHLY and HYBRID clubs get invoiced on their billing day. Creates Stripe invoice +ClubInvoiceDB record. Auto-charges saved card or emails Stripe-hosted invoice URL. Skips: suspended clubs, already-invoiced periods, zero-amount months.apply-scheduled-changes— appliesScheduledChangerecords whereeffectiveDate ≤ now.auto-pay— processes auto-pay charges for class fees. FiresAutoPayPreviewEmail3 days before.execute-promotions— hourly. Runs coupon promotions.- Other crons cover attendance reminders, tournament closures, reminder emails, lane payment-hold expiry (every minute), etc.
Invoice cron logic#
Daily at 00:05 UTC:
- Finds FIXED_MONTHLY and HYBRID clubs whose billing day = today.
- Creates Stripe invoice +
ClubInvoiceDB record. - Detects
charge_automatically(if saved PaymentMethod or legacy default_source) vssend_invoice. - Skips: suspended clubs, already-invoiced periods, zero-amount months.
Webhook extensions#
Stripe webhook at /api/webhooks/stripe handles:
invoice.payment_succeeded→ status: PAID,paidAtset.invoice.payment_failed→ status: OVERDUE, club owner notified. After 3 failures: admin escalation.invoice.voided→ status: VOID.
Two webhook endpoints: connected accounts (club payments) and platform account (platform subscriptions). Separate signing secrets.
Feature gating#
Per-club features gated via ClubFeatureGrant records. Common gates:
- Lane booking system.
- Coaching packages.
- SMS broadcasts.
- Advanced analytics.
- Tournament team builder (always on by default).
Modifying feature flags requires SUPER_ADMIN. Use @/lib/feature-gate helper for checking feature availability in routes and UI.
Chatbot knowledge base management#
/dashboard/admin/config/chatbot (coming in the chatbot implementation phase).
This is where the KB content this file belongs to gets managed. Current plan:
- Per-section .md file uploads (one file per audience).
- Version history with rollback.
- Per-section "live / draft" status.
- Re-indexing trigger (re-embeds the chunks after an upload).
- Unanswered questions panel — surfaces the top chatbot questions that had low-confidence answers, so admin knows what to write next.
Backed by pgvector on Neon. Embeddings via Voyage AI or OpenAI text-embedding-3-small. Claude Haiku 4.5 for answer generation (per the hybrid login model — role + club injected into the system prompt for authenticated users).
Full implementation plan to be delivered as a separate Claude Code document (Phase 2 of this project).
Anti-abuse measures#
Post flag-and-hide#
Any user flags a post → immediately auto-hidden from all feeds → admin reviews → restore or permanently remove. This is safer for a platform serving minors.
Rate limiting#
Notificationtable stores notification history. The in-app rate limit queriescreatedAt ≥ now - 60sand blocks non-exempt types beyond 5/user/60s.RATE_LIMIT_EXEMPTinnotifications.tslists event types that bypass.- Contact form: max 3 submissions per email per hour.
Role conflict validation#
System-wide role conflict prevention (as of V19):
- COACH/CLUB_OWNER blocked from changing to PARENT/STUDENT.
- PARENT/STUDENT blocked from changing to COACH/CLUB_OWNER.
- Sole club owners blocked from any role change until the club has another owner.
Known deferrals#
Tracked in project-state.json → knownDeferrals:
- SMS delivery (Twilio). Currently logs to console only.
- FCM / APN push delivery. Expo push SDK wired, actual delivery logs to console only.
When these go live, the respective notification toggles and broadcast delivery UIs become functional.
Standing rules (non-negotiable)#
From CLAUDE.md:
- Every feature covers both web AND mobile (iOS + Android). No feature is done until both platforms ship. Implementation plans must include explicit mobile phases with mobile-specific file uploads listed.
- Financial forward-only rule. No retroactive changes to existing payment records. New rates apply only to new payments.
- Audit immutability.
ClubActivityLogandActionSnapshotentries are permanent. 2-year retention. 14-day admin revert window. - No SMS or FCM push delivery in code. Both deferred. Log to console only.
- Two-step Prisma pattern. Always
npx prisma db push --skip-generatethennpx prisma generate.
Quick reference — where things live#
| What | Where |
|---|---|
| Next feature number | ./bin/next-feat-number or project-state.json → nextFeatureNumber |
| Next bug number | project-state.json → nextBugNumber |
| Current doc version | project-state.json → currentDocVersion |
| Feature plans | docs/FEAT-XXX-implementation-plan.md |
| Bug log | docs/bugs.md |
| Requirements doc | docs/The_Cricket_Coach_business_plan_requirement_V20_merged_15Apr2026.md |
| Prisma schema | apps/web/prisma/schema.prisma (Neon PostgreSQL) |
| Prisma client | import prisma from "@/lib/prisma" (singleton) |
| Auth resolution | getAuthenticatedUser() from @/lib/auth-mobile (dual web/mobile) |
| Admin role checks | isAdmin(user), isSuperAdmin(user) from @/lib/admin-auth |
| Activity log | logClubActivity() from @/lib/club-activity-log |
| Audit log | logAuditAction() from @/lib/admin-auth |
| Notifications | createNotification(), sendNotification() from @/lib/notifications |
| Email templates | apps/web/src/emails/ (React Email + BaseTemplate.tsx) |
| Email delivery | Resend (primary via @/lib/resend), Nodemailer Gmail SMTP (secondary), console (fallback) |
Commit message standard#
Every commit must follow this format (enforced by .git/hooks/commit-msg):
<type>(FEAT-XXX): <summary under 72 chars>
Body (required):
- What was built or changed, in plain English
- Why — reference the phase from the implementation plan
- Any schema changes, new API routes, or new files created
- Any decisions made that aren't obvious from the diff
Phase: <phase number and name>
Affects: <comma-separated pages or API routes changed>
Types: feat, fix, refactor, test, docs, chore.
If this doesn't help#
For architectural questions, read CLAUDE.md first — it's the single source of truth, updated daily. For feature-specific questions, read docs/FEAT-XXX-implementation-plan.md. For bug context, read docs/bugs.md. For platform rules and locked decisions, read project-state.json.
If none of those answer your question, raise a ticket at https://prodemy.app/contact and tag it as admin-internal.
Was this article helpful?