add-admin-feature
Access: /template add-admin-feature or /t add-admin-feature
Creates admin-only features and dashboards for managing users, teams, subscriptions, and system settings in your MakerKit application.
What It Does
The add-admin-feature command helps you create:
- Admin dashboard with system metrics
- User and team management interfaces
- Subscription and billing oversight
- System configuration panels
- Audit logs and activity monitoring
- Admin-only API endpoints
Usage
bash
/template add-admin-feature "Admin feature description"
# or
/t add-admin-feature "Admin feature description"When prompted, specify:
- Feature type (users, teams, billing, settings, analytics)
- Admin permission requirements
- Dashboard components needed
- Data export capabilities
Prerequisites
- A MakerKit project with authentication
- Admin role configured in the database
- Commercial MakerKit license from MakerKit
What Gets Created
1. Admin Role and Permissions
Database setup for admin access:
sql
-- Add admin role if not exists
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'app_role') THEN
CREATE TYPE app_role AS ENUM ('super_admin', 'admin', 'user');
END IF;
END$$;
-- Admin users table
create table if not exists public.admin_users (
user_id uuid primary key references auth.users(id) on delete cascade,
role app_role not null default 'admin',
permissions text[] default '{}',
created_at timestamptz default now(),
created_by uuid references auth.users(id)
);
-- RLS policies for admin access
create policy "Admins can view all data"
on public.users for select
to authenticated
using (
exists (
select 1 from public.admin_users
where user_id = auth.uid()
and role in ('admin', 'super_admin')
)
);2. Admin Middleware
Protect admin routes:
typescript
// middleware.ts
import { NextResponse } from 'next/server';
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs';
export async function middleware(request: NextRequest) {
const res = NextResponse.next();
const supabase = createMiddlewareClient({ req: request, res });
const { data: { session } } = await supabase.auth.getSession();
// Check admin routes
if (request.nextUrl.pathname.startsWith('/admin')) {
if (!session) {
return NextResponse.redirect(new URL('/auth/sign-in', request.url));
}
// Check admin status
const { data: adminUser } = await supabase
.from('admin_users')
.select('role')
.eq('user_id', session.user.id)
.single();
if (!adminUser || !['admin', 'super_admin'].includes(adminUser.role)) {
return NextResponse.redirect(new URL('/404', request.url));
}
}
return res;
}
export const config = {
matcher: ['/admin/:path*'],
};3. Admin Dashboard
Main admin interface:
typescript
// app/admin/page.tsx
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import {
Users,
DollarSign,
Activity,
AlertCircle
} from 'lucide-react';
export default async function AdminDashboard() {
const stats = await getAdminStats();
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">Admin Dashboard</h1>
<p className="text-muted-foreground">
System overview and management
</p>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<StatsCard
title="Total Users"
value={stats.totalUsers}
icon={<Users />}
change={stats.userGrowth}
/>
<StatsCard
title="Active Subscriptions"
value={stats.activeSubscriptions}
icon={<DollarSign />}
change={stats.revenueGrowth}
/>
<StatsCard
title="API Calls Today"
value={stats.apiCallsToday}
icon={<Activity />}
change={stats.apiGrowth}
/>
<StatsCard
title="System Alerts"
value={stats.systemAlerts}
icon={<AlertCircle />}
variant={stats.systemAlerts > 0 ? 'warning' : 'default'}
/>
</div>
<div className="grid gap-6 md:grid-cols-2">
<RecentUsersCard />
<RevenueChartCard />
</div>
</div>
);
}4. User Management Interface
Admin user management:
typescript
// app/admin/users/page.tsx
'use client';
import { DataTable } from '@kit/ui/data-table';
import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@kit/ui/dropdown-menu';
import { MoreHorizontal } from 'lucide-react';
const columns: ColumnDef<User>[] = [
{
accessorKey: 'email',
header: 'Email',
cell: ({ row }) => (
<div>
<div className="font-medium">{row.original.email}</div>
<div className="text-sm text-muted-foreground">
{row.original.display_name}
</div>
</div>
),
},
{
accessorKey: 'created_at',
header: 'Joined',
cell: ({ row }) => formatDate(row.original.created_at),
},
{
accessorKey: 'subscription',
header: 'Plan',
cell: ({ row }) => (
<Badge variant={row.original.subscription ? 'default' : 'secondary'}>
{row.original.subscription?.plan || 'Free'}
</Badge>
),
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => (
<Badge
variant={row.original.banned ? 'destructive' : 'success'}
>
{row.original.banned ? 'Banned' : 'Active'}
</Badge>
),
},
{
id: 'actions',
cell: ({ row }) => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => viewUserDetails(row.original)}>
View Details
</DropdownMenuItem>
<DropdownMenuItem onClick={() => impersonateUser(row.original)}>
Impersonate
</DropdownMenuItem>
<DropdownMenuItem onClick={() => resetPassword(row.original)}>
Reset Password
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => toggleBan(row.original)}
className="text-destructive"
>
{row.original.banned ? 'Unban' : 'Ban'} User
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
),
},
];
export default function AdminUsersPage() {
const { data: users, isLoading } = useAdminUsers();
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold">User Management</h1>
<div className="flex gap-2">
<Button variant="outline" onClick={exportUsers}>
Export CSV
</Button>
<Button onClick={() => setShowInviteDialog(true)}>
Invite User
</Button>
</div>
</div>
<DataTable
columns={columns}
data={users || []}
loading={isLoading}
searchKey="email"
filters={[
{
column: 'subscription',
title: 'Plan',
options: ['Free', 'Basic', 'Pro', 'Enterprise'],
},
{
column: 'status',
title: 'Status',
options: ['Active', 'Banned'],
},
]}
/>
</div>
);
}5. Team Management
Admin team oversight:
typescript
// app/admin/teams/_components/team-management.tsx
export function TeamManagement() {
const { data: teams } = useAdminTeams();
return (
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Team Statistics</CardTitle>
</CardHeader>
<CardContent>
<div className="grid gap-4 md:grid-cols-3">
<div>
<div className="text-2xl font-bold">{teams?.length || 0}</div>
<p className="text-sm text-muted-foreground">Total Teams</p>
</div>
<div>
<div className="text-2xl font-bold">
{teams?.filter(t => t.subscription).length || 0}
</div>
<p className="text-sm text-muted-foreground">Paid Teams</p>
</div>
<div>
<div className="text-2xl font-bold">
{calculateTotalMembers(teams)}
</div>
<p className="text-sm text-muted-foreground">Total Members</p>
</div>
</div>
</CardContent>
</Card>
<DataTable
columns={teamColumns}
data={teams || []}
searchKey="name"
/>
</div>
);
}6. Billing Management
Subscription oversight:
typescript
// app/admin/billing/page.tsx
export default function AdminBillingPage() {
const { data: metrics } = useBillingMetrics();
return (
<div className="space-y-6">
<BillingMetrics metrics={metrics} />
<Tabs defaultValue="subscriptions">
<TabsList>
<TabsTrigger value="subscriptions">Subscriptions</TabsTrigger>
<TabsTrigger value="invoices">Invoices</TabsTrigger>
<TabsTrigger value="failed">Failed Payments</TabsTrigger>
</TabsList>
<TabsContent value="subscriptions">
<SubscriptionsList />
</TabsContent>
<TabsContent value="invoices">
<InvoicesList />
</TabsContent>
<TabsContent value="failed">
<FailedPaymentsList />
</TabsContent>
</Tabs>
</div>
);
}7. System Settings
Configuration management:
typescript
// app/admin/settings/page.tsx
export default function AdminSettingsPage() {
return (
<Tabs defaultValue="general">
<TabsList>
<TabsTrigger value="general">General</TabsTrigger>
<TabsTrigger value="email">Email</TabsTrigger>
<TabsTrigger value="security">Security</TabsTrigger>
<TabsTrigger value="features">Features</TabsTrigger>
</TabsList>
<TabsContent value="general">
<GeneralSettings />
</TabsContent>
<TabsContent value="email">
<EmailSettings />
</TabsContent>
<TabsContent value="security">
<SecuritySettings />
</TabsContent>
<TabsContent value="features">
<FeatureFlags />
</TabsContent>
</Tabs>
);
}8. Audit Log
Track admin actions:
typescript
// lib/admin/audit-log.ts
export async function logAdminAction({
userId,
action,
resourceType,
resourceId,
metadata,
}: AuditLogEntry) {
const client = getSupabaseServerClient();
await client.from('admin_audit_log').insert({
user_id: userId,
action,
resource_type: resourceType,
resource_id: resourceId,
metadata,
ip_address: getClientIp(),
user_agent: getUserAgent(),
created_at: new Date().toISOString(),
});
}
// Usage in admin actions
export const banUserAction = enhanceAction(
async ({ userId, reason }, admin) => {
// Ban user
await banUser(userId);
// Log action
await logAdminAction({
userId: admin.id,
action: 'user.banned',
resourceType: 'user',
resourceId: userId,
metadata: { reason },
});
return { success: true };
},
{
auth: true,
adminOnly: true,
schema: BanUserSchema,
}
);9. Admin API Routes
Protected API endpoints:
typescript
// app/api/admin/stats/route.ts
import { withAdminAuth } from '@/lib/admin/auth';
export const GET = withAdminAuth(async (request) => {
const stats = await getSystemStats();
return NextResponse.json({
users: stats.users,
revenue: stats.revenue,
growth: stats.growth,
health: stats.health,
});
});Admin Navigation
Separate admin navigation:
typescript
// config/admin-navigation.config.tsx
export const adminNavigation = [
{
label: 'Dashboard',
href: '/admin',
icon: <LayoutDashboard />,
},
{
label: 'Users',
href: '/admin/users',
icon: <Users />,
},
{
label: 'Teams',
href: '/admin/teams',
icon: <Building />,
},
{
label: 'Billing',
href: '/admin/billing',
icon: <CreditCard />,
},
{
label: 'Analytics',
href: '/admin/analytics',
icon: <BarChart />,
},
{
label: 'Settings',
href: '/admin/settings',
icon: <Settings />,
},
{
label: 'Audit Log',
href: '/admin/audit',
icon: <FileText />,
},
];Security Considerations
- Role Verification: Always verify admin status server-side
- Action Logging: Log all admin actions for audit trail
- Rate Limiting: Implement stricter limits for admin endpoints
- 2FA Requirement: Consider requiring 2FA for admin accounts
- IP Whitelisting: Option to restrict admin access by IP
Testing Admin Features
typescript
describe('Admin Features', () => {
it('restricts access to admin users only', async () => {
const regularUser = await createTestUser();
const response = await fetch('/api/admin/stats', {
headers: { Authorization: `Bearer ${regularUser.token}` },
});
expect(response.status).toBe(403);
});
it('logs admin actions', async () => {
const admin = await createTestAdmin();
await banUserAction({ userId: 'user-123', reason: 'Spam' }, admin);
const logs = await getAuditLogs({ userId: admin.id });
expect(logs[0].action).toBe('user.banned');
});
});License Requirement
Important: This command requires a commercial MakerKit license from https://makerkit.dev?atp=MqaGgc MakerKit is a premium SaaS starter kit and requires proper licensing for commercial use.
