diff --git a/apps/frontend/app/dashboard/settings/users/page.tsx b/apps/frontend/app/dashboard/settings/users/page.tsx index bb16dc9..b2e8679 100644 --- a/apps/frontend/app/dashboard/settings/users/page.tsx +++ b/apps/frontend/app/dashboard/settings/users/page.tsx @@ -9,15 +9,18 @@ import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { listUsers, createUser, updateUser, deleteUser } from '@/lib/api'; +import { useAuth } from '@/lib/context/auth-context'; export default function UsersManagementPage() { const queryClient = useQueryClient(); + const { user: currentUser } = useAuth(); const [showInviteModal, setShowInviteModal] = useState(false); const [inviteForm, setInviteForm] = useState({ email: '', firstName: '', lastName: '', - role: 'user' as 'admin' | 'manager' | 'user' | 'viewer', + role: 'USER' as 'ADMIN' | 'MANAGER' | 'USER' | 'VIEWER', + password: '', phoneNumber: '', }); const [error, setError] = useState(''); @@ -30,8 +33,14 @@ export default function UsersManagementPage() { const inviteMutation = useMutation({ mutationFn: (data: typeof inviteForm & { organizationId: string }) => { - // TODO: API should generate password or send invitation email - return createUser(data as any); + return createUser({ + email: data.email, + password: data.password, + firstName: data.firstName, + lastName: data.lastName, + role: data.role, + organizationId: data.organizationId, + }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); @@ -41,33 +50,45 @@ export default function UsersManagementPage() { email: '', firstName: '', lastName: '', - role: 'user', + role: 'USER', + password: '', phoneNumber: '', }); setTimeout(() => setSuccess(''), 3000); }, onError: (err: any) => { setError(err.response?.data?.message || 'Failed to invite user'); + setTimeout(() => setError(''), 5000); }, }); const changeRoleMutation = useMutation({ - mutationFn: ({ id, role }: { id: string; role: 'admin' | 'manager' | 'user' | 'viewer' }) => { - // TODO: Implement changeRole API endpoint - return updateUser(id, { role } as any); + mutationFn: ({ id, role }: { id: string; role: 'ADMIN' | 'MANAGER' | 'USER' | 'VIEWER' }) => { + return updateUser(id, { role }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); + setSuccess('Role updated successfully'); + setTimeout(() => setSuccess(''), 3000); + }, + onError: (err: any) => { + setError(err.response?.data?.message || 'Failed to update role'); + setTimeout(() => setError(''), 5000); }, }); const toggleActiveMutation = useMutation({ mutationFn: ({ id, isActive }: { id: string; isActive: boolean }) => { - // TODO: Implement activate/deactivate API endpoints - return updateUser(id, { isActive: !isActive } as any); + return updateUser(id, { isActive: !isActive }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); + setSuccess('User status updated successfully'); + setTimeout(() => setSuccess(''), 3000); + }, + onError: (err: any) => { + setError(err.response?.data?.message || 'Failed to update user status'); + setTimeout(() => setError(''), 5000); }, }); @@ -75,18 +96,34 @@ export default function UsersManagementPage() { mutationFn: (id: string) => deleteUser(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); + setSuccess('User deleted successfully'); + setTimeout(() => setSuccess(''), 3000); + }, + onError: (err: any) => { + setError(err.response?.data?.message || 'Failed to delete user'); + setTimeout(() => setError(''), 5000); }, }); const handleInvite = (e: React.FormEvent) => { e.preventDefault(); setError(''); - // TODO: Get actual organizationId from auth context - inviteMutation.mutate({ ...inviteForm, organizationId: 'default-org-id' }); + + if (!currentUser?.organizationId) { + setError('Organization ID not found. Please log in again.'); + return; + } + + if (!inviteForm.password || inviteForm.password.length < 8) { + setError('Password must be at least 8 characters long'); + return; + } + + inviteMutation.mutate({ ...inviteForm, organizationId: currentUser.organizationId }); }; const handleRoleChange = (userId: string, newRole: string) => { - changeRoleMutation.mutate({ id: userId, role: newRole as any }); + changeRoleMutation.mutate({ id: userId, role: newRole as 'ADMIN' | 'MANAGER' | 'USER' | 'VIEWER' }); }; const handleToggleActive = (userId: string, isActive: boolean) => { @@ -107,10 +144,10 @@ export default function UsersManagementPage() { const getRoleBadgeColor = (role: string) => { const colors: Record = { - admin: 'bg-red-100 text-red-800', - manager: 'bg-blue-100 text-blue-800', - user: 'bg-green-100 text-green-800', - viewer: 'bg-gray-100 text-gray-800', + ADMIN: 'bg-red-100 text-red-800', + MANAGER: 'bg-blue-100 text-blue-800', + USER: 'bg-green-100 text-green-800', + VIEWER: 'bg-gray-100 text-gray-800', }; return colors[role] || 'bg-gray-100 text-gray-800'; }; @@ -205,21 +242,23 @@ export default function UsersManagementPage() { className={`text-xs font-semibold rounded-full px-3 py-1 ${getRoleBadgeColor( user.role )}`} + disabled={changeRoleMutation.isPending} > - - - - + + + + @@ -230,7 +269,8 @@ export default function UsersManagementPage() { @@ -335,6 +375,22 @@ export default function UsersManagementPage() { /> +
+ + setInviteForm({ ...inviteForm, password: e.target.value })} + className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border" + placeholder="Minimum 8 characters" + /> +

+ Provide a temporary password (minimum 8 characters). User can change it after first login. +

+
+
setInviteForm({ ...inviteForm, role: e.target.value as any })} className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border" > - - - - + + + + -

- A temporary password will be sent to the user's email -