DEV: Migrate `create-account` to the new modal API (#24098)
plugin/theme-breaking changes: 1. `controller:create-account` is gone (use `component:modal/create-account` in modifyClass, **if** absolutely necessary) 2. `create-account-body` css class is gone (target `.d-modal.create-account` or any of the inner classes: `.modal-outer-container`, `.modal-middle-container`, `.modal-inner-container`, or `.modal-body`)
This commit is contained in:
parent
0a4b1b655d
commit
351cbab1a8
|
@ -1,91 +0,0 @@
|
|||
import Component from "@ember/component";
|
||||
import cookie from "discourse/lib/cookie";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["create-account-body"],
|
||||
|
||||
// used for animating the label inside of inputs
|
||||
userInputFocus(event) {
|
||||
const userField = event.target.parentElement.parentElement;
|
||||
if (!userField.classList.contains("value-entered")) {
|
||||
userField.classList.toggle("value-entered");
|
||||
}
|
||||
},
|
||||
|
||||
// used for animating the label inside of inputs
|
||||
userInputFocusOut(event) {
|
||||
const userField = event.target.parentElement.parentElement;
|
||||
if (
|
||||
event.target.value.length === 0 &&
|
||||
userField.classList.contains("value-entered")
|
||||
) {
|
||||
userField.classList.toggle("value-entered");
|
||||
}
|
||||
},
|
||||
|
||||
@bind
|
||||
actionOnEnter(event) {
|
||||
if (!this.disabled && event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.action();
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
@bind
|
||||
selectKitFocus(event) {
|
||||
const target = document.getElementById(event.target.getAttribute("for"));
|
||||
if (target?.classList.contains("select-kit")) {
|
||||
event.preventDefault();
|
||||
target.querySelector(".select-kit-header").click();
|
||||
}
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
if (cookie("email")) {
|
||||
this.set("email", cookie("email"));
|
||||
}
|
||||
|
||||
let userTextFields = document.getElementsByClassName("user-fields")[0];
|
||||
|
||||
if (userTextFields) {
|
||||
userTextFields =
|
||||
userTextFields.getElementsByClassName("ember-text-field");
|
||||
}
|
||||
|
||||
if (userTextFields) {
|
||||
for (let element of userTextFields) {
|
||||
element.addEventListener("focus", this.userInputFocus);
|
||||
element.addEventListener("focusout", this.userInputFocusOut);
|
||||
}
|
||||
}
|
||||
|
||||
this.element.addEventListener("keydown", this.actionOnEnter);
|
||||
this.element.addEventListener("click", this.selectKitFocus);
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.element.removeEventListener("keydown", this.actionOnEnter);
|
||||
this.element.removeEventListener("click", this.selectKitFocus);
|
||||
|
||||
let userTextFields = document.getElementsByClassName("user-fields")[0];
|
||||
|
||||
if (userTextFields) {
|
||||
userTextFields =
|
||||
userTextFields.getElementsByClassName("ember-text-field");
|
||||
}
|
||||
|
||||
if (userTextFields) {
|
||||
for (let element of userTextFields) {
|
||||
element.removeEventListener("focus", this.userInputFocus);
|
||||
element.removeEventListener("focusout", this.userInputFocusOut);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
|
@ -0,0 +1,291 @@
|
|||
<DModal
|
||||
{{on "keydown" this.actionOnEnter}}
|
||||
{{on "click" this.selectKitFocus}}
|
||||
@closeModal={{@closeModal}}
|
||||
@bodyClass={{this.modalBodyClasses}}
|
||||
@flash={{this.flash}}
|
||||
@flashType="error"
|
||||
aria-labelledby="create-account-title"
|
||||
class="create-account"
|
||||
>
|
||||
<:body>
|
||||
<PluginOutlet
|
||||
@name="create-account-before-modal-body"
|
||||
@connectorTagName="div"
|
||||
/>
|
||||
|
||||
<div
|
||||
class={{concat-class
|
||||
"create-account-form"
|
||||
this.model.authOptions.auth_provider
|
||||
}}
|
||||
>
|
||||
<div class="login-welcome-header" id="create-account-title">
|
||||
<h1 class="login-title">{{i18n "create_account.header_title"}}</h1>
|
||||
<img src={{this.wavingHandURL}} alt="" class="waving-hand" />
|
||||
<p class="login-subheader">{{i18n "create_account.subheader_title"}}</p>
|
||||
<PluginOutlet
|
||||
@name="create-account-header-bottom"
|
||||
@outletArgs={{hash showLogin=(route-action "showLogin")}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{{#if this.showCreateForm}}
|
||||
<div class="login-form">
|
||||
<form>
|
||||
{{#if this.associateHtml}}
|
||||
<div class="input-group create-account-associate-link">
|
||||
<span>{{html-safe this.associateHtml}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="input-group create-account-email">
|
||||
<Input
|
||||
{{on "focusout" (action "checkEmailAvailability")}}
|
||||
@type="email"
|
||||
@value={{this.model.accountEmail}}
|
||||
disabled={{this.emailDisabled}}
|
||||
autofocus="autofocus"
|
||||
aria-describedby="account-email-validation"
|
||||
aria-invalid={{this.emailValidation.failed}}
|
||||
name="email"
|
||||
id="new-account-email"
|
||||
class={{value-entered this.model.accountEmail}}
|
||||
/>
|
||||
<label class="alt-placeholder" for="new-account-email">
|
||||
{{i18n "user.email.title"}}
|
||||
{{~#if this.userFields~}}
|
||||
<span class="required">*</span>
|
||||
{{/if}}
|
||||
</label>
|
||||
<InputTip
|
||||
@validation={{this.emailValidation}}
|
||||
@id="account-email-validation"
|
||||
/>
|
||||
<span class="more-info">{{i18n "user.email.instructions"}}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group create-account__username">
|
||||
<Input
|
||||
@value={{this.model.accountUsername}}
|
||||
disabled={{this.usernameDisabled}}
|
||||
maxlength={{this.maxUsernameLength}}
|
||||
aria-describedby="username-validation"
|
||||
aria-invalid={{this.usernameValidation.failed}}
|
||||
autocomplete="off"
|
||||
name="username"
|
||||
id="new-account-username"
|
||||
class={{value-entered this.model.accountUsername}}
|
||||
/>
|
||||
<label class="alt-placeholder" for="new-account-username">
|
||||
{{i18n "user.username.title"}}
|
||||
{{~#if this.userFields~}}
|
||||
<span class="required">*</span>
|
||||
{{/if}}
|
||||
</label>
|
||||
|
||||
<InputTip
|
||||
@validation={{this.usernameValidation}}
|
||||
@id="username-validation"
|
||||
/>
|
||||
<span class="more-info">
|
||||
{{i18n "user.username.instructions"}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group create-account__fullname">
|
||||
{{#if this.fullnameRequired}}
|
||||
<TextField
|
||||
@disabled={{this.nameDisabled}}
|
||||
@value={{this.model.accountName}}
|
||||
@id="new-account-name"
|
||||
@class={{value-entered this.model.accountName}}
|
||||
aria-describedby="fullname-validation"
|
||||
aria-invalid={{this.nameValidation.failed}}
|
||||
/>
|
||||
<label class="alt-placeholder" for="new-account-name">
|
||||
{{i18n "user.name.title"}}
|
||||
{{#if this.siteSettings.full_name_required}}
|
||||
{{~#if this.userFields~}}
|
||||
<span class="required">*</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</label>
|
||||
|
||||
<InputTip
|
||||
@validation={{this.nameValidation}}
|
||||
@id="fullname-validation"
|
||||
/>
|
||||
<span class="more-info">{{this.nameInstructions}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<PluginOutlet
|
||||
@name="create-account-before-password"
|
||||
@outletArgs={{hash
|
||||
accountName=this.model.accountName
|
||||
accountUsername=this.model.accountUsername
|
||||
accountPassword=this.accountPassword
|
||||
userFields=this.userFields
|
||||
authOptions=this.model.authOptions
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="input-group create-account__password">
|
||||
{{#if this.passwordRequired}}
|
||||
<PasswordField
|
||||
@value={{this.accountPassword}}
|
||||
@class={{value-entered this.accountPassword}}
|
||||
@type={{if this.maskPassword "password" "text"}}
|
||||
@autocomplete="current-password"
|
||||
@capsLockOn={{this.capsLockOn}}
|
||||
aria-describedby="password-validation"
|
||||
aria-invalid={{this.passwordValidation.failed}}
|
||||
id="new-account-password"
|
||||
/>
|
||||
<label class="alt-placeholder" for="new-account-password">
|
||||
{{i18n "user.password.title"}}
|
||||
{{~#if this.userFields~}}
|
||||
<span class="required">*</span>
|
||||
{{/if}}
|
||||
</label>
|
||||
|
||||
<div class="create-account__password-info">
|
||||
<div class="create-account__password-tip-validation">
|
||||
<InputTip
|
||||
@validation={{this.passwordValidation}}
|
||||
@id="password-validation"
|
||||
/>
|
||||
<span class="more-info">{{this.passwordInstructions}}</span>
|
||||
<div
|
||||
class={{concat-class
|
||||
"caps-lock-warning"
|
||||
(unless this.capsLockOn "hidden")
|
||||
}}
|
||||
>
|
||||
{{d-icon "exclamation-triangle"}}
|
||||
{{i18n "login.caps_lock_warning"}}
|
||||
</div>
|
||||
</div>
|
||||
<TogglePasswordMask
|
||||
@maskPassword={{this.maskPassword}}
|
||||
@togglePasswordMask={{this.togglePasswordMask}}
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="password-confirmation">
|
||||
<label for="new-account-password-confirmation">
|
||||
{{i18n "user.password_confirmation.title"}}
|
||||
</label>
|
||||
<HoneypotInput
|
||||
@id="new-account-confirmation"
|
||||
@autocomplete="new-password"
|
||||
@value={{this.accountHoneypot}}
|
||||
/>
|
||||
<Input
|
||||
@value={{this.accountChallenge}}
|
||||
id="new-account-challenge"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.requireInviteCode}}
|
||||
<div class="input-group create-account__invite-code">
|
||||
<Input
|
||||
@value={{this.inviteCode}}
|
||||
id="inviteCode"
|
||||
class={{value-entered this.inviteCode}}
|
||||
/>
|
||||
<label class="alt-placeholder" for="invite-code">
|
||||
{{i18n "user.invite_code.title"}}
|
||||
</label>
|
||||
<span class="more-info">
|
||||
{{i18n "user.invite_code.instructions"}}
|
||||
</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<PluginOutlet
|
||||
@name="create-account-after-password"
|
||||
@outletArgs={{hash
|
||||
accountName=this.model.accountName
|
||||
accountUsername=this.model.accountUsername
|
||||
accountPassword=this.accountPassword
|
||||
userFields=this.userFields
|
||||
}}
|
||||
/>
|
||||
|
||||
{{#if this.userFields}}
|
||||
<div class="user-fields">
|
||||
{{#each this.userFields as |f|}}
|
||||
<div class="input-group">
|
||||
{{! adding the value-entered class here to
|
||||
be able to detect if the user field has a value
|
||||
entered }}
|
||||
<UserField
|
||||
{{on "focus" this.userInputFocus}}
|
||||
{{on "focusout" this.userInputFocusOut}}
|
||||
@field={{f.field}}
|
||||
@value={{f.value}}
|
||||
@class={{value-entered f.value}}
|
||||
@validation={{f.validation}}
|
||||
/>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<PluginOutlet
|
||||
@name="create-account-after-user-fields"
|
||||
@outletArgs={{hash
|
||||
accountName=this.model.accountName
|
||||
accountUsername=this.model.accountUsername
|
||||
accountPassword=this.accountPassword
|
||||
userFields=this.userFields
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<div class="disclaimer">
|
||||
{{html-safe this.disclaimerHtml}}
|
||||
</div>
|
||||
|
||||
<DButton
|
||||
@action={{action "createAccount"}}
|
||||
@disabled={{this.submitDisabled}}
|
||||
@label="create_account.title"
|
||||
@isLoading={{this.formSubmitted}}
|
||||
class="btn-large btn-primary"
|
||||
/>
|
||||
|
||||
{{#unless this.hasAuthOptions}}
|
||||
<DButton
|
||||
@action={{route-action "showLogin"}}
|
||||
@disabled={{this.formSubmitted}}
|
||||
@label="log_in"
|
||||
id="login-link"
|
||||
class="btn-large"
|
||||
/>
|
||||
{{/unless}}
|
||||
</div>
|
||||
|
||||
<PluginOutlet
|
||||
@name="create-account-after-modal-footer"
|
||||
@connectorTagName="div"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showExternalLoginButtons}}
|
||||
<div class="create-account-login-buttons">
|
||||
<LoginButtons @externalLogin={{action "externalLogin"}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.model.skipConfirmation}}
|
||||
{{loading-spinner size="large"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</:body>
|
||||
</DModal>
|
|
@ -0,0 +1,522 @@
|
|||
import { A } from "@ember/array";
|
||||
import Component from "@ember/component";
|
||||
import EmberObject, { action } from "@ember/object";
|
||||
import { alias, notEmpty } from "@ember/object/computed";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import $ from "jquery";
|
||||
import { Promise } from "rsvp";
|
||||
import LoginModal from "discourse/components/modal/login";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { setting } from "discourse/lib/computed";
|
||||
import cookie, { removeCookie } from "discourse/lib/cookie";
|
||||
import { userPath } from "discourse/lib/url";
|
||||
import { emailValid } from "discourse/lib/utilities";
|
||||
import { wavingHandURL } from "discourse/lib/waving-hand-url";
|
||||
import NameValidation from "discourse/mixins/name-validation";
|
||||
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 discourseDebounce from "discourse-common/lib/debounce";
|
||||
import discourseComputed, {
|
||||
bind,
|
||||
observes,
|
||||
} from "discourse-common/utils/decorators";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
export default class CreateAccount extends Component.extend(
|
||||
PasswordValidation,
|
||||
UsernameValidation,
|
||||
NameValidation,
|
||||
UserFieldsValidation
|
||||
) {
|
||||
@service modal;
|
||||
@service site;
|
||||
@service siteSettings;
|
||||
|
||||
accountChallenge = 0;
|
||||
accountHoneypot = 0;
|
||||
formSubmitted = false;
|
||||
rejectedEmails = A();
|
||||
prefilledUsername = null;
|
||||
userFields = null;
|
||||
isDeveloper = false;
|
||||
maskPassword = true;
|
||||
|
||||
@notEmpty("model.authOptions") hasAuthOptions;
|
||||
@setting("enable_local_logins") canCreateLocal;
|
||||
@setting("require_invite_code") requireInviteCode;
|
||||
|
||||
// For UsernameValidation mixin
|
||||
@alias("model.authOptions") authOptions;
|
||||
@alias("model.accountEmail") accountEmail;
|
||||
@alias("model.accountUsername") accountUsername;
|
||||
|
||||
init() {
|
||||
super.init(...arguments);
|
||||
|
||||
if (cookie("email")) {
|
||||
this.set("model.accountEmail", cookie("email"));
|
||||
}
|
||||
|
||||
this.fetchConfirmationValue();
|
||||
|
||||
if (this.model.skipConfirmation) {
|
||||
this.performAccountCreation().finally(() =>
|
||||
this.set("model.skipConfirmation", false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// used for animating the label inside of inputs
|
||||
@bind
|
||||
userInputFocus(event) {
|
||||
const userField = event.target.parentElement.parentElement;
|
||||
if (!userField.classList.contains("value-entered")) {
|
||||
userField.classList.toggle("value-entered");
|
||||
}
|
||||
}
|
||||
|
||||
// used for animating the label inside of inputs
|
||||
@bind
|
||||
userInputFocusOut(event) {
|
||||
const userField = event.target.parentElement.parentElement;
|
||||
if (
|
||||
event.target.value.length === 0 &&
|
||||
userField.classList.contains("value-entered")
|
||||
) {
|
||||
userField.classList.toggle("value-entered");
|
||||
}
|
||||
}
|
||||
|
||||
@bind
|
||||
actionOnEnter(event) {
|
||||
if (!this.submitDisabled && event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.createAccount();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@bind
|
||||
selectKitFocus(event) {
|
||||
const target = document.getElementById(event.target.getAttribute("for"));
|
||||
if (target?.classList.contains("select-kit")) {
|
||||
event.preventDefault();
|
||||
target.querySelector(".select-kit-header").click();
|
||||
}
|
||||
}
|
||||
|
||||
@discourseComputed(
|
||||
"hasAuthOptions",
|
||||
"canCreateLocal",
|
||||
"model.skipConfirmation"
|
||||
)
|
||||
showCreateForm(hasAuthOptions, canCreateLocal, skipConfirmation) {
|
||||
return (hasAuthOptions || canCreateLocal) && !skipConfirmation;
|
||||
}
|
||||
|
||||
@discourseComputed("site.desktopView", "hasAuthOptions")
|
||||
showExternalLoginButtons(desktopView, hasAuthOptions) {
|
||||
return desktopView && !hasAuthOptions;
|
||||
}
|
||||
|
||||
@discourseComputed("formSubmitted")
|
||||
submitDisabled() {
|
||||
return this.formSubmitted;
|
||||
}
|
||||
|
||||
@discourseComputed()
|
||||
wavingHandURL() {
|
||||
return wavingHandURL();
|
||||
}
|
||||
|
||||
@discourseComputed("userFields", "hasAtLeastOneLoginButton", "hasAuthOptions")
|
||||
modalBodyClasses(userFields, hasAtLeastOneLoginButton, hasAuthOptions) {
|
||||
const classes = [];
|
||||
if (userFields) {
|
||||
classes.push("has-user-fields");
|
||||
}
|
||||
if (hasAtLeastOneLoginButton && !hasAuthOptions) {
|
||||
classes.push("has-alt-auth");
|
||||
}
|
||||
if (!this.canCreateLocal) {
|
||||
classes.push("no-local-logins");
|
||||
}
|
||||
return classes.join(" ");
|
||||
}
|
||||
|
||||
@discourseComputed("model.authOptions", "model.authOptions.can_edit_username")
|
||||
usernameDisabled(authOptions, canEditUsername) {
|
||||
return authOptions && !canEditUsername;
|
||||
}
|
||||
|
||||
@discourseComputed("model.authOptions", "model.authOptions.can_edit_name")
|
||||
nameDisabled(authOptions, canEditName) {
|
||||
return authOptions && !canEditName;
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
fullnameRequired() {
|
||||
return (
|
||||
this.siteSettings.full_name_required || this.siteSettings.enable_names
|
||||
);
|
||||
}
|
||||
|
||||
@discourseComputed("model.authOptions.auth_provider")
|
||||
passwordRequired(authProvider) {
|
||||
return isEmpty(authProvider);
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
disclaimerHtml() {
|
||||
if (this.site.tos_url && this.site.privacy_policy_url) {
|
||||
return I18n.t("create_account.disclaimer", {
|
||||
tos_link: this.site.tos_url,
|
||||
privacy_link: this.site.privacy_policy_url,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check the email address
|
||||
@discourseComputed(
|
||||
"serverAccountEmail",
|
||||
"serverEmailValidation",
|
||||
"model.accountEmail",
|
||||
"rejectedEmails.[]",
|
||||
"forceValidationReason"
|
||||
)
|
||||
emailValidation(
|
||||
serverAccountEmail,
|
||||
serverEmailValidation,
|
||||
email,
|
||||
rejectedEmails,
|
||||
forceValidationReason
|
||||
) {
|
||||
const failedAttrs = {
|
||||
failed: true,
|
||||
ok: false,
|
||||
element: document.querySelector("#new-account-email"),
|
||||
};
|
||||
|
||||
if (serverAccountEmail === email && serverEmailValidation) {
|
||||
return serverEmailValidation;
|
||||
}
|
||||
|
||||
// If blank, fail without a reason
|
||||
if (isEmpty(email)) {
|
||||
return EmberObject.create(
|
||||
Object.assign(failedAttrs, {
|
||||
message: I18n.t("user.email.required"),
|
||||
reason: forceValidationReason ? I18n.t("user.email.required") : null,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (rejectedEmails.includes(email) || !emailValid(email)) {
|
||||
return EmberObject.create(
|
||||
Object.assign(failedAttrs, {
|
||||
reason: I18n.t("user.email.invalid"),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
this.get("model.authOptions.email") === email &&
|
||||
this.get("model.authOptions.email_valid")
|
||||
) {
|
||||
return EmberObject.create({
|
||||
ok: true,
|
||||
reason: I18n.t("user.email.authenticated", {
|
||||
provider: this.authProviderDisplayName(
|
||||
this.get("model.authOptions.auth_provider")
|
||||
),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return EmberObject.create({
|
||||
ok: true,
|
||||
reason: I18n.t("user.email.ok"),
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
checkEmailAvailability() {
|
||||
if (
|
||||
!this.emailValidation.ok ||
|
||||
this.serverAccountEmail === this.model.accountEmail
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return User.checkEmail(this.model.accountEmail)
|
||||
.then((result) => {
|
||||
if (this.isDestroying || this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.failed) {
|
||||
this.setProperties({
|
||||
serverAccountEmail: this.model.accountEmail,
|
||||
serverEmailValidation: EmberObject.create({
|
||||
failed: true,
|
||||
element: document.querySelector("#new-account-email"),
|
||||
reason: result.errors[0],
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
this.setProperties({
|
||||
serverAccountEmail: this.model.accountEmail,
|
||||
serverEmailValidation: EmberObject.create({
|
||||
ok: true,
|
||||
reason: I18n.t("user.email.ok"),
|
||||
}),
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.setProperties({
|
||||
serverAccountEmail: null,
|
||||
serverEmailValidation: null,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@discourseComputed(
|
||||
"model.accountEmail",
|
||||
"model.authOptions.email",
|
||||
"model.authOptions.email_valid"
|
||||
)
|
||||
emailDisabled() {
|
||||
return (
|
||||
this.get("model.authOptions.email") === this.model.accountEmail &&
|
||||
this.get("model.authOptions.email_valid")
|
||||
);
|
||||
}
|
||||
|
||||
authProviderDisplayName(providerName) {
|
||||
const matchingProvider = findAll().find((provider) => {
|
||||
return provider.name === providerName;
|
||||
});
|
||||
return matchingProvider ? matchingProvider.get("prettyName") : providerName;
|
||||
}
|
||||
|
||||
@observes("emailValidation", "model.accountEmail")
|
||||
prefillUsername() {
|
||||
if (this.prefilledUsername) {
|
||||
// If username field has been filled automatically, and email field just changed,
|
||||
// then remove the username.
|
||||
if (this.model.accountUsername === this.prefilledUsername) {
|
||||
this.set("model.accountUsername", "");
|
||||
}
|
||||
this.set("prefilledUsername", null);
|
||||
}
|
||||
if (
|
||||
this.get("emailValidation.ok") &&
|
||||
(isEmpty(this.model.accountUsername) ||
|
||||
this.get("model.authOptions.email"))
|
||||
) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Determines whether at least one login button is enabled
|
||||
@discourseComputed
|
||||
hasAtLeastOneLoginButton() {
|
||||
return findAll().length > 0;
|
||||
}
|
||||
|
||||
fetchConfirmationValue() {
|
||||
if (this._challengeDate === undefined && this._hpPromise) {
|
||||
// Request already in progress
|
||||
return this._hpPromise;
|
||||
}
|
||||
|
||||
this._hpPromise = ajax("/session/hp.json")
|
||||
.then((json) => {
|
||||
if (this.isDestroying || this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._challengeDate = new Date();
|
||||
// remove 30 seconds for jitter, make sure this works for at least
|
||||
// 30 seconds so we don't have hard loops
|
||||
this._challengeExpiry = parseInt(json.expires_in, 10) - 30;
|
||||
if (this._challengeExpiry < 30) {
|
||||
this._challengeExpiry = 30;
|
||||
}
|
||||
|
||||
this.setProperties({
|
||||
accountHoneypot: json.value,
|
||||
accountChallenge: json.challenge.split("").reverse().join(""),
|
||||
});
|
||||
})
|
||||
.finally(() => (this._hpPromise = undefined));
|
||||
|
||||
return this._hpPromise;
|
||||
}
|
||||
|
||||
performAccountCreation() {
|
||||
if (
|
||||
!this._challengeDate ||
|
||||
new Date() - this._challengeDate > 1000 * this._challengeExpiry
|
||||
) {
|
||||
return this.fetchConfirmationValue().then(() =>
|
||||
this.performAccountCreation()
|
||||
);
|
||||
}
|
||||
|
||||
const attrs = this.getProperties(
|
||||
"model.accountName",
|
||||
"model.accountEmail",
|
||||
"accountPassword",
|
||||
"model.accountUsername",
|
||||
"accountChallenge",
|
||||
"inviteCode"
|
||||
);
|
||||
|
||||
attrs["accountPasswordConfirm"] = this.accountHoneypot;
|
||||
|
||||
const userFields = this.userFields;
|
||||
const destinationUrl = this.get("model.authOptions.destination_url");
|
||||
|
||||
if (!isEmpty(destinationUrl)) {
|
||||
cookie("destination_url", destinationUrl, { path: "/" });
|
||||
}
|
||||
|
||||
// Add the userFields to the data
|
||||
if (!isEmpty(userFields)) {
|
||||
attrs.userFields = {};
|
||||
userFields.forEach(
|
||||
(f) => (attrs.userFields[f.get("field.id")] = f.get("value"))
|
||||
);
|
||||
}
|
||||
|
||||
this.set("formSubmitted", true);
|
||||
return User.createAccount(attrs).then(
|
||||
(result) => {
|
||||
if (this.isDestroying || this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.set("isDeveloper", false);
|
||||
if (result.success) {
|
||||
// invalidate honeypot
|
||||
this._challengeExpiry = 1;
|
||||
|
||||
// Trigger the browser's password manager using the hidden static login form:
|
||||
const $hidden_login_form = $("#hidden-login-form");
|
||||
$hidden_login_form
|
||||
.find("input[name=username]")
|
||||
.val(attrs.accountUsername);
|
||||
$hidden_login_form
|
||||
.find("input[name=password]")
|
||||
.val(attrs.accountPassword);
|
||||
$hidden_login_form
|
||||
.find("input[name=redirect]")
|
||||
.val(userPath("account-created"));
|
||||
$hidden_login_form.submit();
|
||||
return new Promise(() => {}); // This will never resolve, the page will reload instead
|
||||
} else {
|
||||
this.set("flash", result.message || I18n.t("create_account.failed"));
|
||||
if (result.is_developer) {
|
||||
this.set("isDeveloper", true);
|
||||
}
|
||||
if (
|
||||
result.errors &&
|
||||
result.errors.email &&
|
||||
result.errors.email.length > 0 &&
|
||||
result.values
|
||||
) {
|
||||
this.rejectedEmails.pushObject(result.values.email);
|
||||
}
|
||||
if (
|
||||
result.errors &&
|
||||
result.errors.password &&
|
||||
result.errors.password.length > 0
|
||||
) {
|
||||
this.rejectedPasswords.pushObject(attrs.accountPassword);
|
||||
}
|
||||
this.set("formSubmitted", false);
|
||||
removeCookie("destination_url");
|
||||
}
|
||||
},
|
||||
() => {
|
||||
this.set("formSubmitted", false);
|
||||
removeCookie("destination_url");
|
||||
return this.set("flash", I18n.t("create_account.failed"));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@discourseComputed(
|
||||
"model.authOptions.associate_url",
|
||||
"model.authOptions.auth_provider"
|
||||
)
|
||||
associateHtml(url, provider) {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
return I18n.t("create_account.associate", {
|
||||
associate_link: url,
|
||||
provider: I18n.t(`login.${provider}.name`),
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
togglePasswordMask() {
|
||||
this.toggleProperty("maskPassword");
|
||||
}
|
||||
|
||||
@action
|
||||
externalLogin(provider) {
|
||||
// we will automatically redirect to the external auth service
|
||||
this.modal.show(LoginModal, {
|
||||
model: {
|
||||
isExternalLogin: true,
|
||||
externalLoginMethod: provider,
|
||||
signup: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
createAccount() {
|
||||
this.set("flash", "");
|
||||
this.set("forceValidationReason", true);
|
||||
|
||||
const validation = [
|
||||
this.emailValidation,
|
||||
this.usernameValidation,
|
||||
this.nameValidation,
|
||||
this.passwordValidation,
|
||||
this.userFieldsValidation,
|
||||
].find((v) => v.failed);
|
||||
|
||||
if (validation) {
|
||||
const element = validation.element;
|
||||
if (element) {
|
||||
if (element.tagName === "DIV") {
|
||||
if (element.scrollIntoView) {
|
||||
element.scrollIntoView();
|
||||
}
|
||||
element.click();
|
||||
} else {
|
||||
element.focus();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.set("forceValidationReason", false);
|
||||
this.performAccountCreation();
|
||||
}
|
||||
}
|
|
@ -1,472 +0,0 @@
|
|||
import { A } from "@ember/array";
|
||||
import Controller from "@ember/controller";
|
||||
import EmberObject, { action } from "@ember/object";
|
||||
import { notEmpty } from "@ember/object/computed";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import $ from "jquery";
|
||||
import { Promise } from "rsvp";
|
||||
import LoginModal from "discourse/components/modal/login";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { setting } from "discourse/lib/computed";
|
||||
import cookie, { removeCookie } from "discourse/lib/cookie";
|
||||
import { userPath } from "discourse/lib/url";
|
||||
import { emailValid } from "discourse/lib/utilities";
|
||||
import { wavingHandURL } from "discourse/lib/waving-hand-url";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import NameValidation from "discourse/mixins/name-validation";
|
||||
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 discourseDebounce from "discourse-common/lib/debounce";
|
||||
import discourseComputed, {
|
||||
observes,
|
||||
on,
|
||||
} from "discourse-common/utils/decorators";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
export default Controller.extend(
|
||||
ModalFunctionality,
|
||||
PasswordValidation,
|
||||
UsernameValidation,
|
||||
NameValidation,
|
||||
UserFieldsValidation,
|
||||
{
|
||||
modal: service(),
|
||||
|
||||
complete: false,
|
||||
accountChallenge: 0,
|
||||
accountHoneypot: 0,
|
||||
formSubmitted: false,
|
||||
rejectedEmails: A(),
|
||||
prefilledUsername: null,
|
||||
userFields: null,
|
||||
isDeveloper: false,
|
||||
maskPassword: true,
|
||||
|
||||
hasAuthOptions: notEmpty("authOptions"),
|
||||
canCreateLocal: setting("enable_local_logins"),
|
||||
requireInviteCode: setting("require_invite_code"),
|
||||
|
||||
@discourseComputed("hasAuthOptions", "canCreateLocal", "skipConfirmation")
|
||||
showCreateForm(hasAuthOptions, canCreateLocal, skipConfirmation) {
|
||||
return (hasAuthOptions || canCreateLocal) && !skipConfirmation;
|
||||
},
|
||||
|
||||
@discourseComputed("formSubmitted")
|
||||
submitDisabled() {
|
||||
if (this.formSubmitted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
@discourseComputed()
|
||||
wavingHandURL: () => wavingHandURL(),
|
||||
|
||||
@discourseComputed(
|
||||
"userFields",
|
||||
"hasAtLeastOneLoginButton",
|
||||
"hasAuthOptions"
|
||||
)
|
||||
modalBodyClasses(userFields, hasAtLeastOneLoginButton, hasAuthOptions) {
|
||||
const classes = [];
|
||||
if (userFields) {
|
||||
classes.push("has-user-fields");
|
||||
}
|
||||
if (hasAtLeastOneLoginButton && !hasAuthOptions) {
|
||||
classes.push("has-alt-auth");
|
||||
}
|
||||
if (!this.canCreateLocal) {
|
||||
classes.push("no-local-logins");
|
||||
}
|
||||
return classes.join(" ");
|
||||
},
|
||||
|
||||
@discourseComputed("authOptions", "authOptions.can_edit_username")
|
||||
usernameDisabled(authOptions, canEditUsername) {
|
||||
return authOptions && !canEditUsername;
|
||||
},
|
||||
|
||||
@discourseComputed("authOptions", "authOptions.can_edit_name")
|
||||
nameDisabled(authOptions, canEditName) {
|
||||
return authOptions && !canEditName;
|
||||
},
|
||||
|
||||
@discourseComputed
|
||||
fullnameRequired() {
|
||||
return (
|
||||
this.siteSettings.full_name_required || this.siteSettings.enable_names
|
||||
);
|
||||
},
|
||||
|
||||
@discourseComputed("authOptions.auth_provider")
|
||||
passwordRequired(authProvider) {
|
||||
return isEmpty(authProvider);
|
||||
},
|
||||
|
||||
@discourseComputed
|
||||
disclaimerHtml() {
|
||||
if (this.site.tos_url && this.site.privacy_policy_url) {
|
||||
return I18n.t("create_account.disclaimer", {
|
||||
tos_link: this.site.tos_url,
|
||||
privacy_link: this.site.privacy_policy_url,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Check the email address
|
||||
@discourseComputed(
|
||||
"serverAccountEmail",
|
||||
"serverEmailValidation",
|
||||
"accountEmail",
|
||||
"rejectedEmails.[]",
|
||||
"forceValidationReason"
|
||||
)
|
||||
emailValidation(
|
||||
serverAccountEmail,
|
||||
serverEmailValidation,
|
||||
email,
|
||||
rejectedEmails,
|
||||
forceValidationReason
|
||||
) {
|
||||
const failedAttrs = {
|
||||
failed: true,
|
||||
ok: false,
|
||||
element: document.querySelector("#new-account-email"),
|
||||
};
|
||||
|
||||
if (serverAccountEmail === email && serverEmailValidation) {
|
||||
return serverEmailValidation;
|
||||
}
|
||||
|
||||
// If blank, fail without a reason
|
||||
if (isEmpty(email)) {
|
||||
return EmberObject.create(
|
||||
Object.assign(failedAttrs, {
|
||||
message: I18n.t("user.email.required"),
|
||||
reason: forceValidationReason
|
||||
? I18n.t("user.email.required")
|
||||
: null,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (rejectedEmails.includes(email) || !emailValid(email)) {
|
||||
return EmberObject.create(
|
||||
Object.assign(failedAttrs, {
|
||||
reason: I18n.t("user.email.invalid"),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
this.get("authOptions.email") === email &&
|
||||
this.get("authOptions.email_valid")
|
||||
) {
|
||||
return EmberObject.create({
|
||||
ok: true,
|
||||
reason: I18n.t("user.email.authenticated", {
|
||||
provider: this.authProviderDisplayName(
|
||||
this.get("authOptions.auth_provider")
|
||||
),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return EmberObject.create({
|
||||
ok: true,
|
||||
reason: I18n.t("user.email.ok"),
|
||||
});
|
||||
},
|
||||
|
||||
@action
|
||||
checkEmailAvailability() {
|
||||
if (
|
||||
!this.emailValidation.ok ||
|
||||
this.serverAccountEmail === this.accountEmail
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return User.checkEmail(this.accountEmail)
|
||||
.then((result) => {
|
||||
if (this.isDestroying || this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
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(
|
||||
"accountEmail",
|
||||
"authOptions.email",
|
||||
"authOptions.email_valid"
|
||||
)
|
||||
emailDisabled() {
|
||||
return (
|
||||
this.get("authOptions.email") === this.accountEmail &&
|
||||
this.get("authOptions.email_valid")
|
||||
);
|
||||
},
|
||||
|
||||
authProviderDisplayName(providerName) {
|
||||
const matchingProvider = findAll().find((provider) => {
|
||||
return provider.name === providerName;
|
||||
});
|
||||
return matchingProvider
|
||||
? matchingProvider.get("prettyName")
|
||||
: providerName;
|
||||
},
|
||||
|
||||
@observes("emailValidation", "accountEmail")
|
||||
prefillUsername() {
|
||||
if (this.prefilledUsername) {
|
||||
// If username field has been filled automatically, and email field just changed,
|
||||
// then remove the username.
|
||||
if (this.accountUsername === this.prefilledUsername) {
|
||||
this.set("accountUsername", "");
|
||||
}
|
||||
this.set("prefilledUsername", null);
|
||||
}
|
||||
if (
|
||||
this.get("emailValidation.ok") &&
|
||||
(isEmpty(this.accountUsername) || this.get("authOptions.email"))
|
||||
) {
|
||||
// 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);
|
||||
}
|
||||
},
|
||||
|
||||
// Determines whether at least one login button is enabled
|
||||
@discourseComputed
|
||||
hasAtLeastOneLoginButton() {
|
||||
return findAll().length > 0;
|
||||
},
|
||||
|
||||
@on("init")
|
||||
fetchConfirmationValue() {
|
||||
if (this._challengeDate === undefined && this._hpPromise) {
|
||||
// Request already in progress
|
||||
return this._hpPromise;
|
||||
}
|
||||
|
||||
this._hpPromise = ajax("/session/hp.json")
|
||||
.then((json) => {
|
||||
if (this.isDestroying || this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._challengeDate = new Date();
|
||||
// remove 30 seconds for jitter, make sure this works for at least
|
||||
// 30 seconds so we don't have hard loops
|
||||
this._challengeExpiry = parseInt(json.expires_in, 10) - 30;
|
||||
if (this._challengeExpiry < 30) {
|
||||
this._challengeExpiry = 30;
|
||||
}
|
||||
|
||||
this.setProperties({
|
||||
accountHoneypot: json.value,
|
||||
accountChallenge: json.challenge.split("").reverse().join(""),
|
||||
});
|
||||
})
|
||||
.finally(() => (this._hpPromise = undefined));
|
||||
|
||||
return this._hpPromise;
|
||||
},
|
||||
|
||||
performAccountCreation() {
|
||||
if (
|
||||
!this._challengeDate ||
|
||||
new Date() - this._challengeDate > 1000 * this._challengeExpiry
|
||||
) {
|
||||
return this.fetchConfirmationValue().then(() =>
|
||||
this.performAccountCreation()
|
||||
);
|
||||
}
|
||||
|
||||
const attrs = this.getProperties(
|
||||
"accountName",
|
||||
"accountEmail",
|
||||
"accountPassword",
|
||||
"accountUsername",
|
||||
"accountChallenge",
|
||||
"inviteCode"
|
||||
);
|
||||
|
||||
attrs["accountPasswordConfirm"] = this.accountHoneypot;
|
||||
|
||||
const userFields = this.userFields;
|
||||
const destinationUrl = this.get("authOptions.destination_url");
|
||||
|
||||
if (!isEmpty(destinationUrl)) {
|
||||
cookie("destination_url", destinationUrl, { path: "/" });
|
||||
}
|
||||
|
||||
// Add the userfields to the data
|
||||
if (!isEmpty(userFields)) {
|
||||
attrs.userFields = {};
|
||||
userFields.forEach(
|
||||
(f) => (attrs.userFields[f.get("field.id")] = f.get("value"))
|
||||
);
|
||||
}
|
||||
|
||||
this.set("formSubmitted", true);
|
||||
return User.createAccount(attrs).then(
|
||||
(result) => {
|
||||
if (this.isDestroying || this.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.set("isDeveloper", false);
|
||||
if (result.success) {
|
||||
// invalidate honeypot
|
||||
this._challengeExpiry = 1;
|
||||
|
||||
// Trigger the browser's password manager using the hidden static login form:
|
||||
const $hidden_login_form = $("#hidden-login-form");
|
||||
$hidden_login_form
|
||||
.find("input[name=username]")
|
||||
.val(attrs.accountUsername);
|
||||
$hidden_login_form
|
||||
.find("input[name=password]")
|
||||
.val(attrs.accountPassword);
|
||||
$hidden_login_form
|
||||
.find("input[name=redirect]")
|
||||
.val(userPath("account-created"));
|
||||
$hidden_login_form.submit();
|
||||
return new Promise(() => {}); // This will never resolve, the page will reload instead
|
||||
} else {
|
||||
this.flash(
|
||||
result.message || I18n.t("create_account.failed"),
|
||||
"error"
|
||||
);
|
||||
if (result.is_developer) {
|
||||
this.set("isDeveloper", true);
|
||||
}
|
||||
if (
|
||||
result.errors &&
|
||||
result.errors.email &&
|
||||
result.errors.email.length > 0 &&
|
||||
result.values
|
||||
) {
|
||||
this.rejectedEmails.pushObject(result.values.email);
|
||||
}
|
||||
if (
|
||||
result.errors &&
|
||||
result.errors.password &&
|
||||
result.errors.password.length > 0
|
||||
) {
|
||||
this.rejectedPasswords.pushObject(attrs.accountPassword);
|
||||
}
|
||||
this.set("formSubmitted", false);
|
||||
removeCookie("destination_url");
|
||||
}
|
||||
},
|
||||
() => {
|
||||
this.set("formSubmitted", false);
|
||||
removeCookie("destination_url");
|
||||
return this.flash(I18n.t("create_account.failed"), "error");
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
onShow() {
|
||||
if (this.skipConfirmation) {
|
||||
this.performAccountCreation().finally(() =>
|
||||
this.set("skipConfirmation", false)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("authOptions.associate_url", "authOptions.auth_provider")
|
||||
associateHtml(url, provider) {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
return I18n.t("create_account.associate", {
|
||||
associate_link: url,
|
||||
provider: I18n.t(`login.${provider}.name`),
|
||||
});
|
||||
},
|
||||
|
||||
@action
|
||||
togglePasswordMask() {
|
||||
this.toggleProperty("maskPassword");
|
||||
},
|
||||
|
||||
actions: {
|
||||
externalLogin(provider) {
|
||||
// we will automatically redirect to the external auth service
|
||||
this.modal.show(LoginModal, {
|
||||
model: {
|
||||
isExternalLogin: true,
|
||||
externalLoginMethod: provider,
|
||||
signup: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
createAccount() {
|
||||
this.clearFlash();
|
||||
|
||||
this.set("forceValidationReason", true);
|
||||
const validation = [
|
||||
this.emailValidation,
|
||||
this.usernameValidation,
|
||||
this.nameValidation,
|
||||
this.passwordValidation,
|
||||
this.userFieldsValidation,
|
||||
].find((v) => v.failed);
|
||||
|
||||
if (validation) {
|
||||
const element = validation.element;
|
||||
if (element) {
|
||||
if (element.tagName === "DIV") {
|
||||
if (element.scrollIntoView) {
|
||||
element.scrollIntoView();
|
||||
}
|
||||
element.click();
|
||||
} else {
|
||||
element.focus();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.set("forceValidationReason", false);
|
||||
this.performAccountCreation();
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
|
@ -1,4 +1,4 @@
|
|||
import Controller, { inject as controller } from "@ember/controller";
|
||||
import Controller from "@ember/controller";
|
||||
import EmberObject, { action } from "@ember/object";
|
||||
import { alias, bool, not, readOnly } from "@ember/object/computed";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
|
@ -24,8 +24,6 @@ export default Controller.extend(
|
|||
{
|
||||
queryParams: ["t"],
|
||||
|
||||
createAccount: controller(),
|
||||
|
||||
invitedBy: readOnly("model.invited_by"),
|
||||
email: alias("model.email"),
|
||||
accountEmail: alias("email"),
|
||||
|
@ -222,7 +220,7 @@ export default Controller.extend(
|
|||
}
|
||||
|
||||
if (externalAuthEmail && externalAuthEmailValid) {
|
||||
const provider = this.createAccount.authProviderDisplayName(
|
||||
const provider = this.authProviderDisplayName(
|
||||
this.get("authOptions.auth_provider")
|
||||
);
|
||||
|
||||
|
@ -263,6 +261,15 @@ export default Controller.extend(
|
|||
});
|
||||
},
|
||||
|
||||
authProviderDisplayName(providerName) {
|
||||
const matchingProvider = findLoginMethods().find((provider) => {
|
||||
return provider.name === providerName;
|
||||
});
|
||||
return matchingProvider
|
||||
? matchingProvider.get("prettyName")
|
||||
: providerName;
|
||||
},
|
||||
|
||||
@discourseComputed
|
||||
wavingHandURL: () => wavingHandURL(),
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import EmberObject from "@ember/object";
|
||||
import { next } from "@ember/runloop";
|
||||
import CreateAccount from "discourse/components/modal/create-account";
|
||||
import LoginModal from "discourse/components/modal/login";
|
||||
import cookie, { removeCookie } from "discourse/lib/cookie";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import DiscourseUrl from "discourse/lib/url";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
|
@ -116,21 +116,17 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
const skipConfirmation = siteSettings.auth_skip_create_confirm;
|
||||
owner.lookup("controller:createAccount").setProperties({
|
||||
next(() =>
|
||||
modal.show(CreateAccount, {
|
||||
model: {
|
||||
accountEmail: options.email,
|
||||
accountUsername: options.username,
|
||||
accountName: options.name,
|
||||
authOptions: EmberObject.create(options),
|
||||
skipConfirmation,
|
||||
});
|
||||
|
||||
next(() => {
|
||||
showModal("create-account", {
|
||||
modalClass: "create-account",
|
||||
titleAriaElementId: "create-account-title",
|
||||
});
|
||||
});
|
||||
skipConfirmation: siteSettings.auth_skip_create_confirm,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import CreateAccount from "discourse/components/modal/create-account";
|
||||
import ForgotPassword from "discourse/components/modal/forgot-password";
|
||||
import KeyboardShortcutsHelp from "discourse/components/modal/keyboard-shortcuts-help";
|
||||
import LoginModal from "discourse/components/modal/login";
|
||||
|
@ -7,7 +8,6 @@ import { setting } from "discourse/lib/computed";
|
|||
import cookie from "discourse/lib/cookie";
|
||||
import logout from "discourse/lib/logout";
|
||||
import mobile from "discourse/lib/mobile";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
import Category from "discourse/models/category";
|
||||
import Composer from "discourse/models/composer";
|
||||
|
@ -258,11 +258,7 @@ const ApplicationRoute = DiscourseRoute.extend({
|
|||
},
|
||||
});
|
||||
} else {
|
||||
const createAccount = showModal("create-account", {
|
||||
modalClass: "create-account",
|
||||
titleAriaElementId: "create-account-title",
|
||||
});
|
||||
createAccount.setProperties(createAccountProps);
|
||||
this.modal.show(CreateAccount, { model: createAccountProps });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -16,7 +16,6 @@ const KNOWN_LEGACY_MODALS = [
|
|||
"avatar-selector",
|
||||
"change-owner",
|
||||
"change-post-notice",
|
||||
"create-account",
|
||||
"create-invite-bulk",
|
||||
"create-invite",
|
||||
"grant-badge",
|
||||
|
|
|
@ -1,290 +0,0 @@
|
|||
<CreateAccount
|
||||
@email={{this.accountEmail}}
|
||||
@disabled={{this.submitDisabled}}
|
||||
@action={{action "createAccount"}}
|
||||
>
|
||||
{{#unless this.complete}}
|
||||
<span>
|
||||
<PluginOutlet
|
||||
@name="create-account-before-modal-body"
|
||||
@connectorTagName="div"
|
||||
/>
|
||||
</span>
|
||||
<DModalBody
|
||||
@class={{this.modalBodyClasses}}
|
||||
@preventModalAlertHiding={{true}}
|
||||
>
|
||||
<div class="create-account-form {{this.authOptions.auth_provider}}">
|
||||
<div class="login-welcome-header" id="create-account-title">
|
||||
<h1 class="login-title">{{i18n "create_account.header_title"}}</h1>
|
||||
<img src={{this.wavingHandURL}} alt="" class="waving-hand" />
|
||||
<p class="login-subheader">{{i18n
|
||||
"create_account.subheader_title"
|
||||
}}</p>
|
||||
<PluginOutlet
|
||||
@name="create-account-header-bottom"
|
||||
@outletArgs={{hash showLogin=(route-action "showLogin")}}
|
||||
/>
|
||||
</div>
|
||||
{{#if this.showCreateForm}}
|
||||
|
||||
<div class="login-form">
|
||||
<form>
|
||||
{{#if this.associateHtml}}
|
||||
<div class="input-group create-account-associate-link">
|
||||
<span>{{html-safe this.associateHtml}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="input-group create-account-email">
|
||||
<Input
|
||||
@type="email"
|
||||
disabled={{this.emailDisabled}}
|
||||
@value={{this.accountEmail}}
|
||||
id="new-account-email"
|
||||
name="email"
|
||||
class={{value-entered this.accountEmail}}
|
||||
autofocus="autofocus"
|
||||
{{on "focusout" (action "checkEmailAvailability")}}
|
||||
aria-describedby="account-email-validation"
|
||||
aria-invalid={{this.emailValidation.failed}}
|
||||
/>
|
||||
<label class="alt-placeholder" for="new-account-email">
|
||||
{{i18n "user.email.title"}}
|
||||
{{~#if this.userFields~}}
|
||||
<span class="required">*</span>
|
||||
{{/if}}
|
||||
</label>
|
||||
<InputTip
|
||||
@validation={{this.emailValidation}}
|
||||
@id="account-email-validation"
|
||||
/>
|
||||
<span class="more-info">{{i18n
|
||||
"user.email.instructions"
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group create-account__username">
|
||||
<Input
|
||||
@value={{this.accountUsername}}
|
||||
disabled={{this.usernameDisabled}}
|
||||
class={{value-entered this.accountUsername}}
|
||||
id="new-account-username"
|
||||
name="username"
|
||||
maxlength={{this.maxUsernameLength}}
|
||||
aria-describedby="username-validation"
|
||||
aria-invalid={{this.usernameValidation.failed}}
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label class="alt-placeholder" for="new-account-username">
|
||||
{{i18n "user.username.title"}}
|
||||
{{~#if this.userFields~}}
|
||||
<span class="required">*</span>
|
||||
{{/if}}
|
||||
</label>
|
||||
|
||||
<InputTip
|
||||
@validation={{this.usernameValidation}}
|
||||
@id="username-validation"
|
||||
/>
|
||||
<span class="more-info">{{i18n
|
||||
"user.username.instructions"
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group create-account__fullname">
|
||||
{{#if this.fullnameRequired}}
|
||||
<TextField
|
||||
@disabled={{this.nameDisabled}}
|
||||
@value={{this.accountName}}
|
||||
@id="new-account-name"
|
||||
@class={{value-entered this.accountName}}
|
||||
aria-describedby="fullname-validation"
|
||||
aria-invalid={{this.nameValidation.failed}}
|
||||
/>
|
||||
<label class="alt-placeholder" for="new-account-name">
|
||||
{{i18n "user.name.title"}}
|
||||
{{#if this.siteSettings.full_name_required}}
|
||||
{{~#if this.userFields~}}
|
||||
<span class="required">*</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</label>
|
||||
|
||||
<InputTip
|
||||
@validation={{this.nameValidation}}
|
||||
@id="fullname-validation"
|
||||
/>
|
||||
<span class="more-info">{{this.nameInstructions}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<PluginOutlet
|
||||
@name="create-account-before-password"
|
||||
@outletArgs={{hash
|
||||
accountName=this.accountName
|
||||
accountUsername=this.accountUsername
|
||||
accountPassword=this.accountPassword
|
||||
userFields=this.userFields
|
||||
authOptions=this.authOptions
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="input-group create-account__password">
|
||||
{{#if this.passwordRequired}}
|
||||
<PasswordField
|
||||
@value={{this.accountPassword}}
|
||||
@class={{value-entered this.accountPassword}}
|
||||
@type={{if this.maskPassword "password" "text"}}
|
||||
id="new-account-password"
|
||||
@autocomplete="current-password"
|
||||
@capsLockOn={{this.capsLockOn}}
|
||||
aria-describedby="password-validation"
|
||||
aria-invalid={{this.passwordValidation.failed}}
|
||||
/>
|
||||
<label class="alt-placeholder" for="new-account-password">
|
||||
{{i18n "user.password.title"}}
|
||||
{{~#if this.userFields~}}
|
||||
<span class="required">*</span>
|
||||
{{/if}}
|
||||
</label>
|
||||
<div class="create-account__password-info">
|
||||
<div class="create-account__password-tip-validation">
|
||||
<InputTip
|
||||
@validation={{this.passwordValidation}}
|
||||
@id="password-validation"
|
||||
/>
|
||||
<span
|
||||
class="more-info"
|
||||
>{{this.passwordInstructions}}</span>
|
||||
<div
|
||||
class="caps-lock-warning
|
||||
{{unless this.capsLockOn 'hidden'}}"
|
||||
>
|
||||
{{d-icon "exclamation-triangle"}}
|
||||
{{i18n "login.caps_lock_warning"}}
|
||||
</div>
|
||||
</div>
|
||||
<TogglePasswordMask
|
||||
@maskPassword={{this.maskPassword}}
|
||||
@togglePasswordMask={{this.togglePasswordMask}}
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="password-confirmation">
|
||||
<label for="new-account-password-confirmation">{{i18n
|
||||
"user.password_confirmation.title"
|
||||
}}</label>
|
||||
<HoneypotInput
|
||||
@id="new-account-confirmation"
|
||||
@autocomplete="new-password"
|
||||
@value={{this.accountHoneypot}}
|
||||
/>
|
||||
<Input
|
||||
@value={{this.accountChallenge}}
|
||||
id="new-account-challenge"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.requireInviteCode}}
|
||||
<div class="input-group create-account__invite-code">
|
||||
<Input
|
||||
@value={{this.inviteCode}}
|
||||
class={{value-entered this.inviteCode}}
|
||||
id="inviteCode"
|
||||
/>
|
||||
<label class="alt-placeholder" for="invite-code">{{i18n
|
||||
"user.invite_code.title"
|
||||
}}</label>
|
||||
<span class="more-info">{{i18n
|
||||
"user.invite_code.instructions"
|
||||
}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<PluginOutlet
|
||||
@name="create-account-after-password"
|
||||
@outletArgs={{hash
|
||||
accountName=this.accountName
|
||||
accountUsername=this.accountUsername
|
||||
accountPassword=this.accountPassword
|
||||
userFields=this.userFields
|
||||
}}
|
||||
/>
|
||||
|
||||
{{#if this.userFields}}
|
||||
<div class="user-fields">
|
||||
{{#each this.userFields as |f|}}
|
||||
<div class="input-group">
|
||||
{{! adding the value-entered class here to
|
||||
be able to detect if the user field has a value
|
||||
entered }}
|
||||
<UserField
|
||||
@field={{f.field}}
|
||||
@value={{f.value}}
|
||||
@class={{value-entered f.value}}
|
||||
@validation={{f.validation}}
|
||||
/>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<PluginOutlet
|
||||
@name="create-account-after-user-fields"
|
||||
@outletArgs={{hash
|
||||
accountName=this.accountName
|
||||
accountUsername=this.accountUsername
|
||||
accountPassword=this.accountPassword
|
||||
userFields=this.userFields
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<div class="disclaimer">
|
||||
{{html-safe this.disclaimerHtml}}
|
||||
</div>
|
||||
|
||||
<DButton
|
||||
@action={{action "createAccount"}}
|
||||
@disabled={{this.submitDisabled}}
|
||||
@label="create_account.title"
|
||||
@isLoading={{this.formSubmitted}}
|
||||
class="btn-large btn-primary"
|
||||
/>
|
||||
|
||||
{{#unless this.hasAuthOptions}}
|
||||
<DButton
|
||||
@action={{route-action "showLogin"}}
|
||||
@disabled={{this.formSubmitted}}
|
||||
@label="log_in"
|
||||
id="login-link"
|
||||
class="btn-large"
|
||||
/>
|
||||
{{/unless}}
|
||||
|
||||
</div>
|
||||
|
||||
<PluginOutlet
|
||||
@name="create-account-after-modal-footer"
|
||||
@connectorTagName="div"
|
||||
/>
|
||||
|
||||
{{/if}}
|
||||
{{#if this.site.desktopView}}
|
||||
{{#unless this.hasAuthOptions}}
|
||||
<div class="create-account-login-buttons">
|
||||
<LoginButtons @externalLogin={{action "externalLogin"}} />
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
{{#if this.skipConfirmation}}
|
||||
{{loading-spinner size="large"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</DModalBody>
|
||||
{{/unless}}
|
||||
</CreateAccount>
|
|
@ -1,10 +1,10 @@
|
|||
import { currentRouteName, visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
acceptance("Auth Complete", function (needs) {
|
||||
needs.hooks.beforeEach(() => {
|
||||
needs.hooks.beforeEach(function () {
|
||||
const node = document.createElement("meta");
|
||||
node.dataset.authenticationData = JSON.stringify({
|
||||
auth_provider: "test",
|
||||
|
@ -14,10 +14,8 @@ acceptance("Auth Complete", function (needs) {
|
|||
document.querySelector("head").appendChild(node);
|
||||
});
|
||||
|
||||
needs.hooks.afterEach(() => {
|
||||
document
|
||||
.querySelector("head")
|
||||
.removeChild(document.getElementById("data-authentication"));
|
||||
needs.hooks.afterEach(function () {
|
||||
document.getElementById("data-authentication").remove();
|
||||
});
|
||||
|
||||
test("when login not required", async function (assert) {
|
||||
|
@ -29,10 +27,9 @@ acceptance("Auth Complete", function (needs) {
|
|||
"it stays on the homepage"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
exists("#discourse-modal div.create-account-body"),
|
||||
"it shows the registration modal"
|
||||
);
|
||||
assert
|
||||
.dom(".d-modal.create-account")
|
||||
.exists("it shows the registration modal");
|
||||
});
|
||||
|
||||
test("when login required", async function (assert) {
|
||||
|
@ -45,10 +42,9 @@ acceptance("Auth Complete", function (needs) {
|
|||
"it redirects to the login page"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
exists("#discourse-modal div.create-account-body"),
|
||||
"it shows the registration modal"
|
||||
);
|
||||
assert
|
||||
.dom(".d-modal.create-account")
|
||||
.exists("it shows the registration modal");
|
||||
});
|
||||
|
||||
test("Callback added using addBeforeAuthCompleteCallback", async function (assert) {
|
||||
|
@ -69,9 +65,8 @@ acceptance("Auth Complete", function (needs) {
|
|||
"The function added via API was run and it transitioned to 'discovery.categories' route"
|
||||
);
|
||||
|
||||
assert.notOk(
|
||||
exists("#discourse-modal div.create-account-body"),
|
||||
"registration modal is not shown"
|
||||
);
|
||||
assert
|
||||
.dom(".d-modal.create-account")
|
||||
.doesNotExist("registration modal is not shown");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
function setupAuthData(data) {
|
||||
data = {
|
||||
|
@ -18,68 +18,58 @@ function setupAuthData(data) {
|
|||
}
|
||||
|
||||
acceptance("Create Account - external auth", function (needs) {
|
||||
needs.hooks.beforeEach(() => {
|
||||
needs.hooks.beforeEach(function () {
|
||||
setupAuthData();
|
||||
});
|
||||
needs.hooks.afterEach(() => {
|
||||
document
|
||||
.querySelector("head")
|
||||
.removeChild(document.getElementById("data-authentication"));
|
||||
needs.hooks.afterEach(function () {
|
||||
document.getElementById("data-authentication").remove();
|
||||
});
|
||||
|
||||
test("when skip is disabled (default)", async function (assert) {
|
||||
await visit("/");
|
||||
|
||||
assert.ok(
|
||||
exists("#discourse-modal div.create-account-body"),
|
||||
"it shows the registration modal"
|
||||
);
|
||||
assert
|
||||
.dom(".d-modal.create-account")
|
||||
.exists("it shows the registration modal");
|
||||
|
||||
assert.ok(exists("#new-account-username"), "it shows the fields");
|
||||
assert.dom("#new-account-username").exists("it shows the fields");
|
||||
|
||||
assert.notOk(
|
||||
exists(".create-account-associate-link"),
|
||||
"it does not show the associate link"
|
||||
);
|
||||
assert
|
||||
.dom(".create-account-associate-link")
|
||||
.doesNotExist("it does not show the associate link");
|
||||
});
|
||||
|
||||
test("when skip is enabled", async function (assert) {
|
||||
this.siteSettings.auth_skip_create_confirm = true;
|
||||
await visit("/");
|
||||
|
||||
assert.ok(
|
||||
exists("#discourse-modal div.create-account-body"),
|
||||
"it shows the registration modal"
|
||||
);
|
||||
assert
|
||||
.dom(".d-modal.create-account")
|
||||
.exists("it shows the registration modal");
|
||||
|
||||
assert.notOk(
|
||||
exists("#new-account-username"),
|
||||
"it does not show the fields"
|
||||
);
|
||||
assert
|
||||
.dom("#new-account-username")
|
||||
.doesNotExist("it does not show the fields");
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("Create account - with associate link", function (needs) {
|
||||
needs.hooks.beforeEach(() => {
|
||||
needs.hooks.beforeEach(function () {
|
||||
setupAuthData({ associate_url: "/associate/abcde" });
|
||||
});
|
||||
needs.hooks.afterEach(() => {
|
||||
document
|
||||
.querySelector("head")
|
||||
.removeChild(document.getElementById("data-authentication"));
|
||||
needs.hooks.afterEach(function () {
|
||||
document.getElementById("data-authentication").remove();
|
||||
});
|
||||
|
||||
test("displays associate link when allowed", async function (assert) {
|
||||
await visit("/");
|
||||
|
||||
assert.ok(
|
||||
exists("#discourse-modal div.create-account-body"),
|
||||
"it shows the registration modal"
|
||||
);
|
||||
assert.ok(exists("#new-account-username"), "it shows the fields");
|
||||
assert.ok(
|
||||
exists(".create-account-associate-link"),
|
||||
"it shows the associate link"
|
||||
);
|
||||
assert
|
||||
.dom(".d-modal.create-account")
|
||||
.exists("it shows the registration modal");
|
||||
assert.dom("#new-account-username").exists("it shows the fields");
|
||||
assert
|
||||
.dom(".create-account-associate-link")
|
||||
.exists("it shows the associate link");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
import {
|
||||
acceptance,
|
||||
count,
|
||||
exists,
|
||||
query,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
acceptance("Create Account - User Fields", function (needs) {
|
||||
needs.site({
|
||||
|
@ -35,28 +31,25 @@ acceptance("Create Account - User Fields", function (needs) {
|
|||
await visit("/");
|
||||
await click("header .sign-up-button");
|
||||
|
||||
assert.ok(exists(".create-account"), "it shows the create account modal");
|
||||
assert.ok(exists(".user-field"), "it has at least one user field");
|
||||
assert.dom(".create-account").exists("it shows the create account modal");
|
||||
assert.dom(".user-field").exists("it has at least one user field");
|
||||
|
||||
await click(".modal-footer .btn-primary");
|
||||
assert.strictEqual(
|
||||
query("#account-email-validation").innerText.trim(),
|
||||
"Please enter an email address"
|
||||
);
|
||||
assert
|
||||
.dom("#account-email-validation")
|
||||
.hasText(I18n.t("user.email.required"));
|
||||
|
||||
await fillIn("#new-account-name", "Dr. Good Tuna");
|
||||
await fillIn("#new-account-password", "cool password bro");
|
||||
await fillIn("#new-account-email", "good.tuna@test.com");
|
||||
await fillIn("#new-account-username", "goodtuna");
|
||||
|
||||
assert.ok(
|
||||
exists("#username-validation.good"),
|
||||
"the username validation is good"
|
||||
);
|
||||
assert.ok(
|
||||
exists("#account-email-validation.good"),
|
||||
"the email validation is good"
|
||||
);
|
||||
assert
|
||||
.dom("#username-validation.good")
|
||||
.exists("the username validation is good");
|
||||
assert
|
||||
.dom("#account-email-validation.good")
|
||||
.exists("the email validation is good");
|
||||
|
||||
await click(".modal-footer .btn-primary");
|
||||
await fillIn(".user-field input[type=text]:nth-of-type(1)", "Barky");
|
||||
|
@ -67,13 +60,11 @@ acceptance("Create Account - User Fields", function (needs) {
|
|||
test("can submit with enter", async function (assert) {
|
||||
await visit("/");
|
||||
await click("header .sign-up-button");
|
||||
await triggerKeyEvent(".modal-footer .btn-primary", "keydown", "Enter");
|
||||
await triggerKeyEvent("#new-account-email", "keydown", "Enter");
|
||||
|
||||
assert.strictEqual(
|
||||
count("#modal-alert:visible"),
|
||||
1,
|
||||
"hitting Enter triggers action"
|
||||
);
|
||||
assert
|
||||
.dom("#account-email-validation")
|
||||
.hasText(I18n.t("user.email.required"), "hitting Enter triggers action");
|
||||
});
|
||||
|
||||
test("shows validation error for user fields", async function (assert) {
|
||||
|
@ -85,14 +76,12 @@ acceptance("Create Account - User Fields", function (needs) {
|
|||
|
||||
await click(".modal-footer .btn-primary");
|
||||
|
||||
assert.ok(
|
||||
exists(".user-field-what-is-your-pets-name .tip.bad"),
|
||||
"shows required field error"
|
||||
);
|
||||
assert
|
||||
.dom(".user-field-what-is-your-pets-name .tip.bad")
|
||||
.exists("shows required field error");
|
||||
|
||||
assert.ok(
|
||||
exists(".user-field-whats-your-dad-like .tip.bad"),
|
||||
"shows same as password error"
|
||||
);
|
||||
assert
|
||||
.dom(".user-field-whats-your-dad-like .tip.bad")
|
||||
.exists("shows same as password error");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,20 +3,21 @@ import { setupTest } from "ember-qunit";
|
|||
import { module, test } from "qunit";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
module("Unit | Controller | create-account", function (hooks) {
|
||||
module("Unit | Component | create-account", function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test("basicUsernameValidation", function (assert) {
|
||||
const testInvalidUsername = (username, expectedReason) => {
|
||||
const controller = this.owner.lookup("controller:create-account");
|
||||
controller.set("accountUsername", username);
|
||||
const component = this.owner
|
||||
.factoryFor("component:modal/create-account")
|
||||
.create({ model: { accountUsername: username } });
|
||||
|
||||
let validation = controller.basicUsernameValidation(username);
|
||||
assert.ok(validation.failed, "username should be invalid: " + username);
|
||||
const validation = component.basicUsernameValidation(username);
|
||||
assert.true(validation.failed, `username should be invalid: ${username}`);
|
||||
assert.strictEqual(
|
||||
validation.reason,
|
||||
expectedReason,
|
||||
"username validation reason: " + username + ", " + expectedReason
|
||||
`username validation reason: ${username}, ${expectedReason}`
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -27,14 +28,13 @@ module("Unit | Controller | create-account", function (hooks) {
|
|||
I18n.t("user.username.too_long")
|
||||
);
|
||||
|
||||
const controller = this.owner.lookup("controller:create-account");
|
||||
controller.setProperties({
|
||||
accountUsername: "porkchops",
|
||||
prefilledUsername: "porkchops",
|
||||
});
|
||||
const component = this.owner
|
||||
.factoryFor("component:modal/create-account")
|
||||
.create({ model: { accountUsername: "porkchops" } });
|
||||
component.set("prefilledUsername", "porkchops");
|
||||
|
||||
let validation = controller.basicUsernameValidation("porkchops");
|
||||
assert.ok(validation.ok, "Prefilled username is valid");
|
||||
const validation = component.basicUsernameValidation("porkchops");
|
||||
assert.true(validation.ok, "Prefilled username is valid");
|
||||
assert.strictEqual(
|
||||
validation.reason,
|
||||
I18n.t("user.username.prefilled"),
|
||||
|
@ -43,35 +43,33 @@ module("Unit | Controller | create-account", function (hooks) {
|
|||
});
|
||||
|
||||
test("passwordValidation", async function (assert) {
|
||||
const controller = this.owner.lookup("controller:create-account");
|
||||
|
||||
controller.set("authProvider", "");
|
||||
controller.set("accountEmail", "pork@chops.com");
|
||||
controller.set("accountUsername", "porkchops123");
|
||||
controller.set("prefilledUsername", "porkchops123");
|
||||
controller.set("accountPassword", "b4fcdae11f9167");
|
||||
const component = this.owner
|
||||
.factoryFor("component:modal/create-account")
|
||||
.create({
|
||||
model: {
|
||||
accountEmail: "pork@chops.com",
|
||||
accountUsername: "porkchops123",
|
||||
},
|
||||
});
|
||||
|
||||
component.set("prefilledUsername", "porkchops123");
|
||||
component.set("accountPassword", "b4fcdae11f9167");
|
||||
assert.true(component.passwordValidation.ok, "Password is ok");
|
||||
assert.strictEqual(
|
||||
controller.passwordValidation.ok,
|
||||
true,
|
||||
"Password is ok"
|
||||
);
|
||||
assert.strictEqual(
|
||||
controller.passwordValidation.reason,
|
||||
component.passwordValidation.reason,
|
||||
I18n.t("user.password.ok"),
|
||||
"Password is valid"
|
||||
);
|
||||
|
||||
const testInvalidPassword = (password, expectedReason) => {
|
||||
controller.set("accountPassword", password);
|
||||
component.set("accountPassword", password);
|
||||
|
||||
assert.strictEqual(
|
||||
controller.passwordValidation.failed,
|
||||
true,
|
||||
assert.true(
|
||||
component.passwordValidation.failed,
|
||||
`password should be invalid: ${password}`
|
||||
);
|
||||
assert.strictEqual(
|
||||
controller.passwordValidation.reason,
|
||||
component.passwordValidation.reason,
|
||||
expectedReason,
|
||||
`password validation reason: ${password}, ${expectedReason}`
|
||||
);
|
||||
|
@ -93,17 +91,19 @@ module("Unit | Controller | create-account", function (hooks) {
|
|||
});
|
||||
|
||||
test("authProviderDisplayName", function (assert) {
|
||||
const controller = this.owner.lookup("controller:create-account");
|
||||
const component = this.owner
|
||||
.factoryFor("component:modal/create-account")
|
||||
.create({ model: {} });
|
||||
|
||||
assert.strictEqual(
|
||||
controller.authProviderDisplayName("facebook"),
|
||||
component.authProviderDisplayName("facebook"),
|
||||
I18n.t("login.facebook.name"),
|
||||
"provider name is translated correctly"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
controller.authProviderDisplayName("idontexist"),
|
||||
"idontexist",
|
||||
component.authProviderDisplayName("does-not-exist"),
|
||||
"does-not-exist",
|
||||
"provider name falls back if not found"
|
||||
);
|
||||
});
|
|
@ -298,10 +298,11 @@ body.invite-page {
|
|||
}
|
||||
|
||||
@media screen and (min-width: 701px) {
|
||||
.create-account-body {
|
||||
.modal-body {
|
||||
max-width: 40em;
|
||||
}
|
||||
}
|
||||
|
||||
.user-field {
|
||||
input[type="text"] {
|
||||
margin-bottom: 0;
|
||||
|
|
|
@ -206,12 +206,11 @@
|
|||
// create account
|
||||
// modal only
|
||||
.d-modal.create-account {
|
||||
.create-account-body {
|
||||
min-width: 100%;
|
||||
}
|
||||
.modal-body {
|
||||
min-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.has-alt-auth .create-account-form {
|
||||
display: grid;
|
||||
grid-template-columns: 60% 40%;
|
||||
|
|
|
@ -183,15 +183,27 @@
|
|||
// create account
|
||||
// modal only
|
||||
|
||||
#discourse-modal .create-account .modal-body {
|
||||
.d-modal.create-account {
|
||||
.modal-body {
|
||||
max-height: 60vh !important;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@media screen and (max-height: 575px) {
|
||||
max-height: 50vh !important;
|
||||
}
|
||||
}
|
||||
|
||||
.d-modal.create-account {
|
||||
#login-buttons {
|
||||
border-bottom: 1px solid var(--primary-low);
|
||||
}
|
||||
|
||||
.login-form {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.create-account-form {
|
||||
overflow-y: auto;
|
||||
.login-welcome-header {
|
||||
|
@ -205,18 +217,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.create-account .modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
#login-buttons {
|
||||
border-bottom: 1px solid var(--primary-low);
|
||||
}
|
||||
.login-form {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.create-account {
|
||||
.user-fields {
|
||||
display: flex;
|
||||
|
|
Loading…
Reference in New Issue