Skip to content

Commit 37d6eda

Browse files
authored
Merge pull request #1195 from mathematicalthinking/development
Admin status buttons
2 parents 78059b4 + 47ff9fe commit 37d6eda

File tree

9 files changed

+128
-88
lines changed

9 files changed

+128
-88
lines changed

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "client",
3-
"version": "3.3.5",
3+
"version": "3.3.6",
44
"private": true,
55
"dependencies": {
66
"@xstate/react": "^3.0.1",

client/src/Components/UI/ContentBox/DashboardContentBox.js

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -37,47 +37,9 @@ class DashboardContentBox extends PureComponent {
3737
resource,
3838
manageUser,
3939
isSelf,
40+
iconActions,
4041
} = this.props;
4142
const { expanded } = this.state;
42-
let iconActions = null;
43-
let suspendReinstateAction = {
44-
iconClass: 'fas fa-ban',
45-
title: 'Suspend User',
46-
testid: 'suspend',
47-
color: 'red',
48-
onClick: () => {
49-
manageUser(details, 'suspendUser');
50-
},
51-
};
52-
53-
if (details.isSuspended) {
54-
suspendReinstateAction = {
55-
iconClass: 'fas fa-undo',
56-
title: 'Reinstate User',
57-
testid: 'reinstate',
58-
color: 'green',
59-
onClick: () => {
60-
manageUser(details, 'reinstateUser');
61-
},
62-
};
63-
}
64-
65-
const forceLogoutAction = {
66-
iconClass: 'fas fa-power-off',
67-
title: 'Force Logout',
68-
testid: 'force-logout',
69-
onClick: () => {
70-
manageUser(details, 'logoutUser');
71-
},
72-
};
73-
74-
if (resource === 'users') {
75-
iconActions = !isSelf ? [suspendReinstateAction] : [];
76-
77-
if (details.socketId && !details.doForceLogout) {
78-
iconActions.unshift(forceLogoutAction);
79-
}
80-
}
8143

8244
const { firstName = '', lastName = '' } = details;
8345
let fullName;
@@ -260,12 +222,20 @@ DashboardContentBox.propTypes = {
260222
resource: PropTypes.string.isRequired,
261223
manageUser: PropTypes.func,
262224
isSelf: PropTypes.bool.isRequired,
225+
iconActions: PropTypes.arrayOf(
226+
PropTypes.shape({
227+
testId: PropTypes.string,
228+
onClick: PropTypes.func,
229+
title: PropTypes.string,
230+
})
231+
),
263232
};
264233

265234
DashboardContentBox.defaultProps = {
266235
image: null,
267236
roomType: null,
268237
link: null,
269238
manageUser: null,
239+
iconActions: [],
270240
};
271241
export default DashboardContentBox;

client/src/Containers/Dashboard.js

Lines changed: 88 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -204,13 +204,15 @@ class Dashboard extends Component {
204204
this.setQueryParams(filters);
205205
};
206206

207-
logoutUser = (userId) => {
208-
return API.revokeRefreshToken(userId)
207+
_buttonAction = (apiAction, userId, update) => {
208+
return apiAction()
209209
.then((res) => {
210-
const { user } = res.data;
210+
const { user, username } = res.data;
211211

212-
if (user) {
213-
this.updateVisibleResource(userId, { socketId: null });
212+
// API actions return different values if successful,
213+
// so allow for a couple of possibilities.
214+
if (user || username) {
215+
this.updateVisibleResource(userId, update);
214216
}
215217
this.stopManageUser();
216218
})
@@ -221,39 +223,32 @@ class Dashboard extends Component {
221223
});
222224
};
223225

224-
suspendUser = (userId) => {
225-
return API.suspendUser(userId)
226-
.then((res) => {
227-
const { user } = res.data;
226+
logoutUser = (userId) =>
227+
this._buttonAction(() => API.revokeRefreshToken(userId), userId, {
228+
socketId: null,
229+
});
228230

229-
if (user) {
230-
this.updateVisibleResource(userId, { isSuspended: true });
231-
}
232-
this.stopManageUser();
233-
})
234-
.catch((err) => {
235-
this.stopManageUser();
236-
// eslint-disable-next-line no-console
237-
console.log({ err });
238-
});
239-
};
231+
suspendUser = (userId) =>
232+
this._buttonAction(() => API.suspendUser(userId), userId, {
233+
isSuspended: true,
234+
});
240235

241-
reinstateUser = (userId) => {
242-
return API.reinstateUser(userId)
243-
.then((res) => {
244-
const { user } = res.data;
236+
reinstateUser = (userId) =>
237+
this._buttonAction(() => API.reinstateUser(userId), userId, {
238+
isSuspended: false,
239+
});
245240

246-
if (user) {
247-
this.updateVisibleResource(userId, { isSuspended: false });
248-
}
249-
this.stopManageUser();
241+
removeAsAdmin = (userId) =>
242+
this.logoutUser(userId).then(() =>
243+
this._buttonAction(() => API.removeAsAdmin(userId), userId, {
244+
isAdmin: false,
250245
})
251-
.catch((err) => {
252-
this.stopManageUser();
253-
// eslint-disable-next-line no-console
254-
console.log({ err });
255-
});
256-
};
246+
);
247+
248+
makeAdmin = (userId) =>
249+
this.logoutUser(userId).then(() =>
250+
this._buttonAction(() => API.makeAdmin(userId), userId, { isAdmin: true })
251+
);
257252

258253
updateVisibleResource = (itemId, update) => {
259254
const { visibleResources } = this.state;
@@ -282,6 +277,62 @@ class Dashboard extends Component {
282277
});
283278
};
284279

280+
_getIconActions = (details, resource, isSelf) => {
281+
if (resource !== 'users') return [];
282+
const suspendReinstateAction = details.isSuspended
283+
? {
284+
iconClass: 'fas fa-undo',
285+
title: 'Reinstate User',
286+
testid: 'reinstate',
287+
color: 'green',
288+
onClick: () => {
289+
this.manageUser(details, 'reinstateUser');
290+
},
291+
}
292+
: {
293+
iconClass: 'fas fa-ban',
294+
title: 'Suspend User',
295+
testid: 'suspend',
296+
color: 'red',
297+
onClick: () => {
298+
this.manageUser(details, 'suspendUser');
299+
},
300+
};
301+
302+
const forceLogoutAction = {
303+
iconClass: 'fas fa-power-off',
304+
title: 'Force Logout',
305+
testid: 'force-logout',
306+
onClick: () => {
307+
this.manageUser(details, 'logoutUser');
308+
},
309+
};
310+
311+
const makeRemoveAdmin = details.isAdmin
312+
? {
313+
iconClass: 'fas fa-minus',
314+
title: 'Remove as Admin',
315+
testid: 'remove-as-admin',
316+
onClick: () => {
317+
this.manageUser(details, 'removeAsAdmin');
318+
},
319+
}
320+
: {
321+
iconClass: 'fas fa-plus',
322+
title: 'Make into Admin',
323+
testid: 'make-as-admin',
324+
onClick: () => {
325+
this.manageUser(details, 'makeAdmin');
326+
},
327+
};
328+
329+
const iconActions = [];
330+
if (details.socketId && !details.doForceLogout)
331+
iconActions.push(forceLogoutAction);
332+
if (!isSelf) iconActions.push(suspendReinstateAction, makeRemoveAdmin);
333+
return iconActions;
334+
};
335+
285336
render() {
286337
const { match, user } = this.props;
287338
const {
@@ -315,6 +366,8 @@ class Dashboard extends Component {
315366
logoutUser: `Are you sure you want to manually logout ${username}?`,
316367
reinstateUser: `Are you sure you want to reinstate ${username}?`,
317368
suspendUser: `Are you sure you want to suspend ${username}. They will not be able to use VMT until they are reinstated.`,
369+
removeAsAdmin: `Are you sure you want to remove ${username} as an Admin?`,
370+
makeAdmin: `Are you sure you want to give ${username} Admin privileges?`,
318371
};
319372
manageUserPrompt = actionMessageHash[manageUserAction];
320373
}
@@ -340,6 +393,7 @@ class Dashboard extends Component {
340393
manageUser={this.manageUser}
341394
ownUserId={user._id}
342395
isLoading={isLoading}
396+
getIconActions={this._getIconActions}
343397
/>
344398
<Modal show={userToManage !== null} closeModal={this.stopManageUser}>
345399
{manageUserPrompt}

client/src/Layout/AdminDashboard/AdminDashboard.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class AdminDashboard extends Component {
4646
manageUser,
4747
ownUserId,
4848
isLoading,
49+
getIconActions,
4950
} = this.props;
5051

5152
const totalCount = totalCounts ? totalCounts.totalCount || 0 : 0;
@@ -184,6 +185,7 @@ class AdminDashboard extends Component {
184185
resultsMessage={resultsMessage}
185186
manageUser={manageUser}
186187
ownUserId={ownUserId}
188+
getIconActions={getIconActions}
187189
/>
188190
)}
189191
</div>
@@ -222,6 +224,7 @@ AdminDashboard.propTypes = {
222224
setToDate: PropTypes.func.isRequired,
223225
manageUser: PropTypes.func.isRequired,
224226
ownUserId: PropTypes.string.isRequired,
227+
getIconActions: PropTypes.func.isRequired,
225228
};
226229

227230
export default AdminDashboard;

client/src/Layout/DashboardBoxList/DashboardBoxList.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const dashboardBoxList = (props) => {
1515
resultsMessage,
1616
manageUser,
1717
ownUserId,
18+
getIconActions,
1819
} = props;
1920
let listElems = 'No activity within specified time period';
2021
if (list.length > 0) {
@@ -56,6 +57,11 @@ const dashboardBoxList = (props) => {
5657
resource={resource}
5758
manageUser={manageUser}
5859
isSelf={item._id === ownUserId}
60+
iconActions={getIconActions(
61+
item,
62+
resource,
63+
item._id === ownUserId
64+
)}
5965
/>
6066
</div>
6167
);
@@ -97,6 +103,7 @@ dashboardBoxList.propTypes = {
97103
resultsMessage: PropTypes.string,
98104
manageUser: PropTypes.func.isRequired,
99105
ownUserId: PropTypes.string.isRequired,
106+
getIconActions: PropTypes.func.isRequired,
100107
};
101108

102109
dashboardBoxList.defaultProps = {

client/src/Routes/MyVmt.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,60 +32,60 @@ const pages = [
3232
{
3333
path: '/courses/:course_id/:resource',
3434
component: Course,
35-
redirectPath: '/classcode',
35+
redirectPath: '/',
3636
},
3737
{
3838
path: '/courses/:course_id/activities/:activity_id/:resource',
3939
// component: Activity,
4040
component: withPopulatedActivity(Activity),
41-
redirectPath: '/classcode',
41+
redirectPath: '/',
4242
},
4343
{
4444
path:
4545
'/courses/:course_id/activities/:activity_id/rooms/:room_id/:resource',
4646
component: Room,
47-
redirectPath: '/classcode',
47+
redirectPath: '/',
4848
},
4949
{
5050
path: '/activities/:activity_id/rooms/:room_id/:resource',
5151
component: Room,
52-
redirectPath: '/signup',
52+
redirectPath: '/',
5353
},
5454
{
5555
path: '/courses/:course_id/rooms/:room_id/:resource',
5656
component: Room,
57-
redirectPath: '/classcode',
57+
redirectPath: '/',
5858
},
5959
{
6060
path: '/courses/:course_id/rooms/:room_id/:resource',
6161
component: Room,
62-
redirectPath: '/classcode',
62+
redirectPath: '/',
6363
},
6464
{
6565
path: '/rooms/:room_id/:resource',
6666
component: Room,
67-
redirectPath: '/signup',
67+
redirectPath: '/',
6868
},
6969
{
7070
path: '/activities/:activity_id/:resource',
7171
// component: Activity,
7272
component: withPopulatedActivity(Activity),
73-
redirectPath: '/signup',
73+
redirectPath: '/',
7474
},
7575
{
7676
path: '/workspace/:room_id/replayer',
7777
component: withPopulatedRoom(SharedReplayer),
78-
redirectPath: '/signup',
78+
redirectPath: '/',
7979
},
8080
{
8181
path: '/workspace/:activity_id/activity',
8282
component: ActivityWorkspace,
83-
redirectPath: '/signup',
83+
redirectPath: '/',
8484
},
8585
{
8686
path: '/workspace/:room_id',
8787
component: withPopulatedRoom(withControlMachine(Workspace)),
88-
redirectPath: '/signup',
88+
redirectPath: '/',
8989
},
9090
{
9191
path: '/confirmation',

client/src/utils/apiRequests.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,12 @@ export default {
210210
reinstateUser: (userId) => {
211211
return api.post(`/admin/reinstateUser/${userId}`);
212212
},
213+
removeAsAdmin: (userId) => {
214+
return api.put(`/api/user/${userId}`, { isAdmin: false });
215+
},
216+
makeAdmin: (userId) => {
217+
return api.put(`/api/user/${userId}`, { isAdmin: true });
218+
},
213219

214220
archiveRooms: (ids) => {
215221
return api.put(`/api/archiveRooms`, { ids });

server/controllers/UserController.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ module.exports = {
328328
$project: {
329329
username: 1,
330330
latestIpAddress: 1,
331+
isAdmin: 1,
331332
updatedAt: 1,
332333
isSuspended: 1,
333334
socketId: 1,

0 commit comments

Comments
 (0)