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
| Route | Description |
|---|---|
/admin | Dashboard overview |
/admin/users | User management |
/admin/users/[id] | Edit user |
/admin/settings | System settings |
/admin/settings/plugins | Plugin 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;
}