Skip to content

Commit 8b664c3

Browse files
committed
lock user through UI
1 parent febde23 commit 8b664c3

File tree

10 files changed

+114
-10
lines changed

10 files changed

+114
-10
lines changed

src/api/entities/AuditTrail.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export enum AuditTrailEvents {
1919
UpdateDomainNames = 'UpdateDomainNames',
2020
UpdateAppNames = 'UpdateAppNames',
2121
ManageTeamMembers = 'ManageTeamMembers',
22+
ChangeUserLock = 'ChangeUserLock',
2223
}
2324

2425
export class AuditTrail extends BaseModel {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
import express, { Response } from 'express';
2+
import { z } from 'zod';
23

34
import { isSuperUserCheck } from '../middleware/userRoleMiddleware';
45
import { ParticipantRequest } from '../services/participantsService';
6+
import { UserService } from '../services/userService';
57
import { getAllUsersList } from '../services/usersService';
68

79
const handleGetAllUsers = async (req: ParticipantRequest, res: Response) => {
810
const userList = await getAllUsersList();
911
return res.status(200).json(userList);
1012
};
1113

14+
const handleChangeUserLock = async (req: ParticipantRequest, res: Response) => {
15+
const userService = new UserService();
16+
const { userId } = z.object({ userId: z.coerce.number() }).parse(req.params);
17+
const { isLocked } = z.object({ isLocked: z.boolean() }).parse(req.body);
18+
await userService.updateUserLock(req, userId, isLocked);
19+
};
20+
1221
export function createManagementRouter() {
1322
const managementRouter = express.Router();
1423

1524
managementRouter.get('/users', isSuperUserCheck, handleGetAllUsers);
25+
managementRouter.patch('/:userId/changeLock', isSuperUserCheck, handleChangeUserLock);
1626

1727
return managementRouter;
1828
}

src/api/services/userService.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { z } from 'zod';
33

44
import { AuditAction, AuditTrailEvents } from '../entities/AuditTrail';
55
import { ParticipantType } from '../entities/ParticipantType';
6-
import { UserJobFunction } from '../entities/User';
6+
import { User, UserJobFunction } from '../entities/User';
77
import { getUserRoleById, UserRoleId } from '../entities/UserRole';
88
import { UserToParticipantRole } from '../entities/UserToParticipantRole';
99
import { getTraceId } from '../helpers/loggingHelpers';
@@ -142,4 +142,24 @@ export class UserService {
142142
});
143143
});
144144
}
145+
146+
public async updateUserLock(req: UserParticipantRequest, userId: number, isLocked: boolean) {
147+
const requestingUser = await findUserByEmail(req.auth?.payload.email as string);
148+
const updatedUser = await User.query().where('id', userId).first();
149+
const traceId = getTraceId(req);
150+
151+
const auditTrailInsertObject = constructAuditTrailObject(
152+
requestingUser!,
153+
AuditTrailEvents.ChangeUserLock,
154+
{
155+
action: AuditAction.Update,
156+
email: updatedUser?.email,
157+
locked: isLocked,
158+
}
159+
);
160+
161+
await performAsyncOperationWithAuditTrail(auditTrailInsertObject, traceId, async () => {
162+
await User.query().where('id', userId).update({ locked: isLocked });
163+
});
164+
}
145165
}

src/api/services/usersService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ export const inviteUserToParticipant = async (
199199
}
200200
};
201201

202+
// should this be moved somewhere for only superuser actions? managementService?
202203
export const getAllUsersList = async () => {
203204
const userList = await User.query().where('deleted', 0).orderBy('email');
204205
return userList;

src/web/components/PortalHeader/PortalHeader.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@
9797
}
9898

9999
.theme-toggle[data-state='checked'] {
100-
background-color: 'black';
100+
background-color: black;
101101
}
102102

103103
.thumb {

src/web/components/UserManagement/UserManagementItem.scss

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,42 @@
1717
.approver-name {
1818
margin-right: 40px;
1919
}
20+
21+
.theme-switch {
22+
button {
23+
all: unset;
24+
}
25+
26+
color: var(--theme-secondary);
27+
display: flex;
28+
justify-content: space-between;
29+
margin-top: 10px;
30+
margin-bottom: 10px;
31+
32+
.theme-toggle {
33+
width: 42px;
34+
height: 25px;
35+
background-color: gray;
36+
border-radius: 9999px;
37+
margin-left: 10px;
38+
cursor: pointer;
39+
}
40+
41+
.theme-toggle[data-state='checked'] {
42+
background-color: crimson;
43+
}
44+
45+
.thumb {
46+
display: block;
47+
width: 21px;
48+
height: 21px;
49+
background-color: white;
50+
border-radius: 9999px;
51+
transform: translateX(2px);
52+
}
53+
54+
.thumb[data-state='checked'] {
55+
transform: translateX(19px);
56+
}
57+
}
2058
}
Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,42 @@
1+
import * as Switch from '@radix-ui/react-switch';
2+
import { useState } from 'react';
3+
14
import { UserDTO } from '../../../api/entities/User';
25

36
import './UserManagementItem.scss';
47

58
type UserManagementItemProps = Readonly<{
69
user: UserDTO;
10+
onChangeUserLock: (userId: number, isLocked: boolean) => Promise<void>;
711
}>;
812

9-
export function UserManagementItem({ user }: UserManagementItemProps) {
13+
export function UserManagementItem({ user, onChangeUserLock }: UserManagementItemProps) {
14+
const [lockedState, setLockedState] = useState(user.locked);
15+
16+
const onLockedToggle = async () => {
17+
await onChangeUserLock(user.id, !lockedState);
18+
setLockedState(!lockedState);
19+
};
20+
1021
return (
1122
<tr className='user-management-item'>
1223
<td>{user.email}</td>
1324
<td>{user.firstName}</td>
1425
<td>{user.lastName}</td>
1526
<td>{user.jobFunction}</td>
1627
<td>{user.acceptedTerms ? 'True' : 'False'}</td>
17-
<td />
18-
<td />
28+
<td>
29+
<div className='theme-switch'>
30+
<Switch.Root
31+
name='dark-mode'
32+
checked={lockedState}
33+
onCheckedChange={onLockedToggle}
34+
className='theme-toggle clickable-item'
35+
>
36+
<Switch.Thumb className='thumb' />
37+
</Switch.Root>
38+
</div>
39+
</td>
1940
</tr>
2041
);
2142
}

src/web/components/UserManagement/UserManagementTable.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import './UserManagementTable.scss';
1313

1414
type UserManagementTableProps = Readonly<{
1515
users: UserDTO[];
16+
onChangeUserLock: (userId: number, isLocked: boolean) => Promise<void>;
1617
}>;
1718

1819
function NoUsers() {
@@ -23,7 +24,7 @@ function NoUsers() {
2324
);
2425
}
2526

26-
function UserManagementTableContent({ users }: UserManagementTableProps) {
27+
function UserManagementTableContent({ users, onChangeUserLock }: UserManagementTableProps) {
2728
const initialRowsPerPage = 25;
2829
const initialPageNumber = 1;
2930

@@ -96,14 +97,13 @@ function UserManagementTableContent({ users }: UserManagementTableProps) {
9697
<SortableTableHeader<UserDTO> sortKey='lastName' header='Last Name' />
9798
<SortableTableHeader<UserDTO> sortKey='jobFunction' header='Job Function' />
9899
<th>Accepted Terms</th>
99-
<th>Delete Action Here</th>
100100
<th>Lock Action Here</th>
101101
</tr>
102102
</thead>
103103

104104
<tbody>
105105
{pagedRows.map((user) => (
106-
<UserManagementItem key={user.id} user={user} />
106+
<UserManagementItem key={user.id} user={user} onChangeUserLock={onChangeUserLock} />
107107
))}
108108
</tbody>
109109
</table>

src/web/screens/manageUsers.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { defer, useLoaderData } from 'react-router-typesafe';
44
import { Loading } from '../components/Core/Loading/Loading';
55
import { ScreenContentContainer } from '../components/Core/ScreenContentContainer/ScreenContentContainer';
66
import UserManagementTable from '../components/UserManagement/UserManagementTable';
7-
import { GetAllUsers } from '../services/userAccount';
7+
import { ChangeUserLock, GetAllUsers } from '../services/userAccount';
88
import { AwaitTypesafe } from '../utils/AwaitTypesafe';
99
import { RouteErrorBoundary } from '../utils/RouteErrorBoundary';
1010
import { PortalRoute } from './routeUtils';
@@ -17,14 +17,19 @@ const loader = () => {
1717
function ManageUsers() {
1818
const data = useLoaderData<typeof loader>();
1919

20+
const onChangeUserLock = async (userId: number, isLocked: boolean) => {
21+
console.log(`locking user: ${userId}`);
22+
await ChangeUserLock(userId, isLocked);
23+
};
24+
2025
return (
2126
<>
2227
<h1>Manage Users</h1>
2328
<p className='heading-details'>Manage portal users</p>
2429
<ScreenContentContainer>
2530
<Suspense fallback={<Loading message='Loading users...' />}>
2631
<AwaitTypesafe resolve={data.userList}>
27-
{(users) => <UserManagementTable users={users} />}
32+
{(users) => <UserManagementTable users={users} onChangeUserLock={onChangeUserLock} />}
2833
</AwaitTypesafe>
2934
</Suspense>
3035
</ScreenContentContainer>

src/web/services/userAccount.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,11 @@ export async function GetAllUsers() {
120120
throw backendError(e, 'Unable to get user list.');
121121
}
122122
}
123+
124+
export async function ChangeUserLock(userId: number, isLocked: boolean) {
125+
try {
126+
return await axios.patch(`/manage/${userId}/changeLock`, { userId, isLocked });
127+
} catch (e: unknown) {
128+
throw backendError(e, 'Unable to update user lock status.');
129+
}
130+
}

0 commit comments

Comments
 (0)