From bb4f7dd5dfe1e507fcf9194fd6fbb54f9fed1224 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young <108430771+mauberti-bc@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:29:42 -0700 Subject: [PATCH] Update styling for the Add User form (#1355) * update styling of add user form * fix tests * fix: tweaked some props for edit dialog --------- Co-authored-by: Mac Deluca <99926243+MacQSL@users.noreply.github.com> Co-authored-by: Mac Deluca --- app/src/AppRouter.tsx | 4 +- app/src/components/fields/CustomTextField.tsx | 10 +- .../admin/users/ActiveUsersList.test.tsx | 20 +- .../features/admin/users/ActiveUsersList.tsx | 64 +++-- .../admin/users/AddSystemUsersForm.tsx | 253 +++++++----------- .../admin/users/ManageUsersPage.test.tsx | 18 +- 6 files changed, 178 insertions(+), 191 deletions(-) diff --git a/app/src/AppRouter.tsx b/app/src/AppRouter.tsx index 80bbebf558..5e09060113 100644 --- a/app/src/AppRouter.tsx +++ b/app/src/AppRouter.tsx @@ -83,7 +83,9 @@ const AppRouter: React.FC = () => { - + + + diff --git a/app/src/components/fields/CustomTextField.tsx b/app/src/components/fields/CustomTextField.tsx index 38744291fd..4f50ed4ae3 100644 --- a/app/src/components/fields/CustomTextField.tsx +++ b/app/src/components/fields/CustomTextField.tsx @@ -9,6 +9,13 @@ export interface ICustomTextField { * @memberof ICustomTextField */ label: string; + /** + * Placeholder for the text field + * + * @type {string} + * @memberof ICustomTextField + */ + placeholder?: string; /** * Name of the text field, typically this is used to identify the field in the formik context. * @@ -34,13 +41,14 @@ export interface ICustomTextField { const CustomTextField = (props: React.PropsWithChildren) => { const { touched, errors, values, handleChange, handleBlur } = useFormikContext(); - const { name, label, other } = props; + const { name, label, other, placeholder } = props; return ( {} + } as DataLoader +}; + const renderContainer = (props: IActiveUsersListProps) => { const authState = getMockAuthState({ base: SystemAdminAuthState }); return render( - - - + + + + + ); }; diff --git a/app/src/features/admin/users/ActiveUsersList.tsx b/app/src/features/admin/users/ActiveUsersList.tsx index d6e48eabd0..f8ef7845d7 100644 --- a/app/src/features/admin/users/ActiveUsersList.tsx +++ b/app/src/features/admin/users/ActiveUsersList.tsx @@ -16,11 +16,13 @@ import { DialogContext, ISnackbarProps } from 'contexts/dialogContext'; import { APIError } from 'hooks/api/useAxios'; import { useAuthStateContext } from 'hooks/useAuthStateContext'; import { useBiohubApi } from 'hooks/useBioHubApi'; +import { useCodesContext } from 'hooks/useContext'; import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; import { ISystemUser } from 'interfaces/useUserApi.interface'; -import { useContext, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { useHistory } from 'react-router'; import { Link as RouterLink } from 'react-router-dom'; +import { getCodesName } from 'utils/Utils'; import AddSystemUsersForm, { AddSystemUsersFormInitialValues, AddSystemUsersFormYupSchema, @@ -50,6 +52,12 @@ const ActiveUsersList = (props: IActiveUsersListProps) => { const [openAddUserDialog, setOpenAddUserDialog] = useState(false); + const codesContext = useCodesContext(); + + useEffect(() => { + codesContext.codesDataLoader.load(); + }, [codesContext.codesDataLoader]); + const activeUsersColumnDefs: GridColDef[] = [ { field: 'user_identifier', @@ -279,16 +287,16 @@ const ActiveUsersList = (props: IActiveUsersListProps) => { const handleAddSystemUsersSave = async (values: IAddSystemUsersForm) => { setOpenAddUserDialog(false); + const systemUser = values.systemUser; + try { - for (const systemUser of values.systemUsers) { - await biohubApi.admin.addSystemUser( - systemUser.userIdentifier, - systemUser.identitySource, - systemUser.displayName, - systemUser.email, - systemUser.systemRole - ); - } + await biohubApi.admin.addSystemUser( + systemUser.userIdentifier, + systemUser.identitySource, + systemUser.displayName, + systemUser.email, + systemUser.systemRole + ); // Refresh users list refresh(); @@ -297,7 +305,7 @@ const ActiveUsersList = (props: IActiveUsersListProps) => { open: true, snackbarMessage: ( - {values.systemUsers.length} system {values.systemUsers.length > 1 ? 'users' : 'user'} added. + Successfully added {systemUser.displayName} ) }); @@ -307,8 +315,12 @@ const ActiveUsersList = (props: IActiveUsersListProps) => { if (apiError.status === 409) { dialogContext.setErrorDialog({ open: true, - dialogTitle: 'Failed to create users', - dialogText: 'One of the users you added already exists.', + dialogTitle: 'User already exists', + dialogText: `${systemUser.displayName} already exists as a ${getCodesName( + codesContext.codesDataLoader.data, + 'system_roles', + systemUser.systemRole + )}`, onClose: () => { dialogContext.setErrorDialog({ open: false }); }, @@ -380,21 +392,29 @@ const ActiveUsersList = (props: IActiveUsersListProps) => { { - return { value: item.id, label: item.name }; - }) || [] - } - /> + <> + + This form creates a new user that will be linked to an IDIR/BCeID when an account with a matching + username, email, and account type logs in. + + { + return { value: item.id, label: item.name }; + }) || [] + } + /> + ), initialValues: AddSystemUsersFormInitialValues, - validationSchema: AddSystemUsersFormYupSchema + validationSchema: AddSystemUsersFormYupSchema, + validateOnBlur: false }} onCancel={() => setOpenAddUserDialog(false)} onSave={(values) => { diff --git a/app/src/features/admin/users/AddSystemUsersForm.tsx b/app/src/features/admin/users/AddSystemUsersForm.tsx index 02fafd2ba8..2f13b1d813 100644 --- a/app/src/features/admin/users/AddSystemUsersForm.tsx +++ b/app/src/features/admin/users/AddSystemUsersForm.tsx @@ -1,17 +1,9 @@ -import { mdiPlus, mdiTrashCanOutline } from '@mdi/js'; -import Icon from '@mdi/react'; import Box from '@mui/material/Box'; -import Button from '@mui/material/Button'; -import FormControl from '@mui/material/FormControl'; -import FormHelperText from '@mui/material/FormHelperText'; -import Grid from '@mui/material/Grid'; -import IconButton from '@mui/material/IconButton'; -import InputLabel from '@mui/material/InputLabel'; -import MenuItem from '@mui/material/MenuItem'; -import Select from '@mui/material/Select'; +import Stack from '@mui/material/Stack'; +import AutocompleteField, { IAutocompleteFieldOption } from 'components/fields/AutocompleteField'; import CustomTextField from 'components/fields/CustomTextField'; import { SYSTEM_IDENTITY_SOURCE } from 'constants/auth'; -import { FieldArray, FieldArrayRenderProps, useFormikContext } from 'formik'; +import { useFormikContext } from 'formik'; import React from 'react'; import yup from 'utils/YupSchema'; @@ -24,7 +16,7 @@ export interface IAddSystemUsersFormArrayItem { } export interface IAddSystemUsersForm { - systemUsers: IAddSystemUsersFormArrayItem[]; + systemUser: IAddSystemUsersFormArrayItem; } export const AddSystemUsersFormArrayItemInitialValues: IAddSystemUsersFormArrayItem = { @@ -36,168 +28,107 @@ export const AddSystemUsersFormArrayItemInitialValues: IAddSystemUsersFormArrayI }; export const AddSystemUsersFormInitialValues: IAddSystemUsersForm = { - systemUsers: [AddSystemUsersFormArrayItemInitialValues] + systemUser: AddSystemUsersFormArrayItemInitialValues }; export const AddSystemUsersFormYupSchema = yup.object().shape({ - systemUsers: yup.array().of( - yup.object().shape({ - userIdentifier: yup.string().required('Username is required'), - displayName: yup.string().required('Display Name is required'), - email: yup.string().email('Must be a valid email').required('Email is required'), - identitySource: yup.string().required('Login Method is required'), - systemRole: yup.number().required('Role is required') - }) - ) + systemUser: yup.object().shape({ + userIdentifier: yup.string().required('Username is required'), + displayName: yup.string().required('Display Name is required'), + email: yup.string().email('Must be a valid email').required('Email is required'), + identitySource: yup.string().required('Account Type is required'), + systemRole: yup.number().required('System Role is required') + }) }); export interface AddSystemUsersFormProps { - systemRoles: any[]; + systemRoles: IAutocompleteFieldOption[]; } +/** + * Returns form component for manually adding system users before access is requested + * + * @param props + * @returns + */ const AddSystemUsersForm: React.FC = (props) => { - const { values, handleChange, handleSubmit, getFieldMeta } = useFormikContext(); + const { values, handleSubmit, getFieldMeta } = useFormikContext(); + + const userIdentifierMeta = getFieldMeta('systemUser.userIdentifier'); + const displayNameMeta = getFieldMeta('systemUser.displayName'); + const emailMeta = getFieldMeta('systemUser.email'); + + const { systemRoles } = props; + const identitySources: IAutocompleteFieldOption[] = [ + { value: SYSTEM_IDENTITY_SOURCE.IDIR as string, label: SYSTEM_IDENTITY_SOURCE.IDIR }, + { value: SYSTEM_IDENTITY_SOURCE.BCEID_BASIC as string, label: SYSTEM_IDENTITY_SOURCE.BCEID_BASIC }, + { value: SYSTEM_IDENTITY_SOURCE.BCEID_BUSINESS as string, label: SYSTEM_IDENTITY_SOURCE.BCEID_BUSINESS }, + { value: SYSTEM_IDENTITY_SOURCE.UNVERIFIED as string, label: SYSTEM_IDENTITY_SOURCE.UNVERIFIED } + ]; return (
- ( - - - {values.systemUsers?.map((systemUser: IAddSystemUsersFormArrayItem, index: number) => { - const userIdentifierMeta = getFieldMeta(`systemUsers.[${index}].userIdentifier`); - const displayNameMeta = getFieldMeta(`systemUsers.[${index}].displayName`); - const emailMeta = getFieldMeta(`systemUsers.[${index}].email`); - const identitySourceMeta = getFieldMeta(`systemUsers.[${index}].identitySource`); - const systemRoleMeta = getFieldMeta(`systemUsers.[${index}].roleId`); - - return ( - - - - - - - - - - - - - - - - Login Method - - - {identitySourceMeta.touched && identitySourceMeta.error} - - - - - - System Role - - - {systemRoleMeta.touched && systemRoleMeta.error} - - - - - arrayHelpers.remove(index)} - sx={{ - marginTop: '8px' - }}> - - - - - - ); - })} - - - - + + + + - )} - /> + + + + + + + + + + + + ); }; diff --git a/app/src/features/admin/users/ManageUsersPage.test.tsx b/app/src/features/admin/users/ManageUsersPage.test.tsx index 3cd33589cc..c9d44764ba 100644 --- a/app/src/features/admin/users/ManageUsersPage.test.tsx +++ b/app/src/features/admin/users/ManageUsersPage.test.tsx @@ -1,8 +1,11 @@ import { AuthStateContext } from 'contexts/authStateContext'; +import { CodesContext, ICodesContext } from 'contexts/codesContext'; import { createMemoryHistory } from 'history'; import { useBiohubApi } from 'hooks/useBioHubApi'; +import { DataLoader } from 'hooks/useDataLoader'; import { Router } from 'react-router'; import { getMockAuthState, SystemAdminAuthState } from 'test-helpers/auth-helpers'; +import { codes } from 'test-helpers/code-helpers'; import { cleanup, render, waitFor } from 'test-helpers/test-utils'; import ManageUsersPage from './ManageUsersPage'; @@ -11,11 +14,20 @@ const history = createMemoryHistory(); const renderContainer = () => { const authState = getMockAuthState({ base: SystemAdminAuthState }); + const mockCodesContext: ICodesContext = { + codesDataLoader: { + data: codes, + load: () => {} + } as DataLoader + }; + return render( - - - + + + + + ); };