Skip to content

Commit

Permalink
feat: update member password
Browse files Browse the repository at this point in the history
  • Loading branch information
victorggonzalez committed Jul 6, 2022
1 parent 270a569 commit 4dca7de
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/components/member/MemberProfileScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -131,6 +132,7 @@ const MemberProfileScreen = () => {
</Grid>

<AvatarSetting user={member} />
<PasswordSetting user={member} />
<DeleteMemberDialog id={member?.get('id')} />
</Card>
</Main>
Expand Down
228 changes: 228 additions & 0 deletions src/components/member/PasswordSetting.js
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Grid container spacing={3} className={classes.mainContainer}>
<Grid item xs={8}>
<Grid item xs={12}>
{user.get('password') !== null ? (
<Typography variant="h5">{t('Change Password')}</Typography>
) : (
<Typography variant="h5">{t('Set Password')}</Typography>
)}
</Grid>
<Grid
container
spacing={3}
className={classes.changePasswordContainer}
>
<Grid item xs={12} sm={6}>
{user.get('password') !== null ? (
<TextField
className={classes.input}
required
label={t('Current Password')}
variant="outlined"
value={currentPassword}
error={currentPasswordError}
helperText={currentPasswordError}
onChange={handleCurrentPasswordInput}
id={USER_CURRENT_PASSWORD_INPUT_ID}
type="password"
/>
) : (
<Grid item xs={12} sm={6} />
)}
</Grid>
<Grid item xs={12} sm={6} />
<Grid item xs={12} sm={6}>
<TextField
className={classes.input}
required
label={t('New Password')}
variant="outlined"
value={newPassword}
error={newPasswordError}
helperText={newPasswordError}
onChange={handleNewPasswordInput}
id={USER_NEW_PASSWORD_INPUT_ID}
type="password"
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
className={classes.input}
required
label={t('Confirm Password')}
variant="outlined"
value={confirmPassword}
error={confirmPasswordError}
helperText={confirmPasswordError}
onChange={handleConfirmPasswordInput}
id={USER_CONFIRM_PASSWORD_INPUT_ID}
type="password"
/>
</Grid>
<Grid container direction="row">
<Grid item xs={12} sm={6} className={classes.buttonItem}>
<Typography className={classes.note}>
{t(
'Make sure it is at least 8 characters including a number, a lowercase letter and an uppercase letter.',
)}
</Typography>
<Button
id={CONFIRM_CHANGE_PASSWORD_BUTTON_ID}
variant="contained"
color="primary"
onClick={() => handleChangePassword()}
>
{t('Update password')}
</Button>
</Grid>
<Grid item xs={12} sm={6} className={classes.buttonItem}>
<Typography className={classes.note}>
{t('I forgot my password:')}
</Typography>
<Button
id={CONFIRM_RESET_PASSWORD_BUTTON_ID}
variant="outlined"
disabled
// TO DO:
// onClick={() => handleChangePassword()}
>
{t('Request a reset')}
</Button>
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
</>
);
};

PasswordSetting.propTypes = {
user: PropTypes.instanceOf(Map).isRequired,
};

export default PasswordSetting;
7 changes: 7 additions & 0 deletions src/config/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"';
10 changes: 10 additions & 0 deletions src/config/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
44 changes: 44 additions & 0 deletions src/utils/validation.js
Original file line number Diff line number Diff line change
@@ -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;
};

0 comments on commit 4dca7de

Please sign in to comment.