diff --git a/app/assets/javascripts/discourse/app/components/modal/create-account.js b/app/assets/javascripts/discourse/app/components/modal/create-account.js index 698a12716d5..9470d63ee32 100644 --- a/app/assets/javascripts/discourse/app/components/modal/create-account.js +++ b/app/assets/javascripts/discourse/app/components/modal/create-account.js @@ -15,17 +15,16 @@ import discourseDebounce from "discourse/lib/debounce"; import discourseComputed, { bind } from "discourse/lib/decorators"; import NameValidationHelper from "discourse/lib/name-validation-helper"; import { userPath } from "discourse/lib/url"; +import UsernameValidationHelper from "discourse/lib/username-validation-helper"; import { emailValid } from "discourse/lib/utilities"; import PasswordValidation from "discourse/mixins/password-validation"; import UserFieldsValidation from "discourse/mixins/user-fields-validation"; -import UsernameValidation from "discourse/mixins/username-validation"; import { findAll } from "discourse/models/login-method"; import User from "discourse/models/user"; import { i18n } from "discourse-i18n"; export default class CreateAccount extends Component.extend( PasswordValidation, - UsernameValidation, UserFieldsValidation ) { @service site; @@ -44,15 +43,13 @@ export default class CreateAccount extends Component.extend( passwordValidationVisible = false; emailValidationVisible = false; nameValidationHelper = new NameValidationHelper(this); + usernameValidationHelper = new UsernameValidationHelper(this); @notEmpty("model.authOptions") hasAuthOptions; @setting("enable_local_logins") canCreateLocal; @setting("require_invite_code") requireInviteCode; // For NameValidation mixin @alias("model.accountName") accountName; - // For UsernameValidation mixin - @alias("model.authOptions") authOptions; - @alias("model.accountEmail") accountEmail; init() { super.init(...arguments); @@ -75,6 +72,21 @@ export default class CreateAccount extends Component.extend( this.accountUsername = event.target.value; } + @dependentKeyCompat + get accountEmail() { + return this.model?.accountEmail; + } + + @dependentKeyCompat + get authOptions() { + return this.model?.authOptions; + } + + @dependentKeyCompat + get usernameValidation() { + return this.usernameValidationHelper.usernameValidation; + } + get nameTitle() { return this.nameValidationHelper.nameTitle; } @@ -192,11 +204,10 @@ export default class CreateAccount extends Component.extend( ); } - @discourseComputed("usernameValidation.reason") - showUsernameInstructions(usernameValidationReason) { + get showUsernameInstructions() { return ( this.siteSettings.show_signup_form_username_instructions && - !usernameValidationReason + !this.usernameValidation.reason ); } @@ -366,7 +377,11 @@ export default class CreateAccount extends Component.extend( // If email is valid and username has not been entered yet, // or email and username were filled automatically by 3rd party auth, // then look for a registered username that matches the email. - discourseDebounce(this, this.fetchExistingUsername, 500); + discourseDebounce( + this, + this.usernameValidationHelper.fetchExistingUsername, + 500 + ); } } diff --git a/app/assets/javascripts/discourse/app/controllers/invites-show.js b/app/assets/javascripts/discourse/app/controllers/invites-show.js index 73a00c0f63c..e65b5ce6649 100644 --- a/app/assets/javascripts/discourse/app/controllers/invites-show.js +++ b/app/assets/javascripts/discourse/app/controllers/invites-show.js @@ -10,22 +10,22 @@ import discourseComputed from "discourse/lib/decorators"; import getUrl from "discourse/lib/get-url"; import NameValidationHelper from "discourse/lib/name-validation-helper"; import DiscourseURL from "discourse/lib/url"; +import UsernameValidationHelper from "discourse/lib/username-validation-helper"; import { emailValid } from "discourse/lib/utilities"; import PasswordValidation from "discourse/mixins/password-validation"; import UserFieldsValidation from "discourse/mixins/user-fields-validation"; -import UsernameValidation from "discourse/mixins/username-validation"; import { findAll as findLoginMethods } from "discourse/models/login-method"; import { i18n } from "discourse-i18n"; export default class InvitesShowController extends Controller.extend( PasswordValidation, - UsernameValidation, UserFieldsValidation ) { @tracked accountUsername; @tracked isDeveloper; queryParams = ["t"]; nameValidationHelper = new NameValidationHelper(this); + usernameValidationHelper = new UsernameValidationHelper(this); successMessage = null; @readOnly("model.is_invite_link") isInviteLink; @readOnly("model.invited_by") invitedBy; @@ -50,6 +50,11 @@ export default class InvitesShowController extends Controller.extend( this.accountUsername = event.target.value; } + @dependentKeyCompat + get usernameValidation() { + return this.usernameValidationHelper.usernameValidation; + } + get nameTitle() { return this.nameValidationHelper.nameTitle; } diff --git a/app/assets/javascripts/discourse/app/lib/username-validation-helper.js b/app/assets/javascripts/discourse/app/lib/username-validation-helper.js index 9ee9fd0da2e..47a86b7657f 100644 --- a/app/assets/javascripts/discourse/app/lib/username-validation-helper.js +++ b/app/assets/javascripts/discourse/app/lib/username-validation-helper.js @@ -27,7 +27,7 @@ export default class UsernameValidationHelper { } async fetchExistingUsername() { - const result = await User.checkUsername(null, this.owner.accountEmail); + const result = await User.checkUsername(null, this.owner?.accountEmail); if ( result.suggestion && diff --git a/app/assets/javascripts/discourse/app/mixins/username-validation.js b/app/assets/javascripts/discourse/app/mixins/username-validation.js deleted file mode 100644 index 21d12770d7c..00000000000 --- a/app/assets/javascripts/discourse/app/mixins/username-validation.js +++ /dev/null @@ -1,131 +0,0 @@ -import EmberObject, { computed } from "@ember/object"; -import Mixin from "@ember/object/mixin"; -import { isEmpty } from "@ember/utils"; -import { setting } from "discourse/lib/computed"; -import discourseDebounce from "discourse/lib/debounce"; -import User from "discourse/models/user"; -import { i18n } from "discourse-i18n"; - -function failedResult(attrs) { - return EmberObject.create({ - shouldCheck: false, - failed: true, - ok: false, - element: document.querySelector("#new-account-username"), - ...attrs, - }); -} - -function validResult(attrs) { - return EmberObject.create({ ok: true, ...attrs }); -} - -export default Mixin.create({ - checkedUsername: null, - usernameValidationResult: null, - uniqueUsernameValidation: null, - maxUsernameLength: setting("max_username_length"), - minUsernameLength: setting("min_username_length"), - - async fetchExistingUsername() { - const result = await User.checkUsername(null, this.accountEmail); - - if ( - result.suggestion && - (isEmpty(this.accountUsername) || - this.accountUsername === this.get("authOptions.username")) - ) { - this.setProperties({ - accountUsername: result.suggestion, - prefilledUsername: result.suggestion, - }); - } - }, - - usernameValidation: computed( - "usernameValidationResult", - "accountUsername", - "forceValidationReason", - function () { - if ( - this.usernameValidationResult && - this.checkedUsername === this.accountUsername - ) { - return this.usernameValidationResult; - } - - const result = this.basicUsernameValidation(this.accountUsername); - - if (result.shouldCheck) { - discourseDebounce(this, this.checkUsernameAvailability, 500); - } - - return result; - } - ), - - basicUsernameValidation(username) { - if (username && username === this.prefilledUsername) { - return validResult({ reason: i18n("user.username.prefilled") }); - } - - if (isEmpty(username)) { - return failedResult({ - message: i18n("user.username.required"), - reason: this.forceValidationReason - ? i18n("user.username.required") - : null, - }); - } - - if (username.length < this.siteSettings.min_username_length) { - return failedResult({ reason: i18n("user.username.too_short") }); - } - - if (username.length > this.maxUsernameLength) { - return failedResult({ reason: i18n("user.username.too_long") }); - } - - return failedResult({ - shouldCheck: true, - reason: i18n("user.username.checking"), - }); - }, - - async checkUsernameAvailability() { - const result = await User.checkUsername( - this.accountUsername, - this.accountEmail - ); - - if (this.isDestroying || this.isDestroyed) { - return; - } - - this.set("checkedUsername", this.accountUsername); - this.isDeveloper = !!result.is_developer; - - if (result.available) { - this.set( - "usernameValidationResult", - validResult({ reason: i18n("user.username.available") }) - ); - } else if (result.suggestion) { - this.set( - "usernameValidationResult", - failedResult({ - reason: i18n("user.username.not_available", result), - }) - ); - } else { - this.set( - "usernameValidationResult", - failedResult({ - reason: result.errors - ? result.errors.join(" ") - : i18n("user.username.not_available_no_suggestion"), - }) - ); - } - }, -}); diff --git a/app/assets/javascripts/discourse/tests/unit/components/create-account-test.js b/app/assets/javascripts/discourse/tests/unit/components/create-account-test.js index 7de9f4ce77a..bb80e023108 100644 --- a/app/assets/javascripts/discourse/tests/unit/components/create-account-test.js +++ b/app/assets/javascripts/discourse/tests/unit/components/create-account-test.js @@ -13,7 +13,8 @@ module("Unit | Component | create-account", function (hooks) { .factoryFor("component:modal/create-account") .create({ model: { accountUsername: username } }); - const validation = component.basicUsernameValidation(username); + const validation = + component.usernameValidationHelper.basicUsernameValidation(username); assert.true(validation.failed, `username should be invalid: ${username}`); assert.strictEqual( validation.reason, @@ -34,7 +35,8 @@ module("Unit | Component | create-account", function (hooks) { .create({ model: { accountUsername: "porkchops" } }); component.set("prefilledUsername", "porkchops"); - const validation = component.basicUsernameValidation("porkchops"); + const validation = + component.usernameValidationHelper.basicUsernameValidation("porkchops"); assert.true(validation.ok, "Prefilled username is valid"); assert.strictEqual( validation.reason,