'use client' import { useCallback, useEffect, useState } from 'react' import { useCurrentUser } from '@/lib/hooks/usePermissions' import { broadcastPermissionsRefresh, usePermissions } from '@/lib/hooks/usePermissions' interface Permission { id: string resource: string action: string description: string | null } interface Role { id: string name: string description: string | null } interface User { id: string name: string email: string isActive: boolean role: Role | null roleId: string | null createdAt: string } export default function UserManagementClient() { const [users, setUsers] = useState([]) const [roles, setRoles] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [showCreateModal, setShowCreateModal] = useState(false) const [newUserName, setNewUserName] = useState('') const [newUserEmail, setNewUserEmail] = useState('') const [newUserRoleId, setNewUserRoleId] = useState('') const [newUserPassword, setNewUserPassword] = useState('') const [newUserPasswordConfirm, setNewUserPasswordConfirm] = useState('') // Bulk role assignment const [selectedUsers, setSelectedUsers] = useState([]) const [bulkRoleId, setBulkRoleId] = useState('') const { userId } = useCurrentUser() const { hasPermission, loading: permsLoading } = usePermissions() const fetchUsers = useCallback(async () => { if (!userId) return try { const res = await fetch('/api/users', { headers: { 'x-user-id': userId || '', }, cache: 'no-store', }) if (!res.ok) throw new Error('Failed to fetch users') const data = await res.json() setUsers(data) setLoading(false) } catch (err: any) { setError(err.message) setLoading(false) } }, [userId]) const fetchRoles = useCallback(async () => { if (!userId) return try { const res = await fetch('/api/roles', { headers: { 'x-user-id': userId || '', }, cache: 'no-store', }) if (!res.ok) throw new Error('Failed to fetch roles') const data = await res.json() setRoles(data) } catch (err: any) { console.error('Error fetching roles:', err) } }, [userId]) useEffect(() => { if (!userId) return fetchUsers() fetchRoles() }, [fetchRoles, fetchUsers, userId]) const handleToggleUserStatus = async (user: User) => { try { const res = await fetch(`/api/users/${user.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'x-user-id': userId || '', }, cache: 'no-store', body: JSON.stringify({ isActive: !user.isActive, }), }) if (!res.ok) throw new Error('Failed to update user status') await fetchUsers() broadcastPermissionsRefresh() } catch (err: any) { alert('Error updating user: ' + err.message) } } const toggleUserSelection = (userId: string) => { setSelectedUsers(prev => prev.includes(userId) ? prev.filter(id => id !== userId) : [...prev, userId] ) } const toggleSelectAll = () => { if (selectedUsers.length === users.length) { setSelectedUsers([]) } else { setSelectedUsers(users.map(u => u.id)) } } const handleBulkAssignRole = async () => { if (selectedUsers.length === 0) { alert('Please select at least one user') return } if (!bulkRoleId) { alert('Please select a role to assign') return } try { // Update all selected users await Promise.all( selectedUsers.map(selectedUserId => fetch(`/api/users/${selectedUserId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'x-user-id': userId || '', }, cache: 'no-store', body: JSON.stringify({ roleId: bulkRoleId }), }) ) ) setSelectedUsers([]) setBulkRoleId('') await fetchUsers() broadcastPermissionsRefresh() alert(`Successfully assigned role to ${selectedUsers.length} user(s)`) } catch (err: any) { alert('Error assigning roles: ' + err.message) } } const handleCreateUser = async () => { if (!newUserName || !newUserEmail) { alert('Please enter name and email') return } const wantsToSetPassword = Boolean(newUserPassword || newUserPasswordConfirm) const canManagePasswords = !permsLoading && hasPermission('users', 'manage') if (wantsToSetPassword && !canManagePasswords) { alert('You do not have permission to set passwords. Ask an admin to set it after the user is created.') return } if (wantsToSetPassword) { if (newUserPassword.length < 8) { alert('Password must be at least 8 characters') return } if (newUserPassword !== newUserPasswordConfirm) { alert('Passwords do not match') return } } try { const res = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-user-id': userId || '', }, cache: 'no-store', body: JSON.stringify({ name: newUserName, email: newUserEmail, roleId: newUserRoleId || null, }), }) if (!res.ok) { const data = await res.json() throw new Error(data.error || 'Failed to create user') } const data = await res.json().catch(() => ({} as any)) const createdUser: User | undefined = data?.user if (wantsToSetPassword && createdUser?.id) { const pwRes = await fetch(`/api/users/${createdUser.id}/password`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'x-user-id': userId || '', }, cache: 'no-store', body: JSON.stringify({ password: newUserPassword }), }) const pwData = await pwRes.json().catch(() => ({})) if (!pwRes.ok) { throw new Error(pwData?.error || 'User created, but failed to set password') } } setShowCreateModal(false) setNewUserName('') setNewUserEmail('') setNewUserRoleId('') setNewUserPassword('') setNewUserPasswordConfirm('') await fetchUsers() broadcastPermissionsRefresh() alert(wantsToSetPassword ? 'User created and password set successfully!' : 'User created successfully!') } catch (err: any) { alert('Error creating user: ' + err.message) } } const handleDeleteUser = async (user: User) => { if (!confirm(`Are you sure you want to delete ${user.name}? This action cannot be undone.`)) { return } try { const res = await fetch(`/api/users/${user.id}`, { method: 'DELETE', headers: { 'x-user-id': userId || '', }, cache: 'no-store', }) if (!res.ok) { const data = await res.json() throw new Error(data.error || 'Failed to delete user') } await fetchUsers() broadcastPermissionsRefresh() alert('User deleted successfully!') } catch (err: any) { alert('Error deleting user: ' + err.message) } } const handleSetUserPassword = async (user: User) => { if (!userId) { alert('Missing user session. Please sign in again.') return } const pw1 = prompt(`Set a new password for ${user.email} (min 8 chars):`) || '' if (!pw1) return if (pw1.length < 8) { alert('Password must be at least 8 characters') return } const pw2 = prompt('Confirm new password:') || '' if (pw1 !== pw2) { alert('Passwords do not match') return } try { const res = await fetch(`/api/users/${user.id}/password`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'x-user-id': userId || '', }, cache: 'no-store', body: JSON.stringify({ password: pw1 }), }) const data = await res.json().catch(() => ({})) if (!res.ok) { alert(data?.error || 'Failed to set password') return } alert('Password set successfully') } catch (err: any) { alert('Error setting password: ' + (err?.message || String(err))) } } if (loading) { return (
Loading users...
) } if (error) { return (
Error: {error}
) } return (

User Management

{/* Bulk Role Assignment */} {selectedUsers.length > 0 && (
{selectedUsers.length} user(s) selected
)}
{users.map((user) => ( ))}
0} onChange={toggleSelectAll} className="w-4 h-4 rounded border-gray-300 focus:ring-2 focus:ring-blue-500 cursor-pointer" /> User Email Role Status Actions
toggleUserSelection(user.id)} className="w-4 h-4 rounded border-gray-300 focus:ring-2 focus:ring-blue-500 cursor-pointer" />
{user.name}
{user.email}
{user.role?.name || 'No Role'} {user.isActive ? 'Active' : 'Inactive'} {!permsLoading && hasPermission('users', 'manage') && ( )}
{/* Create User Modal */} {showCreateModal && (

Add New User

setNewUserName(e.target.value)} placeholder="John Doe" className="w-full border rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" />
setNewUserEmail(e.target.value)} placeholder="user@company.com" className="w-full border rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" />
{!permsLoading && hasPermission('users', 'manage') && ( <>
setNewUserPassword(e.target.value)} placeholder="Minimum 8 characters" className="w-full border rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" />
setNewUserPasswordConfirm(e.target.value)} placeholder="Re-enter password" className="w-full border rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" />
)}
)}
) }