Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

style: add user modal #313

Merged
merged 1 commit into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion web/src/i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ const en: BaseTranslation = {
},
addUser: {
title: 'Add new user',
messages: {
userAdded: 'User added',
},
form: {
submit: 'Add user',
fields: {
Expand Down Expand Up @@ -346,7 +349,7 @@ const en: BaseTranslation = {
label: 'Phone',
},
enableEnrollment: {
label: 'Use remote enrollment',
label: 'Use enrollment process',
},
},
},
Expand Down
16 changes: 14 additions & 2 deletions web/src/i18n/i18n-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,12 @@ type RootTranslation = {
* A​d​d​ ​n​e​w​ ​u​s​e​r
*/
title: string
messages: {
/**
* U​s​e​r​ ​a​d​d​e​d
*/
userAdded: string
}
form: {
/**
* A​d​d​ ​u​s​e​r
Expand Down Expand Up @@ -725,7 +731,7 @@ type RootTranslation = {
}
enableEnrollment: {
/**
* U​s​e​ ​r​e​m​o​t​e​ ​e​n​r​o​l​l​m​e​n​t
* U​s​e​ ​e​n​r​o​l​l​m​e​n​t​ ​p​r​o​c​e​s​s
*/
label: string
}
Expand Down Expand Up @@ -3926,6 +3932,12 @@ export type TranslationFunctions = {
* Add new user
*/
title: () => LocalizedString
messages: {
/**
* User added
*/
userAdded: () => LocalizedString
}
form: {
/**
* Add user
Expand Down Expand Up @@ -3994,7 +4006,7 @@ export type TranslationFunctions = {
}
enableEnrollment: {
/**
* Use remote enrollment
* Use enrollment process
*/
label: () => LocalizedString
}
Expand Down
3 changes: 3 additions & 0 deletions web/src/i18n/pl/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ const pl: Translation = {
},
},
addUser: {
messages: {
userAdded: 'Stworzono użytkownika',
},
title: 'Dodaj nowego użytkownika',
form: {
submit: 'Dodaj użytkownika',
Expand Down
10 changes: 4 additions & 6 deletions web/src/pages/users/UsersOverview/UsersOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@ import {
SelectSelectedValue,
SelectSizeVariant,
} from '../../../shared/defguard-ui/components/Layout/Select/types';
import { useModalStore } from '../../../shared/hooks/store/useModalStore';
import useApi from '../../../shared/hooks/useApi';
import { QueryKeys } from '../../../shared/queries';
import { User } from '../../../shared/types';
import { UsersList } from './components/UsersList/UsersList';
import AddUserModal from './modals/AddUserModal/AddUserModal';
import { StartEnrollmentModal } from './modals/StartEnrollmentModal/StartEnrollmentModal';
import { AddUserModal } from './modals/AddUserModal/AddUserModal';
import { useAddUserModal } from './modals/AddUserModal/hooks/useAddUserModal';

enum FilterOptions {
ALL = 'all',
Expand Down Expand Up @@ -83,7 +82,7 @@ export const UsersOverview = () => {

const [usersSearchValue, setUsersSearchValue] = useState('');

const setUserAddModalState = useModalStore((state) => state.setAddUserModal);
const openAddUserModal = useAddUserModal((state) => state.open);

const filteredUsers = useMemo(() => {
if (!users || (users && !users.length)) {
Expand Down Expand Up @@ -161,7 +160,7 @@ export const UsersOverview = () => {
)}
<Button
className="add-item"
onClick={() => setUserAddModalState({ visible: true })}
onClick={openAddUserModal}
size={ButtonSize.SMALL}
styleVariant={ButtonStyleVariant.PRIMARY}
icon={<SvgIconUserAddNew />}
Expand All @@ -188,7 +187,6 @@ export const UsersOverview = () => {
</div>
)}
<AddUserModal />
<StartEnrollmentModal />
</section>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useAuthStore } from '../../../../../shared/hooks/store/useAuthStore';
import { useModalStore } from '../../../../../shared/hooks/store/useModalStore';
import { useUserProfileStore } from '../../../../../shared/hooks/store/useUserProfileStore';
import { User } from '../../../../../shared/types';
import { useEnrollmentModalStore } from '../../modals/StartEnrollmentModal/hooks/useEnrollmentModalStore';
import { useAddUserModal } from '../../modals/AddUserModal/hooks/useAddUserModal';

type Props = {
user: User;
Expand All @@ -20,16 +20,22 @@ export const UserEditButton = ({ user }: Props) => {
const setProvisionKeyModal = useModalStore((state) => state.setProvisionKeyModal);
const setDeleteUserModal = useModalStore((state) => state.setDeleteUserModal);
const setChangePasswordModal = useModalStore((state) => state.setChangePasswordModal);
const openEnrollmentModal = useEnrollmentModalStore((state) => state.open);
const setUserProfile = useUserProfileStore((state) => state.setState);
const setAddUserModal = useAddUserModal((state) => state.setState);
const currentUser = useAuthStore((state) => state.user);
return (
<EditButton>
{!user.is_active && (
<EditButtonOption
key="start-enrollment"
text={LL.usersOverview.list.editButton.startEnrollment()}
onClick={() => openEnrollmentModal(user)}
onClick={() =>
setAddUserModal({
visible: true,
step: 1,
user: user,
})
}
/>
)}
<EditButtonOption
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
import './style.scss';

import { ReactNode } from 'react';
import { shallow } from 'zustand/shallow';

import { useI18nContext } from '../../../../../i18n/i18n-react';
import { ModalWithTitle } from '../../../../../shared/defguard-ui/components/Layout/modals/ModalWithTitle/ModalWithTitle';
import { useModalStore } from '../../../../../shared/hooks/store/useModalStore';
import { AddUserForm } from './AddUserForm';
import { AddUserForm } from './components/AddUserForm/AddUserForm';
import { EnrollmentTokenCard } from './components/EnrollmentTokenCard/EnrollmentTokenCard';
import { StartEnrollmentForm } from './components/StartEnrollmentForm/StartEnrollmentForm';
import { useAddUserModal } from './hooks/useAddUserModal';

const AddUserModal = () => {
const [{ visible: isOpen }, setModalState] = useModalStore(
(state) => [state.addUserModal, state.setAddUserModal],
const steps: ReactNode[] = [
<AddUserForm key={0} />,
<StartEnrollmentForm key={1} />,
<EnrollmentTokenCard key={2} />,
];

export const AddUserModal = () => {
const { LL } = useI18nContext();

const [currentStep, visible] = useAddUserModal(
(state) => [state.step, state.visible],
shallow,
);

const setIsOpen = (v: boolean) => setModalState({ visible: v });
const { LL } = useI18nContext();
const [reset, close] = useAddUserModal((state) => [state.reset, state.close], shallow);

return (
<ModalWithTitle
backdrop
title={LL.modals.addUser.title()}
isOpen={isOpen}
setIsOpen={setIsOpen}
id="add-user-modal"
>
<AddUserForm />
</ModalWithTitle>
backdrop
title={
currentStep === 0 ? LL.modals.addUser.title() : LL.modals.startEnrollment.title()
}
onClose={close}
afterClose={reset}
steps={steps}
currentStep={currentStep}
isOpen={visible}
/>
);
};

export default AddUserModal;
Original file line number Diff line number Diff line change
@@ -1,40 +1,43 @@
import './style.scss';

import { yupResolver } from '@hookform/resolvers/yup';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { omit } from 'lodash-es';
import { useMemo, useRef, useState } from 'react';
import { SubmitHandler, useController, useForm } from 'react-hook-form';
import * as yup from 'yup';
import { shallow } from 'zustand/shallow';

import { useI18nContext } from '../../../../../i18n/i18n-react';
import { FormCheckBox } from '../../../../../shared/defguard-ui/components/Form/FormCheckBox/FormCheckBox';
import { FormInput } from '../../../../../shared/defguard-ui/components/Form/FormInput/FormInput';
import { Button } from '../../../../../shared/defguard-ui/components/Layout/Button/Button';
import { useI18nContext } from '../../../../../../../i18n/i18n-react';
import { FormCheckBox } from '../../../../../../../shared/defguard-ui/components/Form/FormCheckBox/FormCheckBox';
import { FormInput } from '../../../../../../../shared/defguard-ui/components/Form/FormInput/FormInput';
import { Button } from '../../../../../../../shared/defguard-ui/components/Layout/Button/Button';
import {
ButtonSize,
ButtonStyleVariant,
} from '../../../../../shared/defguard-ui/components/Layout/Button/types';
import { useModalStore } from '../../../../../shared/hooks/store/useModalStore';
import useApi from '../../../../../shared/hooks/useApi';
import { useToaster } from '../../../../../shared/hooks/useToaster';
} from '../../../../../../../shared/defguard-ui/components/Layout/Button/types';
import useApi from '../../../../../../../shared/hooks/useApi';
import { useToaster } from '../../../../../../../shared/hooks/useToaster';
import {
patternDigitOrLowercase,
patternNoSpecialChars,
patternStartsWithDigit,
patternValidEmail,
patternValidPhoneNumber,
} from '../../../../../shared/patterns';
import { QueryKeys } from '../../../../../shared/queries';
import { passwordValidator } from '../../../../../shared/validators/password';
import { useEnrollmentModalStore } from '../StartEnrollmentModal/hooks/useEnrollmentModalStore';
} from '../../../../../../../shared/patterns';
import { QueryKeys } from '../../../../../../../shared/queries';
import { passwordValidator } from '../../../../../../../shared/validators/password';
import { useAddUserModal } from '../../hooks/useAddUserModal';

interface Inputs {
username: string;
password?: string;
email: string;
last_name: string;
first_name: string;
phone?: string;
// had to add field for conditional form validation to work
enable_enrollment: boolean;
// disabled when enableEnrollment is true
password?: string;
phone?: string;
}

export const AddUserForm = () => {
Expand Down Expand Up @@ -120,24 +123,30 @@ export const AddUserForm = () => {

const queryClient = useQueryClient();

const setModalState = useModalStore((state) => state.setAddUserModal);
const openEnrollmentModal = useEnrollmentModalStore((state) => state.open);

const toaster = useToaster();

const [setModalState, nextStep, close] = useAddUserModal(
(state) => [state.setState, state.nextStep, state.close],
shallow,
);

const addUserMutation = useMutation(addUser, {
onSuccess: (user) => {
queryClient.invalidateQueries([QueryKeys.FETCH_USERS_LIST]);
toaster.success('User added.');
if (enableEnrollment) {
openEnrollmentModal(user);
toaster.success(LL.modals.addUser.messages.userAdded());
setModalState({
user: user,
});
nextStep();
} else {
close();
}
setModalState({ visible: false });
},
onError: (err) => {
close();
toaster.error(LL.messages.error());
console.error(err);
setModalState({ visible: false });
toaster.error('Error occurred.');
},
});

Expand All @@ -148,14 +157,16 @@ export const AddUserForm = () => {
usernameAvailable(data.username)
.then(() => {
setCheckingUsername(false);
let userData = data;
if (enableEnrollment) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { password, ...rest } = data;
userData = rest;
if (data.enable_enrollment) {
const userData = omit(data, ['password', 'enable_enrollment']);
addUserMutation.mutate(userData);
} else {
if (data.password) {
addUserMutation.mutate(omit(data, ['enable_enrollment']));
} else {
trigger('password', { shouldFocus: true });
}
}
// TODO: add notification toggle to form
addUserMutation.mutate(userData);
})
.catch(() => {
setCheckingUsername(false);
Expand All @@ -166,7 +177,16 @@ export const AddUserForm = () => {
};

return (
<form data-testid="add-user-form" onSubmit={handleSubmit(onSubmit)}>
<form
id="add-user-form"
data-testid="add-user-form"
onSubmit={handleSubmit(onSubmit)}
>
<FormCheckBox
labelPlacement="right"
label={LL.modals.addUser.form.fields.enableEnrollment.label()}
controller={{ control, name: 'enable_enrollment' }}
/>
<div className="row">
<div className="item">
<FormInput
Expand All @@ -188,10 +208,6 @@ export const AddUserForm = () => {
required={!enableEnrollment}
disabled={enableEnrollment}
/>
<FormCheckBox
label={LL.modals.addUser.form.fields.enableEnrollment.label()}
controller={{ control, name: 'enable_enrollment' }}
/>
<FormInput
label={LL.modals.addUser.form.fields.email.label()}
placeholder={LL.modals.addUser.form.fields.email.placeholder()}
Expand Down
Loading
Loading