Admin Panel
Manage users, monitor subscriptions, analyze growth metrics, and maintain your SaaS with a powerful admin dashboard.
RankThis includes a complete admin panel for managing your growing SaaS business with real-time analytics, user management, and subscription oversight.
🎛️ Management Tools
- • User account management
- • Subscription monitoring
- • Waitlist management
- • Revenue analytics
- • Growth metrics dashboard
- • System health monitoring
📊 Analytics Features
- • Monthly recurring revenue (MRR)
- • User growth charts
- • Subscription conversion rates
- • Churn analysis
- • Top performing features
- • Geographic user distribution
Admin Access Control
Secure admin access with role-based permissions:
1// Update user schema to include admin role2model User {3 id String @id @default(cuid())4 name String?5 email String @unique6 emailVerified DateTime?7 image String?8 role String @default("user") // Add role field910 accounts Account[]11 sessions Session[]12 subscriptions Subscription[]1314 @@map("users")15}1617// Admin role check utility18export function isAdmin(user: User | null): boolean {19 return user?.role === "admin";20}2122// Protected admin route middleware23import { auth } from "~/auth";24import { redirect } from "next/navigation";2526export async function requireAdmin() {27 const session = await auth();2829 if (!session?.user || !isAdmin(session.user)) {30 redirect("/auth/signin");31 }3233 return session.user;34}
🔐 Security Note
Manually set the first admin user in your database or create a secure setup script. Never expose admin role assignment in your UI.
Protect admin routes with authentication middleware:
1// src/app/admin/layout.tsx2import { requireAdmin } from "~/lib/auth-utils";3import { redirect } from "next/navigation";45export default async function AdminLayout({6 children,7}: {8 children: React.ReactNode;9}) {10 const user = await requireAdmin();1112 return (13 <div className="min-h-screen bg-background">14 <nav className="border-b">15 <div className="flex h-16 items-center px-6">16 <h1 className="text-xl font-semibold">Admin Dashboard</h1>17 <div className="ml-auto flex items-center gap-4">18 <span className="text-sm text-muted-foreground">19 {user.email}20 </span>21 <Badge variant="secondary">Admin</Badge>22 </div>23 </div>24 </nav>2526 <main className="p-6">27 {children}28 </main>29 </div>30 );31}
Dashboard Components
Real-time business metrics and growth analytics:
1"use client";23import { useState, useEffect } from "react";4import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";5import { useAllAnalytics } from "~/hooks/useAnalytics";67export function AnalyticsCharts() {8 const { growth, isLoading, isError } = useAllAnalytics();910 if (isLoading) {11 return <div>Loading analytics...</div>;12 }1314 if (isError || !growth.data) {15 return <div>Error loading analytics data.</div>;16 }1718 const analytics = growth.data;1920 return (21 <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4">22 {/* Key Metrics */}23 <Card>24 <CardHeader>25 <CardTitle className="text-sm font-medium">Total Revenue</CardTitle>26 </CardHeader>27 <CardContent>28 <div className="text-2xl font-bold">29 ${analytics.totalMRR.toLocaleString()}30 </div>31 <p className="text-xs text-muted-foreground">32 +{analytics.mrrGrowthRate}% from last month33 </p>34 </CardContent>35 </Card>3637 <Card>38 <CardHeader>39 <CardTitle className="text-sm font-medium">Active Users</CardTitle>40 </CardHeader>41 <CardContent>42 <div className="text-2xl font-bold">43 {analytics.totalUsers.toLocaleString()}44 </div>45 <p className="text-xs text-muted-foreground">46 +{analytics.customerGrowthRate}% from last month47 </p>48 </CardContent>49 </Card>5051 <Card>52 <CardHeader>53 <CardTitle className="text-sm font-medium">Average Revenue</CardTitle>54 </CardHeader>55 <CardContent>56 <div className="text-2xl font-bold">57 ${analytics.averageRevenuePerUser.toFixed(2)}58 </div>59 <p className="text-xs text-muted-foreground">60 Per user per month61 </p>62 </CardContent>63 </Card>6465 <Card>66 <CardHeader>67 <CardTitle className="text-sm font-medium">Lifetime Value</CardTitle>68 </CardHeader>69 <CardContent>70 <div className="text-2xl font-bold">71 ${analytics.lifetimeValue.toFixed(2)}72 </div>73 <p className="text-xs text-muted-foreground">74 Average customer LTV75 </p>76 </CardContent>77 </Card>78 </div>79 );80}
Comprehensive user management with search and filtering:
1"use client";23import { useState, useEffect } from "react";4import { Button } from "~/components/ui/button";5import { Badge } from "~/components/ui/badge";6import { Input } from "~/components/ui/input";78interface User {9 id: string;10 email: string;11 name?: string;12 role: string;13 createdAt: Date;14 subscription?: {15 status: string;16 plan: string;17 };18}1920export function UserManagement() {21 const [users, setUsers] = useState<User[]>([]);22 const [search, setSearch] = useState("");23 const [filter, setFilter] = useState("all");2425 useEffect(() => {26 fetchUsers();27 }, []);2829 async function fetchUsers() {30 const response = await fetch("/api/admin/users");31 const data = await response.json();32 setUsers(data.users);33 }3435 const filteredUsers = users.filter(user => {36 const matchesSearch = user.email.toLowerCase().includes(search.toLowerCase()) ||37 user.name?.toLowerCase().includes(search.toLowerCase());3839 const matchesFilter = filter === "all" ||40 (filter === "subscribed" && user.subscription) ||41 (filter === "free" && !user.subscription);4243 return matchesSearch && matchesFilter;44 });4546 return (47 <div className="space-y-4">48 {/* Filters */}49 <div className="flex gap-4">50 <Input51 placeholder="Search users..."52 value={search}53 onChange={(e) => setSearch(e.target.value)}54 className="max-w-sm"55 />5657 <select58 value={filter}59 onChange={(e) => setFilter(e.target.value)}60 className="rounded border px-3 py-2"61 >62 <option value="all">All Users</option>63 <option value="subscribed">Subscribed</option>64 <option value="free">Free Users</option>65 </select>66 </div>6768 {/* Users Table */}69 <div className="overflow-x-auto">70 <table className="w-full border-collapse border">71 <thead>72 <tr className="border-b bg-muted/50">73 <th className="p-3 text-left">User</th>74 <th className="p-3 text-left">Status</th>75 <th className="p-3 text-left">Plan</th>76 <th className="p-3 text-left">Joined</th>77 <th className="p-3 text-left">Actions</th>78 </tr>79 </thead>80 <tbody>81 {filteredUsers.map((user) => (82 <tr key={user.id} className="border-b">83 <td className="p-3">84 <div>85 <div className="font-medium">{user.name || "Anonymous"}</div>86 <div className="text-sm text-muted-foreground">{user.email}</div>87 </div>88 </td>89 <td className="p-3">90 <Badge variant={user.subscription ? "default" : "secondary"}>91 {user.subscription?.status || "Free"}92 </Badge>93 </td>94 <td className="p-3">95 {user.subscription?.plan || "-"}96 </td>97 <td className="p-3 text-sm text-muted-foreground">98 {new Date(user.createdAt).toLocaleDateString()}99 </td>100 <td className="p-3">101 <Button variant="outline" size="sm">102 View Details103 </Button>104 </td>105 </tr>106 ))}107 </tbody>108 </table>109 </div>110 </div>111 );112}
Admin Actions
Powerful server actions for admin operations:
1"use server";23import { db } from "~/server/db";4import { requireAdmin } from "~/lib/auth-utils";5import { revalidatePath } from "next/cache";67export async function getAdminAnalytics() {8 await requireAdmin();910 const [11 totalUsers,12 activeSubscriptions,13 totalRevenue,14 waitlistCount,15 ] = await Promise.all([16 db.user.count(),17 db.subscription.count({ where: { status: "active" } }),18 db.subscription.aggregate({19 where: { status: "active" },20 _sum: { amount: true },21 }),22 db.waitlistEntry.count({ where: { isActive: true } }),23 ]);2425 // Calculate monthly growth26 const lastMonth = new Date();27 lastMonth.setMonth(lastMonth.getMonth() - 1);2829 const newUsersThisMonth = await db.user.count({30 where: { createdAt: { gte: lastMonth } },31 });3233 return {34 totalUsers,35 activeSubscriptions,36 totalRevenue: totalRevenue._sum.amount || 0,37 waitlistCount,38 newUsersThisMonth,39 userGrowth: Math.round((newUsersThisMonth / Math.max(totalUsers - newUsersThisMonth, 1)) * 100),40 };41}4243export async function getUsersForAdmin(page: number = 1, limit: number = 50) {44 await requireAdmin();4546 const users = await db.user.findMany({47 take: limit,48 skip: (page - 1) * limit,49 orderBy: { createdAt: "desc" },50 include: {51 subscriptions: {52 where: { status: "active" },53 select: { status: true, plan: true },54 },55 },56 });5758 const total = await db.user.count();5960 return {61 users: users.map(user => ({62 ...user,63 subscription: user.subscriptions[0] || null,64 })),65 pagination: {66 page,67 limit,68 total,69 pages: Math.ceil(total / limit),70 },71 };72}7374export async function updateUserRole(userId: string, role: string) {75 await requireAdmin();7677 await db.user.update({78 where: { id: userId },79 data: { role },80 });8182 revalidatePath("/admin");83 return { success: true };84}8586export async function deactivateUser(userId: string) {87 await requireAdmin();8889 // Cancel active subscriptions90 await db.subscription.updateMany({91 where: {92 userId,93 status: "active",94 },95 data: { status: "canceled" },96 });9798 // Optionally delete user data or mark as inactive99 await db.user.update({100 where: { id: userId },101 data: { role: "inactive" },102 });103104 revalidatePath("/admin");105 return { success: true };106}
Custom hook for real-time analytics data:
1"use client";23import { useState, useEffect } from "react";45interface AnalyticsData {6 totalUsers: number;7 activeSubscriptions: number;8 totalRevenue: number;9 waitlistCount: number;10 userGrowth: number;11 revenueGrowth: number;12 churnRate: number;13}1415export function useAnalytics() {16 const [analytics, setAnalytics] = useState<AnalyticsData | null>(null);17 const [isLoading, setIsLoading] = useState(true);18 const [error, setError] = useState<string | null>(null);1920 useEffect(() => {21 async function fetchAnalytics() {22 try {23 const response = await fetch("/api/admin/analytics");2425 if (!response.ok) {26 throw new Error("Failed to fetch analytics");27 }2829 const data = await response.json();30 setAnalytics(data);31 setError(null);32 } catch (err) {33 setError(err instanceof Error ? err.message : "Unknown error");34 } finally {35 setIsLoading(false);36 }37 }3839 fetchAnalytics();4041 // Refresh analytics every 5 minutes42 const interval = setInterval(fetchAnalytics, 5 * 60 * 1000);4344 return () => clearInterval(interval);45 }, []);4647 return { analytics, isLoading, error };48}
Admin API Endpoints
Secure API endpoints for admin operations:
1// GET /api/admin/analytics - Get business analytics2export async function GET() {3 try {4 const analytics = await getAdminAnalytics();5 return Response.json(analytics);6 } catch (error) {7 return Response.json(8 { error: "Unauthorized" },9 { status: 401 }10 );11 }12}1314// GET /api/admin/users - Get paginated users15export async function GET(request: Request) {16 const { searchParams } = new URL(request.url);17 const page = parseInt(searchParams.get("page") || "1");18 const limit = parseInt(searchParams.get("limit") || "50");1920 try {21 const result = await getUsersForAdmin(page, limit);22 return Response.json(result);23 } catch (error) {24 return Response.json(25 { error: "Unauthorized" },26 { status: 401 }27 );28 }29}3031// POST /api/admin/users/[id]/role - Update user role32export async function POST(33 request: Request,34 { params }: { params: { id: string } }35) {36 const { role } = await request.json();3738 try {39 await updateUserRole(params.id, role);40 return Response.json({ success: true });41 } catch (error) {42 return Response.json(43 { error: "Unauthorized" },44 { status: 401 }45 );46 }47}4849// DELETE /api/admin/users/[id] - Deactivate user50export async function DELETE(51 request: Request,52 { params }: { params: { id: string } }53) {54 try {55 await deactivateUser(params.id);56 return Response.json({ success: true });57 } catch (error) {58 return Response.json(59 { error: "Unauthorized" },60 { status: 401 }61 );62 }63}
Authentication
- • Always verify admin role server-side
- • Use session-based authentication
- • Implement rate limiting on admin APIs
- • Log all admin actions for audit
Data Protection
- • Never expose sensitive user data
- • Hash/encrypt sensitive information
- • Implement proper data retention
- • Follow GDPR/privacy regulations
Key Metrics to Monitor
- • Failed payment rates
- • User churn patterns
- • System performance metrics
- • Error rates and exceptions
Alert Setup Example
1// Set up alerts for critical metrics2if (analytics.churnRate > 10) {3 await sendSlackAlert('High churn rate detected: ' + analytics.churnRate + '%');4}56if (analytics.revenueGrowth < -5) {7 await sendEmailAlert('Revenue declining', adminEmails);8}
Admin Panel Complete!
Your admin dashboard is ready for managing users, monitoring growth, and scaling your SaaS. Next, set up the blog system for content marketing.