From 0b74d3a0efcdbc844ce82442d93c2509b1006388 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 18 Nov 2020 12:28:46 +0000 Subject: [PATCH] Use field validation for PasswordLogin instead of global errors --- src/components/structures/auth/Login.js | 27 --- src/components/views/auth/PasswordLogin.js | 207 ++++++++++++++++++--- src/i18n/strings/en_EN.json | 13 +- 3 files changed, 184 insertions(+), 63 deletions(-) diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js index c3cbac0442..3aba4a571e 100644 --- a/src/components/structures/auth/Login.js +++ b/src/components/structures/auth/Login.js @@ -32,9 +32,6 @@ import SettingsStore from "../../../settings/SettingsStore"; import {UIFeature} from "../../../settings/UIFeature"; import CountlyAnalytics from "../../../CountlyAnalytics"; -// For validating phone numbers without country codes -const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/; - // Phases // Show controls to configure server details const PHASE_SERVER_DETAILS = 0; @@ -151,13 +148,6 @@ export default class LoginComponent extends React.Component { this._initLoginLogic(newProps.serverConfig.hsUrl, newProps.serverConfig.isUrl); } - onPasswordLoginError = errorText => { - this.setState({ - errorText, - loginIncorrect: Boolean(errorText), - }); - }; - isBusy = () => this.state.busy || this.props.busy; onPasswordLogin = async (username, phoneCountry, phoneNumber, password) => { @@ -330,21 +320,6 @@ export default class LoginComponent extends React.Component { }); }; - onPhoneNumberBlur = phoneNumber => { - // Validate the phone number entered - if (!PHONE_NUMBER_REGEX.test(phoneNumber)) { - this.setState({ - errorText: _t('The phone number entered looks invalid'), - canTryLogin: false, - }); - } else { - this.setState({ - errorText: null, - canTryLogin: true, - }); - } - }; - onRegisterClick = ev => { ev.preventDefault(); ev.stopPropagation(); @@ -590,7 +565,6 @@ export default class LoginComponent extends React.Component { return ( this.setState({}, resolve)); + + if (this.allFieldsValid()) { + return true; + } + + const invalidField = this.findFirstInvalidField(fieldIDsInDisplayOrder); + + if (!invalidField) { + return true; + } + + // Focus the first invalid field and show feedback in the stricter mode + // that no longer allows empty values for required fields. + invalidField.focus(); + invalidField.validate({ allowEmpty: false, focused: true }); + return false; + } + + allFieldsValid() { + const keys = Object.keys(this.state.fieldValid); + for (let i = 0; i < keys.length; ++i) { + if (!this.state.fieldValid[keys[i]]) { + return false; + } + } + return true; + } + + findFirstInvalidField(fieldIDs) { + for (const fieldID of fieldIDs) { + if (!this.state.fieldValid[fieldID] && this[fieldID]) { + return this[fieldID]; + } + } + return null; + } + + markFieldValid(fieldID, valid) { + const { fieldValid } = this.state; + fieldValid[fieldID] = valid; + this.setState({ + fieldValid, + }); + } + + validateUsernameRules = withValidation({ + rules: [ + { + key: "required", + test({ value, allowEmpty }) { + return allowEmpty || !!value; + }, + invalid: () => _t("Enter username"), + }, + ], + }); + + onUsernameValidate = async (fieldState) => { + const result = await this.validateUsernameRules(fieldState); + this.markFieldValid(PasswordLogin.LOGIN_FIELD_MXID, result.valid); + return result; + }; + + validateEmailRules = withValidation({ + rules: [ + { + key: "required", + test({ value, allowEmpty }) { + return allowEmpty || !!value; + }, + invalid: () => _t("Enter email address"), + }, { + key: "email", + test: ({ value }) => !value || Email.looksValid(value), + invalid: () => _t("Doesn't look like a valid email address"), + }, + ], + }); + + onEmailValidate = async (fieldState) => { + const result = await this.validateEmailRules(fieldState); + this.markFieldValid(PasswordLogin.LOGIN_FIELD_EMAIL, result.valid); + return result; + }; + + validatePhoneNumberRules = withValidation({ + rules: [ + { + key: "required", + test({ value, allowEmpty }) { + return allowEmpty || !!value; + }, + invalid: () => _t("Enter phone number"), + }, { + key: "number", + test: ({ value }) => !value || PHONE_NUMBER_REGEX.test(value), + invalid: () => _t("Doesn't look like a valid phone number"), + }, + ], + }); + + onPhoneNumberValidate = async (fieldState) => { + const result = await this.validatePhoneNumberRules(fieldState); + this.markFieldValid(PasswordLogin.LOGIN_FIELD_PHONE, result.valid); + return result; + }; + + validatePasswordRules = withValidation({ + rules: [ + { + key: "required", + test({ value, allowEmpty }) { + return allowEmpty || !!value; + }, + invalid: () => _t("Enter password"), + }, + ], + }); + + onPasswordValidate = async (fieldState) => { + const result = await this.validatePasswordRules(fieldState); + this.markFieldValid(PasswordLogin.LOGIN_FIELD_PASSWORD, result.valid); + return result; + } + renderLoginField(loginType, autoFocus) { const Field = sdk.getComponent('elements.Field'); @@ -226,6 +369,8 @@ export default class PasswordLogin extends React.Component { onBlur={this.onUsernameBlur} disabled={this.props.disableSubmit} autoFocus={autoFocus} + onValidate={this.onEmailValidate} + ref={field => this[PasswordLogin.LOGIN_FIELD_EMAIL] = field} />; case PasswordLogin.LOGIN_FIELD_MXID: classes.error = this.props.loginIncorrect && !this.state.username; @@ -241,6 +386,8 @@ export default class PasswordLogin extends React.Component { onBlur={this.onUsernameBlur} disabled={this.props.disableSubmit} autoFocus={autoFocus} + onValidate={this.onUsernameValidate} + ref={field => this[PasswordLogin.LOGIN_FIELD_MXID] = field} />; case PasswordLogin.LOGIN_FIELD_PHONE: { const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown'); @@ -266,6 +413,8 @@ export default class PasswordLogin extends React.Component { onBlur={this.onPhoneNumberBlur} disabled={this.props.disableSubmit} autoFocus={autoFocus} + onValidate={this.onPhoneNumberValidate} + ref={field => this[PasswordLogin.LOGIN_FIELD_PHONE] = field} />; } } @@ -363,6 +512,8 @@ export default class PasswordLogin extends React.Component { onChange={this.onPasswordChanged} disabled={this.props.disableSubmit} autoFocus={autoFocusPassword} + onValidate={this.onPasswordValidate} + ref={field => this[PasswordLogin.LOGIN_FIELD_PASSWORD] = field} /> {forgotPasswordJsx} { !this.props.busy && enable unsafe scripts.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.",