fix(user): edit & invite user .

This commit is contained in:
elforjani3
2021-04-11 17:04:02 +02:00
parent f394390b98
commit 32c4fdc0bd
11 changed files with 351 additions and 26 deletions

View File

@@ -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() {
<AccountDialog dialogName={'account-form'} />
<CurrencyFormDialog dialogName={'currency-form'} />
<InviteUserDialog dialogName={'invite-user'} />
<UserFormDialog dialogName={'user-form'} />
<ExchangeRateFormDialog dialogName={'exchangeRate-form'} />
<ItemCategoryDialog dialogName={'item-category-form'} />
<InventoryAdjustmentDialog dialogName={'inventory-adjustment'} />

View File

@@ -35,7 +35,6 @@ function InviteUserFormContent({
className={classNames('form-group--email', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="email" />}
inline={true}
>
<InputGroup medium={true} {...field} />
</FormGroup>

View File

@@ -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 (
<Formik
validationSchema={UserFormSchema}
initialValues={initialValues}
onSubmit={handleSubmit}
>
<UserFormContent />
</Formik>
);
}
export default compose(withDialogActions)(UserForm);

View File

@@ -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;

View File

@@ -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 (
<Form>
<div className={Classes.DIALOG_BODY}>
{/* ----------- Email ----------- */}
<FastField name={'email'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'email'} />}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--email', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="email" />}
>
<InputGroup medium={true} {...field} />
</FormGroup>
)}
</FastField>
{/* ----------- First name ----------- */}
<FastField name={'first_name'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'first_name'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'first_name'} />}
>
<InputGroup intent={inputIntent({ error, touched })} {...field} />
</FormGroup>
)}
</FastField>
{/* ----------- Last name ----------- */}
<FastField name={'last_name'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'last_name'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'last_name'} />}
>
<InputGroup intent={inputIntent({ error, touched })} {...field} />
</FormGroup>
)}
</FastField>
{/* ----------- Phone name ----------- */}
<FastField name={'phone_number'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'phone_number'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'phone_number'} />}
>
<InputGroup intent={inputIntent({ error, touched })} {...field} />
</FormGroup>
)}
</FastField>
</div>
<div className={CLASSES.DIALOG_FOOTER}>
<div className={CLASSES.DIALOG_FOOTER_ACTIONS}>
<Button onClick={handleClose}>
<T id={'cancel'} />
</Button>
<Button intent={Intent.PRIMARY} type="submit" disabled={isSubmitting}>
<T id={'edit'} />
</Button>
</div>
</div>
</Form>
);
}
export default compose(withDialogActions)(UserFormContent);

View File

@@ -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 (
<UserFormProvider userId={userId} dialogName={dialogName}>
<UserForm />
</UserFormProvider>
);
}

View File

@@ -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 (
<DialogContent isLoading={isUserLoading} name={'user-form'}>
<UserFormContext.Provider value={provider} {...props} />
</DialogContent>
);
}
const useUserFormContext = () => useContext(UserFormContext);
export { UserFormProvider, useUserFormContext };

View File

@@ -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 (
<Dialog
name={dialogName}
title={<T id={'edit_user'} />}
className={'dialog--user-form'}
autoFocus={true}
canEscapeKeyClose={true}
isOpen={isOpen}
>
<DialogSuspense>
<UserFormDialogContent
dialogName={dialogName}
userId={payload.userId}
action={payload.action}
/>
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(UserFormDialog);

View File

@@ -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],
);

View File

@@ -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,
);
}

View File

@@ -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;
}
}
}