Skip to content

Commit dde268d

Browse files
committed
Add promote user
1 parent d137db9 commit dde268d

File tree

8 files changed

+127
-14
lines changed

8 files changed

+127
-14
lines changed

frontend/src/data/users/UserImpl.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ export class UserImpl implements IUser {
4141
async deleteUser(userId: string): Promise<any> {
4242
return this.dataSource.deleteUser(userId);
4343
}
44+
45+
async updateUserPrivilege(userId: string, isAdmin: boolean): Promise<any> {
46+
return this.dataSource.updateUserPrivilege(userId, isAdmin);
47+
}
4448
}
4549

4650
export const userImpl = new UserImpl();

frontend/src/data/users/UserRemoteDataSource.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ export class UserRemoteDataSource extends BaseApi {
5858
async deleteUser(userId: string) {
5959
return await this.protectedDelete<any>(`/users/${userId}`);
6060
}
61+
62+
async updateUserPrivilege(userId: string, isAdmin: boolean) {
63+
return await this.protectedPatch(`/users/${userId}/privilege`, { isAdmin });
64+
}
6165
}
6266

6367
export const userRemoteDataSource = new UserRemoteDataSource();

frontend/src/data/users/mockUser.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,22 @@ export class MockUser {
165165
users = users.filter((user) => user._id !== userId);
166166
});
167167
}
168+
169+
async updateUserPrivilege(userId: string, isAdmin: boolean): Promise<any> {
170+
return new Promise((resolve, reject) => {
171+
try {
172+
const foundUser = this.users.find((u) => u._id === userId);
173+
if (!foundUser) {
174+
resolve({ message: "User not found" });
175+
} else {
176+
foundUser.isAdmin = isAdmin;
177+
resolve({ message: "User privileges updated", data: foundUser });
178+
}
179+
} catch (error) {
180+
reject(error);
181+
}
182+
});
183+
}
168184
}
169185

170186
export const mockUser = new MockUser();

frontend/src/domain/usecases/UserUseCases.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,23 +108,31 @@ export class UserUseCases {
108108
*/
109109
async getUser(userId: string): Promise<User> {
110110
const data = await this.user.getUser(userId);
111-
if (!data) {
112-
throw new AuthenticationError("User not found or access denied");
111+
if (!data.data) {
112+
throw new AuthenticationError(data.message);
113113
}
114114
return data.data;
115115
}
116116

117117
async getAllUsers(): Promise<User[]> {
118118
const data = await this.user.getAllUsers();
119-
if (!data) {
120-
throw new AuthenticationError("Unable to fetch users or access denied");
119+
if (!data.data) {
120+
throw new AuthenticationError(data.message);
121121
}
122122
return data.data;
123123
}
124124

125125
async deleteUser(userId: string): Promise<void> {
126126
await this.user.deleteUser(userId);
127127
}
128+
129+
async updateUserPrivilege(userId: string, isAdmin: boolean): Promise<User> {
130+
const data = await this.user.updateUserPrivilege(userId, isAdmin);
131+
if (!data.data) {
132+
throw new AuthenticationError(data.message);
133+
}
134+
return data.data;
135+
}
128136
}
129137

130138
export const userUseCases = new UserUseCases(userImpl);

frontend/src/domain/users/IUser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@ export interface IUser {
2727
getUser(userId: string): Promise<any>;
2828
getAllUsers(): Promise<any>;
2929
deleteUser(userId: string): Promise<any>;
30+
updateUserPrivilege(userId: string, isAdmin: boolean): Promise<any>;
3031
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.btnGroup {
2+
display: flex;
3+
gap: 8px;
4+
margin-top: 8px;
5+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Button } from "antd";
2+
import { User } from "domain/entities/User";
3+
import styles from "./UpdateUserPrivilegeForm.module.css";
4+
import { toast } from "react-toastify";
5+
import { handleError } from "presentation/utils/errorHandler";
6+
import { userUseCases } from "domain/usecases/UserUseCases";
7+
8+
interface UpdateUserPrivilegeFormProps {
9+
user: User;
10+
onSubmit?: (user: User) => void;
11+
onCancel?: () => void;
12+
}
13+
export const UpdateUserPrivilegeForm: React.FC<UpdateUserPrivilegeFormProps> = ({ user, onSubmit, onCancel }) => {
14+
const handlePromoteUser = async () => {
15+
try {
16+
const updatedUser = await userUseCases.updateUserPrivilege(user._id, true);
17+
toast.success(`Successfully promoted ${user.username} to admin`);
18+
onSubmit?.(updatedUser);
19+
} catch (err) {
20+
console.error(err);
21+
toast.error(handleError(err));
22+
}
23+
};
24+
25+
const handleCancel = () => {
26+
onCancel?.();
27+
};
28+
29+
return (
30+
<>
31+
<p>
32+
Are you sure you want to promote <b>{user.username}</b> to admin?
33+
</p>
34+
35+
<div className={styles.btnGroup}>
36+
<Button onClick={handleCancel}>Cancel</Button>
37+
<Button type="primary" onClick={handlePromoteUser}>
38+
Confirm
39+
</Button>
40+
</div>
41+
</>
42+
);
43+
};

frontend/src/presentation/pages/UserManagement.tsx

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import { User } from "domain/entities/User";
22
import { userUseCases } from "domain/usecases/UserUseCases";
33
import { useEffect, useState } from "react";
4-
import { EditOutlined, DeleteOutlined, CrownFilled } from "@ant-design/icons";
4+
import { EditOutlined, DeleteOutlined, CrownFilled, CrownOutlined } from "@ant-design/icons";
55
import styles from "./UserManagement..module.css";
6-
import { Button, Modal } from "antd";
6+
import { Modal } from "antd";
77
import { UpdateProfileForm } from "presentation/components/UpdateProfileForm/UpdateProfileForm";
88
import { DeleteUserForm } from "presentation/components/DeleteUserForm/DeleteUserForm";
9-
import { toast } from "react-toastify";
9+
import { UpdateUserPrivilegeForm } from "presentation/components/UpdateUserPrivilegeForm/UpdateUserPrivilegeForm";
10+
1011

1112
export const UserManagement: React.FC<{}> = () => {
1213
const [users, setUsers] = useState<User[]>([]);
14+
1315
const [editingUser, setEditingUser] = useState<User | null>(null);
1416
const [deletingUser, setDeletingUser] = useState<User | null>(null);
17+
const [promotingUser, setPromotingUser] = useState<User | null>(null);
1518

1619
useEffect(() => {
1720
const getAllUsers = async () => {
@@ -42,6 +45,11 @@ export const UserManagement: React.FC<{}> = () => {
4245
{!user.isAdmin && (
4346
<div className={styles.userControls}>
4447
<DeleteOutlined onClick={() => setDeletingUser(user)} />
48+
<CrownOutlined
49+
onClick={() => {
50+
setPromotingUser(user);
51+
}}
52+
/>
4553
<EditOutlined onClick={() => setEditingUser(user)} />
4654
</div>
4755
)}
@@ -50,19 +58,28 @@ export const UserManagement: React.FC<{}> = () => {
5058
};
5159

5260
const onEditUser = (updatedUser: User) => {
61+
refreshUsersList(updatedUser);
62+
setEditingUser(null);
63+
};
64+
65+
const onDeleteUser = (userId: string) => {
66+
setUsers((users) => users.filter((user) => user._id !== userId));
67+
setDeletingUser(null);
68+
};
69+
70+
const onPromoteUser = (updatedUser: User) => {
71+
refreshUsersList(updatedUser);
72+
setPromotingUser(null);
73+
};
74+
75+
const refreshUsersList = (updatedUser: User) => {
5376
const updatedUsers = users.map((user) => {
5477
if (user._id === updatedUser._id) {
5578
return updatedUser;
5679
}
5780
return user;
5881
});
5982
setUsers(updatedUsers);
60-
setEditingUser(null);
61-
};
62-
63-
const onDelete = (userId: string) => {
64-
setUsers((users) => users.filter((user) => user._id !== userId));
65-
setDeletingUser(null);
6683
};
6784

6885
return (
@@ -90,7 +107,22 @@ export const UserManagement: React.FC<{}> = () => {
90107
<DeleteUserForm
91108
user={deletingUser}
92109
onCancel={() => setDeletingUser(null)}
93-
onSubmit={() => onDelete(deletingUser?._id)}
110+
onSubmit={() => onDeleteUser(deletingUser?._id)}
111+
/>
112+
</Modal>
113+
)}
114+
{promotingUser && (
115+
<Modal
116+
open={promotingUser !== null}
117+
closable={false}
118+
title="Promote user"
119+
footer={null}
120+
maskClosable={false}
121+
>
122+
<UpdateUserPrivilegeForm
123+
user={promotingUser}
124+
onSubmit={onPromoteUser}
125+
onCancel={() => setPromotingUser(null)}
94126
/>
95127
</Modal>
96128
)}

0 commit comments

Comments
 (0)