From 32c4fdc0bda5d1a22ded667215b1933e5a81ebc2 Mon Sep 17 00:00:00 2001 From: elforjani3 Date: Sun, 11 Apr 2021 17:04:02 +0200 Subject: [PATCH] fix(user): edit & invite user . --- client/src/components/DialogsContainer.js | 2 + .../InviteUserDialog/InviteUserFormContent.js | 1 - .../Dialogs/UserFormDialog/UserForm.js | 83 ++++++++++++++ .../Dialogs/UserFormDialog/UserForm.schema.js | 21 ++++ .../Dialogs/UserFormDialog/UserFormContent.js | 107 ++++++++++++++++++ .../UserFormDialog/UserFormDialogContent.js | 16 +++ .../UserFormDialog/UserFormProvider.js | 42 +++++++ .../Dialogs/UserFormDialog/index.js | 35 ++++++ .../Preferences/Users/UsersDataTable.js | 2 +- client/src/hooks/query/users.js | 40 +++---- .../src/style/pages/Users/UserFormDialog.scss | 28 +++++ 11 files changed, 351 insertions(+), 26 deletions(-) create mode 100644 client/src/containers/Dialogs/UserFormDialog/UserForm.js create mode 100644 client/src/containers/Dialogs/UserFormDialog/UserForm.schema.js create mode 100644 client/src/containers/Dialogs/UserFormDialog/UserFormContent.js create mode 100644 client/src/containers/Dialogs/UserFormDialog/UserFormDialogContent.js create mode 100644 client/src/containers/Dialogs/UserFormDialog/UserFormProvider.js create mode 100644 client/src/containers/Dialogs/UserFormDialog/index.js create mode 100644 client/src/style/pages/Users/UserFormDialog.scss diff --git a/client/src/components/DialogsContainer.js b/client/src/components/DialogsContainer.js index 1dfdd0998..1419f7638 100644 --- a/client/src/components/DialogsContainer.js +++ b/client/src/components/DialogsContainer.js @@ -2,6 +2,7 @@ import React from 'react'; import AccountDialog from 'containers/Dialogs/AccountDialog'; import InviteUserDialog from 'containers/Dialogs/InviteUserDialog'; +import UserFormDialog from 'containers/Dialogs/UserFormDialog'; import ItemCategoryDialog from 'containers/Dialogs/ItemCategoryDialog'; import CurrencyFormDialog from 'containers/Dialogs/CurrencyFormDialog'; import ExchangeRateFormDialog from 'containers/Dialogs/ExchangeRateFormDialog'; @@ -22,6 +23,7 @@ export default function DialogsContainer() { + diff --git a/client/src/containers/Dialogs/InviteUserDialog/InviteUserFormContent.js b/client/src/containers/Dialogs/InviteUserDialog/InviteUserFormContent.js index 79981128c..9bf6400b3 100644 --- a/client/src/containers/Dialogs/InviteUserDialog/InviteUserFormContent.js +++ b/client/src/containers/Dialogs/InviteUserDialog/InviteUserFormContent.js @@ -35,7 +35,6 @@ function InviteUserFormContent({ className={classNames('form-group--email', CLASSES.FILL)} intent={inputIntent({ error, touched })} helperText={} - inline={true} > diff --git a/client/src/containers/Dialogs/UserFormDialog/UserForm.js b/client/src/containers/Dialogs/UserFormDialog/UserForm.js new file mode 100644 index 000000000..79702ed3b --- /dev/null +++ b/client/src/containers/Dialogs/UserFormDialog/UserForm.js @@ -0,0 +1,83 @@ +import React from 'react'; +import { Formik } from 'formik'; +import { Intent } from '@blueprintjs/core'; +import { useIntl } from 'react-intl'; +import { pick, snakeCase } from 'lodash'; +import { AppToaster } from 'components'; + +import withDialogActions from 'containers/Dialog/withDialogActions'; + +import { UserFormSchema } from './UserForm.schema'; +import UserFormContent from './UserFormContent'; +import { useUserFormContext } from './UserFormProvider'; + +import { compose, objectKeysTransform } from 'utils'; + +/** + * User form. + */ +function UserForm({ + // #withDialogActions + closeDialog, +}) { + const { formatMessage } = useIntl(); + + const { + dialogName, + user, + userId, + isEditMode, + EditUserMutate, + } = useUserFormContext(); + console.log(user, 'EE'); + const initialValues = { + ...(isEditMode && + pick( + objectKeysTransform(user, snakeCase), + Object.keys(UserFormSchema.fields), + )), + }; + + const handleSubmit = (values, { setSubmitting, setErrors }) => { + const form = { ...values }; + + // Handle close the dialog after success response. + const afterSubmit = () => { + closeDialog(dialogName); + }; + + const onSuccess = ({ response }) => { + AppToaster.show({ + message: formatMessage({ + id: 'teammate_invited_to_organization_account', + }), + intent: Intent.SUCCESS, + }); + afterSubmit(response); + }; + + // Handle the response error. + const onError = (error) => { + const { + response: { + data: { errors }, + }, + } = error; + + setSubmitting(false); + }; + + EditUserMutate([userId, form]).then(onSuccess).catch(onError); + }; + + return ( + + + + ); +} +export default compose(withDialogActions)(UserForm); diff --git a/client/src/containers/Dialogs/UserFormDialog/UserForm.schema.js b/client/src/containers/Dialogs/UserFormDialog/UserForm.schema.js new file mode 100644 index 000000000..99bc2dfca --- /dev/null +++ b/client/src/containers/Dialogs/UserFormDialog/UserForm.schema.js @@ -0,0 +1,21 @@ +import * as Yup from 'yup'; +import { formatMessage } from 'services/intl'; + +const Schema = Yup.object().shape({ + email: Yup.string() + .email() + .required() + .label(formatMessage({ id: 'email' })), + first_name: Yup.string() + .required() + .label(formatMessage({ id: 'first_name_' })), + last_name: Yup.string() + .required() + .label(formatMessage({ id: 'last_name_' })), + phone_number: Yup.string() + .matches() + .required() + .label(formatMessage({ id: 'phone_number_' })), +}); + +export const UserFormSchema = Schema; diff --git a/client/src/containers/Dialogs/UserFormDialog/UserFormContent.js b/client/src/containers/Dialogs/UserFormDialog/UserFormContent.js new file mode 100644 index 000000000..6e3ab48c5 --- /dev/null +++ b/client/src/containers/Dialogs/UserFormDialog/UserFormContent.js @@ -0,0 +1,107 @@ +import React from 'react'; +import { + FormGroup, + InputGroup, + Intent, + Classes, + Button, +} from '@blueprintjs/core'; +import { FastField, Form, useFormikContext, ErrorMessage } from 'formik'; +import { FormattedMessage as T } from 'react-intl'; +import { CLASSES } from 'common/classes'; +import classNames from 'classnames'; +import { inputIntent } from 'utils'; +import { FieldRequiredHint } from 'components'; +import { useUserFormContext } from './UserFormProvider'; +import withDialogActions from 'containers/Dialog/withDialogActions'; +import { compose } from 'utils'; + +/** + * User form content. + */ +function UserFormContent({ + // #withDialogActions + closeDialog, +}) { + const { isSubmitting } = useFormikContext(); + const { dialogName } = useUserFormContext(); + + const handleClose = () => { + closeDialog(dialogName); + }; + + return ( +
+
+ {/* ----------- Email ----------- */} + + {({ field, meta: { error, touched } }) => ( + } + labelInfo={} + className={classNames('form-group--email', CLASSES.FILL)} + intent={inputIntent({ error, touched })} + helperText={} + > + + + )} + + + {/* ----------- First name ----------- */} + + {({ form, field, meta: { error, touched } }) => ( + } + labelInfo={} + intent={inputIntent({ error, touched })} + helperText={} + > + + + )} + + + {/* ----------- Last name ----------- */} + + {({ form, field, meta: { error, touched } }) => ( + } + labelInfo={} + intent={inputIntent({ error, touched })} + helperText={} + > + + + )} + + {/* ----------- Phone name ----------- */} + + {({ form, field, meta: { error, touched } }) => ( + } + labelInfo={} + intent={inputIntent({ error, touched })} + helperText={} + > + + + )} + +
+ +
+
+ + + +
+
+
+ ); +} +export default compose(withDialogActions)(UserFormContent); diff --git a/client/src/containers/Dialogs/UserFormDialog/UserFormDialogContent.js b/client/src/containers/Dialogs/UserFormDialog/UserFormDialogContent.js new file mode 100644 index 000000000..f027c472f --- /dev/null +++ b/client/src/containers/Dialogs/UserFormDialog/UserFormDialogContent.js @@ -0,0 +1,16 @@ +import React from 'react'; + +import UserForm from './UserForm'; +import { UserFormProvider } from './UserFormProvider'; +import 'style/pages/Users/UserFormDialog.scss'; + +/** + * User form dialog content. + */ +export default function UserFormDialogContent({ userId, dialogName }) { + return ( + + + + ); +} diff --git a/client/src/containers/Dialogs/UserFormDialog/UserFormProvider.js b/client/src/containers/Dialogs/UserFormDialog/UserFormProvider.js new file mode 100644 index 000000000..253b6f3bd --- /dev/null +++ b/client/src/containers/Dialogs/UserFormDialog/UserFormProvider.js @@ -0,0 +1,42 @@ +import React, { createContext, useContext } from 'react'; +import { useEditUser, useUser } from 'hooks/query'; + +import { DialogContent } from 'components'; + +const UserFormContext = createContext(); + +/** + * User Form provider. + */ +function UserFormProvider({ userId, dialogName, ...props }) { + // edit user mutations. + const { mutateAsync: EditUserMutate } = useEditUser(); + + // fetch user detail. + const { data: user, isLoading: isUserLoading } = useUser(userId, { + enabled: !!userId, + }); + + const isEditMode = userId; + + // Provider state. + const provider = { + userId, + dialogName, + + user, + EditUserMutate, + + isEditMode, + }; + + return ( + + + + ); +} + +const useUserFormContext = () => useContext(UserFormContext); + +export { UserFormProvider, useUserFormContext }; diff --git a/client/src/containers/Dialogs/UserFormDialog/index.js b/client/src/containers/Dialogs/UserFormDialog/index.js new file mode 100644 index 000000000..45881727a --- /dev/null +++ b/client/src/containers/Dialogs/UserFormDialog/index.js @@ -0,0 +1,35 @@ +import React, { lazy } from 'react'; +import { FormattedMessage as T } from 'react-intl'; +import { Dialog, DialogSuspense } from 'components'; +import withDialogRedux from 'components/DialogReduxConnect'; +import { compose } from 'utils'; + +const UserFormDialogContent = lazy(() => import('./UserFormDialogContent')); + +function UserFormDialog({ + dialogName, + payload = { action: '', userId: null }, + isOpen, +}) { + + return ( + } + className={'dialog--user-form'} + autoFocus={true} + canEscapeKeyClose={true} + isOpen={isOpen} + > + + + + + ); +} + +export default compose(withDialogRedux())(UserFormDialog); diff --git a/client/src/containers/Preferences/Users/UsersDataTable.js b/client/src/containers/Preferences/Users/UsersDataTable.js index 15bcf9053..d71657e9c 100644 --- a/client/src/containers/Preferences/Users/UsersDataTable.js +++ b/client/src/containers/Preferences/Users/UsersDataTable.js @@ -27,7 +27,7 @@ function UsersDataTable({ // Handle edit user action. const handleEditUserAction = useCallback( (user) => { - openDialog('userList-form', { action: 'edit', userId: user.id }); + openDialog('user-form', { action: 'edit', userId: user.id }); }, [openDialog], ); diff --git a/client/src/hooks/query/users.js b/client/src/hooks/query/users.js index cf513d371..4b98ce945 100644 --- a/client/src/hooks/query/users.js +++ b/client/src/hooks/query/users.js @@ -46,38 +46,30 @@ export function useInactivateUser(props) { const apiRequest = useApiRequest(); const queryClient = useQueryClient(); - return useMutation( - (userId) => apiRequest.put(`users/${userId}/inactivate`), - { - onSuccess: (res, userId) => { - queryClient.invalidateQueries([t.USER, userId]); + return useMutation((userId) => apiRequest.put(`users/${userId}/inactivate`), { + onSuccess: (res, userId) => { + queryClient.invalidateQueries([t.USER, userId]); - // Common invalidate queries. - commonInvalidateQueries(queryClient); - }, - ...props, + // Common invalidate queries. + commonInvalidateQueries(queryClient); }, - ); + ...props, + }); } - - export function useActivateUser(props) { const apiRequest = useApiRequest(); const queryClient = useQueryClient(); - return useMutation( - (userId) => apiRequest.put(`users/${userId}/activate`), - { - onSuccess: (res, userId) => { - queryClient.invalidateQueries([t.USER, userId]); + return useMutation((userId) => apiRequest.put(`users/${userId}/activate`), { + onSuccess: (res, userId) => { + queryClient.invalidateQueries([t.USER, userId]); - // Common invalidate queries. - commonInvalidateQueries(queryClient); - }, - ...props, + // Common invalidate queries. + commonInvalidateQueries(queryClient); }, - ); + ...props, + }); } /** @@ -123,8 +115,8 @@ export function useUser(id, props) { const apiRequest = useApiRequest(); return useQueryTenant( - ['USER', id], - () => apiRequest.get(`users/${id}`).then((response) => response.data.item), + [t.USER, id], + () => apiRequest.get(`users/${id}`).then((response) => response.data.user), props, ); } diff --git a/client/src/style/pages/Users/UserFormDialog.scss b/client/src/style/pages/Users/UserFormDialog.scss new file mode 100644 index 000000000..fd1b9821d --- /dev/null +++ b/client/src/style/pages/Users/UserFormDialog.scss @@ -0,0 +1,28 @@ +.dialog--user-form { + width: 450px; + + .bp3-form-group { + margin-bottom: 15px; + } + .bp3-dialog-body { + margin-bottom: 30px; + + .bp3-form-group.bp3-inline { + .bp3-label { + min-width: 140px; + } + .bp3-form-content { + width: 250px; + } + } + .bp3-dialog-header { + height: 170px; + } + } + + .bp3-dialog-footer { + .bp3-button { + min-width: 75px; + } + } +}