From dd83278c83508415c1f874e6f6736b1ccc8a317e Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Wed, 15 Jun 2022 14:13:22 +0200 Subject: [PATCH] feat: Add ability to edit users Closes #60 --- client/src/actions/entry/user.js | 45 +++++ client/src/components/UserEmailEditPopup.jsx | 5 + .../UserEmailEditStep.jsx} | 64 +++--- .../UserEmailEditStep.module.scss} | 0 .../src/components/UserEmailEditStep/index.js | 3 + .../UserInformationEdit.jsx} | 10 +- .../UserInformationEdit.module.scss} | 0 .../components/UserInformationEdit/index.js | 3 + .../components/UserInformationEditStep.jsx | 44 +++++ .../src/components/UserPasswordEditPopup.jsx | 5 + .../UserPasswordEditStep.jsx} | 63 +++--- .../UserPasswordEditStep.module.scss} | 0 .../components/UserPasswordEditStep/index.js | 3 + .../AccountPane/AccountPane.jsx | 25 +-- .../src/components/UserUsernameEditPopup.jsx | 5 + .../UserUsernameEditStep.jsx} | 68 ++++--- .../UserUsernameEditStep.module.scss} | 0 .../components/UserUsernameEditStep/index.js | 3 + client/src/components/UsersModal/Item.jsx | 56 ------ .../UsersModal/Item/ActionsPopup.jsx | 181 +++++++++++++++++ .../UsersModal/Item/ActionsPopup.module.scss | 11 ++ .../src/components/UsersModal/Item/Item.jsx | 103 ++++++++++ .../UsersModal/{ => Item}/Item.module.scss | 0 .../src/components/UsersModal/Item/index.js | 3 + .../src/components/UsersModal/UsersModal.jsx | 182 ++++++++++++------ client/src/constants/EntryActionTypes.js | 6 + client/src/containers/UsersModalContainer.js | 18 +- client/src/locales/en/core.js | 2 + client/src/models/User.js | 37 ++++ client/src/sagas/core/watchers/user.js | 34 +++- 30 files changed, 775 insertions(+), 204 deletions(-) create mode 100644 client/src/components/UserEmailEditPopup.jsx rename client/src/components/{UserSettingsModal/AccountPane/EmailEditPopup.jsx => UserEmailEditStep/UserEmailEditStep.jsx} (73%) rename client/src/components/{UserSettingsModal/AccountPane/EmailEditPopup.module.scss => UserEmailEditStep/UserEmailEditStep.module.scss} (100%) create mode 100644 client/src/components/UserEmailEditStep/index.js rename client/src/components/{UserSettingsModal/AccountPane/InformationEdit.jsx => UserInformationEdit/UserInformationEdit.jsx} (88%) rename client/src/components/{UserSettingsModal/AccountPane/InformationEdit.module.scss => UserInformationEdit/UserInformationEdit.module.scss} (100%) create mode 100644 client/src/components/UserInformationEdit/index.js create mode 100644 client/src/components/UserInformationEditStep.jsx create mode 100644 client/src/components/UserPasswordEditPopup.jsx rename client/src/components/{UserSettingsModal/AccountPane/PasswordEditPopup.jsx => UserPasswordEditStep/UserPasswordEditStep.jsx} (70%) rename client/src/components/{UserSettingsModal/AccountPane/PasswordEditPopup.module.scss => UserPasswordEditStep/UserPasswordEditStep.module.scss} (100%) create mode 100644 client/src/components/UserPasswordEditStep/index.js create mode 100644 client/src/components/UserUsernameEditPopup.jsx rename client/src/components/{UserSettingsModal/AccountPane/UsernameEditPopup.jsx => UserUsernameEditStep/UserUsernameEditStep.jsx} (71%) rename client/src/components/{UserSettingsModal/AccountPane/UsernameEditPopup.module.scss => UserUsernameEditStep/UserUsernameEditStep.module.scss} (100%) create mode 100644 client/src/components/UserUsernameEditStep/index.js delete mode 100755 client/src/components/UsersModal/Item.jsx create mode 100644 client/src/components/UsersModal/Item/ActionsPopup.jsx create mode 100644 client/src/components/UsersModal/Item/ActionsPopup.module.scss create mode 100755 client/src/components/UsersModal/Item/Item.jsx rename client/src/components/UsersModal/{ => Item}/Item.module.scss (100%) create mode 100644 client/src/components/UsersModal/Item/index.js diff --git a/client/src/actions/entry/user.js b/client/src/actions/entry/user.js index 03c6686e..fc18d3f5 100755 --- a/client/src/actions/entry/user.js +++ b/client/src/actions/entry/user.js @@ -41,6 +41,14 @@ export const handleUserUpdate = (user) => ({ }, }); +export const updateUserEmail = (id, data) => ({ + type: EntryActionTypes.USER_EMAIL_UPDATE, + payload: { + id, + data, + }, +}); + export const updateCurrentUserEmail = (data) => ({ type: EntryActionTypes.CURRENT_USER_EMAIL_UPDATE, payload: { @@ -48,11 +56,26 @@ export const updateCurrentUserEmail = (data) => ({ }, }); +export const clearUserEmailUpdateError = (id) => ({ + type: EntryActionTypes.USER_EMAIL_UPDATE_ERROR_CLEAR, + payload: { + id, + }, +}); + export const clearCurrentUserEmailUpdateError = () => ({ type: EntryActionTypes.CURRENT_USER_EMAIL_UPDATE_ERROR_CLEAR, payload: {}, }); +export const updateUserPassword = (id, data) => ({ + type: EntryActionTypes.USER_PASSWORD_UPDATE, + payload: { + id, + data, + }, +}); + export const updateCurrentUserPassword = (data) => ({ type: EntryActionTypes.CURRENT_USER_PASSWORD_UPDATE, payload: { @@ -60,11 +83,26 @@ export const updateCurrentUserPassword = (data) => ({ }, }); +export const clearUserPasswordUpdateError = (id) => ({ + type: EntryActionTypes.USER_PASSWORD_UPDATE_ERROR_CLEAR, + payload: { + id, + }, +}); + export const clearCurrentUserPasswordUpdateError = () => ({ type: EntryActionTypes.CURRENT_USER_PASSWORD_UPDATE_ERROR_CLEAR, payload: {}, }); +export const updateUserUsername = (id, data) => ({ + type: EntryActionTypes.USER_USERNAME_UPDATE, + payload: { + id, + data, + }, +}); + export const updateCurrentUserUsername = (data) => ({ type: EntryActionTypes.CURRENT_USER_USERNAME_UPDATE, payload: { @@ -72,6 +110,13 @@ export const updateCurrentUserUsername = (data) => ({ }, }); +export const clearUserUsernameUpdateError = (id) => ({ + type: EntryActionTypes.USER_USERNAME_UPDATE_ERROR_CLEAR, + payload: { + id, + }, +}); + export const clearCurrentUserUsernameUpdateError = () => ({ type: EntryActionTypes.CURRENT_USER_USERNAME_UPDATE_ERROR_CLEAR, payload: {}, diff --git a/client/src/components/UserEmailEditPopup.jsx b/client/src/components/UserEmailEditPopup.jsx new file mode 100644 index 00000000..6da3314a --- /dev/null +++ b/client/src/components/UserEmailEditPopup.jsx @@ -0,0 +1,5 @@ +import { withPopup } from '../lib/popup'; + +import UserEmailEditStep from './UserEmailEditStep'; + +export default withPopup(UserEmailEditStep); diff --git a/client/src/components/UserSettingsModal/AccountPane/EmailEditPopup.jsx b/client/src/components/UserEmailEditStep/UserEmailEditStep.jsx similarity index 73% rename from client/src/components/UserSettingsModal/AccountPane/EmailEditPopup.jsx rename to client/src/components/UserEmailEditStep/UserEmailEditStep.jsx index 6bd0fd32..953528fa 100644 --- a/client/src/components/UserSettingsModal/AccountPane/EmailEditPopup.jsx +++ b/client/src/components/UserEmailEditStep/UserEmailEditStep.jsx @@ -1,15 +1,15 @@ +import omit from 'lodash/omit'; import isEmail from 'validator/lib/isEmail'; import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; import { Button, Form, Message } from 'semantic-ui-react'; -import { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks'; -import { withPopup } from '../../../lib/popup'; -import { Input, Popup } from '../../../lib/custom-ui'; +import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks'; +import { Input, Popup } from '../../lib/custom-ui'; -import { useForm } from '../../../hooks'; +import { useForm } from '../../hooks'; -import styles from './EmailEditPopup.module.scss'; +import styles from './UserEmailEditStep.module.scss'; const createMessage = (error) => { if (!error) { @@ -35,8 +35,18 @@ const createMessage = (error) => { } }; -const EmailEditStep = React.memo( - ({ defaultData, email, isSubmitting, error, onUpdate, onMessageDismiss, onClose }) => { +const UserEmailEditStep = React.memo( + ({ + defaultData, + email, + isSubmitting, + error, + usePasswordConfirmation, + onUpdate, + onMessageDismiss, + onBack, + onClose, + }) => { const [t] = useTranslation(); const wasSubmitting = usePrevious(isSubmitting); @@ -68,13 +78,13 @@ const EmailEditStep = React.memo( return; } - if (!cleanData.currentPassword) { + if (usePasswordConfirmation && !cleanData.currentPassword) { currentPasswordField.current.focus(); return; } - onUpdate(cleanData); - }, [email, onUpdate, onClose, data]); + onUpdate(usePasswordConfirmation ? cleanData : omit(cleanData, 'currentPassword')); + }, [email, usePasswordConfirmation, onUpdate, onClose, data]); useEffect(() => { emailField.current.select(); @@ -110,7 +120,7 @@ const EmailEditStep = React.memo( return ( <> - + {t('common.editEmail', { context: 'title', })} @@ -138,15 +148,19 @@ const EmailEditStep = React.memo( className={styles.field} onChange={handleFieldChange} /> -
{t('common.currentPassword')}
- + {usePasswordConfirmation && ( + <> +
{t('common.currentPassword')}
+ + + )} + + + + ); + }, +); + +Item.propTypes = { + email: PropTypes.string.isRequired, + username: PropTypes.string, + name: PropTypes.string.isRequired, + organization: PropTypes.string, + phone: PropTypes.string, + isAdmin: PropTypes.bool.isRequired, + /* eslint-disable react/forbid-prop-types */ + emailUpdateForm: PropTypes.object.isRequired, + passwordUpdateForm: PropTypes.object.isRequired, + usernameUpdateForm: PropTypes.object.isRequired, + /* eslint-enable react/forbid-prop-types */ + onUpdate: PropTypes.func.isRequired, + onUsernameUpdate: PropTypes.func.isRequired, + onUsernameUpdateMessageDismiss: PropTypes.func.isRequired, + onEmailUpdate: PropTypes.func.isRequired, + onEmailUpdateMessageDismiss: PropTypes.func.isRequired, + onPasswordUpdate: PropTypes.func.isRequired, + onPasswordUpdateMessageDismiss: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, +}; + +Item.defaultProps = { + username: undefined, + organization: undefined, + phone: undefined, +}; + +export default Item; diff --git a/client/src/components/UsersModal/Item.module.scss b/client/src/components/UsersModal/Item/Item.module.scss similarity index 100% rename from client/src/components/UsersModal/Item.module.scss rename to client/src/components/UsersModal/Item/Item.module.scss diff --git a/client/src/components/UsersModal/Item/index.js b/client/src/components/UsersModal/Item/index.js new file mode 100644 index 00000000..01ed425e --- /dev/null +++ b/client/src/components/UsersModal/Item/index.js @@ -0,0 +1,3 @@ +import Item from './Item'; + +export default Item; diff --git a/client/src/components/UsersModal/UsersModal.jsx b/client/src/components/UsersModal/UsersModal.jsx index 38a01c46..df33c044 100755 --- a/client/src/components/UsersModal/UsersModal.jsx +++ b/client/src/components/UsersModal/UsersModal.jsx @@ -6,68 +6,140 @@ import { Button, Modal, Table } from 'semantic-ui-react'; import UserAddPopupContainer from '../../containers/UserAddPopupContainer'; import Item from './Item'; -const UsersModal = React.memo(({ items, onUpdate, onDelete, onClose }) => { - const [t] = useTranslation(); +const UsersModal = React.memo( + ({ + items, + onUpdate, + onUsernameUpdate, + onUsernameUpdateMessageDismiss, + onEmailUpdate, + onEmailUpdateMessageDismiss, + onPasswordUpdate, + onPasswordUpdateMessageDismiss, + onDelete, + onClose, + }) => { + const [t] = useTranslation(); - const handleUpdate = useCallback( - (id, data) => { - onUpdate(id, data); - }, - [onUpdate], - ); + const handleUpdate = useCallback( + (id, data) => { + onUpdate(id, data); + }, + [onUpdate], + ); - const handleDelete = useCallback( - (id) => { - onDelete(id); - }, - [onDelete], - ); + const handleUsernameUpdate = useCallback( + (id, data) => { + onUsernameUpdate(id, data); + }, + [onUsernameUpdate], + ); - return ( - - - {t('common.users', { - context: 'title', - })} - - - - - - {t('common.name')} - {t('common.username')} - {t('common.email')} - {t('common.administrator')} - - - - - {items.map((item) => ( - handleUpdate(item.id, data)} - onDelete={() => handleDelete(item.id)} - /> - ))} - -
-
- - -