Admin Panel
Manage users, monitor subscriptions, and analyze growth metrics.
Management
- • User account management
- • Subscription monitoring
- • Waitlist management
- • Revenue analytics
Analytics
- • Monthly recurring revenue
- • User growth charts
- • Conversion rates
- • Churn analysis
Adding Admins
Admin access is controlled via the ADMIN_EMAILS environment variable. Add a comma-separated list of admin emails:
# Single adminADMIN_EMAILS="you@example.com"# Multiple adminsADMIN_EMAILS="you@example.com,cofounder@example.com,dev@example.com"
Local Development
Add to your .env file in the project root.
Production (Vercel)
Add via Settings → Environment Variables in your Vercel dashboard. Changes take effect on next deployment.
Benefits: No code changes needed to add/remove admins. Different admin lists per environment (dev vs production).
How It Works
The checkIsAdmin() function reads from the environment variable and compares against the logged-in user's email:
1// src/server/actions/admin.ts2export async function checkIsAdmin(): Promise<boolean> {3 const session = await auth();4 if (!session?.user?.email) return false;56 // Read from ADMIN_EMAILS environment variable7 const adminEmailsEnv = process.env.ADMIN_EMAILS ?? "";8 const adminEmails = adminEmailsEnv9 .split(",")10 .map((email) => email.trim().toLowerCase())11 .filter((email) => email.length > 0);1213 return adminEmails.includes(session.user.email.toLowerCase());14}
Route Protection
Admin routes are protected server-side. The check happens on every request:
1// src/app/admin/page.tsx2import { auth } from "~/auth";3import { checkIsAdmin } from "~/server/actions/admin";4import { redirect } from "next/navigation";56export default async function AdminPage() {7 const session = await auth();8 if (!session) redirect("/auth/signin");910 const isAdmin = await checkIsAdmin();11 if (!isAdmin) return <div>Access Denied</div>;1213 return <AdminDashboard />;14}
Admin status is determined by environment variables only. Never expose admin assignment in the UI.
Key Metrics
1"use server";23export async function getAdminAnalytics() {4 await requireAdmin();56 const [totalUsers, activeSubscriptions, waitlistCount] = await Promise.all([7 db.user.count(),8 db.subscription.count({ where: { status: "active" } }),9 db.waitlistEntry.count({ where: { isActive: true } }),10 ]);1112 const lastMonth = new Date();13 lastMonth.setMonth(lastMonth.getMonth() - 1);1415 const newUsersThisMonth = await db.user.count({16 where: { createdAt: { gte: lastMonth } },17 });1819 return {20 totalUsers,21 activeSubscriptions,22 waitlistCount,23 newUsersThisMonth,24 userGrowth: Math.round(25 (newUsersThisMonth / Math.max(totalUsers - newUsersThisMonth, 1)) * 10026 ),27 };28}
User Management
1"use server";23export async function getUsersForAdmin(page = 1, limit = 50) {4 await requireAdmin();56 const [users, total] = await Promise.all([7 db.user.findMany({8 include: { subscription: { select: { type: true, status: true } } },9 orderBy: { createdAt: "desc" },10 skip: (page - 1) * limit,11 take: limit,12 }),13 db.user.count(),14 ]);1516 return {17 users,18 pagination: { page, limit, total, pages: Math.ceil(total / limit) },19 };20}
Security
Authentication
- • Verify admin role server-side
- • Use session-based auth
- • Implement rate limiting
- • Log all admin actions
Data Protection
- • Never expose sensitive data
- • Encrypt sensitive info
- • Proper data retention
- • Follow GDPR/privacy laws
Next: Account Management
User profile and billing pages