diff --git a/src/languages/en.js b/src/languages/en.js index acb1a603eb12..f3262c914a68 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -253,7 +253,10 @@ export default { setPasswordPage: { passwordCannotBeBlank: 'Password cannot be blank', enterPassword: 'Enter a password', + confirmNewPassword: 'Confirm the password', setPassword: 'Set Password', + passwordsDontMatch: 'Passwords must match', + newPasswordPrompt: 'Your password must have at least 8 characters,\n1 capital letter, 1 lowercase letter, 1 number.', }, addBankAccountPage: { enterPassword: 'Enter password', diff --git a/src/pages/SetPasswordPage.js b/src/pages/SetPasswordPage.js index 29ebc213033e..99f79f8f9053 100755 --- a/src/pages/SetPasswordPage.js +++ b/src/pages/SetPasswordPage.js @@ -2,7 +2,6 @@ import React, {Component} from 'react'; import { SafeAreaView, Text, - TextInput, View, } from 'react-native'; import PropTypes from 'prop-types'; @@ -14,11 +13,10 @@ import styles from '../styles/styles'; import {setPassword} from '../libs/actions/Session'; import ONYXKEYS from '../ONYXKEYS'; import Button from '../components/Button'; -import themeColors from '../styles/themes/default'; import SignInPageLayout from './signin/SignInPageLayout'; -import canFocusInputOnScreenFocus from '../libs/canFocusInputOnScreenFocus'; import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; import compose from '../libs/compose'; +import NewPasswordForm from './settings/NewPasswordForm'; const propTypes = { /* Onyx Props */ @@ -63,7 +61,7 @@ class SetPasswordPage extends Component { this.state = { password: '', - formError: null, + isFormValid: false, }; } @@ -71,16 +69,9 @@ class SetPasswordPage extends Component { * Validate the form and then submit it */ validateAndSubmitForm() { - if (!this.state.password.trim()) { - this.setState({ - formError: this.props.translate('setPasswordPage.passwordCannotBeBlank'), - }); + if (!this.state.isFormValid) { return; } - - this.setState({ - formError: null, - }); setPassword( this.state.password, lodashGet(this.props.route, 'params.validateCode', ''), @@ -93,20 +84,11 @@ class SetPasswordPage extends Component { - - {this.props.translate('setPasswordPage.enterPassword')} - - this.setState({password: text})} + this.setState({password})} + updateIsFormValid={isValid => this.setState({isFormValid: isValid})} onSubmitEditing={this.validateAndSubmitForm} - autoCapitalize="none" - placeholderTextColor={themeColors.placeholderText} - autoFocus={canFocusInputOnScreenFocus()} /> @@ -119,12 +101,6 @@ class SetPasswordPage extends Component { /> - {this.state.formError && ( - - {this.state.formError} - - )} - {!_.isEmpty(this.props.account.error) && ( {this.props.account.error} diff --git a/src/pages/settings/NewPasswordForm.js b/src/pages/settings/NewPasswordForm.js new file mode 100644 index 000000000000..c37f7151b53a --- /dev/null +++ b/src/pages/settings/NewPasswordForm.js @@ -0,0 +1,138 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {TextInput, View} from 'react-native'; +import Text from '../../components/Text'; +import withLocalize, { + withLocalizePropTypes, +} from '../../components/withLocalize'; +import CONST from '../../CONST'; +import styles from '../../styles/styles'; + +const propTypes = { + /** String to control the first password box in the form */ + password: PropTypes.string.isRequired, + + /** Function to update the first password box in the form */ + updatePassword: PropTypes.func.isRequired, + + /** Callback function called with boolean value for if the password form is valid */ + updateIsFormValid: PropTypes.func.isRequired, + + /** Callback function for when form is submitted */ + onSubmitEditing: PropTypes.func.isRequired, + ...withLocalizePropTypes, +}; + +class NewPasswordForm extends React.Component { + constructor(props) { + super(props); + + this.state = { + confirmNewPassword: '', + passwordHintError: false, + shouldShowPasswordConfirmError: false, + }; + } + + componentDidUpdate(prevProps, prevState) { + const eitherPasswordChanged = (this.props.password !== prevProps.password) + || this.state.confirmNewPassword !== prevState.confirmNewPassword; + if (eitherPasswordChanged) { + this.props.updateIsFormValid(this.isValidForm()); + } + } + + onBlurNewPassword() { + if (this.state.passwordHintError) { + return; + } + if (this.props.password && !this.isValidPassword()) { + this.setState({passwordHintError: true}); + } + } + + onBlurConfirmPassword() { + if (this.state.shouldShowPasswordConfirmError) { + return; + } + if (this.state.confirmNewPassword && !this.doPasswordsMatch()) { + this.setState({shouldShowPasswordConfirmError: true}); + } + } + + isValidPassword() { + return this.props.password.match(CONST.PASSWORD_COMPLEXITY_REGEX_STRING); + } + + doPasswordsMatch() { + return this.props.password === this.state.confirmNewPassword; + } + + isValidForm() { + return this.isValidPassword() && this.doPasswordsMatch(); + } + + showPasswordMatchError() { + return ( + !this.doPasswordsMatch() + && this.state.shouldShowPasswordConfirmError + && this.state.confirmNewPassword + ); + } + + render() { + let passwordHintStyle; + if (this.state.passwordHintError && this.props.password && !this.isValidPassword()) { + passwordHintStyle = styles.formError; + } + if (this.isValidPassword()) { + passwordHintStyle = styles.formSuccess; + } + + return ( + <> + + + {`${this.props.translate('setPasswordPage.enterPassword')}`} + + this.props.updatePassword(password)} + onBlur={() => this.onBlurNewPassword()} + /> + + {this.props.translate('setPasswordPage.newPasswordPrompt')} + + + + + {`${this.props.translate('setPasswordPage.confirmNewPassword')}*`} + + this.setState({confirmNewPassword})} + onSubmitEditing={() => this.props.onSubmitEditing()} + onBlur={() => this.onBlurConfirmPassword()} + /> + {this.showPasswordMatchError() && ( + + {this.props.translate('setPasswordPage.passwordsDontMatch')} + + )} + + + ); + } +} + +NewPasswordForm.propTypes = propTypes; + +export default withLocalize(NewPasswordForm);