Skip to content
GitHubTwitterDiscord

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.

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

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;

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

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 };
}
'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>
  );
}

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 interface
  • AdminUsersStatsManager.tsx: User analytics
  • UserStatsCards.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 management
  • ProductForm.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 management
  • OrdersCharts.tsx: Revenue analytics
  • MMRChart.tsx: Monthly recurring revenue
  • OrderDetailsSheet.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();
};
'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>

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();
  }
};