DEV: replace UsernameValidation mixin with helper in InvitesShowController and CreateAccount (#31330)

Follows up: https://github.com/discourse/discourse/pull/31107

This PR replaces the use of the UsernameValidation mixin with the helper
class for the InvitesShowController and CreateAccount modal component,
and deletes the mixin.
This commit is contained in:
Kelv
2025-02-17 07:52:30 +08:00
committed by GitHub
parent 049bbf6883
commit 566c772980
5 changed files with 36 additions and 145 deletions

View File

@@ -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
);
}
}

View File

@@ -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;
}

View File

@@ -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 &&

View File

@@ -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"),
})
);
}
},
});

View File

@@ -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,