Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Change password hint color to red when invalid, else grey #5130

Merged
merged 26 commits into from
Nov 22, 2021
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
254e1d3
fix(password-hint-color): Change password hint color to red when it i…
mananjadhav Sep 8, 2021
5a3e33b
fix(password-hint-color): Changed function flow from isInvalidPasswo…
mananjadhav Sep 9, 2021
d33a958
Merge branch 'main' of https://github.com/mananjadhav/App into fix/pa…
mananjadhav Sep 10, 2021
25e16ad
style(password-hint-color): Fixed style changes
mananjadhav Sep 10, 2021
821a6b1
style(password-hint-color): Moved style prop to newline
mananjadhav Sep 10, 2021
226a6cd
Merge branch 'main' of https://github.com/mananjadhav/App into fix/pa…
mananjadhav Sep 12, 2021
8a8e16b
fix(password-hint-color): Changed logic from onBlur to onClick
mananjadhav Sep 13, 2021
52501c6
Merge branch 'main' of https://github.com/mananjadhav/App into fix/pa…
mananjadhav Sep 26, 2021
5cfca27
fix(password-onsubmit-error): Changed all errors to onSubmit
mananjadhav Sep 28, 2021
7e90a49
fix(password-onsubmit-error): Clear error when user starts typing
mananjadhav Sep 28, 2021
835794e
fix(password-hint-color): Added jsdoc comments
mananjadhav Oct 18, 2021
bb49ca2
Merge branch 'main' of https://github.com/mananjadhav/App into fix/pa…
mananjadhav Oct 18, 2021
c9f7c97
fix(password-hint-color): Moved isValidPassword to utils and removed …
mananjadhav Oct 18, 2021
4c4bb9e
fix(password-hint-color): Split functions validate and submit
mananjadhav Oct 18, 2021
b76536d
fix(password-hint-color): Updated translations for password errors
mananjadhav Oct 19, 2021
a167491
fix(password-hint-color): Split errors into different flags and updat…
mananjadhav Oct 19, 2021
5d1648c
fix(password-hint-color): Show error only for one of the flags
mananjadhav Oct 19, 2021
097c007
fix(password-hint-color): Show API error only if no UI error
mananjadhav Oct 19, 2021
dfc570a
fix(password-hint-color): Show errors and hint based condition
mananjadhav Nov 2, 2021
73db2f5
Merge branch 'main' of https://github.com/mananjadhav/App into fix/pa…
mananjadhav Nov 2, 2021
c6b2766
fix(password-hinit-color): Fixed lint issues and minor typos
mananjadhav Nov 4, 2021
e9399f3
Merge branch 'main' of https://github.com/mananjadhav/App into fix/pa…
mananjadhav Nov 4, 2021
8d97c12
fix(password-hint-color): Removed unwanted comments and renamed params
mananjadhav Nov 7, 2021
d3c5afc
fix(password-hint-color): Updated conditions and moved new password p…
mananjadhav Nov 7, 2021
7be90c8
fix(password-hinit-color): Changed _.some to _.every for better reada…
mananjadhav Nov 7, 2021
c94d639
fix(password-hint-color): Changed redirect from Settings to back
mananjadhav Nov 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,12 @@ export default {
newPassword: 'New password',
newPasswordPrompt: 'New password must be different than your old password, have at least 8 characters,\n1 capital letter, 1 lowercase letter, and 1 number.',
confirmNewPassword: 'Confirm new password',
errors: {
currentPassword: 'Current password is required',
confirmNewPassword: 'Confirm password is required',
newPasswordSameAsOld: 'New password must be different than your old password',
newPassword: 'Your password must have at least 8 characters,\n1 capital letter, 1 lowercase letter, and 1 number.',
},
},
addPayPalMePage: {
enterYourUsernameToGetPaidViaPayPal: 'Enter your username to get paid back via PayPal.',
Expand Down
6 changes: 6 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,12 @@ export default {
newPassword: 'Nueva contraseña',
newPasswordPrompt: 'La nueva contraseña debe ser diferente de la antigua, tener al menos 8 caracteres,\n1 letra mayúscula, 1 letra minúscula y 1 número.',
confirmNewPassword: 'Confirma la nueva contraseña',
errors: {
currentPassword: 'Contraseña actual es requerido',
confirmNewPassword: 'Confirma la nueva contraseña es requerido',
newPasswordSameAsOld: 'La nueva contraseña tiene que ser diferente de la antigua',
newPassword: 'Su contraseña debe tener al menos 8 caracteres, \n1 letra mayúscula, 1 letra minúscula y 1 número.',
},
},
addPayPalMePage: {
enterYourUsernameToGetPaidViaPayPal: 'Escribe tu nombre de usuario para que otros puedan pagarte a través de PayPal.',
Expand Down
9 changes: 9 additions & 0 deletions src/libs/ValidationUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,14 @@ function isValidUSPhone(phoneNumber) {
return CONST.REGEX.PHONE_E164_PLUS.test(phoneNumber.replace(CONST.REGEX.NON_ALPHA_NUMERIC, '')) && CONST.REGEX.US_PHONE.test(phoneNumber);
}

/**
* @param {String} password
* @returns {Boolean}
*/
function isValidPassword(password) {
return password.match(CONST.PASSWORD_COMPLEXITY_REGEX_STRING);
}

/**
* Checks whether a value is a numeric string including `(`, `)`, `-` and optional leading `+`
* @param {String} input
Expand Down Expand Up @@ -262,6 +270,7 @@ export {
isValidUSPhone,
isValidURL,
validateIdentity,
isValidPassword,
isNumericWithSpecialChars,
isValidLengthForFirstOrLastName,
isValidPaypalUsername,
Expand Down
158 changes: 106 additions & 52 deletions src/pages/settings/PasswordPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, {Component} from 'react';
import {View, ScrollView} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import PropTypes from 'prop-types';
import {isEmpty} from 'underscore';
import _ from 'underscore';

import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
import Navigation from '../../libs/Navigation/Navigation';
Expand All @@ -11,15 +11,14 @@ import ScreenWrapper from '../../components/ScreenWrapper';
import Text from '../../components/Text';
import styles from '../../styles/styles';
import ONYXKEYS from '../../ONYXKEYS';
import CONST from '../../CONST';
import Button from '../../components/Button';
import {isValidPassword} from '../../libs/ValidationUtils';
import * as User from '../../libs/actions/User';
import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
import compose from '../../libs/compose';
import KeyboardAvoidingView from '../../components/KeyboardAvoidingView';
import FixedFooter from '../../components/FixedFooter';
import ExpensiTextInput from '../../components/ExpensiTextInput';
import InlineErrorText from '../../components/InlineErrorText';
import {clearAccountMessages} from '../../libs/actions/Session';

const propTypes = {
Expand Down Expand Up @@ -51,57 +50,109 @@ class PasswordPage extends Component {
currentPassword: '',
newPassword: '',
confirmNewPassword: '',
isPasswordRequirementsVisible: false,
shouldShowPasswordConfirmError: false,
errors: {
currentPassword: false,
newPassword: false,
confirmNewPassword: false,
confirmPasswordMatch: false,
newPasswordSameAsOld: false,
},
};

this.handleChangePassword = this.handleChangePassword.bind(this);
this.submit = this.submit.bind(this);
this.getErrorText = this.getErrorText.bind(this);
this.validate = this.validate.bind(this);
this.clearErrorAndSetValue = this.clearErrorAndSetValue.bind(this);
this.currentPasswordInputRef = null;

this.errorKeysMap = {
currentPassword: 'passwordPage.errors.currentPassword',
confirmNewPassword: 'passwordPage.errors.confirmNewPassword',
newPasswordSameAsOld: 'passwordPage.errors.newPasswordSameAsOld',
confirmPasswordMatch: 'setPasswordPage.passwordsDontMatch',
newPassword: 'passwordPage.errors.newPassword',
};
}

componentWillUnmount() {
clearAccountMessages();
}

onBlurNewPassword() {
const stateToUpdate = {};
if (!this.state.newPassword || !this.isValidPassword()) {
stateToUpdate.isPasswordRequirementsVisible = true;
} else {
stateToUpdate.isPasswordRequirementsVisible = false;
/**
* @param {String} field
* @returns {String}
*/
getErrorText(field) {
if (this.state.errors[field]) {
return this.props.translate(this.errorKeysMap[field]);
}
return '';
}

if (this.state.newPassword && this.state.confirmNewPassword && !this.doPasswordsMatch()) {
stateToUpdate.shouldShowPasswordConfirmError = true;
} else {
stateToUpdate.shouldShowPasswordConfirmError = false;

/**
* @param {String} field
* @param {String} value
* @param {String[]} additionalErrorsToClear
*/
clearErrorAndSetValue(field, value, additionalErrorsToClear) {
const errorsToReset = {
[field]: false,
};
if (additionalErrorsToClear) {
_.each(additionalErrorsToClear, (errorFlag) => {
errorsToReset[errorFlag] = false;
});
}

this.setState(stateToUpdate);
this.setState(prevState => ({
[field]: value,
errors: {...prevState.errors, ...errorsToReset},
}));
}

onBlurConfirmPassword() {
if ((this.state.newPassword && !this.state.confirmNewPassword) || !this.doPasswordsMatch()) {
this.setState({shouldShowPasswordConfirmError: true});
} else {
this.setState({shouldShowPasswordConfirmError: false});
/**
* @returns {Boolean}
*/
validate() {
const errors = {};

if (!this.state.currentPassword) {
errors.currentPassword = true;
}
}

isValidPassword() {
return this.state.newPassword.match(CONST.PASSWORD_COMPLEXITY_REGEX_STRING);
}
if (!this.state.newPassword || !isValidPassword(this.state.newPassword)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB, !this.state.newPassword check should be performed inside isValidPassword()?

errors.newPassword = true;
}

if (!this.state.confirmNewPassword) {
errors.confirmNewPassword = true;
}

handleChangePassword() {
User.changePasswordAndNavigate(this.state.currentPassword, this.state.newPassword);
if (this.state.currentPassword && this.state.newPassword && _.isEqual(this.state.currentPassword, this.state.newPassword)) {
errors.newPasswordSameAsOld = true;
}

if (isValidPassword(this.state.newPassword) && this.state.confirmNewPassword && !_.isEqual(this.state.newPassword, this.state.confirmNewPassword)) {
errors.confirmPasswordMatch = true;
}

this.setState({errors});
return _.size(errors) === 0;
}

doPasswordsMatch() {
return this.state.newPassword === this.state.confirmNewPassword;
/**
* Submit the form
*/
submit() {
if (!this.validate()) {
return;
}
User.changePasswordAndNavigate(this.state.currentPassword, this.state.newPassword);
}

render() {
const shouldShowNewPasswordPrompt = !this.state.errors.newPassword && !this.state.errors.newPasswordSameAsOld;
return (
<ScreenWrapper onTransitionEnd={() => {
if (this.currentPasswordInputRef) {
Expand All @@ -128,8 +179,10 @@ class PasswordPage extends Component {
autoCompleteType="password"
textContentType="password"
value={this.state.currentPassword}
onChangeText={currentPassword => this.setState({currentPassword})}
onChangeText={text => this.clearErrorAndSetValue('currentPassword', text)}
returnKeyType="done"
hasError={this.state.errors.currentPassword}
errorText={this.getErrorText('currentPassword')}
/>
</View>
<View style={styles.mb6}>
Expand All @@ -139,15 +192,25 @@ class PasswordPage extends Component {
autoCompleteType="password"
textContentType="password"
value={this.state.newPassword}
onChangeText={newPassword => this.setState({newPassword})}
onFocus={() => this.setState({isPasswordRequirementsVisible: true})}
onBlur={() => this.onBlurNewPassword()}
hasError={this.state.errors.newPassword || this.state.errors.newPasswordSameAsOld}
errorText={this.state.errors.newPasswordSameAsOld
? this.getErrorText('newPasswordSameAsOld')
: this.getErrorText('newPassword')}
onChangeText={text => this.clearErrorAndSetValue('newPassword', text, ['newPasswordSameAsOld'])}
/>
{this.state.isPasswordRequirementsVisible && (
<Text style={[styles.textLabelSupporting, styles.mt1]}>
{

shouldShowNewPasswordPrompt && (
<Text
style={[
styles.textLabelSupporting,
styles.mt1,
]}
>
{this.props.translate('passwordPage.newPasswordPrompt')}
</Text>
)}
)
}
</View>
<View style={styles.mb6}>
<ExpensiTextInput
Expand All @@ -156,34 +219,25 @@ class PasswordPage extends Component {
autoCompleteType="password"
textContentType="password"
value={this.state.confirmNewPassword}
onChangeText={confirmNewPassword => this.setState({confirmNewPassword})}
onSubmitEditing={this.handleChangePassword}
onBlur={() => this.onBlurConfirmPassword()}
onChangeText={text => this.clearErrorAndSetValue('confirmNewPassword', text, ['confirmPasswordMatch'])}
hasError={this.state.errors.confirmNewPassword || this.state.errors.confirmPasswordMatch}
errorText={this.getErrorText(this.state.errors.confirmNewPassword ? 'confirmNewPassword' : 'confirmPasswordMatch')}
onSubmitEditing={this.validateAndSubmitForm}
/>
</View>
{!this.state.shouldShowPasswordConfirmError && !isEmpty(this.props.account.error) && (
{_.every(this.state.errors, error => !error) && !_.isEmpty(this.props.account.error) && (
<Text style={styles.formError}>
{this.props.account.error}
</Text>
)}
{this.state.shouldShowPasswordConfirmError && (
<InlineErrorText>
{this.props.translate('setPasswordPage.passwordsDontMatch')}
</InlineErrorText>
)}
</ScrollView>
<FixedFooter style={[styles.flexGrow0]}>
<Button
success
style={[styles.mb2]}
isDisabled={!this.state.currentPassword || !this.state.newPassword
|| !this.state.confirmNewPassword
|| (this.state.newPassword !== this.state.confirmNewPassword)
|| (this.state.currentPassword === this.state.newPassword)
|| !this.state.newPassword.match(CONST.PASSWORD_COMPLEXITY_REGEX_STRING)}
isLoading={this.props.account.loading}
text={this.props.translate('common.save')}
onPress={this.handleChangePassword}
onPress={this.submit}
/>
</FixedFooter>
</KeyboardAvoidingView>
Expand Down