Skip to content

Commit

Permalink
Merge pull request #115 from CS3219-AY2425S1/feature/user-service/adm…
Browse files Browse the repository at this point in the history
…in-update-privilege

Implement admin edit user privilege
  • Loading branch information
jq1836 authored Sep 27, 2024
2 parents fe18a50 + ae73b82 commit 710f1f1
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 25 deletions.
135 changes: 113 additions & 22 deletions frontend/components/admin-user-management/admin-edit-user-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ 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 { Switch } from "../ui/switch";
import { updateUserPrivilege } from "@/lib/update-user-privilege";
import { User } from "@/lib/schemas/user-schema";

interface AdminEditUserModalProps extends React.HTMLProps<HTMLDivElement> {
Expand All @@ -33,6 +35,7 @@ const AdminEditUserModal: React.FC<AdminEditUserModalProps> = ({
id?: string;
username?: string;
email?: string;
isAdmin?: boolean;
}
| undefined
>();
Expand All @@ -49,6 +52,19 @@ const AdminEditUserModal: React.FC<AdminEditUserModalProps> = ({
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();

updateInfo()
.then(() => updatePrivilege())
.then(() => {
toast({
title: "Success",
description: "User updated successfully!",
});
closeModal();
})
.catch(() => null);
};

const updateInfo = async () => {
if (!auth?.token) {
// Will not reach this point as button is disabled
// when token is missing
Expand All @@ -75,68 +91,129 @@ const AdminEditUserModal: React.FC<AdminEditUserModalProps> = ({
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;
props.onUserUpdate();
return;
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;
break;
case 401:
toast({
title: "Access denied",
description: "Invalid session",
});
return;
break;
case 403:
toast({
title: "Access denied",
description: "Only admins can update other user",
});
return;
break;
case 404:
toast({
title: "User not found",
description: "User with specified ID not found",
});
return;
break;
case 409:
toast({
title: "Duplicated Username or Email",
description: "The username or email you entered is already in use",
});
return;
break;
case 500:
toast({
title: "Server Error",
description: "The server encountered an error",
});
return;
break;
default:
toast({
title: "Unknown Error",
description: "An unexpected error has occured",
});
return;
break;
}
throw new Error("Update user info request failed");
};

const updatePrivilege = async () => {
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;
}

// Remove old states, update UI and close modal
props.onUserUpdate();
closeModal();
if (editingUser?.isAdmin == null) {
// Will not reach this point as button is disabled
// when field is missing
toast({
title: "Invalid selection",
description: "No user selected",
});
return;
}

const response = await updateUserPrivilege(
auth.token,
editingUser?.id,
editingUser?.isAdmin
);

switch (response.status) {
case 200:
props.onUserUpdate();
return;
case 400:
// In theory, they should never be able to send out a request
// with missing fields due to disabled submission button
break;
case 401:
toast({
title: "Access denied",
description: "Invalid session",
});
break;
case 403:
toast({
title: "Access denied",
description: "Only admins can update other user",
});
break;
case 404:
toast({
title: "User not found",
description: "User with specified ID not found",
});
break;
default:
toast({
title: "Unknown Error",
description: "An unexpected error has occured",
});
break;
}
throw new Error("Update user privilege request failed");
};

return (
Expand Down Expand Up @@ -177,14 +254,28 @@ const AdminEditUserModal: React.FC<AdminEditUserModalProps> = ({
required
/>
</div>
<div className="grid gap-2 mt-5">
<div className="flex items-center">
<Label htmlFor="isAdmin">Admin</Label>
</div>
<Switch
id="isAdmin"
checked={editingUser?.isAdmin}
onCheckedChange={(e) =>
setEditingUser({ ...editingUser, isAdmin: e })
}
required
/>
</div>
</form>
<DialogFooter>
<Button
onClick={handleSubmit}
disabled={
!auth?.token ||
!editingUser?.id ||
(!editingUser?.email && !editingUser?.username)
(!editingUser?.email && !editingUser?.username) ||
editingUser?.isAdmin == null
}
>
Save changes
Expand Down
4 changes: 2 additions & 2 deletions frontend/components/ui/switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ const Switch = React.forwardRef<
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-green-500 data-[state=unchecked]:bg-gray-500",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
"pointer-events-none block h-6 w-6 rounded-full bg-white shadow-lg ring-0 transition-transform duration-200 ease-in-out data-[state=checked]:translate-x-6 data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitives.Root>
Expand Down
17 changes: 17 additions & 0 deletions frontend/lib/update-user-privilege.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const updateUserPrivilege = async (
jwtToken: string,
id: string,
isAdmin: boolean
) => {
const body = { isAdmin };

const response = await fetch(`http://localhost:3001/users/${id}/privilege`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${jwtToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
return response;
};
3 changes: 2 additions & 1 deletion user-service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
- Required: `userId` path parameter

- Body

- At least one of the following fields is required: `username` (string), `email` (string), `password` (string), `skillLevel` (string)

```json
Expand Down Expand Up @@ -173,7 +174,7 @@

- HTTP Method: `PATCH`

- Endpoint: http://localhost:3001/users/{userId}
- Endpoint: http://localhost:3001/users/{userId}/privilege

- Parameters

Expand Down

0 comments on commit 710f1f1

Please sign in to comment.