From 08beb9a0d6f7d89569f205f1fad712d01ed70b83 Mon Sep 17 00:00:00 2001 From: elforjani3 Date: Wed, 10 Feb 2021 00:51:08 +0200 Subject: [PATCH] refactor: invite user. --- .../InviteUserDialogContent.js | 94 ++----------------- .../InviteUserDialog/InviteUserForm.js | 77 +++++++++++++++ ...DialogForm.js => InviteUserFormContent.js} | 31 +++--- .../InviteUserFormProvider.js | 35 +++++++ client/src/hooks/query/index.js | 3 +- client/src/hooks/query/users.js | 73 ++++++++++++++ 6 files changed, 212 insertions(+), 101 deletions(-) create mode 100644 client/src/containers/Dialogs/InviteUserDialog/InviteUserForm.js rename client/src/containers/Dialogs/InviteUserDialog/{InviteUserDialogForm.js => InviteUserFormContent.js} (64%) create mode 100644 client/src/containers/Dialogs/InviteUserDialog/InviteUserFormProvider.js create mode 100644 client/src/hooks/query/users.js diff --git a/client/src/containers/Dialogs/InviteUserDialog/InviteUserDialogContent.js b/client/src/containers/Dialogs/InviteUserDialog/InviteUserDialogContent.js index baf2dfd4b..846cfc2d0 100644 --- a/client/src/containers/Dialogs/InviteUserDialog/InviteUserDialogContent.js +++ b/client/src/containers/Dialogs/InviteUserDialog/InviteUserDialogContent.js @@ -1,101 +1,21 @@ import React from 'react'; -import { Intent } from '@blueprintjs/core'; -import { pick, snakeCase } from 'lodash'; -import { queryCache, useQuery } from 'react-query'; -import { useIntl } from 'react-intl'; -import { Formik } from 'formik'; -import { AppToaster, DialogContent } from 'components'; -import withUsersActions from 'containers/Users/withUsersActions'; -import withDialogActions from 'containers/Dialog/withDialogActions'; +import InviteUserForm from './InviteUserForm'; +import { InviteUserFormProvider } from './InviteUserFormProvider'; -import { compose, objectKeysTransform } from 'utils'; -import { InviteUserFormSchema } from './InviteUserDialog.schema'; -import UserFormDialogForm from './InviteUserDialogForm'; - -import { transformApiErrors } from './utils'; - -import 'style/pages/Users/InviteFormDialog.scss' +import 'style/pages/Users/InviteFormDialog.scss'; /** * Invite user dialog content. */ -function InviteUserDialogContent({ - // #wihtCurrenciesActions - requestFetchUser, - requestSubmitInvite, - - // #withDialogActions - closeDialog, - - // #ownProp +export default function InviteUserDialogContent({ action, userId, dialogName, }) { - const { formatMessage } = useIntl(); - - // Fetch user details. - const fetchHook = useQuery( - ['user', userId], - (key, id) => requestFetchUser(id), - { enabled: userId }, - ); - - const initialValues = { - status: 1, - ...(action === 'edit' && - pick( - objectKeysTransform(userId, snakeCase), - Object.keys(InviteUserFormSchema.fields), - )), - }; - - const handleSubmit = (values, { setSubmitting, setErrors }) => { - const form = { ...values }; - - requestSubmitInvite(form) - .then((response) => { - closeDialog(dialogName); - AppToaster.show({ - message: formatMessage({ - id: 'teammate_invited_to_organization_account', - }), - intent: Intent.SUCCESS, - }); - setSubmitting(false); - queryCache.invalidateQueries('users-table'); - }) - .catch((errors) => { - const errorsTransformed = transformApiErrors(errors); - - setErrors({ ...errorsTransformed }); - setSubmitting(false); - }); - }; - - const handleCancelBtnClick = () => { - closeDialog('invite-user'); - }; - return ( - - - - - + + + ); } - -export default compose( - // UserFormDialogConnect, - withDialogActions, - withUsersActions, -)(InviteUserDialogContent); diff --git a/client/src/containers/Dialogs/InviteUserDialog/InviteUserForm.js b/client/src/containers/Dialogs/InviteUserDialog/InviteUserForm.js new file mode 100644 index 000000000..7c322db10 --- /dev/null +++ b/client/src/containers/Dialogs/InviteUserDialog/InviteUserForm.js @@ -0,0 +1,77 @@ +import React from 'react'; +import { Formik } from 'formik'; +import { Intent } from '@blueprintjs/core'; +import { pick, snakeCase } from 'lodash'; +import { useIntl } from 'react-intl'; +import { AppToaster } from 'components'; + +import withDialogActions from 'containers/Dialog/withDialogActions'; + +import { InviteUserFormSchema } from './InviteUserDialog.schema'; +import InviteUserFormContent from './InviteUserFormContent'; +import { useInviteUserFormContext } from './InviteUserFormProvider'; + +import { transformApiErrors } from './utils'; + +import { compose, objectKeysTransform } from 'utils'; + +function InviteUserForm({ + // #withDialogActions + closeDialog, +}) { + const { formatMessage } = useIntl(); + + const { + dialogName, + isEditMode, + inviteUserMutate, + userId, + } = useInviteUserFormContext(); + + const initialValues = { + status: 1, + ...(isEditMode && + pick( + objectKeysTransform(userId, snakeCase), + Object.keys(InviteUserFormSchema.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 = (errors) => { + const errorsTransformed = transformApiErrors(errors); + + setErrors({ ...errorsTransformed }); + setSubmitting(false); + }; + inviteUserMutate(form).then(onSuccess).catch(onError); + }; + + return ( + + + + ); +} +export default compose(withDialogActions)(InviteUserForm); diff --git a/client/src/containers/Dialogs/InviteUserDialog/InviteUserDialogForm.js b/client/src/containers/Dialogs/InviteUserDialog/InviteUserFormContent.js similarity index 64% rename from client/src/containers/Dialogs/InviteUserDialog/InviteUserDialogForm.js rename to client/src/containers/Dialogs/InviteUserDialog/InviteUserFormContent.js index a8efbbe1f..79981128c 100644 --- a/client/src/containers/Dialogs/InviteUserDialog/InviteUserDialogForm.js +++ b/client/src/containers/Dialogs/InviteUserDialog/InviteUserFormContent.js @@ -1,21 +1,24 @@ import React from 'react'; -import { - FormGroup, - InputGroup, - Intent, - Button, -} from '@blueprintjs/core'; +import { FormGroup, InputGroup, Intent, 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, saveInvoke } from 'utils'; +import { inputIntent } from 'utils'; +import { useInviteUserFormContext } from './InviteUserFormProvider'; -export default function InviteUserDialogForm({ onCancelClick, action }) { +import withDialogActions from 'containers/Dialog/withDialogActions'; +import { compose } from 'utils'; + +function InviteUserFormContent({ + // #withDialogActions + closeDialog, +}) { const { isSubmitting } = useFormikContext(); + const { isEditMode, dialogName } = useInviteUserFormContext(); - const handleCancelBtnClick = (event) => { - saveInvoke(onCancelClick, event); + const handleClose = () => { + closeDialog(dialogName); }; return ( @@ -38,19 +41,21 @@ export default function InviteUserDialogForm({ onCancelClick, action }) { )} - +
-
); } + +export default compose(withDialogActions)(InviteUserFormContent); diff --git a/client/src/containers/Dialogs/InviteUserDialog/InviteUserFormProvider.js b/client/src/containers/Dialogs/InviteUserDialog/InviteUserFormProvider.js new file mode 100644 index 000000000..cd8c92ac7 --- /dev/null +++ b/client/src/containers/Dialogs/InviteUserDialog/InviteUserFormProvider.js @@ -0,0 +1,35 @@ +import React, { createContext } from 'react'; +import { useCreateInviteUser, useUsers } from 'hooks/query'; +import { DialogContent } from 'components'; + +const InviteUserFormContext = createContext(); + +/** + * Invite user Form page provider. + */ +function InviteUserFormProvider({ userId, isEditMode, dialogName, ...props }) { + // Create and edit item currency mutations. + const { mutateAsync: inviteUserMutate } = useCreateInviteUser(); + + // fetch users list. + const { isFetching: isUsersLoading } = useUsers(); + + // Provider state. + const provider = { + inviteUserMutate, + dialogName, + userId, + isUsersLoading, + isEditMode, + }; + + return ( + + + + ); +} + +const useInviteUserFormContext = () => React.useContext(InviteUserFormContext); + +export { InviteUserFormProvider, useInviteUserFormContext }; diff --git a/client/src/hooks/query/index.js b/client/src/hooks/query/index.js index 937a0539b..640214bfd 100644 --- a/client/src/hooks/query/index.js +++ b/client/src/hooks/query/index.js @@ -16,4 +16,5 @@ export * from './estimates'; export * from './receipts'; export * from './paymentReceives'; export * from './paymentMades'; -export * from './settings'; \ No newline at end of file +export * from './settings'; +export * from './users'; \ No newline at end of file diff --git a/client/src/hooks/query/users.js b/client/src/hooks/query/users.js new file mode 100644 index 000000000..875cdf507 --- /dev/null +++ b/client/src/hooks/query/users.js @@ -0,0 +1,73 @@ +import { useMutation, useQueryClient, useQuery } from 'react-query'; +import { defaultTo } from 'lodash'; +import ApiService from 'services/ApiService'; + +/** + * Create a new invite user. + */ +export function useCreateInviteUser(props) { + const queryClient = useQueryClient(); + return useMutation((values) => ApiService.post('invite/send', values), { + onSuccess: () => { + queryClient.invalidateQueries('USERS'); + }, + ...props, + }); +} + +/** + * Edits the given user. + * + */ +export function useEditUser(props) { + const queryClient = useQueryClient(); + + return useMutation(([id, values]) => ApiService.post(`users/${id}`, values), { + onSuccess: () => { + queryClient.invalidateQueries('USERS'); + }, + ...props, + }); +} + +/** + * Deletes the given user. + */ +export function useDeleteUser(props) { + const queryClient = useQueryClient(); + + return useMutation((id) => ApiService.delete(`users/${id}`), { + onSuccess: () => { + queryClient.invalidateQueries('USERS'); + queryClient.invalidateQueries('USER'); + }, + ...props, + }); +} + +/** + * Retrieves users list. + */ +export function useUsers(props) { + const result = useQuery( + ['USERS'], + () => ApiService.get(`USERS`).then((response) => response.data.users), + props, + ); + + return { + ...result, + data: defaultTo(result.data, {}), + }; +} + +/** + * Retrieve details of the given user. + */ +export function useUser(id, props) { + return useQuery( + ['USER', id], + () => ApiService.get(`users/${id}`).then((response) => response.data.item), + props, + ); +}