From e5a12b4bbf91c9be3683308c8d4bbe062f7288f6 Mon Sep 17 00:00:00 2001 From: Fluturecode Date: Fri, 26 May 2023 08:26:29 -0400 Subject: [PATCH 1/8] updated userApi.ts --- src/common/api/userApi.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/common/api/userApi.ts b/src/common/api/userApi.ts index 1de230dd0..4c8b97c19 100644 --- a/src/common/api/userApi.ts +++ b/src/common/api/userApi.ts @@ -40,6 +40,8 @@ export type UpdateProfilePictureRequest = { profilePicture: FormData; }; export type DeleteProfilePictureRequest = Pick; +export type DisableUserRequest = Pick; +export type EnableUserRequest = Pick; export const userApi = createApi({ reducerPath: 'userApi', @@ -76,7 +78,7 @@ export const userApi = createApi({ }), getUserHistory: builder.query>, string>({ - query: url => url + query: url => url, }), inviteUser: builder.mutation({ @@ -215,6 +217,22 @@ export const userApi = createApi({ }), invalidatesTags: ['User'], }), + + disableUser: builder.mutation({ + query: ({ id }) => ({ + url: `/users/${id}/disable/`, + method: 'POST', + }), + invalidatesTags: ['User'], + }), + + enableUser: builder.mutation({ + query: ({ id }) => ({ + url: `/users/${id}/enable/`, + method: 'POST', + }), + invalidatesTags: ['User'], + }), }), }); @@ -240,4 +258,6 @@ export const { useUpdateProfilePictureMutation, useDeleteProfilePictureMutation, useGetUserHistoryQuery, + useDisableUserMutation, + useEnableUserMutation, } = userApi; From 63736124cb416cc8c78e101c5b2c24ca8015dfd7 Mon Sep 17 00:00:00 2001 From: Fluturecode Date: Fri, 26 May 2023 08:27:26 -0400 Subject: [PATCH 2/8] updatd User model --- src/common/models/user.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/models/user.ts b/src/common/models/user.ts index 03be1de7c..e25d4203e 100644 --- a/src/common/models/user.ts +++ b/src/common/models/user.ts @@ -10,4 +10,6 @@ export interface User { profilePicture: Image | null; role: Role; newEmail: string | null; + isActive: boolean; + disabledAt: string | null; } From 65efc2c58f2c0e7f2364b5eceea35e89036b224f Mon Sep 17 00:00:00 2001 From: Fluturecode Date: Fri, 26 May 2023 08:28:30 -0400 Subject: [PATCH 3/8] updated UserDetailForm --- .../components/UserDetailForm.tsx | 102 +++++++++++++++--- 1 file changed, 89 insertions(+), 13 deletions(-) diff --git a/src/features/user-dashboard/components/UserDetailForm.tsx b/src/features/user-dashboard/components/UserDetailForm.tsx index 168152a20..cdd445cbf 100644 --- a/src/features/user-dashboard/components/UserDetailForm.tsx +++ b/src/features/user-dashboard/components/UserDetailForm.tsx @@ -5,20 +5,36 @@ import WithUnsavedChangesPrompt from 'common/components/WithUnsavedChangesPrompt import { addServerErrors } from 'common/error/utilities'; import { Role, User, RoleOption, ServerValidationErrors } from 'common/models'; import { FC, useEffect } from 'react'; -import { Col, Row } from 'react-bootstrap'; +import { Button, Col, Row } from 'react-bootstrap'; import Form from 'react-bootstrap/Form'; import { Controller, useForm } from 'react-hook-form'; import * as yup from 'yup'; +import styled from 'styled-components'; +import { useModalWithData } from 'common/hooks/useModalWithData'; +import { SimpleConfirmModal } from 'common/components/SimpleConfirmModal'; +import * as notificationService from 'common/services/notification'; +import { useDisableUserMutation, useEnableUserMutation } from 'common/api/userApi'; +import { handleApiError, isFetchBaseQueryError } from 'common/api/handleApiError'; import { Trans, useTranslation } from 'react-i18next'; import { Constants } from 'utils/constants'; -export type FormData = Pick; +const DisableButton = styled(Button)` + margin-left: 100px; + .spinner-container { + padding-right: 8px; + } +`; + +export type DisableUserRequest = { + id: string; +}; + +export type FormData = Pick; export interface Props { availableRoles: Role[]; defaultValues?: Partial; submitButtonLabel?: string; - isRoleSelectorDisabled?: boolean; onSubmit: (data: FormData) => void; serverValidationErrors: ServerValidationErrors | null; } @@ -26,7 +42,6 @@ export interface Props { export const UserDetailForm: FC = ({ availableRoles, defaultValues = {}, - isRoleSelectorDisabled, onSubmit, submitButtonLabel = 'Submit', serverValidationErrors, @@ -66,6 +81,62 @@ export const UserDetailForm: FC = ({ } }, [serverValidationErrors, setError]); + const [disableUser] = useDisableUserMutation(); + const [enableUser] = useEnableUserMutation(); + + const [showDisableModal, hideDisableModal] = useModalWithData( + user => + // eslint-disable-next-line react/no-unstable-nested-components + ({ in: open, onExited }) => { + const onSubmit = async () => { + try { + if (user.isActive) { + await disableUser({ id: user.id }).unwrap(); + notificationService.showSuccessMessage('User disabled.'); + } else { + await enableUser({ id: user.id }).unwrap(); + notificationService.showSuccessMessage('User enabled.'); + } + + hideDisableModal(); + } catch (error) { + if (isFetchBaseQueryError(error)) { + handleApiError(error); + } else { + if (user.isActive) { + notificationService.showErrorMessage('Could not disable user.'); + } else { + notificationService.showErrorMessage('Could not enable user.'); + } + throw error; + } + } + }; + + return ( + +

+ {user.isActive + ? 'Are you sure you want to disable this user?' + : 'Are you sure you want to enable this user?'} +

+ + } + /> + ); + }, + [], + ); + return (
@@ -114,17 +185,13 @@ export const UserDetailForm: FC = ({ Access Information - - {t('role', { ns: 'common' })} - {isRoleSelectorDisabled &&

{t('userDetail.roleRestriction')}

} -
+ {t('role', { ns: 'common' })} ( placeholder={t('userDetail.selectRole')!} - isDisabled={isRoleSelectorDisabled} defaultValue={{ label: defaultValues?.role?.toString() || '', value: defaultValues?.role || Role.USER }} options={options} onChange={option => onChange(option.value)} @@ -138,10 +205,19 @@ export const UserDetailForm: FC = ({
-
- - {submitButtonLabel} - +
+
+ + {submitButtonLabel} + +
+
+ {defaultValues.id ? ( + showDisableModal(defaultValues as User)} variant='btn btn-danger'> + {defaultValues.isActive ? 'Disable' : 'Enable'} + + ) : null} +
From 581466d023bbc0c6fa7e2b67f38c1cdcd210f683 Mon Sep 17 00:00:00 2001 From: Fluturecode Date: Fri, 26 May 2023 08:29:52 -0400 Subject: [PATCH 4/8] modified userUserTableData --- .../user-dashboard/hooks/useUserTableData.tsx | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/features/user-dashboard/hooks/useUserTableData.tsx b/src/features/user-dashboard/hooks/useUserTableData.tsx index b8eea5f0e..7a510d1bb 100644 --- a/src/features/user-dashboard/hooks/useUserTableData.tsx +++ b/src/features/user-dashboard/hooks/useUserTableData.tsx @@ -23,8 +23,10 @@ export type UserTableItem = { role: RoleType; profilePicture: Image | null; actions: ActionButtonProps[]; + isActive: boolean; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + disabledAt: string | null | any; }; - export const useUserTableData = (users: User[] = []) => { const { userHasPermission } = useRbac(); const { t, i18n } = useTranslation(['translation', 'common']); @@ -194,6 +196,11 @@ export const useUserTableData = (users: User[] = []) => { ), }, + { + accessor: 'isActive', + Header: 'Account Status', + Cell: ({ value: isActive }) => {isActive ? 'Enabled' : 'Disabled'}, + }, { accessor: 'activatedAt', Header: t('activatedDate', { ns: 'common' })!, @@ -212,6 +219,21 @@ export const useUserTableData = (users: User[] = []) => { ), }, + { + accessor: 'disabledAt', + Header: 'Disabled On', + Cell: ({ value: disabledAt }) => ( + <> + {disabledAt instanceof Date ? ( + + ) : ( + <> + )} + + ), + }, { accessor: 'actions', Header: '', @@ -246,7 +268,9 @@ export const useUserTableData = (users: User[] = []) => { firstName: user.firstName, email: user.email, role: user.role, + isActive: user.isActive, profilePicture: user.profilePicture, + disabledAt: user.disabledAt ? new Date(user.disabledAt) : '', activatedAt: user.activatedAt ? new Date(user.activatedAt) : { From e4ab42ba77d1f2eb840dc32add50c62c53a7298f Mon Sep 17 00:00:00 2001 From: Fluturecode Date: Fri, 26 May 2023 08:30:49 -0400 Subject: [PATCH 5/8] updated UserListView.tsx --- src/features/user-dashboard/pages/UserListView.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/features/user-dashboard/pages/UserListView.tsx b/src/features/user-dashboard/pages/UserListView.tsx index 3ce97190a..ad798c724 100644 --- a/src/features/user-dashboard/pages/UserListView.tsx +++ b/src/features/user-dashboard/pages/UserListView.tsx @@ -57,11 +57,24 @@ export const UserListView: FC = () => { { label: t('admin', { ns: 'common' }), value: 'ADMIN' }, ]), }, + { + attribute: 'active', + attributeLabel: 'Account Status', + FilterUI: EnumFilter([ + { label: 'Enabled', value: 'ENABLED' }, + { label: 'Disabled', value: 'DISABLED' }, + ]), + }, { attribute: 'activatedAt', attributeLabel: t('activatedDate', { ns: 'common' }), FilterUI: RecentDateFilter([30, 90, 180]), }, + { + attribute: 'deactivatedAt', + attributeLabel: 'Deactivated Date', + FilterUI: RecentDateFilter([30, 90, 180]), + }, ], [t], ); From f7e23eceb1e7f9c92083a087a396c7d438cce076 Mon Sep 17 00:00:00 2001 From: Fluturecode Date: Fri, 26 May 2023 08:39:26 -0400 Subject: [PATCH 6/8] fixed interface props for UserDetailForm --- src/features/user-dashboard/components/UserDetailForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/features/user-dashboard/components/UserDetailForm.tsx b/src/features/user-dashboard/components/UserDetailForm.tsx index cdd445cbf..6d21c7367 100644 --- a/src/features/user-dashboard/components/UserDetailForm.tsx +++ b/src/features/user-dashboard/components/UserDetailForm.tsx @@ -35,6 +35,7 @@ export interface Props { availableRoles: Role[]; defaultValues?: Partial; submitButtonLabel?: string; + isRoleSelectorDisabled?: boolean; onSubmit: (data: FormData) => void; serverValidationErrors: ServerValidationErrors | null; } From 28bdac4dee2534b84d835e4b02623af15b7105c4 Mon Sep 17 00:00:00 2001 From: Fluturecode Date: Fri, 26 May 2023 08:47:43 -0400 Subject: [PATCH 7/8] added back isRoleSelectedDisabled --- src/features/user-dashboard/components/UserDetailForm.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/features/user-dashboard/components/UserDetailForm.tsx b/src/features/user-dashboard/components/UserDetailForm.tsx index 6d21c7367..fe2b0bda0 100644 --- a/src/features/user-dashboard/components/UserDetailForm.tsx +++ b/src/features/user-dashboard/components/UserDetailForm.tsx @@ -44,6 +44,7 @@ export const UserDetailForm: FC = ({ availableRoles, defaultValues = {}, onSubmit, + isRoleSelectorDisabled, submitButtonLabel = 'Submit', serverValidationErrors, }) => { @@ -186,7 +187,10 @@ export const UserDetailForm: FC = ({ Access Information - {t('role', { ns: 'common' })} + + {t('role', { ns: 'common' })} + {isRoleSelectorDisabled &&

{t('userDetail.roleRestriction')}

} +
Date: Fri, 26 May 2023 08:51:24 -0400 Subject: [PATCH 8/8] fixed reverted lines --- src/features/user-dashboard/components/UserDetailForm.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/features/user-dashboard/components/UserDetailForm.tsx b/src/features/user-dashboard/components/UserDetailForm.tsx index fe2b0bda0..58bf7864b 100644 --- a/src/features/user-dashboard/components/UserDetailForm.tsx +++ b/src/features/user-dashboard/components/UserDetailForm.tsx @@ -44,8 +44,8 @@ export const UserDetailForm: FC = ({ availableRoles, defaultValues = {}, onSubmit, - isRoleSelectorDisabled, submitButtonLabel = 'Submit', + isRoleSelectorDisabled, serverValidationErrors, }) => { const { t } = useTranslation(['translation', 'common']); @@ -197,6 +197,7 @@ export const UserDetailForm: FC = ({ render={({ field: { onChange } }) => ( placeholder={t('userDetail.selectRole')!} + isDisabled={isRoleSelectorDisabled} defaultValue={{ label: defaultValues?.role?.toString() || '', value: defaultValues?.role || Role.USER }} options={options} onChange={option => onChange(option.value)}