diff --git a/frontend/components/admin-user-management/admin-edit-user-modal.tsx b/frontend/components/admin-user-management/admin-edit-user-modal.tsx new file mode 100644 index 0000000000..c33a1868ff --- /dev/null +++ b/frontend/components/admin-user-management/admin-edit-user-modal.tsx @@ -0,0 +1,203 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "../ui/dialog"; +import { Button } from "../ui/button"; +import { Input } from "../ui/input"; +import { Label } from "../ui/label"; +import { updateUser } from "@/lib/update-user"; +import { useAuth } from "@/app/auth/auth-context"; +import { useToast } from "@/components/hooks/use-toast"; +import { User } from "@/lib/schemas/user-schema"; + +interface AdminEditUserModalProps extends React.HTMLProps { + showModal?: boolean; + setShowModal: (show: boolean) => void; + user: User | undefined; + onUserUpdate: () => void; +} + +const AdminEditUserModal: React.FC = ({ + ...props +}) => { + const auth = useAuth(); + const { toast } = useToast(); + const [editingUser, setEditingUser] = useState< + | { + id?: string; + username?: string; + email?: string; + } + | undefined + >(); + + useEffect(() => { + setEditingUser(props.user); + }, [props.user]); + + const closeModal = () => { + setEditingUser(props.user); + props.setShowModal(false); + }; + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + if (!auth?.token) { + // Will not reach this point as button is disabled + // when token is missing + toast({ + title: "Access denied", + description: "No authentication token found", + }); + return; + } + + if (!editingUser?.id) { + // Will not reach this point as button is disabled + // when editing user's id is missing + toast({ + title: "Invalid selection", + description: "No user selected", + }); + return; + } + + const response = await updateUser( + auth.token, + editingUser.id, + editingUser?.username, + editingUser?.email + ); + if (!response.ok) { + toast({ + title: "Unknown Error", + description: "An unexpected error has occurred", + }); + } + switch (response.status) { + case 200: + toast({ + title: "Success", + description: "User updated successfully!", + }); + break; + case 400: + // In theory, they should never be able to send out a request + // with missing fields due to disabled submission button + toast({ + title: "Missing Fields", + description: "Please fill in at least 1 field", + }); + return; + case 401: + toast({ + title: "Access denied", + description: "Invalid session", + }); + return; + case 403: + toast({ + title: "Access denied", + description: "Only admins can update other user", + }); + return; + case 404: + toast({ + title: "User not found", + description: "User with specified ID not found", + }); + return; + case 409: + toast({ + title: "Duplicated Username or Email", + description: "The username or email you entered is already in use", + }); + return; + case 500: + toast({ + title: "Server Error", + description: "The server encountered an error", + }); + return; + default: + toast({ + title: "Unknown Error", + description: "An unexpected error has occured", + }); + return; + } + + // Remove old states, update UI and close modal + props.onUserUpdate(); + closeModal(); + }; + + return ( + <> + {props.showModal && ( + + + + Edit user + +
+
+
+ +
+ + setEditingUser({ ...editingUser, username: e.target.value }) + } + required + /> +
+
+
+ +
+ + setEditingUser({ ...editingUser, email: e.target.value }) + } + required + /> +
+
+ + + + +
+
+ )} + + ); +}; + +export default AdminEditUserModal; diff --git a/frontend/components/admin-user-management/admin-user-management.tsx b/frontend/components/admin-user-management/admin-user-management.tsx index 8cf03f880b..5ee32ff8f2 100644 --- a/frontend/components/admin-user-management/admin-user-management.tsx +++ b/frontend/components/admin-user-management/admin-user-management.tsx @@ -13,6 +13,8 @@ import { TableRow, } from "@/components/ui/table"; import LoadingScreen from "@/components/common/loading-screen"; +import AdminEditUserModal from "@/components/admin-user-management/admin-edit-user-modal"; +import { PencilIcon, Trash2Icon } from "lucide-react"; import AuthPageWrapper from "@/components/auth/auth-page-wrapper"; import { User, UserArraySchema } from "@/lib/schemas/user-schema"; @@ -40,9 +42,15 @@ const fetcher = async (url: string): Promise => { export default function AdminUserManagement() { const auth = useAuth(); - const { data, isLoading } = useSWR("http://localhost:3001/users", fetcher); + + const { data, isLoading, mutate } = useSWR( + "http://localhost:3001/users", + fetcher + ); const [users, setUsers] = useState([]); + const [showModal, setShowModal] = useState(false); + const [selectedUser, setSelectedUser] = useState(); useEffect(() => { if (data) { @@ -74,10 +82,21 @@ export default function AdminUserManagement() { setUsers(users.filter((user) => user.id !== userId)); }; + const onUserUpdate = () => { + mutate(); + setSelectedUser(undefined); + }; + return (

User Management

+ @@ -96,14 +115,21 @@ export default function AdminUserManagement() { {user.isAdmin ? "Admin" : "User"} {user.skillLevel} - diff --git a/frontend/lib/update-user.ts b/frontend/lib/update-user.ts new file mode 100644 index 0000000000..4f1c603c16 --- /dev/null +++ b/frontend/lib/update-user.ts @@ -0,0 +1,24 @@ +export const updateUser = async ( + jwtToken: string, + id: string, + username?: string, + email?: string, + password?: string, + skillLevel?: string +) => { + if (!username && !email && !password && !skillLevel) { + throw new Error("Require at least one field"); + } + + const body = { username, email, password, skillLevel }; + + const response = await fetch(`http://localhost:3001/users/${id}`, { + method: "PATCH", + headers: { + Authorization: `Bearer ${jwtToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + return response; +}; diff --git a/user-service/README.md b/user-service/README.md index 408c7021d4..9b2b5a311e 100644 --- a/user-service/README.md +++ b/user-service/README.md @@ -136,14 +136,14 @@ - Required: `userId` path parameter - Body - - - At least one of the following fields is required: `username` (string), `email` (string), `password` (string) + - At least one of the following fields is required: `username` (string), `email` (string), `password` (string), `skillLevel` (string) ```json { "username": "SampleUserName", "email": "sample@gmail.com", - "password": "SecurePassword" + "password": "SecurePassword", + "skillLevel": "SkillLevel", } ```