import { useState, useEffect } from 'react'; import { Search, Shield, Trash2, Ban, AlertTriangle } from 'lucide-react'; import { supabase } from '@/lib/supabaseClient'; import { useAuth } from '@/hooks/useAuth'; import { useUserRole, UserRole } from '@/hooks/useUserRole'; import { useSuperuserGuard } from '@/hooks/useSuperuserGuard'; import { AdminUserDeletionDialog } from '@/components/admin/AdminUserDeletionDialog'; import { BanUserDialog } from '@/components/admin/BanUserDialog'; import { Card, CardContent } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { handleError, handleSuccess, handleNonCriticalError, getErrorMessage } from '@/lib/errorHandler'; interface UserProfile { id: string; user_id: string; username: string; email?: string; display_name?: string; avatar_url?: string; banned: boolean; created_at: string; roles: UserRole[]; } export function ProfileManager() { const { user } = useAuth(); const { permissions, loading: roleLoading } = useUserRole(); const [profiles, setProfiles] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); const [statusFilter, setStatusFilter] = useState<'all' | 'active' | 'banned'>('all'); const [roleFilter, setRoleFilter] = useState<'all' | UserRole>('all'); const [actionLoading, setActionLoading] = useState(null); const [deletionTarget, setDeletionTarget] = useState(null); const superuserGuard = useSuperuserGuard(); useEffect(() => { if (!roleLoading && permissions?.can_view_all_profiles) { fetchProfiles(); } }, [roleLoading, permissions]); const fetchProfiles = async () => { try { setLoading(true); // Fetch profiles with emails using secure RPC function const { data: profilesData, error: profilesError } = await supabase .rpc('get_users_with_emails'); if (profilesError) throw profilesError; // Fetch roles for each user const profilesWithRoles = await Promise.all( (profilesData || []).map(async (profile) => { const { data: rolesData } = await supabase .from('user_roles') .select('role') .eq('user_id', profile.user_id); return { ...profile, roles: rolesData?.map(r => r.role as UserRole) || [] }; }) ); setProfiles(profilesWithRoles); } catch (error: unknown) { handleError(error, { action: 'Load User Profiles', userId: user?.id }); } finally { setLoading(false); } }; const handleBanUser = async ( targetUserId: string, ban: boolean, banReason?: string, banExpiresAt?: Date | null ) => { if (!user || !permissions) return; setActionLoading(targetUserId); try { // Prepare update data interface ProfileUpdateData { banned: boolean; ban_reason?: string | null; ban_expires_at?: string | null; } const updateData: ProfileUpdateData = { banned: ban }; if (ban && banReason) { updateData.ban_reason = banReason; updateData.ban_expires_at = banExpiresAt ? banExpiresAt.toISOString() : null; } else if (!ban) { // Clear ban data when unbanning updateData.ban_reason = null; updateData.ban_expires_at = null; } // Update banned status const { error: updateError } = await supabase .from('profiles') .update(updateData) .eq('user_id', targetUserId); if (updateError) throw updateError; // Log the action const { error: logError } = await supabase .rpc('log_admin_action', { _admin_user_id: user.id, _target_user_id: targetUserId, _action: ban ? 'ban_user' : 'unban_user', _details: { banned: ban, ban_reason: banReason, ban_expires_at: banExpiresAt?.toISOString() } }); if (logError) { handleNonCriticalError(logError, { action: 'Log admin action (ban/unban)', userId: user?.id, metadata: { targetUserId, ban, banReason } }); } handleSuccess( 'Success', ban ? 'User banned successfully. They have been signed out and cannot access the application.' : 'User unbanned successfully. They can now access the application normally.' ); // Refresh profiles fetchProfiles(); } catch (error: unknown) { handleError(error, { action: `${ban ? 'Ban' : 'Unban'} User`, userId: user?.id, metadata: { targetUserId, ban, banReason, banExpiresAt } }); } finally { setActionLoading(null); } }; const handleRoleChange = async (targetUserId: string, newRole: UserRole | 'remove', currentRoles: UserRole[]) => { if (!user || !permissions) return; setActionLoading(targetUserId); try { if (newRole === 'remove') { // Remove all roles except superuser (which can't be removed via UI) const rolesToRemove = currentRoles.filter(role => role !== 'superuser'); for (const role of rolesToRemove) { const { error } = await supabase .from('user_roles') .delete() .eq('user_id', targetUserId) .eq('role', role); if (error) throw error; } } else { // Check permissions before allowing role assignment if (newRole === 'admin' && !permissions.can_manage_admin_roles) { handleError(new Error('Insufficient permissions'), { action: 'Assign Admin Role', userId: user?.id, metadata: { targetUserId, attemptedRole: newRole } }); return; } if (newRole === 'superuser') { handleError(new Error('Cannot assign superuser via UI'), { action: 'Assign Superuser Role', userId: user?.id, metadata: { targetUserId } }); return; } // Add new role const { error } = await supabase .from('user_roles') .upsert({ user_id: targetUserId, role: newRole, granted_by: user.id }); if (error) throw error; } // Log the action const { error: logError } = await supabase .rpc('log_admin_action', { _admin_user_id: user.id, _target_user_id: targetUserId, _action: newRole === 'remove' ? 'remove_roles' : 'assign_role', _details: { role: newRole, previous_roles: currentRoles } }); if (logError) { handleNonCriticalError(logError, { action: 'Log admin action (role change)', userId: user?.id, metadata: { targetUserId, newRole, previousRoles: currentRoles } }); } handleSuccess('Success', 'User role updated successfully.'); // Refresh profiles fetchProfiles(); } catch (error: unknown) { handleError(error, { action: 'Update User Role', userId: user?.id, metadata: { targetUserId, newRole, previousRoles: currentRoles } }); } finally { setActionLoading(null); } }; // Check if current superuser can delete a specific user const canDeleteUser = (targetProfile: UserProfile) => { if (!superuserGuard.isSuperuser) return false; if (!superuserGuard.canPerformAction) return false; // Cannot delete other superusers if (targetProfile.roles.includes('superuser')) return false; // Cannot delete self if (targetProfile.user_id === user?.id) return false; return true; }; const canManageUser = (targetProfile: UserProfile) => { if (!permissions) return false; // Superuser can manage anyone except other superusers if (permissions.role_level === 'superuser') { return !targetProfile.roles.includes('superuser'); } // Admin can manage moderators and users, but not admins or superusers if (permissions.role_level === 'admin') { return !targetProfile.roles.some(role => ['admin', 'superuser'].includes(role)); } // Moderator can only ban users with no roles or user role if (permissions.role_level === 'moderator') { return targetProfile.roles.length === 0 || (targetProfile.roles.length === 1 && targetProfile.roles[0] === 'user'); } return false; }; const filteredProfiles = profiles.filter(profile => { const matchesSearch = profile.username.toLowerCase().includes(searchTerm.toLowerCase()) || profile.display_name?.toLowerCase().includes(searchTerm.toLowerCase()); const matchesStatus = statusFilter === 'all' || (statusFilter === 'banned' && profile.banned) || (statusFilter === 'active' && !profile.banned); const matchesRole = roleFilter === 'all' || profile.roles.includes(roleFilter as UserRole) || (roleFilter === 'user' && profile.roles.length === 0); return matchesSearch && matchesStatus && matchesRole; }); if (roleLoading) { return (

Loading permissions...

); } if (!permissions?.can_view_all_profiles) { return (

Access Denied

You don't have permission to manage user profiles.

); } return (
{/* Filters */}
setSearchTerm(e.target.value)} className="pl-10" />
{/* Users List */} {loading ? (
) : (
{filteredProfiles.map((profile) => (
{profile.display_name?.[0] || profile.username[0]}

{profile.display_name || profile.username}

{profile.banned && ( Banned )}

@{profile.username}

{profile.roles.length > 0 ? ( profile.roles.map((role) => ( {role} )) ) : ( User )}
{(canManageUser(profile) || canDeleteUser(profile)) && (
{/* Ban/Unban Button */} {canManageUser(profile) && permissions.can_ban_any_user && ( )} {/* Delete User Button - Superusers Only */} {canDeleteUser(profile) && ( )} {/* Role Management */} {canManageUser(profile) && (permissions.can_manage_moderator_roles || permissions.can_manage_admin_roles) && ( )}
)}
))} {filteredProfiles.length === 0 && (

No Users Found

No users match your current filters.

)}
)} {/* User Deletion Dialog */} {deletionTarget && ( !open && setDeletionTarget(null)} targetUser={{ userId: deletionTarget.user_id, username: deletionTarget.username, email: deletionTarget.email || 'Email not found', displayName: deletionTarget.display_name || undefined, roles: deletionTarget.roles }} onDeletionComplete={() => { setDeletionTarget(null); fetchProfiles(); }} /> )}
); }