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")}}