Admin System & User Roles
LaunchKit includes a comprehensive admin system with role-based access control. This guide covers setting up admin users, using the admin interface, and implementing custom admin functionality.
Overview
Section titled “Overview”The admin system provides:
- Role-based access control with the
useIsAdmin
hook - Admin dashboard with user, product, and order management
- Analytics and reporting tools
- Bulk operations for managing data
- Security features with proper authorization
Admin User Setup
Section titled “Admin User Setup”1. Creating Your First Admin User
Section titled “1. Creating Your First Admin User”Direct database update (recommended for first admin):
-- Connect to your Supabase database and run:
UPDATE profiles
SET is_admin = true
WHERE email = '[email protected]';
Verify the update:
SELECT email, is_admin, created_at
FROM profiles
WHERE is_admin = true;
Once you have at least one admin, you can promote others:
- Go to
/dashboard/admin/users
- Find the user you want to promote
- Click the “Make Admin” button
- Confirm the action
Programmatically:
const promoteToAdmin = async (userId: string) => {
const { error } = await supabase
.from('profiles')
.update({ is_admin: true })
.eq('id', userId);
if (error) throw error;
// Log the action for audit trail
console.log(`User ${userId} promoted to admin`);
};
2. Admin User Requirements
Section titled “2. Admin User Requirements”To become an admin, a user must:
- Have a verified email address
- Be an existing user with a profile record
- Be promoted by another admin or via database
Admin users can:
- Access
/dashboard/admin
routes - Manage other users and their permissions
- View and export all data
- Manage products and pricing
- Access analytics and reports
- Create and manage other admins
Using the useIsAdmin Hook
Section titled “Using the useIsAdmin Hook”1. Hook Implementation
Section titled “1. Hook Implementation”The useIsAdmin
hook (/hooks/use-admin.tsx
) provides real-time admin status:
'use client';
import { useState, useEffect } from 'react';
import { createClient } from '@/libs/supabase/client';
import { User } from '@supabase/supabase-js';
export function useIsAdmin() {
const [isAdmin, setIsAdmin] = useState<boolean | null>(null);
const [loading, setLoading] = useState(true);
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const supabase = createClient();
const checkAdminStatus = async () => {
try {
// Get current user
const { data: { user: currentUser } } = await supabase.auth.getUser();
setUser(currentUser);
if (!currentUser) {
setIsAdmin(false);
setLoading(false);
return;
}
// Check admin status in profiles table
const { data: profile } = await supabase
.from('profiles')
.select('is_admin')
.eq('id', currentUser.id)
.single();
setIsAdmin(profile?.is_admin || false);
} catch (error) {
console.error('Error checking admin status:', error);
setIsAdmin(false);
} finally {
setLoading(false);
}
};
checkAdminStatus();
// Listen for auth changes
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_OUT') {
setIsAdmin(false);
setUser(null);
setLoading(false);
} else if (event === 'SIGNED_IN' && session?.user) {
setUser(session.user);
checkAdminStatus();
}
});
return () => subscription.unsubscribe();
}, []);
return { isAdmin, loading, user };
}
2. Hook Usage Examples
Section titled “2. Hook Usage Examples”'use client';
import { useIsAdmin } from '@/hooks/use-admin';
function AdminOnlyComponent() {
const { isAdmin, loading, user } = useIsAdmin();
if (loading) {
return (
<div className="flex items-center justify-center p-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
);
}
if (!isAdmin) {
return (
<div className="text-center p-8">
<h2 className="text-xl font-semibold text-gray-900 mb-2">
Access Denied
</h2>
<p className="text-gray-600">
You need admin privileges to view this content.
</p>
</div>
);
}
return (
<div>
<h1>Admin Dashboard</h1>
<p>Welcome, {user?.email}</p>
{/* Admin-only content */}
</div>
);
}
'use client';
import { useIsAdmin } from '@/hooks/use-admin';
function NavigationMenu() {
const { isAdmin, loading } = useIsAdmin();
return (
<nav>
<ul>
<li><Link href="/dashboard">Dashboard</Link></li>
<li><Link href="/dashboard/products">Products</Link></li>
<li><Link href="/dashboard/settings">Settings</Link></li>
{/* Show admin menu only to admins */}
{!loading && isAdmin && (
<li>
<Link href="/dashboard/admin" className="text-orange-600 font-semibold">
Admin Panel
</Link>
</li>
)}
</ul>
</nav>
);
}
// middleware.ts - Route protection
import { createServerClient } from '@supabase/ssr';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export async function middleware(request: NextRequest) {
let response = NextResponse.next();
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
response.cookies.set(name, value, options)
);
},
},
}
);
// Check auth for admin routes
if (request.nextUrl.pathname.startsWith('/dashboard/admin')) {
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
return NextResponse.redirect(new URL('/signin', request.url));
}
// Check admin status
const { data: profile } = await supabase
.from('profiles')
.select('is_admin')
.eq('id', user.id)
.single();
if (!profile?.is_admin) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
}
return response;
}
export const config = {
matcher: ['/dashboard/admin/:path*'],
};
Admin Dashboard Features
Section titled “Admin Dashboard Features”1. User Management (/dashboard/admin/users
)
Section titled “1. User Management (/dashboard/admin/users)”Features:
- User list with search and filtering
- User profile editing
- Admin promotion/demotion
- Account status management
- Bulk operations
Key Components:
AdminUsersTable.tsx
: Main user management interfaceAdminUsersStatsManager.tsx
: User analyticsUserStatsCards.tsx
: Overview metrics
Example Operations:
// Promote user to admin
const promoteUser = async (userId: string) => {
const { error } = await supabase
.from('profiles')
.update({ is_admin: true })
.eq('id', userId);
if (error) throw error;
};
// Grant product access
const grantAccess = async (userId: string) => {
const { error } = await supabase
.from('profiles')
.update({ has_access: true })
.eq('id', userId);
if (error) throw error;
};
// Get user statistics
const getUserStats = async () => {
const { data, error } = await supabase
.from('profiles')
.select('id, created_at, has_access, is_admin, last_login');
if (error) throw error;
return {
total: data.length,
active: data.filter(u => u.has_access).length,
admins: data.filter(u => u.is_admin).length,
newThisMonth: data.filter(u =>
new Date(u.created_at) > new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
).length,
};
};
2. Product Management (/dashboard/admin/products
)
Section titled “2. Product Management (/dashboard/admin/products)”Features:
- Product creation and editing
- Pricing configuration
- Feature management
- Stripe integration
- Bulk operations
Key Components:
AdminProductsTable.tsx
: Product listing and managementProductForm.tsx
: Product creation/editing form
Product Operations:
// Create new product
const createProduct = async (productData: {
name: string;
description: string;
price: number;
stripe_price_id: string;
type: 'github' | 'link';
billing_type: 'one_time' | 'subscription';
features: string[];
}) => {
const { data, error } = await supabase
.from('products')
.insert({
...productData,
features: JSON.stringify(productData.features),
is_active: true,
created_at: new Date().toISOString(),
})
.select()
.single();
if (error) throw error;
return data;
};
// Update product
const updateProduct = async (productId: string, updates: Partial<Product>) => {
const { error } = await supabase
.from('products')
.update({
...updates,
updated_at: new Date().toISOString(),
})
.eq('id', productId);
if (error) throw error;
};
3. Order Management (/dashboard/admin/orders
)
Section titled “3. Order Management (/dashboard/admin/orders)”Features:
- Order tracking and status updates
- Revenue analytics
- Refund processing
- Customer communication
Key Components:
OrdersTable.tsx
: Order listing and managementOrdersCharts.tsx
: Revenue analyticsMMRChart.tsx
: Monthly recurring revenueOrderDetailsSheet.tsx
: Detailed order view
4. Lead Management (/dashboard/admin/leads
)
Section titled “4. Lead Management (/dashboard/admin/leads)”Features:
- Lead capture tracking
- Email export functionality
- Conversion analytics
- Marketing campaign integration
Key Components:
AdminLeadsTable.tsx
: Lead management interface
Lead Operations:
// Get all leads
const getLeads = async () => {
const { data, error } = await supabase
.from('leads')
.select('*')
.order('created_at', { ascending: false });
if (error) throw error;
return data;
};
// Export leads to CSV
const exportLeads = async () => {
const leads = await getLeads();
const csv = [
['Name', 'Email', 'Created At'],
...leads.map(lead => [lead.name || '', lead.email, lead.created_at])
].map(row => row.join(',')).join('\n');
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'leads.csv';
a.click();
};
Custom Admin Components
Section titled “Custom Admin Components”1. Creating Admin-Only Features
Section titled “1. Creating Admin-Only Features”'use client';
import { useIsAdmin } from '@/hooks/use-admin';
interface AdminFeatureProps {
children: React.ReactNode;
fallback?: React.ReactNode;
requireSuperAdmin?: boolean;
}
function AdminFeature({
children,
fallback = null,
requireSuperAdmin = false
}: AdminFeatureProps) {
const { isAdmin, loading } = useIsAdmin();
if (loading) {
return <div className="animate-pulse bg-gray-200 h-8 rounded"></div>;
}
if (!isAdmin) {
return <>{fallback}</>;
}
// Add super admin check if needed
if (requireSuperAdmin) {
// Implement super admin logic
return <>{children}</>;
}
return <>{children}</>;
}
// Usage
<AdminFeature fallback={<div>Not available</div>}>
<Button onClick={dangerousAction} variant="destructive">
Delete All Data
</Button>
</AdminFeature>
Troubleshooting Admin Issues
Section titled “Troubleshooting Admin Issues”Common Problems
Section titled “Common Problems”Admin Status Not Updating:
// Force refresh admin status
const refreshAdminStatus = async () => {
const { data: { user } } = await supabase.auth.getUser();
if (user) {
const { data: profile } = await supabase
.from('profiles')
.select('is_admin')
.eq('id', user.id)
.single();
console.log('Current admin status:', profile?.is_admin);
// Force component re-render
window.location.reload();
}
};