From 4dca7de381ac816f5f345204fd2f24c43b022e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Gonz=C3=A1lez?= <36533965+victorggonzalez@users.noreply.github.com> Date: Wed, 6 Jul 2022 12:44:18 +0200 Subject: [PATCH] feat: update member password --- src/components/member/MemberProfileScreen.js | 2 + src/components/member/PasswordSetting.js | 228 +++++++++++++++++++ src/config/messages.js | 7 + src/config/selectors.js | 10 + src/utils/validation.js | 44 ++++ 5 files changed, 291 insertions(+) create mode 100644 src/components/member/PasswordSetting.js create mode 100644 src/utils/validation.js diff --git a/src/components/member/MemberProfileScreen.js b/src/components/member/MemberProfileScreen.js index eb5f61e17..b71aeaab6 100644 --- a/src/components/member/MemberProfileScreen.js +++ b/src/components/member/MemberProfileScreen.js @@ -22,6 +22,7 @@ import Main from '../main/Main'; import { CurrentUserContext } from '../context/CurrentUserContext'; import AvatarSetting from './AvatarSetting'; import DeleteMemberDialog from './DeleteMemberDialog'; +import PasswordSetting from './PasswordSetting'; const useStyles = makeStyles((theme) => ({ root: { @@ -131,6 +132,7 @@ const MemberProfileScreen = () => { + diff --git a/src/components/member/PasswordSetting.js b/src/components/member/PasswordSetting.js new file mode 100644 index 000000000..4b13ce12f --- /dev/null +++ b/src/components/member/PasswordSetting.js @@ -0,0 +1,228 @@ +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'react-toastify'; +import PropTypes from 'prop-types'; +import Typography from '@material-ui/core/Typography'; +import { makeStyles } from '@material-ui/core/styles'; +import Grid from '@material-ui/core/Grid'; +import { Button, TextField } from '@material-ui/core'; +import { MUTATION_KEYS } from '@graasp/query-client'; +import { useMutation } from '../../config/queryClient'; +import { + CONFIRM_CHANGE_PASSWORD_BUTTON_ID, + CONFIRM_RESET_PASSWORD_BUTTON_ID, + USER_CONFIRM_PASSWORD_INPUT_ID, + USER_CURRENT_PASSWORD_INPUT_ID, + USER_NEW_PASSWORD_INPUT_ID, +} from '../../config/selectors'; +import { + newPasswordValidator, + passwordValidator, + strengthValidator, +} from '../../utils/validation'; + +const useStyles = makeStyles(() => ({ + mainContainer: { + flexDirection: 'column', + alignItems: 'flex-start', + margin: '12px 0px', + }, + changePasswordContainer: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + margin: '12px 0px', + }, + buttonItem: { + display: 'flex', + padding: '12px', + flexDirection: 'column', + justifyContent: 'flex-end', + alignItems: 'flex-start', + }, + note: { + fontSize: '12px', + margin: '4px 0 2px', + }, +})); + +const PasswordSetting = ({ user }) => { + const { t } = useTranslation(); + const classes = useStyles(); + + const [currentPassword, setCurrentPassword] = useState(''); + const [newPassword, setNewPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [currentPasswordError, setCurrentPasswordError] = useState(null); + const [newPasswordError, setNewPasswordError] = useState(null); + const [confirmPasswordError, setConfirmPasswordError] = useState(null); + const { mutate: onUpdatePassword } = useMutation( + MUTATION_KEYS.UPDATE_PASSWORD, + ); + + const userId = user.get('id'); + + const onClose = () => { + setCurrentPassword(''); + setNewPassword(''); + setConfirmPassword(''); + }; + + const handleChangePassword = () => { + const checkingCurrentPassword = passwordValidator(currentPassword); + const checkingNewPassword = passwordValidator(newPassword); + const checkingConfirmPassword = passwordValidator(confirmPassword); + // verify empty fields + if ( + user.get('password') && + (checkingCurrentPassword || + checkingNewPassword || + checkingConfirmPassword) + ) { + setCurrentPasswordError(checkingCurrentPassword); + setNewPasswordError(checkingNewPassword); + setConfirmPasswordError(checkingConfirmPassword); + } else { + const checkingPasswordUpdate = newPasswordValidator( + currentPassword, + newPassword, + confirmPassword, + ); + if (checkingPasswordUpdate) { + toast.error(checkingPasswordUpdate); + } else { + const checkingStrength = strengthValidator(newPassword); + if (checkingStrength) { + toast.error(checkingStrength); + } else { + onUpdatePassword({ + id: userId, + password: newPassword, + currentPassword, + }); + onClose(); + } + } + } + }; + + const handleCurrentPasswordInput = (event) => { + setCurrentPassword(event.target.value); + setCurrentPasswordError(passwordValidator(event.target.value)); + }; + const handleNewPasswordInput = (event) => { + setNewPassword(event.target.value); + setNewPasswordError(passwordValidator(event.target.value)); + }; + const handleConfirmPasswordInput = (event) => { + setConfirmPassword(event.target.value); + setConfirmPasswordError(passwordValidator(event.target.value)); + }; + + return ( + <> + + + + {user.get('password') !== null ? ( + {t('Change Password')} + ) : ( + {t('Set Password')} + )} + + + + {user.get('password') !== null ? ( + + ) : ( + + )} + + + + + + + + + + + + {t( + 'Make sure it is at least 8 characters including a number, a lowercase letter and an uppercase letter.', + )} + + + + + + {t('I forgot my password:')} + + + + + + + + + ); +}; + +PasswordSetting.propTypes = { + user: PropTypes.instanceOf(Map).isRequired, +}; + +export default PasswordSetting; diff --git a/src/config/messages.js b/src/config/messages.js index 3f59b0528..ec06b6fb4 100644 --- a/src/config/messages.js +++ b/src/config/messages.js @@ -49,3 +49,10 @@ export const IMPORT_ZIP_PROGRESS_MESSAGE = 'The ZIP is being processed. Please wait a moment.'; export const EXPORT_ZIP_FAILURE_MESSAGE = 'An error occurred while downloading the item as ZIP archive. Please try again later.'; + +export const PASSWORD_EMPTY_ERROR = 'Please enter a valid password'; +export const PASSWORD_WEAK_ERROR = 'Password not strong enough'; +export const PASSWORD_EQUAL_ERROR = + 'Please enter a new password different from your current one'; +export const PASSWORD_CONFIRM_ERROR = + 'Please make sure "New Password" matches "Confirm password"'; diff --git a/src/config/selectors.js b/src/config/selectors.js index 009676051..123dbd986 100644 --- a/src/config/selectors.js +++ b/src/config/selectors.js @@ -219,3 +219,13 @@ export const CO_EDITOR_SETTINGS_RADIO_GROUP_ID = 'coEditorSettingsRadioGroup'; export const buildCoEditorSettingsRadioButtonId = (id) => `coEditorSettingsRadioButton-${id}`; export const EMAIL_NOTIFICATION_CHECKBOX = 'emailNotificationCheckbox'; + +export const MEMBER_CURRENT_PASSWORD_ID = 'memberCurrentPassword'; +export const MEMBER_NEW_PASSWORD_ID = 'memberNewPassword'; +export const MEMBER_NEW_PASSWORD_CONFIRMATION_ID = + 'memberNewPasswordConfirmation'; +export const CONFIRM_CHANGE_PASSWORD_BUTTON_ID = 'confirmChangePasswordButton'; +export const CONFIRM_RESET_PASSWORD_BUTTON_ID = 'confirmResetPasswordButton'; +export const USER_CURRENT_PASSWORD_INPUT_ID = 'currentPasswordInput'; +export const USER_NEW_PASSWORD_INPUT_ID = 'newPasswordInput'; +export const USER_CONFIRM_PASSWORD_INPUT_ID = 'confirmPasswordInput'; diff --git a/src/utils/validation.js b/src/utils/validation.js new file mode 100644 index 000000000..c4442c31b --- /dev/null +++ b/src/utils/validation.js @@ -0,0 +1,44 @@ +import validator from 'validator'; + +import { + PASSWORD_CONFIRM_ERROR, + PASSWORD_EMPTY_ERROR, + PASSWORD_EQUAL_ERROR, + PASSWORD_WEAK_ERROR, +} from '../config/messages'; + +export const strengthValidator = (password) => { + if ( + !validator.isStrongPassword(password, { + minLength: 8, + minLowercase: 1, + minUppercase: 1, + minNumbers: 1, + minSymbols: 0, + }) + ) { + return PASSWORD_WEAK_ERROR; + } + return null; +}; + +export const passwordValidator = (password) => { + if (validator.isEmpty(password)) { + return PASSWORD_EMPTY_ERROR; + } + return null; +}; + +export const newPasswordValidator = ( + currentPassword, + newPassword, + confirmPassword, +) => { + if (currentPassword === newPassword) { + return PASSWORD_EQUAL_ERROR; + } + if (newPassword !== confirmPassword) { + return PASSWORD_CONFIRM_ERROR; + } + return null; +};