DEV: refactor username validation mixin to helper class (#31107)

This PR refactors the use of the UsernameValidation mixin to a helper
class for the SignupController component. We'll extend this to the
CreateAccount modal and InvitesShowController in follow-up PRs.
This commit is contained in:
Kelv 2025-02-03 22:27:45 +08:00 committed by GitHub
parent c8ccf79545
commit 80fdb6f2e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 128 additions and 3 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 SignupPageController extends Controller.extend(
PasswordValidation,
UsernameValidation,
UserFieldsValidation
) {
@service site;
@ -44,6 +43,7 @@ export default class SignupPageController extends Controller.extend(
passwordValidationVisible = false;
emailValidationVisible = false;
nameValidationHelper = new NameValidationHelper(this);
usernameValidationHelper = new UsernameValidationHelper(this);
@notEmpty("authOptions") hasAuthOptions;
@setting("enable_local_logins") canCreateLocal;
@ -59,6 +59,11 @@ export default class SignupPageController extends Controller.extend(
this.fetchConfirmationValue();
}
@dependentKeyCompat
get usernameValidation() {
return this.usernameValidationHelper.usernameValidation;
}
get nameTitle() {
return this.nameValidationHelper.nameTitle;
}
@ -356,7 +361,11 @@ export default class SignupPageController extends Controller.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

@ -0,0 +1,116 @@
import { tracked } from "@glimmer/tracking";
import { isEmpty } from "@ember/utils";
import discourseDebounce from "discourse/lib/debounce";
import User from "discourse/models/user";
import { i18n } from "discourse-i18n";
function failedResult(attrs) {
return {
shouldCheck: false,
failed: true,
ok: false,
element: document.querySelector("#new-account-username"),
...attrs,
};
}
function validResult(attrs) {
return { ok: true, ...attrs };
}
export default class UsernameValidationHelper {
@tracked usernameValidationResult;
checkedUsername = null;
constructor(owner) {
this.owner = owner;
}
async fetchExistingUsername() {
const result = await User.checkUsername(null, this.owner.accountEmail);
if (
result.suggestion &&
(isEmpty(this.owner.accountUsername) ||
this.owner.accountUsername === this.owner.get("authOptions.username"))
) {
this.owner.accountUsername = result.suggestion;
this.owner.prefilledUsername = result.suggestion;
}
}
get usernameValidation() {
if (
this.usernameValidationResult &&
this.checkedUsername === this.owner.accountUsername
) {
return this.usernameValidationResult;
}
const result = this.basicUsernameValidation(this.owner.accountUsername);
if (result.shouldCheck) {
discourseDebounce(this, this.checkUsernameAvailability, 500);
}
return result;
}
basicUsernameValidation(username) {
if (username && username === this.owner.prefilledUsername) {
return validResult({ reason: i18n("user.username.prefilled") });
}
if (isEmpty(username)) {
return failedResult({
message: i18n("user.username.required"),
reason: this.owner.forceValidationReason
? i18n("user.username.required")
: null,
});
}
if (username.length < this.owner.siteSettings.min_username_length) {
return failedResult({ reason: i18n("user.username.too_short") });
}
if (username.length > this.owner.siteSettings.max_username_length) {
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.owner.accountUsername,
this.owner.accountEmail
);
if (this.owner.isDestroying || this.owner.isDestroyed) {
return;
}
this.checkedUsername = this.owner.accountUsername;
this.owner.isDeveloper = !!result.is_developer;
if (result.available) {
this.usernameValidationResult = validResult({
reason: i18n("user.username.available"),
});
} else if (result.suggestion) {
this.usernameValidationResult = failedResult({
reason: i18n("user.username.not_available", result),
});
} else {
this.usernameValidationResult = failedResult({
reason: result.errors
? result.errors.join(" ")
: i18n("user.username.not_available_no_suggestion"),
});
}
}
}