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 10 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
4 changes: 4 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ 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, 1 number.',
Copy link
Contributor

Choose a reason for hiding this comment

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

This error message seems wrong. If your problem is that the new password isn't different from the old password then that's what we should say. We're kind of mixing feedback here instead of telling the user exactly what's wrong.

Copy link
Contributor

Choose a reason for hiding this comment

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

As a user, I'm going to wonder which is my problem? Passwords need to change? Or format is bad?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right, but I wasn't sure if this is supposed to be changed. Updated

Copy link
Contributor

Choose a reason for hiding this comment

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

Apologies, to clarify, mainly wanted to discuss this improvement and get some opinions before updating.

confirmNewPassword: 'Confirm new password',
errors: {
currentPassword: 'Current password is required',
confirmNewPassword: 'Confirm password is required',
},
},
addPayPalMePage: {
enterYourUsernameToGetPaidViaPayPal: 'Enter your username to get paid back via PayPal.',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ export default {
newPassword: 'Nueva contraseña',
newPasswordPrompt: 'La nueva contraseña tiene que ser diferente de la antigua, tener al menos 8 letras,\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',
},
},
addPayPalMePage: {
enterYourUsernameToGetPaidViaPayPal: 'Escribe tu nombre de usuario para que otros puedan pagarte a través de PayPal.',
Expand Down
119 changes: 76 additions & 43 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 Onyx, {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 Down Expand Up @@ -50,45 +50,76 @@ class PasswordPage extends Component {
currentPassword: '',
newPassword: '',
confirmNewPassword: '',
isPasswordRequirementsVisible: false,
shouldShowPasswordConfirmError: false,
errors: {
currentPassword: false,
newPassword: false,
confirmNewPassword: false,
confirmPasswordMatch: false,
},
};

this.handleChangePassword = this.handleChangePassword.bind(this);
this.getErrorText = this.getErrorText.bind(this);
this.isValidPassword = this.isValidPassword.bind(this);
this.validateAndSubmitForm = this.validateAndSubmitForm.bind(this);
this.clearErrorAndSetValue = this.clearErrorAndSetValue.bind(this);
this.currentPasswordInputRef = null;

this.errorKeysMap = {
currentPassword: 'passwordPage.errors.currentPassword',
confirmNewPassword: 'passwordPage.errors.confirmNewPassword',
};
}

componentWillUnmount() {
Onyx.merge(ONYXKEYS.ACCOUNT, {error: '', success: ''});
}

onBlurNewPassword() {
const stateToUpdate = {};
if (!this.state.newPassword || !this.isValidPassword()) {
stateToUpdate.isPasswordRequirementsVisible = true;
} else {
stateToUpdate.isPasswordRequirementsVisible = false;
getErrorText(field) {
if (this.state.errors[field]) {
return this.props.translate(this.errorKeysMap[field]);
}
return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

If the doc says we are returning a string then don't return null return an empty string.

}

if (this.state.newPassword && this.state.confirmNewPassword && !this.doPasswordsMatch()) {
stateToUpdate.shouldShowPasswordConfirmError = true;
} else {
stateToUpdate.shouldShowPasswordConfirmError = false;
}
isValidPassword(password) {
return password.match(CONST.PASSWORD_COMPLEXITY_REGEX_STRING);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we move this to ValidationUtils.js?

meetsAgeRequirements,
isValidAddress,
isValidDate,
isValidSecurityCode,
isValidExpirationDate,
isValidDebitCard,
isValidIndustryCode,
isValidZipCode,
isRequiredFulfilled,
isValidPhoneWithSpecialChars,
isValidUSPhone,
isValidURL,
validateIdentity,


this.setState(stateToUpdate);
clearErrorAndSetValue(field, value) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Add jsdocs here and to other functions

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated. Sorry, not sure how I missed these 🤔

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

onBlurConfirmPassword() {
if ((this.state.newPassword && !this.state.confirmNewPassword) || !this.doPasswordsMatch()) {
this.setState({shouldShowPasswordConfirmError: true});
} else {
this.setState({shouldShowPasswordConfirmError: false});
validateAndSubmitForm() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we please make one function that validates and another that submits? Here's an example:

submit() {
if (!this.validate()) {
showBankAccountErrorModal();
return;
}
const incorporationDate = moment(this.state.incorporationDate).format(CONST.DATE.MOMENT_FORMAT_STRING);
setupWithdrawalAccount({...this.state, incorporationDate});
}

const errors = {};

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

if (!this.state.newPassword || !this.isValidPassword(this.state.newPassword)) {
errors.newPassword = true;
}
}

isValidPassword() {
return this.state.newPassword.match(CONST.PASSWORD_COMPLEXITY_REGEX_STRING);
if (!this.state.confirmNewPassword) {
errors.confirmNewPassword = true;
}

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

if (this.state.newPassword && this.state.confirmNewPassword && !this.doPasswordsMatch()) {
errors.confirmPasswordMatch = true;
}

this.setState({errors});
if (_.isEmpty(errors)) {
this.handleChangePassword();
}
}


Expand Down Expand Up @@ -132,8 +163,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 @@ -143,15 +176,19 @@ 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}
errorText=""
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not needed. We should default to an empty string if not passing this value.

onChangeText={text => this.clearErrorAndSetValue('newPassword', text)}
/>
{this.state.isPasswordRequirementsVisible && (
<Text style={[styles.textLabelSupporting, styles.mt1]}>
{this.props.translate('passwordPage.newPasswordPrompt')}
</Text>
)}
<Text
style={[
styles.textLabelSupporting,
styles.mt1,
this.state.errors.newPassword && styles.formError,
]}
>
{this.props.translate('passwordPage.newPasswordPrompt')}
</Text>
</View>
<View style={styles.mb6}>
<ExpensiTextInput
Expand All @@ -160,17 +197,18 @@ 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)}
hasError={this.state.errors.confirmNewPassword}
errorText={this.getErrorText('confirmNewPassword')}
onSubmitEditing={this.validateAndSubmitForm}
/>
</View>
{!this.state.shouldShowPasswordConfirmError && !isEmpty(this.props.account.error) && (
{!this.state.errors.confirmPasswordMatch && !_.isEmpty(this.props.account.error) && (
<Text style={styles.formError}>
{this.props.account.error}
</Text>
)}
{this.state.shouldShowPasswordConfirmError && (
{this.state.errors.confirmPasswordMatch && (
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we still be showing this error? It feels kind of out of place and I'm not sure it's correct.

If the first password is valid but the confirmation password doesn't match then the password confirmation field is the one with the "error"? Seems like it wouldn't make sense to show this:

2021-10-18_09-42-58

Copy link
Collaborator Author

@mananjadhav mananjadhav Oct 19, 2021

Choose a reason for hiding this comment

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

I've updated. I was a bit unsure about guidelines on tagging multiple error flags to a single field. I've added an approach. Let me know if this is fine.

Copy link
Contributor

Choose a reason for hiding this comment

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

This also is something I wanted to discuss (perhaps back on the original issue or via Slack) before moving forward on.

<InlineErrorText>
{this.props.translate('setPasswordPage.passwordsDontMatch')}
</InlineErrorText>
Expand All @@ -180,14 +218,9 @@ class PasswordPage extends Component {
<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.validateAndSubmitForm}
/>
</FixedFooter>
</KeyboardAvoidingView>
Expand Down