Skip to content

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

  1. Role Verification: Always verify admin status server-side
  2. Action Logging: Log all admin actions for audit trail
  3. Rate Limiting: Implement stricter limits for admin endpoints
  4. 2FA Requirement: Consider requiring 2FA for admin accounts
  5. 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.

Built with ❤️ for the AI Coding community, by Praney Behl