import { useState, useRef, useEffect } from 'react'; import { toast } from 'sonner'; import { MoreHorizontal, ShieldCheck, KeyRound, UserX, UserCheck, LogOut, Smartphone, ChevronRight, Loader2, Trash2, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { useConfirmAction } from '@/hooks/useConfirmAction'; import { useUpdateRole, useResetPassword, useDisableMfa, useEnforceMfa, useRemoveMfaEnforcement, useToggleUserActive, useRevokeSessions, useDeleteUser, getErrorMessage, } from '@/hooks/useAdmin'; import type { AdminUserDetail, UserRole } from '@/types'; import { cn } from '@/lib/utils'; interface UserActionsMenuProps { user: AdminUserDetail; currentUsername: string | null; } const ROLES: { value: UserRole; label: string }[] = [ { value: 'admin', label: 'Admin' }, { value: 'standard', label: 'Standard' }, { value: 'public_event_manager', label: 'Public Event Manager' }, ]; export default function UserActionsMenu({ user, currentUsername }: UserActionsMenuProps) { const [open, setOpen] = useState(false); const [roleSubmenuOpen, setRoleSubmenuOpen] = useState(false); const [tempPassword, setTempPassword] = useState(null); const menuRef = useRef(null); const updateRole = useUpdateRole(); const resetPassword = useResetPassword(); const disableMfa = useDisableMfa(); const enforceMfa = useEnforceMfa(); const removeMfaEnforcement = useRemoveMfaEnforcement(); const toggleActive = useToggleUserActive(); const revokeSessions = useRevokeSessions(); const deleteUser = useDeleteUser(); // Close on outside click useEffect(() => { const handleOutside = (e: MouseEvent) => { if (menuRef.current && !menuRef.current.contains(e.target as Node)) { setOpen(false); setRoleSubmenuOpen(false); } }; if (open) document.addEventListener('mousedown', handleOutside); return () => document.removeEventListener('mousedown', handleOutside); }, [open]); const handleAction = async (fn: () => Promise, successMsg: string) => { try { await fn(); toast.success(successMsg); setOpen(false); } catch (err) { toast.error(getErrorMessage(err, 'Action failed')); } }; // Two-click confirms const disableMfaConfirm = useConfirmAction(() => { handleAction(() => disableMfa.mutateAsync(user.id), 'MFA disabled'); }); const toggleActiveConfirm = useConfirmAction(() => { handleAction( () => toggleActive.mutateAsync({ userId: user.id, active: !user.is_active }), user.is_active ? 'Account disabled' : 'Account enabled' ); }); const revokeSessionsConfirm = useConfirmAction(() => { handleAction(() => revokeSessions.mutateAsync(user.id), 'Sessions revoked'); }); const deleteUserConfirm = useConfirmAction(async () => { try { const result = await deleteUser.mutateAsync(user.id); toast.success(`User '${(result as { deleted_username: string }).deleted_username}' permanently deleted`); setOpen(false); } catch (err) { toast.error(getErrorMessage(err, 'Delete failed')); } }); const isLoading = updateRole.isPending || resetPassword.isPending || disableMfa.isPending || enforceMfa.isPending || removeMfaEnforcement.isPending || toggleActive.isPending || revokeSessions.isPending || deleteUser.isPending; return (
{open && (
{/* Edit Role */}
{roleSubmenuOpen && (
setRoleSubmenuOpen(true)} onMouseLeave={() => setRoleSubmenuOpen(false)} > {ROLES.map(({ value, label }) => ( ))}
)}
{/* Reset Password */} {tempPassword ? (

Temporary password:

{tempPassword}
) : ( )}
{/* MFA actions */} {user.mfa_enforce_pending ? ( ) : ( )} {user.totp_enabled && ( )}
{/* Disable / Enable Account */} {/* Revoke Sessions */} {/* Delete User — hidden for own account */} {currentUsername !== user.username && ( <>
)}
)}
); }