diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 2cb2a2a1a9c..4c65fac9834 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -29,8 +29,8 @@ import ServerPicker from "../../views/elements/ServerPicker"; import PassphraseField from '../../views/auth/PassphraseField'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm'; - -import { IValidationResult } from "../../views/elements/Validation"; +import withValidation, { IValidationResult } from "../../views/elements/Validation"; +import * as Email from "../../../email"; import InlineSpinner from '../../views/elements/InlineSpinner'; import { logger } from "matrix-js-sdk/src/logger"; @@ -68,6 +68,7 @@ interface IState { serverErrorIsFatal: boolean; serverDeadError: string; + emailFieldValid: boolean; passwordFieldValid: boolean; currentHttpRequest?: Promise; } @@ -90,6 +91,7 @@ export default class ForgotPassword extends React.Component { serverIsAlive: true, serverErrorIsFatal: false, serverDeadError: "", + emailFieldValid: false, passwordFieldValid: false, }; @@ -169,10 +171,13 @@ export default class ForgotPassword extends React.Component { // refresh the server errors, just in case the server came back online await this.handleHttpRequest(this.checkServerLiveliness(this.props.serverConfig)); + await this['email_field'].validate({ allowEmpty: false }); await this['password_field'].validate({ allowEmpty: false }); if (!this.state.email) { this.showErrorDialog(_t('The email address linked to your account must be entered.')); + } else if (!this.state.emailFieldValid) { + this.showErrorDialog(_t("The email address doesn't appear to be valid.")); } else if (!this.state.password || !this.state.password2) { this.showErrorDialog(_t('A new password must be entered.')); } else if (!this.state.passwordFieldValid) { @@ -222,6 +227,32 @@ export default class ForgotPassword extends React.Component { }); } + private 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"), + }, + ], + }); + + private onEmailValidate = async (fieldState) => { + const result = await this.validateEmailRules(fieldState); + + this.setState({ + emailFieldValid: result.valid, + }); + + return result; + }; + private onPasswordValidate(result: IValidationResult) { this.setState({ passwordFieldValid: result.valid, @@ -277,7 +308,9 @@ export default class ForgotPassword extends React.Component { label={_t('Email')} value={this.state.email} onChange={this.onInputChanged.bind(this, "email")} + ref={field => this['email_field'] = field} autoFocus + onValidate={this.onEmailValidate} onFocus={() => CountlyAnalytics.instance.track("onboarding_forgot_password_email_focus")} onBlur={() => CountlyAnalytics.instance.track("onboarding_forgot_password_email_blur")} /> diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 24ac3d162d8..ce2177ba13a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3015,6 +3015,7 @@ "Skip verification for now": "Skip verification for now", "Failed to send email": "Failed to send email", "The email address linked to your account must be entered.": "The email address linked to your account must be entered.", + "The email address doesn't appear to be valid.": "The email address doesn't appear to be valid.", "A new password must be entered.": "A new password must be entered.", "Please choose a strong password": "Please choose a strong password", "New passwords must match each other.": "New passwords must match each other.",