diff --git a/app/assets/javascripts/discourse/app/controllers/create-account.js b/app/assets/javascripts/discourse/app/controllers/create-account.js index 198dbe92a8c..adbebd4227c 100644 --- a/app/assets/javascripts/discourse/app/controllers/create-account.js +++ b/app/assets/javascripts/discourse/app/controllers/create-account.js @@ -5,7 +5,7 @@ import discourseComputed, { on, } from "discourse-common/utils/decorators"; import { A } from "@ember/array"; -import EmberObject from "@ember/object"; +import EmberObject, { action } from "@ember/object"; import I18n from "I18n"; import ModalFunctionality from "discourse/mixins/modal-functionality"; import NameValidation from "discourse/mixins/name-validation"; @@ -17,6 +17,7 @@ import UsernameValidation from "discourse/mixins/username-validation"; import { ajax } from "discourse/lib/ajax"; import { emailValid } from "discourse/lib/utilities"; import { findAll } from "discourse/models/login-method"; +import discourseDebounce from "discourse-common/lib/debounce"; import getURL from "discourse-common/lib/get-url"; import { isEmpty } from "@ember/utils"; import { notEmpty } from "@ember/object/computed"; @@ -58,6 +59,8 @@ export default Controller.extend( accountEmail: "", accountUsername: "", accountPassword: "", + serverAccountEmail: null, + serverEmailValidation: null, authOptions: null, complete: false, formSubmitted: false, @@ -130,13 +133,27 @@ export default Controller.extend( }, // Check the email address - @discourseComputed("accountEmail", "rejectedEmails.[]") - emailValidation(email, rejectedEmails) { + @discourseComputed( + "serverAccountEmail", + "serverEmailValidation", + "accountEmail", + "rejectedEmails.[]" + ) + emailValidation( + serverAccountEmail, + serverEmailValidation, + email, + rejectedEmails + ) { const failedAttrs = { failed: true, element: document.querySelector("#new-account-email"), }; + if (serverAccountEmail === email && serverEmailValidation) { + return serverEmailValidation; + } + // If blank, fail without a reason if (isEmpty(email)) { return EmberObject.create( @@ -146,7 +163,7 @@ export default Controller.extend( ); } - if (rejectedEmails.includes(email)) { + if (rejectedEmails.includes(email) || !emailValid(email)) { return EmberObject.create( Object.assign(failedAttrs, { reason: I18n.t("user.email.invalid"), @@ -168,18 +185,48 @@ export default Controller.extend( }); } - if (emailValid(email)) { - return EmberObject.create({ - ok: true, - reason: I18n.t("user.email.ok"), - }); + return EmberObject.create({ + ok: true, + reason: I18n.t("user.email.ok"), + }); + }, + + @action + checkEmailAvailability() { + if ( + !this.emailValidation.ok || + this.serverAccountEmail === this.accountEmail + ) { + return; } - return EmberObject.create( - Object.assign(failedAttrs, { - reason: I18n.t("user.email.invalid"), + return User.checkEmail(this.accountEmail) + .then((result) => { + if (result.failed) { + this.setProperties({ + serverAccountEmail: this.accountEmail, + serverEmailValidation: EmberObject.create({ + failed: true, + element: document.querySelector("#new-account-email"), + reason: result.errors[0], + }), + }); + } else { + this.setProperties({ + serverAccountEmail: this.accountEmail, + serverEmailValidation: EmberObject.create({ + ok: true, + reason: I18n.t("user.email.ok"), + }), + }); + } }) - ); + .catch(() => { + this.setProperties({ + serverAccountEmail: null, + serverEmailValidation: null, + }); + }); }, @discourseComputed( @@ -220,7 +267,7 @@ export default Controller.extend( // If email is valid and username has not been entered yet, // or email and username were filled automatically by 3rd parth auth, // then look for a registered username that matches the email. - this.fetchExistingUsername(); + discourseDebounce(this, this.fetchExistingUsername, 500); } }, diff --git a/app/assets/javascripts/discourse/app/mixins/username-validation.js b/app/assets/javascripts/discourse/app/mixins/username-validation.js index 54d20cb3cd1..b8f14bc818d 100644 --- a/app/assets/javascripts/discourse/app/mixins/username-validation.js +++ b/app/assets/javascripts/discourse/app/mixins/username-validation.js @@ -29,24 +29,18 @@ export default Mixin.create({ minUsernameLength: setting("min_username_length"), fetchExistingUsername() { - discourseDebounce( - this, - function () { - User.checkUsername(null, this.accountEmail).then((result) => { - if ( - result.suggestion && - (isEmpty(this.accountUsername) || - this.accountUsername === this.get("authOptions.username")) - ) { - this.setProperties({ - accountUsername: result.suggestion, - prefilledUsername: result.suggestion, - }); - } + User.checkUsername(null, this.accountEmail).then((result) => { + if ( + result.suggestion && + (isEmpty(this.accountUsername) || + this.accountUsername === this.get("authOptions.username")) + ) { + this.setProperties({ + accountUsername: result.suggestion, + prefilledUsername: result.suggestion, }); - }, - 500 - ); + } + }); }, @observes("accountUsername") @@ -55,7 +49,7 @@ export default Mixin.create({ let result = this.basicUsernameValidation(accountUsername); if (result.shouldCheck) { - this.checkUsernameAvailability(); + discourseDebounce(this, this.checkUsernameAvailability, 500); } this.set("usernameValidation", result); }, @@ -84,43 +78,37 @@ export default Mixin.create({ }, checkUsernameAvailability() { - discourseDebounce( - this, - function () { - return User.checkUsername(this.accountUsername, this.accountEmail).then( - (result) => { - this.set("isDeveloper", false); - if (result.available) { - if (result.is_developer) { - this.set("isDeveloper", true); - } - return this.set( - "usernameValidation", - validResult({ reason: I18n.t("user.username.available") }) - ); - } else { - if (result.suggestion) { - return this.set( - "usernameValidation", - failedResult({ - reason: I18n.t("user.username.not_available", result), - }) - ); - } else { - return this.set( - "usernameValidation", - failedResult({ - reason: result.errors - ? result.errors.join(" ") - : I18n.t("user.username.not_available_no_suggestion"), - }) - ); - } - } + return User.checkUsername(this.accountUsername, this.accountEmail).then( + (result) => { + this.set("isDeveloper", false); + if (result.available) { + if (result.is_developer) { + this.set("isDeveloper", true); } - ); - }, - 500 + return this.set( + "usernameValidation", + validResult({ reason: I18n.t("user.username.available") }) + ); + } else { + if (result.suggestion) { + return this.set( + "usernameValidation", + failedResult({ + reason: I18n.t("user.username.not_available", result), + }) + ); + } else { + return this.set( + "usernameValidation", + failedResult({ + reason: result.errors + ? result.errors.join(" ") + : I18n.t("user.username.not_available_no_suggestion"), + }) + ); + } + } + } ); }, }); diff --git a/app/assets/javascripts/discourse/app/models/user.js b/app/assets/javascripts/discourse/app/models/user.js index 5f7892bee53..f0a0541bcea 100644 --- a/app/assets/javascripts/discourse/app/models/user.js +++ b/app/assets/javascripts/discourse/app/models/user.js @@ -1044,6 +1044,10 @@ User.reopenClass(Singleton, { }); }, + checkEmail(email) { + return ajax(userPath("check_email"), { data: { email } }); + }, + groupStats(stats) { const responses = UserActionStat.create({ count: 0, diff --git a/app/assets/javascripts/discourse/app/templates/modal/create-account.hbs b/app/assets/javascripts/discourse/app/templates/modal/create-account.hbs index f2b71839b36..fc5ccc83e64 100644 --- a/app/assets/javascripts/discourse/app/templates/modal/create-account.hbs +++ b/app/assets/javascripts/discourse/app/templates/modal/create-account.hbs @@ -25,7 +25,7 @@ {{#if emailValidated}} {{accountEmail}} {{else}} - {{input type="email" value=accountEmail id="new-account-email" name="email" class=(value-entered accountEmail) autofocus="autofocus"}} + {{input type="email" value=accountEmail id="new-account-email" name="email" class=(value-entered accountEmail) autofocus="autofocus" focusOut=(action "checkEmailAvailability")}}