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 EmberObject, { action } from "@ember/object";
|
||||||
import { alias, bool, not, readOnly } from "@ember/object/computed";
|
import { alias, bool, not, readOnly } from "@ember/object/computed";
|
||||||
import { isEmpty } from "@ember/utils";
|
import { isEmpty } from "@ember/utils";
|
||||||
|
@ -24,8 +24,6 @@ export default Controller.extend(
|
||||||
{
|
{
|
||||||
queryParams: ["t"],
|
queryParams: ["t"],
|
||||||
|
|
||||||
createAccount: controller(),
|
|
||||||
|
|
||||||
invitedBy: readOnly("model.invited_by"),
|
invitedBy: readOnly("model.invited_by"),
|
||||||
email: alias("model.email"),
|
email: alias("model.email"),
|
||||||
accountEmail: alias("email"),
|
accountEmail: alias("email"),
|
||||||
|
@ -222,7 +220,7 @@ export default Controller.extend(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (externalAuthEmail && externalAuthEmailValid) {
|
if (externalAuthEmail && externalAuthEmailValid) {
|
||||||
const provider = this.createAccount.authProviderDisplayName(
|
const provider = this.authProviderDisplayName(
|
||||||
this.get("authOptions.auth_provider")
|
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
|
@discourseComputed
|
||||||
wavingHandURL: () => wavingHandURL(),
|
wavingHandURL: () => wavingHandURL(),
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import EmberObject from "@ember/object";
|
import EmberObject from "@ember/object";
|
||||||
import { next } from "@ember/runloop";
|
import { next } from "@ember/runloop";
|
||||||
|
import CreateAccount from "discourse/components/modal/create-account";
|
||||||
import LoginModal from "discourse/components/modal/login";
|
import LoginModal from "discourse/components/modal/login";
|
||||||
import cookie, { removeCookie } from "discourse/lib/cookie";
|
import cookie, { removeCookie } from "discourse/lib/cookie";
|
||||||
import showModal from "discourse/lib/show-modal";
|
|
||||||
import DiscourseUrl from "discourse/lib/url";
|
import DiscourseUrl from "discourse/lib/url";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
|
|
||||||
|
@ -116,21 +116,17 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const skipConfirmation = siteSettings.auth_skip_create_confirm;
|
next(() =>
|
||||||
owner.lookup("controller:createAccount").setProperties({
|
modal.show(CreateAccount, {
|
||||||
accountEmail: options.email,
|
model: {
|
||||||
accountUsername: options.username,
|
accountEmail: options.email,
|
||||||
accountName: options.name,
|
accountUsername: options.username,
|
||||||
authOptions: EmberObject.create(options),
|
accountName: options.name,
|
||||||
skipConfirmation,
|
authOptions: EmberObject.create(options),
|
||||||
});
|
skipConfirmation: siteSettings.auth_skip_create_confirm,
|
||||||
|
},
|
||||||
next(() => {
|
})
|
||||||
showModal("create-account", {
|
);
|
||||||
modalClass: "create-account",
|
|
||||||
titleAriaElementId: "create-account-title",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
|
import CreateAccount from "discourse/components/modal/create-account";
|
||||||
import ForgotPassword from "discourse/components/modal/forgot-password";
|
import ForgotPassword from "discourse/components/modal/forgot-password";
|
||||||
import KeyboardShortcutsHelp from "discourse/components/modal/keyboard-shortcuts-help";
|
import KeyboardShortcutsHelp from "discourse/components/modal/keyboard-shortcuts-help";
|
||||||
import LoginModal from "discourse/components/modal/login";
|
import LoginModal from "discourse/components/modal/login";
|
||||||
|
@ -7,7 +8,6 @@ import { setting } from "discourse/lib/computed";
|
||||||
import cookie from "discourse/lib/cookie";
|
import cookie from "discourse/lib/cookie";
|
||||||
import logout from "discourse/lib/logout";
|
import logout from "discourse/lib/logout";
|
||||||
import mobile from "discourse/lib/mobile";
|
import mobile from "discourse/lib/mobile";
|
||||||
import showModal from "discourse/lib/show-modal";
|
|
||||||
import DiscourseURL from "discourse/lib/url";
|
import DiscourseURL from "discourse/lib/url";
|
||||||
import Category from "discourse/models/category";
|
import Category from "discourse/models/category";
|
||||||
import Composer from "discourse/models/composer";
|
import Composer from "discourse/models/composer";
|
||||||
|
@ -258,11 +258,7 @@ const ApplicationRoute = DiscourseRoute.extend({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const createAccount = showModal("create-account", {
|
this.modal.show(CreateAccount, { model: createAccountProps });
|
||||||
modalClass: "create-account",
|
|
||||||
titleAriaElementId: "create-account-title",
|
|
||||||
});
|
|
||||||
createAccount.setProperties(createAccountProps);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,7 +16,6 @@ const KNOWN_LEGACY_MODALS = [
|
||||||
"avatar-selector",
|
"avatar-selector",
|
||||||
"change-owner",
|
"change-owner",
|
||||||
"change-post-notice",
|
"change-post-notice",
|
||||||
"create-account",
|
|
||||||
"create-invite-bulk",
|
"create-invite-bulk",
|
||||||
"create-invite",
|
"create-invite",
|
||||||
"grant-badge",
|
"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 { currentRouteName, visit } from "@ember/test-helpers";
|
||||||
import { test } from "qunit";
|
import { test } from "qunit";
|
||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
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) {
|
acceptance("Auth Complete", function (needs) {
|
||||||
needs.hooks.beforeEach(() => {
|
needs.hooks.beforeEach(function () {
|
||||||
const node = document.createElement("meta");
|
const node = document.createElement("meta");
|
||||||
node.dataset.authenticationData = JSON.stringify({
|
node.dataset.authenticationData = JSON.stringify({
|
||||||
auth_provider: "test",
|
auth_provider: "test",
|
||||||
|
@ -14,10 +14,8 @@ acceptance("Auth Complete", function (needs) {
|
||||||
document.querySelector("head").appendChild(node);
|
document.querySelector("head").appendChild(node);
|
||||||
});
|
});
|
||||||
|
|
||||||
needs.hooks.afterEach(() => {
|
needs.hooks.afterEach(function () {
|
||||||
document
|
document.getElementById("data-authentication").remove();
|
||||||
.querySelector("head")
|
|
||||||
.removeChild(document.getElementById("data-authentication"));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("when login not required", async function (assert) {
|
test("when login not required", async function (assert) {
|
||||||
|
@ -29,10 +27,9 @@ acceptance("Auth Complete", function (needs) {
|
||||||
"it stays on the homepage"
|
"it stays on the homepage"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
exists("#discourse-modal div.create-account-body"),
|
.dom(".d-modal.create-account")
|
||||||
"it shows the registration modal"
|
.exists("it shows the registration modal");
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("when login required", async function (assert) {
|
test("when login required", async function (assert) {
|
||||||
|
@ -45,10 +42,9 @@ acceptance("Auth Complete", function (needs) {
|
||||||
"it redirects to the login page"
|
"it redirects to the login page"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
exists("#discourse-modal div.create-account-body"),
|
.dom(".d-modal.create-account")
|
||||||
"it shows the registration modal"
|
.exists("it shows the registration modal");
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Callback added using addBeforeAuthCompleteCallback", async function (assert) {
|
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"
|
"The function added via API was run and it transitioned to 'discovery.categories' route"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.notOk(
|
assert
|
||||||
exists("#discourse-modal div.create-account-body"),
|
.dom(".d-modal.create-account")
|
||||||
"registration modal is not shown"
|
.doesNotExist("registration modal is not shown");
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { visit } from "@ember/test-helpers";
|
import { visit } from "@ember/test-helpers";
|
||||||
import { test } from "qunit";
|
import { test } from "qunit";
|
||||||
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
|
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||||
|
|
||||||
function setupAuthData(data) {
|
function setupAuthData(data) {
|
||||||
data = {
|
data = {
|
||||||
|
@ -18,68 +18,58 @@ function setupAuthData(data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
acceptance("Create Account - external auth", function (needs) {
|
acceptance("Create Account - external auth", function (needs) {
|
||||||
needs.hooks.beforeEach(() => {
|
needs.hooks.beforeEach(function () {
|
||||||
setupAuthData();
|
setupAuthData();
|
||||||
});
|
});
|
||||||
needs.hooks.afterEach(() => {
|
needs.hooks.afterEach(function () {
|
||||||
document
|
document.getElementById("data-authentication").remove();
|
||||||
.querySelector("head")
|
|
||||||
.removeChild(document.getElementById("data-authentication"));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("when skip is disabled (default)", async function (assert) {
|
test("when skip is disabled (default)", async function (assert) {
|
||||||
await visit("/");
|
await visit("/");
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
exists("#discourse-modal div.create-account-body"),
|
.dom(".d-modal.create-account")
|
||||||
"it shows the registration modal"
|
.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(
|
assert
|
||||||
exists(".create-account-associate-link"),
|
.dom(".create-account-associate-link")
|
||||||
"it does not show the associate link"
|
.doesNotExist("it does not show the associate link");
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("when skip is enabled", async function (assert) {
|
test("when skip is enabled", async function (assert) {
|
||||||
this.siteSettings.auth_skip_create_confirm = true;
|
this.siteSettings.auth_skip_create_confirm = true;
|
||||||
await visit("/");
|
await visit("/");
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
exists("#discourse-modal div.create-account-body"),
|
.dom(".d-modal.create-account")
|
||||||
"it shows the registration modal"
|
.exists("it shows the registration modal");
|
||||||
);
|
|
||||||
|
|
||||||
assert.notOk(
|
assert
|
||||||
exists("#new-account-username"),
|
.dom("#new-account-username")
|
||||||
"it does not show the fields"
|
.doesNotExist("it does not show the fields");
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
acceptance("Create account - with associate link", function (needs) {
|
acceptance("Create account - with associate link", function (needs) {
|
||||||
needs.hooks.beforeEach(() => {
|
needs.hooks.beforeEach(function () {
|
||||||
setupAuthData({ associate_url: "/associate/abcde" });
|
setupAuthData({ associate_url: "/associate/abcde" });
|
||||||
});
|
});
|
||||||
needs.hooks.afterEach(() => {
|
needs.hooks.afterEach(function () {
|
||||||
document
|
document.getElementById("data-authentication").remove();
|
||||||
.querySelector("head")
|
|
||||||
.removeChild(document.getElementById("data-authentication"));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("displays associate link when allowed", async function (assert) {
|
test("displays associate link when allowed", async function (assert) {
|
||||||
await visit("/");
|
await visit("/");
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
exists("#discourse-modal div.create-account-body"),
|
.dom(".d-modal.create-account")
|
||||||
"it shows the registration modal"
|
.exists("it shows the registration modal");
|
||||||
);
|
assert.dom("#new-account-username").exists("it shows the fields");
|
||||||
assert.ok(exists("#new-account-username"), "it shows the fields");
|
assert
|
||||||
assert.ok(
|
.dom(".create-account-associate-link")
|
||||||
exists(".create-account-associate-link"),
|
.exists("it shows the associate link");
|
||||||
"it shows the associate link"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers";
|
import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers";
|
||||||
import { test } from "qunit";
|
import { test } from "qunit";
|
||||||
import {
|
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||||
acceptance,
|
import I18n from "discourse-i18n";
|
||||||
count,
|
|
||||||
exists,
|
|
||||||
query,
|
|
||||||
} from "discourse/tests/helpers/qunit-helpers";
|
|
||||||
|
|
||||||
acceptance("Create Account - User Fields", function (needs) {
|
acceptance("Create Account - User Fields", function (needs) {
|
||||||
needs.site({
|
needs.site({
|
||||||
|
@ -35,28 +31,25 @@ acceptance("Create Account - User Fields", function (needs) {
|
||||||
await visit("/");
|
await visit("/");
|
||||||
await click("header .sign-up-button");
|
await click("header .sign-up-button");
|
||||||
|
|
||||||
assert.ok(exists(".create-account"), "it shows the create account modal");
|
assert.dom(".create-account").exists("it shows the create account modal");
|
||||||
assert.ok(exists(".user-field"), "it has at least one user field");
|
assert.dom(".user-field").exists("it has at least one user field");
|
||||||
|
|
||||||
await click(".modal-footer .btn-primary");
|
await click(".modal-footer .btn-primary");
|
||||||
assert.strictEqual(
|
assert
|
||||||
query("#account-email-validation").innerText.trim(),
|
.dom("#account-email-validation")
|
||||||
"Please enter an email address"
|
.hasText(I18n.t("user.email.required"));
|
||||||
);
|
|
||||||
|
|
||||||
await fillIn("#new-account-name", "Dr. Good Tuna");
|
await fillIn("#new-account-name", "Dr. Good Tuna");
|
||||||
await fillIn("#new-account-password", "cool password bro");
|
await fillIn("#new-account-password", "cool password bro");
|
||||||
await fillIn("#new-account-email", "good.tuna@test.com");
|
await fillIn("#new-account-email", "good.tuna@test.com");
|
||||||
await fillIn("#new-account-username", "goodtuna");
|
await fillIn("#new-account-username", "goodtuna");
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
exists("#username-validation.good"),
|
.dom("#username-validation.good")
|
||||||
"the username validation is good"
|
.exists("the username validation is good");
|
||||||
);
|
assert
|
||||||
assert.ok(
|
.dom("#account-email-validation.good")
|
||||||
exists("#account-email-validation.good"),
|
.exists("the email validation is good");
|
||||||
"the email validation is good"
|
|
||||||
);
|
|
||||||
|
|
||||||
await click(".modal-footer .btn-primary");
|
await click(".modal-footer .btn-primary");
|
||||||
await fillIn(".user-field input[type=text]:nth-of-type(1)", "Barky");
|
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) {
|
test("can submit with enter", async function (assert) {
|
||||||
await visit("/");
|
await visit("/");
|
||||||
await click("header .sign-up-button");
|
await click("header .sign-up-button");
|
||||||
await triggerKeyEvent(".modal-footer .btn-primary", "keydown", "Enter");
|
await triggerKeyEvent("#new-account-email", "keydown", "Enter");
|
||||||
|
|
||||||
assert.strictEqual(
|
assert
|
||||||
count("#modal-alert:visible"),
|
.dom("#account-email-validation")
|
||||||
1,
|
.hasText(I18n.t("user.email.required"), "hitting Enter triggers action");
|
||||||
"hitting Enter triggers action"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("shows validation error for user fields", async function (assert) {
|
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");
|
await click(".modal-footer .btn-primary");
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
exists(".user-field-what-is-your-pets-name .tip.bad"),
|
.dom(".user-field-what-is-your-pets-name .tip.bad")
|
||||||
"shows required field error"
|
.exists("shows required field error");
|
||||||
);
|
|
||||||
|
|
||||||
assert.ok(
|
assert
|
||||||
exists(".user-field-whats-your-dad-like .tip.bad"),
|
.dom(".user-field-whats-your-dad-like .tip.bad")
|
||||||
"shows same as password error"
|
.exists("shows same as password error");
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,20 +3,21 @@ import { setupTest } from "ember-qunit";
|
||||||
import { module, test } from "qunit";
|
import { module, test } from "qunit";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
|
|
||||||
module("Unit | Controller | create-account", function (hooks) {
|
module("Unit | Component | create-account", function (hooks) {
|
||||||
setupTest(hooks);
|
setupTest(hooks);
|
||||||
|
|
||||||
test("basicUsernameValidation", function (assert) {
|
test("basicUsernameValidation", function (assert) {
|
||||||
const testInvalidUsername = (username, expectedReason) => {
|
const testInvalidUsername = (username, expectedReason) => {
|
||||||
const controller = this.owner.lookup("controller:create-account");
|
const component = this.owner
|
||||||
controller.set("accountUsername", username);
|
.factoryFor("component:modal/create-account")
|
||||||
|
.create({ model: { accountUsername: username } });
|
||||||
|
|
||||||
let validation = controller.basicUsernameValidation(username);
|
const validation = component.basicUsernameValidation(username);
|
||||||
assert.ok(validation.failed, "username should be invalid: " + username);
|
assert.true(validation.failed, `username should be invalid: ${username}`);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
validation.reason,
|
validation.reason,
|
||||||
expectedReason,
|
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")
|
I18n.t("user.username.too_long")
|
||||||
);
|
);
|
||||||
|
|
||||||
const controller = this.owner.lookup("controller:create-account");
|
const component = this.owner
|
||||||
controller.setProperties({
|
.factoryFor("component:modal/create-account")
|
||||||
accountUsername: "porkchops",
|
.create({ model: { accountUsername: "porkchops" } });
|
||||||
prefilledUsername: "porkchops",
|
component.set("prefilledUsername", "porkchops");
|
||||||
});
|
|
||||||
|
|
||||||
let validation = controller.basicUsernameValidation("porkchops");
|
const validation = component.basicUsernameValidation("porkchops");
|
||||||
assert.ok(validation.ok, "Prefilled username is valid");
|
assert.true(validation.ok, "Prefilled username is valid");
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
validation.reason,
|
validation.reason,
|
||||||
I18n.t("user.username.prefilled"),
|
I18n.t("user.username.prefilled"),
|
||||||
|
@ -43,35 +43,33 @@ module("Unit | Controller | create-account", function (hooks) {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("passwordValidation", async function (assert) {
|
test("passwordValidation", async function (assert) {
|
||||||
const controller = this.owner.lookup("controller:create-account");
|
const component = this.owner
|
||||||
|
.factoryFor("component:modal/create-account")
|
||||||
controller.set("authProvider", "");
|
.create({
|
||||||
controller.set("accountEmail", "pork@chops.com");
|
model: {
|
||||||
controller.set("accountUsername", "porkchops123");
|
accountEmail: "pork@chops.com",
|
||||||
controller.set("prefilledUsername", "porkchops123");
|
accountUsername: "porkchops123",
|
||||||
controller.set("accountPassword", "b4fcdae11f9167");
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
component.set("prefilledUsername", "porkchops123");
|
||||||
|
component.set("accountPassword", "b4fcdae11f9167");
|
||||||
|
assert.true(component.passwordValidation.ok, "Password is ok");
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
controller.passwordValidation.ok,
|
component.passwordValidation.reason,
|
||||||
true,
|
|
||||||
"Password is ok"
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
controller.passwordValidation.reason,
|
|
||||||
I18n.t("user.password.ok"),
|
I18n.t("user.password.ok"),
|
||||||
"Password is valid"
|
"Password is valid"
|
||||||
);
|
);
|
||||||
|
|
||||||
const testInvalidPassword = (password, expectedReason) => {
|
const testInvalidPassword = (password, expectedReason) => {
|
||||||
controller.set("accountPassword", password);
|
component.set("accountPassword", password);
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.true(
|
||||||
controller.passwordValidation.failed,
|
component.passwordValidation.failed,
|
||||||
true,
|
|
||||||
`password should be invalid: ${password}`
|
`password should be invalid: ${password}`
|
||||||
);
|
);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
controller.passwordValidation.reason,
|
component.passwordValidation.reason,
|
||||||
expectedReason,
|
expectedReason,
|
||||||
`password validation reason: ${password}, ${expectedReason}`
|
`password validation reason: ${password}, ${expectedReason}`
|
||||||
);
|
);
|
||||||
|
@ -93,17 +91,19 @@ module("Unit | Controller | create-account", function (hooks) {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("authProviderDisplayName", function (assert) {
|
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(
|
assert.strictEqual(
|
||||||
controller.authProviderDisplayName("facebook"),
|
component.authProviderDisplayName("facebook"),
|
||||||
I18n.t("login.facebook.name"),
|
I18n.t("login.facebook.name"),
|
||||||
"provider name is translated correctly"
|
"provider name is translated correctly"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
controller.authProviderDisplayName("idontexist"),
|
component.authProviderDisplayName("does-not-exist"),
|
||||||
"idontexist",
|
"does-not-exist",
|
||||||
"provider name falls back if not found"
|
"provider name falls back if not found"
|
||||||
);
|
);
|
||||||
});
|
});
|
|
@ -298,10 +298,11 @@ body.invite-page {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 701px) {
|
@media screen and (min-width: 701px) {
|
||||||
.create-account-body {
|
.modal-body {
|
||||||
max-width: 40em;
|
max-width: 40em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-field {
|
.user-field {
|
||||||
input[type="text"] {
|
input[type="text"] {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
|
@ -206,12 +206,11 @@
|
||||||
// create account
|
// create account
|
||||||
// modal only
|
// modal only
|
||||||
.d-modal.create-account {
|
.d-modal.create-account {
|
||||||
.create-account-body {
|
|
||||||
min-width: 100%;
|
|
||||||
}
|
|
||||||
.modal-body {
|
.modal-body {
|
||||||
|
min-width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.has-alt-auth .create-account-form {
|
.has-alt-auth .create-account-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 60% 40%;
|
grid-template-columns: 60% 40%;
|
||||||
|
|
|
@ -183,15 +183,27 @@
|
||||||
// create account
|
// create account
|
||||||
// modal only
|
// modal only
|
||||||
|
|
||||||
#discourse-modal .create-account .modal-body {
|
|
||||||
max-height: 60vh !important;
|
|
||||||
overflow: hidden;
|
|
||||||
@media screen and (max-height: 575px) {
|
|
||||||
max-height: 50vh !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.d-modal.create-account {
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login-buttons {
|
||||||
|
border-bottom: 1px solid var(--primary-low);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.create-account-form {
|
.create-account-form {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
.login-welcome-header {
|
.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 {
|
.create-account {
|
||||||
.user-fields {
|
.user-fields {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
Loading…
Reference in New Issue