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