Skip to main content

Admin Dashboard Plugin

A complete admin panel with user management, settings, and plugin configuration.

Features

  • User management (CRUD)
  • Role-based access control
  • System settings
  • Plugin configuration
  • Dashboard with stats
  • Activity logging

Installation

Included by default with most archetypes:

npx autodeploybase init my-app --archetype saas

Or add manually:

npx autodeploybase add admin-dashboard

Generated Pages

RouteDescription
/adminDashboard overview
/admin/usersUser management
/admin/users/[id]Edit user
/admin/settingsSystem settings
/admin/settings/pluginsPlugin settings

Access Control

Only users with ADMIN or SUPER_ADMIN roles can access admin pages.

Middleware Protection

middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verifyToken } from '@/lib/auth';

export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/admin')) {
const token = request.cookies.get('accessToken');

if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}

const user = await verifyToken(token.value);

if (!user || !['ADMIN', 'SUPER_ADMIN'].includes(user.role)) {
return NextResponse.redirect(new URL('/unauthorized', request.url));
}
}

return NextResponse.next();
}

Dashboard Overview

The main dashboard shows:

  • User statistics
  • Recent activity
  • System health
  • Quick actions
app/admin/page.tsx
import { AdminStats } from '@/components/admin/AdminStats';
import { RecentActivity } from '@/components/admin/RecentActivity';
import { QuickActions } from '@/components/admin/QuickActions';

export default async function AdminDashboard() {
return (
<div className="space-y-6">
<h1 className="text-2xl font-bold">Dashboard</h1>

<AdminStats />

<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<RecentActivity />
<QuickActions />
</div>
</div>
);
}

User Management

List Users

app/admin/users/page.tsx
import { prisma } from '@/lib/db';
import { UsersTable } from '@/components/admin/UsersTable';

export default async function UsersPage() {
const users = await prisma.user.findMany({
orderBy: { createdAt: 'desc' },
select: {
id: true,
email: true,
name: true,
role: true,
createdAt: true
}
});

return (
<div>
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold">Users</h1>
<a href="/admin/users/new" className="btn btn-primary">
Add User
</a>
</div>

<UsersTable users={users} />
</div>
);
}

Edit User

app/admin/users/[id]/page.tsx
import { prisma } from '@/lib/db';
import { UserForm } from '@/components/admin/UserForm';
import { notFound } from 'next/navigation';

export default async function EditUserPage({ params }: { params: { id: string } }) {
const user = await prisma.user.findUnique({
where: { id: params.id }
});

if (!user) {
notFound();
}

return (
<div>
<h1 className="text-2xl font-bold mb-6">Edit User</h1>
<UserForm user={user} />
</div>
);
}

Settings

System Settings

app/admin/settings/page.tsx
import { getSettings } from '@/lib/settings';
import { SettingsForm } from '@/components/admin/SettingsForm';

export default async function SettingsPage() {
const settings = await getSettings();

return (
<div>
<h1 className="text-2xl font-bold mb-6">Settings</h1>

<div className="space-y-6">
<SettingsForm section="general" settings={settings.general} />

<SettingsForm section="email" settings={settings.email} />

<SettingsForm section="security" settings={settings.security} />
</div>
</div>
);
}

Plugin Settings

app/admin/settings/plugins/page.tsx
import { getPlugins } from '@/lib/plugins';
import { PluginSettingsCard } from '@/components/admin/PluginSettingsCard';

export default async function PluginSettingsPage() {
const plugins = await getPlugins();

return (
<div>
<h1 className="text-2xl font-bold mb-6">Plugin Settings</h1>

<div className="space-y-4">
{plugins.map(plugin => (
<PluginSettingsCard key={plugin.id} plugin={plugin} />
))}
</div>
</div>
);
}

Components

AdminLayout

components/admin/AdminLayout.tsx
import { AdminSidebar } from './AdminSidebar';
import { AdminHeader } from './AdminHeader';

export function AdminLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-screen">
<AdminSidebar />

<div className="flex-1 flex flex-col">
<AdminHeader />

<main className="flex-1 p-6 overflow-auto bg-gray-50">{children}</main>
</div>
</div>
);
}

AdminSidebar

components/admin/AdminSidebar.tsx
import { getAdminMenus } from '@/lib/admin';
import { SidebarItem } from './SidebarItem';

export async function AdminSidebar() {
const menus = await getAdminMenus();

return (
<aside className="w-64 bg-gray-900 text-white">
<div className="p-4">
<h2 className="text-xl font-bold">Admin</h2>
</div>

<nav className="mt-4">
{menus.map(menu => (
<SidebarItem key={menu.id} item={menu} />
))}
</nav>
</aside>
);
}

DataTable

components/admin/DataTable.tsx
interface Column<T> {
key: keyof T;
label: string;
render?: (value: T[keyof T], row: T) => React.ReactNode;
}

interface DataTableProps<T> {
columns: Column<T>[];
data: T[];
onRowClick?: (row: T) => void;
}

export function DataTable<T>({ columns, data, onRowClick }: DataTableProps<T>) {
return (
<table className="w-full">
<thead>
<tr className="border-b">
{columns.map(col => (
<th key={String(col.key)} className="text-left p-3">
{col.label}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, i) => (
<tr
key={i}
onClick={() => onRowClick?.(row)}
className="border-b hover:bg-gray-50 cursor-pointer"
>
{columns.map(col => (
<td key={String(col.key)} className="p-3">
{col.render ? col.render(row[col.key], row) : String(row[col.key])}
</td>
))}
</tr>
))}
</tbody>
</table>
);
}

API Endpoints

Users API

app/api/admin/users/route.ts
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/db';
import { requireAdmin } from '@/lib/auth';

export async function GET(request: Request) {
await requireAdmin(request);

const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1');
const limit = parseInt(searchParams.get('limit') || '10');

const [users, total] = await Promise.all([
prisma.user.findMany({
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' },
select: {
id: true,
email: true,
name: true,
role: true,
createdAt: true
}
}),
prisma.user.count()
]);

return NextResponse.json({
users,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
});
}

Settings API

app/api/admin/settings/route.ts
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/db';
import { requireAdmin } from '@/lib/auth';

export async function GET(request: Request) {
await requireAdmin(request);

const settings = await prisma.setting.findMany();

return NextResponse.json(settings.reduce((acc, s) => ({ ...acc, [s.key]: s.value }), {}));
}

export async function PUT(request: Request) {
await requireAdmin(request);

const body = await request.json();

await Promise.all(
Object.entries(body).map(([key, value]) =>
prisma.setting.upsert({
where: { key },
update: { value: value as string },
create: { key, value: value as string }
})
)
);

return NextResponse.json({ success: true });
}

Customization

Custom Dashboard Widgets

// Register in your plugin
await sdk.registerDashboardWidget({
id: 'my-widget',
label: 'My Widget',
component: '@/plugins/my-plugin/widgets/MyWidget',
size: 'medium'
});

Custom Menu Items

await sdk.registerAdminMenu({
id: 'my-section',
label: 'My Section',
icon: 'Star',
href: '/admin/my-section'
});

Theme Customization

globals.css
:root {
--admin-sidebar-bg: #1a1a2e;
--admin-sidebar-text: #ffffff;
--admin-header-bg: #ffffff;
--admin-accent: #6366f1;
}