Login signup zazz (#11957)

This PR re-styles the login and create account modals.
This commit is contained in:
Jordan Vidrine 2021-02-10 14:53:18 -06:00 committed by GitHub
parent 50c3cc7d75
commit 4580595bd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 865 additions and 561 deletions

View File

@ -21,4 +21,4 @@
label="admin.user_fields.create"
icon="plus"}}
</div>
</div>

View File

@ -4,6 +4,23 @@ import cookie from "discourse/lib/cookie";
export default Component.extend({
classNames: ["create-account"],
userInputFocus(event) {
let label = event.target.parentElement.previousElementSibling;
if (!label.classList.contains("value-entered")) {
label.classList.toggle("value-entered");
}
},
userInputFocusOut(event) {
let label = event.target.parentElement.previousElementSibling;
if (
event.target.value.length === 0 &&
label.classList.contains("value-entered")
) {
label.classList.toggle("value-entered");
}
},
didInsertElement() {
this._super(...arguments);
@ -11,6 +28,21 @@ export default Component.extend({
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).on("keydown.discourse-create-account", (e) => {
if (!this.disabled && e.keyCode === 13) {
e.preventDefault();
@ -36,5 +68,20 @@ export default Component.extend({
$(this.element).off("keydown.discourse-create-account");
$(this.element).off("click.dropdown-user-field-label");
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);
}
}
},
});

View File

@ -22,6 +22,7 @@ import { isEmpty } from "@ember/utils";
import { notEmpty } from "@ember/object/computed";
import { setting } from "discourse/lib/computed";
import { userPath } from "discourse/lib/url";
import { helperContext } from "discourse-common/lib/helpers";
export default Controller.extend(
ModalFunctionality,
@ -77,6 +78,15 @@ export default Controller.extend(
return false;
},
@discourseComputed()
wavingHandURL() {
const emojiSet = helperContext().siteSettings.emoji_set;
// random number between 2 -6 to render multiple skin tone waving hands
const random = Math.floor(Math.random() * (7 - 2) + 2);
return getURL(`/images/emoji/${emojiSet}/wave/${random}.png`);
},
@discourseComputed("userFields", "hasAtLeastOneLoginButton")
modalBodyClasses(userFields, hasAtLeastOneLoginButton) {
const classes = [];

View File

@ -1,5 +1,5 @@
import Controller, { inject as controller } from "@ember/controller";
import { alias, or, readOnly } from "@ember/object/computed";
import { alias, not, or, readOnly } from "@ember/object/computed";
import { areCookiesEnabled, escapeExpression } from "discourse/lib/utilities";
import cookie, { removeCookie } from "discourse/lib/cookie";
import { next, schedule } from "@ember/runloop";
@ -18,6 +18,7 @@ import { getWebauthnCredential } from "discourse/lib/webauthn";
import { isEmpty } from "@ember/utils";
import { setting } from "discourse/lib/computed";
import showModal from "discourse/lib/show-modal";
import { helperContext } from "discourse-common/lib/helpers";
// This is happening outside of the app via popup
const AuthErrors = [
@ -45,6 +46,8 @@ export default Controller.extend(ModalFunctionality, {
loginRequired: alias("application.loginRequired"),
secondFactorMethod: SECOND_FACTOR_METHODS.TOTP,
noLoginLocal: not("canLoginLocal"),
resetForm() {
this.setProperties({
loggingIn: false,
@ -62,20 +65,42 @@ export default Controller.extend(ModalFunctionality, {
return showSecondFactor || showSecurityKey ? "hidden" : "";
},
@discourseComputed()
wavingHandURL() {
const emojiSet = helperContext().siteSettings.emoji_set;
// random number between 2 -6 to render multiple skin tone waving hands
const random = Math.floor(Math.random() * (7 - 2) + 2);
return getURL(`/images/emoji/${emojiSet}/wave/${random}.png`);
},
@discourseComputed("showSecondFactor", "showSecurityKey")
secondFactorClass(showSecondFactor, showSecurityKey) {
return showSecondFactor || showSecurityKey ? "" : "hidden";
},
@discourseComputed("awaitingApproval", "hasAtLeastOneLoginButton")
modalBodyClasses(awaitingApproval, hasAtLeastOneLoginButton) {
@discourseComputed(
"awaitingApproval",
"hasAtLeastOneLoginButton",
"showSecondFactor",
"canLoginLocal"
)
modalBodyClasses(
awaitingApproval,
hasAtLeastOneLoginButton,
showSecondFactor,
canLoginLocal
) {
const classes = ["login-modal"];
if (awaitingApproval) {
classes.push("awaiting-approval");
}
if (hasAtLeastOneLoginButton) {
if (hasAtLeastOneLoginButton && !showSecondFactor) {
classes.push("has-alt-auth");
}
if (!canLoginLocal) {
classes.push("no-local-login");
}
return classes.join(" ");
},

View File

@ -0,0 +1,11 @@
import { registerUnbound } from "discourse-common/lib/helpers";
registerUnbound("value-entered", function (value) {
if (!value) {
return "";
} else if (value.length > 0) {
return "value-entered";
} else {
return "";
}
});

View File

@ -1,6 +1,10 @@
{{#login-modal loginName=loginName loginPassword=loginPassword secondFactorToken=secondFactorToken action=(action "login")}}
{{plugin-outlet name="login-before-modal-body" tagName=""}}
{{#d-modal-body title="login.title" class=modalBodyClasses}}
{{#d-modal-body class=modalBodyClasses}}
<div class="login-welcome-header">
<h1 class="login-title">{{i18n "login.header_title"}}</h1> <img src={{wavingHandURL}} alt="" class="waving-hand">
<p class="login-subheader">{{i18n "login.subheader_title"}}</p>
</div>
{{#if showLoginButtons}}
{{login-buttons
showLoginWithEmailLink=showLoginWithEmailLink
@ -12,32 +16,15 @@
{{#if canLoginLocal}}
<form id="login-form" method="post">
<div id="credentials" class={{credentialsClass}}>
<table>
<tbody>
<tr>
<td>
<label for="login-account-name">{{i18n "login.username"}}</label>
</td>
<td>
{{text-field value=loginName type="email" placeholderKey="login.email_placeholder" id="login-account-name" autocorrect="off" autocapitalize="off" disabled=showSecondFactor}}
</td>
</tr>
<tr>
<td>
<label for="login-account-password">{{i18n "login.password"}}</label>
</td>
<td>
{{text-field value=loginPassword type="password" id="login-account-password" maxlength="200" disabled=showSecondFactor}}
</td>
</tr>
<tr>
<td></td>
<td>
<a href id="forgot-password-link" {{action "showForgotPassword"}}>{{i18n "forgot_password.action"}}</a>
</td>
</tr>
</tbody>
</table>
<div class="input-group">
{{input value=loginName class=(value-entered loginName)type="email" id="login-account-name" autocorrect="off" autocapitalize="off" disabled=showSecondFactor}}
<label class="alt-placeholder" for="login-account-name">{{i18n "login.email_placeholder"}}</label>
</div>
<div class="input-group">
{{input value=loginPassword class=(value-entered loginPassword) type="password" id="login-account-password" maxlength="200" disabled=showSecondFactor}}
<label class="alt-placeholder" for="login-account-password">{{i18n "login.password"}}</label>
<a href id="forgot-password-link" {{action "showForgotPassword"}}>{{i18n "forgot_password.action"}}</a>
</div>
</div>
{{#second-factor-form
secondFactorMethod=secondFactorMethod

View File

@ -1,172 +1,152 @@
{{#create-account email=accountEmail disabled=submitDisabled action=(action "createAccount")}}
{{#unless complete}}
{{plugin-outlet name="create-account-before-modal-body"}}
{{#d-modal-body title="create_account.title" class=modalBodyClasses}}
{{#unless hasAuthOptions}}
{{login-buttons externalLogin=(action "externalLogin")}}
{{/unless}}
{{#d-modal-body class=modalBodyClasses}}
{{#if skipConfirmation}}
{{loading-spinner size="large"}}
{{/if}}
{{#if showCreateForm}}
<div class="login-form">
<form>
<table>
<tbody>
<tr class="input create-account-email">
<td class="label">
<label for="new-account-email">
{{i18n "user.email.title"}}
{{~#if userFields~}}
<span class="required">*</span>
{{/if}}
</label>
</td>
<td>
{{#if emailValidated}}
<span class="value">{{accountEmail}}</span>
{{else}}
{{input type="email" value=accountEmail id="new-account-email" name="email" autofocus="autofocus"}}
<div class="create-account-form">
<div class="login-welcome-header">
<h1 class="login-title">{{i18n "create_account.header_title"}}</h1> <img src={{wavingHandURL}} alt="" class="waving-hand">
<p class="login-subheader">{{i18n "create_account.subheader_title"}}</p>
</div>
{{#unless hasAuthOptions}}
<div class="create-account-login-buttons">
{{login-buttons externalLogin=(action "externalLogin")}}
</div>
{{/unless}}
<div class="login-form">
<form>
<div class="input-group">
{{#if emailValidated}}
<span class="value">{{accountEmail}}</span>
{{else}}
{{input type="email" value=accountEmail id="new-account-email" name="email" class=(value-entered accountEmail)}}
<label class="alt-placeholder" for="new-account-email">
{{i18n "user.email.title"}}
{{~#if userFields~}}
<span class="required">*</span>
{{/if}}
</td>
</tr>
</label>
{{/if}}
{{input-tip validation=emailValidation id="account-email-validation"}}
<label class="more-info" for="new-account-email">{{i18n "user.email.instructions"}}</label>
</div>
<tr class="instructions create-account-email">
<td></td>
{{input-tip validation=emailValidation id="account-email-validation"}}
<td><label>{{i18n "user.email.instructions"}}</label></td>
</tr>
<tr class="input">
<td class="label">
<label for="new-account-username">
{{i18n "user.username.title"}}
{{~#if userFields~}}
<span class="required">*</span>
{{/if}}
</label>
</td>
<td>
{{#if usernameDisabled}}
<span class="value">{{accountUsername}}</span>
{{else}}
{{input value=accountUsername id="new-account-username" name="username" maxlength=maxUsernameLength autocomplete="discourse"}}
<div class="input-group">
{{#if usernameDisabled}}
<span class="value">{{accountUsername}}</span>
{{else}}
{{input value=accountUsername class=(value-entered accountUsername) id="new-account-username" name="username" maxlength=maxUsernameLength
autocomplete="discourse"}}
<label class="alt-placeholder" for="new-account-username">
{{i18n "user.username.title"}}
{{~#if userFields~}}
<span class="required">*</span>
{{/if}}
</td>
</tr>
<tr class="instructions">
<td></td>
{{input-tip validation=usernameValidation id="username-validation"}}
<td><label>{{i18n "user.username.instructions"}}</label></td>
</tr>
{{#if fullnameRequired}}
<tr class="input">
<td class="label">
<label for="new-account-name">
{{i18n "user.name.title"}}
{{#if siteSettings.full_name_required}}
{{~#if userFields~}}
<span class="required">*</span>
{{/if}}
{{/if}}
</label>
</td>
<td>
{{#if nameDisabled}}
<span class="value">{{accountName}}</span>
{{else}}
{{text-field value=accountName id="new-account-name"}}
{{/if}}
</td>
</tr>
<tr class="instructions">
<td></td>
{{input-tip validation=nameValidation}}
<td><label>{{nameInstructions}}</label></td>
</tr>
</label>
{{/if}}
{{plugin-outlet
name="create-account-before-password"
noTags=true
args=(hash
accountName=accountName
accountUsername=accountUsername
accountPassword=accountPassword
userFields=userFields
authOptions=authOptions
)
}}
{{input-tip validation=usernameValidation id="username-validation"}}
<label class="more-info" for="new-account-username">{{i18n "user.username.instructions"}}</label>
</div>
{{#if passwordRequired}}
<tr class="input">
<td class="label">
<label for="new-account-password">
{{i18n "user.password.title"}}
<div class="input-group">
{{#if fullnameRequired}}
{{#if nameDisabled}}
<span class="value">{{accountName}}</span>
{{else}}
{{text-field value=accountName id="new-account-name" class=(value-entered accountName)}}
<label class="alt-placeholder" for="new-account-name">
{{i18n "user.name.title"}}
{{#if siteSettings.full_name_required}}
{{~#if userFields~}}
<span class="required">*</span>
{{/if}}
</label>
</td>
<td>
{{password-field value=accountPassword type="password" id="new-account-password" capsLockOn=capsLockOn}}
</td>
</tr>
<tr class="instructions">
<td></td>
{{input-tip validation=passwordValidation}}
<td>
<label>{{passwordInstructions}}</label>
<div class="caps-lock-warning {{unless capsLockOn "hidden"}}">
{{d-icon "exclamation-triangle"}} {{i18n "login.caps_lock_warning"}}
</div>
</td>
</tr>
{{/if}}
</label>
{{/if}}
{{input-tip validation=nameValidation}}
<label class="more-info" for="new-account-name">{{nameInstructions}}</label>
{{/if}}
<tr class="password-confirmation">
<td><label for="new-account-password-confirmation">{{i18n "user.password_confirmation.title"}}</label></td>
<td>
{{honeypot-input id="new-account-confirmation" autocomplete="new-password" value=accountHoneypot}}
{{input value=accountChallenge id="new-account-challenge"}}
</td>
</tr>
{{#if requireInviteCode }}
<tr class="invite-code">
<td><label for="invite-code">{{i18n "user.invite_code.title"}}</label></td>
<td>
{{input value=inviteCode id="inviteCode"}}
</td>
<td><label>{{i18n "user.invite_code.instructions"}}</label></td>
</tr>
{{/if}}
{{plugin-outlet
name="create-account-after-password"
noTags=true
args=(hash
accountName=accountName
accountUsername=accountUsername
accountPassword=accountPassword
userFields=userFields
)
}}
</tbody>
</table>
{{#if userFields}}
<div class="user-fields">
{{#each userFields as |f|}}
{{user-field field=f.field value=f.value}}
{{/each}}
</div>
{{/if}}
</form>
{{plugin-outlet
name="create-account-before-password"
noTags=true
args=(hash
accountName=accountName
accountUsername=accountUsername
accountPassword=accountPassword
userFields=userFields
authOptions=authOptions
)
}}
<div class="input-group">
{{#if passwordRequired}}
{{password-field value=accountPassword class=(value-entered accountPassword) type="password" id="new-account-password" autocomplete="current-password" capsLockOn=capsLockOn}}
<label class="alt-placeholder" for="new-account-password">
{{i18n "user.password.title"}}
{{~#if userFields~}}
<span class="required">*</span>
{{/if}}
</label>
{{input-tip validation=passwordValidation}}
<label class="more-info" for="new-account-password">{{passwordInstructions}}</label>
<div class="caps-lock-warning {{unless capsLockOn " hidden"}}">
{{d-icon "exclamation-triangle"}} {{i18n "login.caps_lock_warning"}}
</div>
{{/if}}
<div class="password-confirmation">
<label for="new-account-password-confirmation">{{i18n "user.password_confirmation.title"}}</label>
{{honeypot-input id="new-account-confirmation" autocomplete="new-password" value=accountHoneypot}}
{{input value=accountChallenge id="new-account-challenge"}}
</div>
</div>
{{#if requireInviteCode }}
<div class="input-group">
{{input value=inviteCode class=(value-entered inviteCode) id="inviteCode"}}
<label class="alt-placeholder" for="invite-code">{{i18n "user.invite_code.title"}}</label>
<label class="more-info" for="invite-code">{{i18n "user.invite_code.instructions"}}</label>
</div>
{{/if}}
{{plugin-outlet
name="create-account-after-password"
noTags=true
args=(hash
accountName=accountName
accountUsername=accountUsername
accountPassword=accountPassword
userFields=userFields
)
}}
{{#if userFields}}
<div class="user-fields">
{{#each 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 --}}
{{user-field field=f.field value=f.value
class=(value-entered f.value)}}
</div>
{{/each}}
</div>
{{/if}}
</form>
</div>
</div>
{{/if}}
{{/d-modal-body}}
@ -185,10 +165,12 @@
{{d-button class="btn-large" id="login-link" action=(route-action "showLogin") disabled=formSubmitted label="log_in"}}
{{/unless}}
<div class="disclaimer">{{html-safe disclaimerHtml}}</div>
<div class="disclaimer">
{{html-safe disclaimerHtml}}
</div>
</div>
{{plugin-outlet name="create-account-after-modal-footer" tagName=""}}
{{/if}}
{{/unless}}
{{/create-account}}
{{/create-account}}

View File

@ -1,85 +1,89 @@
{{#login-modal loginName=loginName loginPassword=loginPassword secondFactorToken=secondFactorToken action=(action "login")}}
{{plugin-outlet name="login-before-modal-body" tagName=""}}
{{#d-modal-body title="login.title" class=modalBodyClasses}}
{{#d-modal-body class=modalBodyClasses}}
{{#if canLoginLocal}}
<form id="login-form" method="post">
<div id="credentials" class={{credentialsClass}}>
<table>
<tbody>
<tr>
<td><label for="login-account-name">{{i18n "login.username"}}</label></td>
<td>{{text-field value=loginName placeholderKey="login.email_placeholder" id="login-account-name" autocorrect="off" autocapitalize="off" autofocus="autofocus" disabled=disableLoginFields}}</td>
</tr>
<tr>
<td><label for="login-account-password">{{i18n "login.password"}}</label></td>
<td>{{password-field value=loginPassword type="password" id="login-account-password" maxlength="200" capsLockOn=capsLockOn disabled=disableLoginFields}}</td>
<td><a href id="forgot-password-link" {{action "forgotPassword"}}>{{i18n "forgot_password.action"}}</a></td>
</tr>
<tr>
<td></td>
<td><div class="caps-lock-warning {{unless capsLockOn "hidden"}}">{{d-icon "exclamation-triangle"}} {{i18n "login.caps_lock_warning"}}</div></td>
<td></td>
</tr>
</tbody>
</table>
<div class="login-left-side">
<div class="login-welcome-header">
<h1 class="login-title">{{i18n "login.header_title"}}</h1> <img src={{wavingHandURL}} alt="" class="waving-hand">
<p class="login-subheader">{{i18n "login.subheader_title"}}</p>
</div>
{{#second-factor-form
secondFactorMethod=secondFactorMethod
secondFactorToken=secondFactorToken
class=secondFactorClass
backupEnabled=backupEnabled
isLogin=true}}
{{#if showSecurityKey}}
{{#security-key-form
allowedCredentialIds=securityKeyAllowedCredentialIds
challenge=securityKeyChallenge
showSecurityKey=showSecurityKey
showSecondFactor=showSecondFactor
secondFactorMethod=secondFactorMethod
otherMethodAllowed=otherMethodAllowed
action=(action "authenticateSecurityKey")}}
{{/security-key-form}}
{{else}}
{{second-factor-input value=secondFactorToken inputId="login-second-factor" secondFactorMethod=secondFactorMethod backupEnabled=backupEnabled}}
<form id="login-form" method="post">
<div id="credentials" class={{credentialsClass}}>
<div class="input-group">
{{input value=loginName type="email" id="login-account-name" class=(value-entered loginName) autocomplete="username" autocorrect="off" autocapitalize="off" disabled=showSecondFactor}}
<label class="alt-placeholder" for="login-account-name">{{i18n "login.email_placeholder"}}</label>
</div>
<div class="input-group">
{{input value=loginPassword type="password" class=(value-entered loginPassword) id="login-account-password" autocomplete="current-password" maxlength="200" capsLockOn=capsLockOn disabled=disableLoginFields}}
<label class="alt-placeholder" for="login-account-password">{{i18n "login.password"}}</label>
<a href id="forgot-password-link" {{action "forgotPassword"}}>{{i18n "forgot_password.action"}}</a>
<div class="caps-lock-warning {{unless capsLockOn "hidden"}}">{{d-icon "exclamation-triangle"}} {{i18n "login.caps_lock_warning"}}</div>
</div>
</div>
{{#second-factor-form
secondFactorMethod=secondFactorMethod
secondFactorToken=secondFactorToken
class=secondFactorClass
backupEnabled=backupEnabled
isLogin=true}}
{{#if showSecurityKey}}
{{#security-key-form
allowedCredentialIds=securityKeyAllowedCredentialIds
challenge=securityKeyChallenge
showSecurityKey=showSecurityKey
showSecondFactor=showSecondFactor
secondFactorMethod=secondFactorMethod
otherMethodAllowed=otherMethodAllowed
action=(action "authenticateSecurityKey")}}
{{/security-key-form}}
{{else}}
{{second-factor-input value=secondFactorToken inputId="login-second-factor" secondFactorMethod=secondFactorMethod backupEnabled=backupEnabled}}
{{/if}}
{{/second-factor-form}}
</form>
<div class="modal-footer">
{{#if canLoginLocal}}
{{#unless showSecurityKey }}
{{d-button
action=(action "login")
id="login-button"
form="login-form"
icon="unlock"
label=loginButtonLabel
disabled=loginDisabled
class="btn btn-large btn-primary"}}
{{/unless}}
{{#if showSignupLink}}
{{d-button
class="btn-large"
id="new-account-link"
action=(action "createAccount")
label="create_account.title"}}
{{/if}}
{{/if}}
{{/second-factor-form}}
</form>
{{conditional-loading-spinner condition=showSpinner size="small"}}
</div>
</div>
{{/if}}
{{#if showLoginButtons}}
{{login-buttons
showLoginWithEmailLink=showLoginWithEmailLink
processingEmailLink=processingEmailLink
emailLogin=(action "emailLogin")
externalLogin=(action "externalLogin")}}
<div class="login-right-side">
{{#if noLoginLocal}}
<div class="login-welcome-header">
<h1 class="login-title">{{i18n "login.header_title"}}</h1> <img src={{wavingHandURL}} alt="" class="waving-hand">
<p class="login-subheader">{{i18n "login.subheader_title"}}</p>
</div>
{{/if}}
{{login-buttons
showLoginWithEmailLink=showLoginWithEmailLink
processingEmailLink=processingEmailLink
emailLogin=(action "emailLogin")
externalLogin=(action "externalLogin")}}
</div>
{{/if}}
{{/d-modal-body}}
<div class="modal-footer">
{{#if canLoginLocal}}
{{#unless showSecurityKey }}
{{d-button
action=(action "login")
id="login-button"
form="login-form"
icon="unlock"
label=loginButtonLabel
disabled=loginDisabled
class="btn btn-large btn-primary"}}
{{/unless}}
{{#if showSignupLink}}
{{d-button
class="btn-large"
id="new-account-link"
action=(action "createAccount")
label="create_account.title"}}
{{/if}}
{{/if}}
{{conditional-loading-spinner condition=showSpinner size="small"}}
</div>
{{plugin-outlet name="login-after-modal-footer" tagName=""}}
<div class={{alertClass}} id="login-alert">{{alert}}</div>

View File

@ -12,19 +12,185 @@
display: none;
}
#login-form {
table {
width: 100%;
.login-modal:not(.hidden).has-alt-auth.no-local-login {
min-width: fit-content;
background: var(--secondary);
#login-buttons:not(.hidden) {
.btn.btn-social,
.btn,
.login-with-email-button {
border: 1px solid var(--primary-low-mid);
&:hover {
background: var(--primary-very-low);
}
}
}
a {
color: var(--primary-high);
}
// Create Account + Login
.d-modal.create-account,
.d-modal.login-modal {
.modal-inner-container {
position: relative;
}
td {
padding-right: 5px;
.modal-body {
padding: 0;
}
#credentials {
tr:first-of-type {
margin-bottom: 0.75em;
.modal-header {
border-bottom: none;
padding: 0;
position: absolute;
top: 1em;
right: 1em;
z-index: z("max");
}
.login-welcome-header {
z-index: z("modal", "content");
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr;
background: var(--secondary);
.login-title {
margin-bottom: 0;
}
.login-subheader {
align-self: start;
margin: 0;
}
.waving-hand {
width: 35px;
height: 35px;
margin-left: 1em;
align-self: center;
}
}
#login-buttons {
.btn {
justify-content: flex-start;
min-width: fit-content;
}
}
.modal-footer {
.inline-spinner {
display: inline-flex;
}
}
.modal-body.no-local-login + .modal-footer {
display: none;
}
&.awaiting-approval {
display: none;
}
}
// Login Form Styles
.login-modal:not(.hidden),
.create-account {
#login-form,
.login-form {
.input-group {
position: relative;
display: flex;
flex-direction: column;
margin-bottom: 1em;
&:last-child {
margin-bottom: 2em;
}
input {
padding: 0.75em 0.5em;
border-radius: 0.25em;
min-width: 250px;
box-shadow: none;
margin-bottom: 0.25em;
}
input:focus {
outline: none;
border: 1px solid var(--tertiary);
box-shadow: 0 0 0 2px rgba(var(--tertiary-rgb), 0.25);
}
label.more-info {
color: var(--primary-medium);
}
label.alt-placeholder {
color: var(--primary-medium);
font-size: 16px;
font-weight: normal;
position: absolute;
pointer-events: none;
left: 1em;
top: 10px;
box-shadow: 0 0 0 0px rgba(var(--tertiary-rgb), 0);
transition: 0.2s ease all;
}
input:focus + label.alt-placeholder,
input.value-entered + label.alt-placeholder {
top: -8px;
left: calc(1em - 0.25em);
background-color: var(--secondary);
padding: 0 0.25em 0 0.25em;
font-size: $font-down-1;
color: var(--primary-medium);
}
input.alt-placeholder:invalid {
color: var(--primary);
}
.tip:not(:empty) + label.more-info {
display: none;
}
}
#second-factor {
input {
width: 100%;
padding: 0.75em 0.5em;
border-radius: 0.25em;
min-width: 250px;
box-shadow: none;
margin-bottom: 2em;
}
input:focus {
outline: none;
border: 1px solid var(--tertiary);
box-shadow: 0 0 0 2px rgba(var(--tertiary-rgb), 0.25);
}
}
// user fields input groups will
// be styled differently
.user-fields .input-group {
.user-field {
&.text {
label.control-label {
color: var(--primary-medium);
font-size: 16px;
position: absolute;
pointer-events: none;
left: 1em;
top: 12px;
transition: 0.2s ease all;
max-width: calc(100% - 2em);
white-space: nowrap;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
}
&.value-entered label.control-label,
label.control-label.value-entered {
top: -8px;
left: calc(1em - 0.25em);
background-color: var(--secondary);
padding: 0 0.25em 0 0.25em;
font-size: 14px;
color: var(--primary-high);
}
}
.controls .checkbox-label {
input[type="checkbox"].ember-checkbox {
width: 1em !important;
min-width: unset;
}
}
}
}
}
}
@ -33,72 +199,21 @@
cursor: pointer;
}
.login-modal,
.create-account {
tbody {
border: none;
}
tr {
border: none;
}
tr:not(.instructions) {
td {
padding: 10px 0 0 0;
}
}
td {
label,
input {
margin-bottom: 0;
width: 100%;
}
}
.modal-footer {
.inline-spinner {
display: inline-flex;
}
}
&.awaiting-approval {
display: none;
}
}
.create-account {
.disclaimer {
color: var(--primary-medium);
margin-top: 0.5em;
}
tr.input span.value {
margin-left: 10px;
}
.user-field {
> label {
margin-top: 0.75em;
margin-bottom: 0;
padding-top: 5px;
}
&:first-of-type > label {
margin-top: 0;
}
input[type="text"] {
margin-bottom: 0;
width: 100%;
}
.select-kit {
width: 100%;
}
.controls {
padding-top: 5px;
label {
width: auto;
text-align: left;
font-weight: normal;
}
.instructions {
margin-top: 0.15em;
color: var(--primary-medium);
@ -162,15 +277,6 @@
}
}
.modal tr.instructions {
display: flex;
flex-direction: column;
margin-top: 0.15em;
label {
color: var(--primary-medium);
}
}
// alternate login / create new account buttons should be de-emphasized
button#login-link,

View File

@ -161,14 +161,16 @@
// Social buttons
// --------------------------------------------------
.btn-social {
.d-icon {
color: #fff;
}
color: #fff;
background: #666;
.btn-social,
.login-with-email-button {
border-radius: 0.25em;
color: #000;
background: #ffffff;
border: 1px solid transparent;
&:hover {
color: #fff;
color: #000 !important;
background: #ececec !important;
box-shadow: 0 0 0 2px rgba(var(--primary-rgb), 0.25);
}
&[href] {
color: var(--secondary);
@ -180,7 +182,6 @@
&.google_oauth2 {
background: var(--google);
color: #333;
border: 1px solid var(--primary-low);
// non-FA SVG icon for Google in login-buttons.hbs
.d-icon {
opacity: 0.9;
@ -188,38 +189,57 @@
&:hover,
&:focus {
color: currentColor;
background: var(--google-hover);
}
}
&.facebook {
background: $facebook;
&:hover,
&:focus {
background: var(--facebook-hover);
.d-icon {
color: $facebook;
}
&:hover {
.d-icon {
color: $facebook;
}
}
}
&.cas {
background: var(--cas);
.d-icon {
color: var(--cas);
}
&:hover {
.d-icon {
color: var(--cas);
}
}
}
&.twitter {
background: var(--twitter);
&:hover,
&:focus {
background: var(--twitter-hover);
.d-icon {
color: var(--twitter);
}
&:hover {
.d-icon {
color: var(--twitter);
}
}
}
&.github {
background: var(--github);
&:hover,
&:focus {
background: var(--github-hover);
.d-icon {
color: var(--github);
}
&:hover {
.d-icon {
color: var(--github);
}
}
}
&.discord {
background: var(--discord);
&:hover,
&:focus {
background: var(--discord-hover);
.d-icon {
color: var(--discord);
}
&:hover {
.d-icon {
color: var(--discord);
}
}
}
}

View File

@ -22,7 +22,7 @@ $google: #ffffff !default;
$google-hover: darken($google, 5%) !default;
$instagram: #e1306c !default;
$instagram-hover: darken($instagram, 15%) !default;
$facebook: #4267b2 !default;
$facebook: #1877f2 !default;
$facebook-hover: darken($facebook, 15%) !default;
$cas: #70ba61 !default;
$twitter: #1da1f2 !default;

View File

@ -3,21 +3,41 @@
// create account modals
.login-modal,
.create-account {
.modal-body {
&.has-alt-auth {
background: var(--tertiary);
}
@media (prefers-color-scheme: dark) {
&.has-alt-auth {
background: rgba(var(--tertiary-rgb), 0.5);
}
}
}
#modal-alert {
padding: 15px 16px;
}
.btn-flat.btn.modal-close svg {
color: rgba(var(--primary-rgb), 0.5);
&:hover {
color: rgba(var(--primary-rgb), 0.85);
}
}
#login-buttons:not(.hidden) {
display: flex;
flex: 0 1 auto;
flex-direction: column;
align-items: stretch;
min-height: 75px;
padding: 0 24px;
order: 2;
button {
display: flex;
text-align: left;
&:first-of-type {
margin-top: 2.8em;
}
&:last-of-type {
margin-bottom: 3em;
}
margin: 0.35em;
white-space: nowrap;
align-items: center;
&:lang(zh_CN) {
min-width: 200px;
}
@ -32,57 +52,14 @@
}
}
form {
min-width: 300px;
max-width: 100%;
}
#modal-alert {
max-width: 100%;
margin-bottom: 0;
padding: 8px 16px;
padding: 8px 40px 8px 16px;
min-height: 35px;
}
.login-modal {
#login-buttons:not(.hidden) {
button {
&:first-of-type {
margin-top: 2.8em;
}
&:last-of-type {
margin-bottom: 3em;
}
}
}
}
.create-account {
#login-buttons:not(.hidden) {
justify-content: flex-start;
padding-top: 2.7em;
+ .login-form {
border-right: 1px solid var(--primary-low); // Only show border when login-form is present
}
}
}
#login-form {
box-sizing: border-box;
flex: 1 0 auto;
padding: 0 24px;
+ #login-buttons {
border-left: 1px solid var(--primary-low); // Only show border when login-form is present
}
}
tr:not(.instructions) {
td {
display: flex;
padding: 5px 0 0 0;
}
}
.tip,
td label {
.tip {
display: block;
overflow: hidden;
word-wrap: break-word;
@ -95,25 +72,107 @@
}
.has-alt-auth {
.tip,
td label {
.tip {
max-width: 100%;
}
}
.login-modal:not(.hidden).has-alt-auth.no-local-login {
padding: 0 2em !important;
min-width: 350px !important;
.login-right-side {
width: 100%;
padding: 2em 0;
#login-buttons:not(.hidden) {
width: 80%;
.btn.btn-social:first-of-type {
margin-top: 2em;
}
.btn.btn-social,
.btn,
.login-with-email-button {
border: 1px solid var(--primary-low-mid);
margin-bottom: 0.5em;
&:hover {
background: var(--primary-very-low) !important;
}
}
}
}
}
// styles used on
// login modal only
.d-modal.login-modal {
.login-modal:not(.hidden) {
padding: 0 !important;
&.has-alt-auth {
min-width: 700px;
.login-left-side {
width: calc(60% - 4em);
padding: 1em 2em;
}
}
.login-left-side {
background: var(--secondary);
width: calc(100% - 4em);
display: flex;
flex-direction: column;
padding: 1em 4em 1em 2em;
.login-welcome-header {
.login-title {
font-size: 2.75em;
}
.login-subheader {
font-size: 1.125em !important;
}
}
#credentials {
display: flex;
flex-direction: column;
align-items: flex-start !important;
}
#login-form {
margin: 1.5em 0 0 0;
}
.modal-footer {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
padding: 0;
border: 0;
}
}
.login-right-side {
display: none;
}
&.has-alt-auth .login-right-side {
width: 40%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
#login-buttons {
button {
&.login-with-email-button {
border-radius: 4px;
.d-icon {
color: #000 !important;
}
&:hover {
box-shadow: 0 0 0 2px rgba(var(--primary-rgb), 0.25);
.d-icon {
color: #000 !important;
}
}
}
}
}
}
.modal-body,
#credentials {
display: flex;
align-items: center;
padding: 15px 0;
tr {
display: flex;
flex-direction: column;
}
&.hidden {
display: none;
@ -125,111 +184,151 @@
// create account
// modal only
.d-modal.create-account {
.modal-body {
display: flex;
padding: 15px 0;
.create-account {
position: relative;
.modal-body:not(.reorder-categories):not(.poll-ui-builder):not(.poll-breakdown) {
max-height: 65vh !important;
overflow: hidden;
}
}
.create-account-form tr {
.has-alt-auth .create-account-form {
display: grid;
grid-template-columns: 65% 35%;
grid-template-rows: 20% 80%;
grid-template-areas:
"header login-buttons"
"form login-buttons";
}
.create-account-form {
display: flex;
flex-direction: column;
width: 100%;
}
.create-account-login-buttons {
display: none;
}
.has-alt-auth .create-account-login-buttons {
grid-area: login-buttons;
padding: 1em;
display: flex;
flex-direction: column;
justify-content: center;
h3 {
color: var(--secondary);
text-align: center;
}
#login-buttons {
button:first-of-type {
margin-top: 0;
}
button {
margin-bottom: 1em;
}
}
}
.modal-body {
display: flex;
padding: 0;
}
.login-welcome-header {
grid-area: header;
padding: 1em 1em 1em 1em;
.login-title {
font-size: 2.75em;
}
.login-subheader {
font-size: 1.125em !important;
align-self: start;
}
}
.login-form {
position: relative;
display: flex;
overflow: hidden;
flex: 1 1 auto;
.modal-body:not(.has-alt-auth) .login-form {
&:after,
&:before {
width: 100%;
}
&:before {
top: calc(8em - 10px) !important;
}
}
.has-alt-auth .login-form,
.login-form {
background: var(--secondary);
padding: 1em;
grid-area: form;
overflow-y: scroll;
&:after,
&:before {
content: "";
display: block;
position: absolute;
width: 100%;
width: 65%;
pointer-events: none;
z-index: 100;
margin-left: -1em;
height: 1em;
}
&:after {
bottom: 0;
height: 35px;
@media screen and (max-height: 650px) {
height: 45px;
}
bottom: 93px;
background-image: linear-gradient(
to bottom,
rgba(var(--secondary-rgb), 0),
rgba(var(--secondary-rgb), 0.9)
rgba(var(--secondary-rgb), 1)
);
}
&:before {
top: 0;
height: 25px;
top: calc(8em + 4px);
background-image: linear-gradient(
to top,
rgba(var(--secondary-rgb), 0),
rgba(var(--secondary-rgb), 1)
);
}
form {
box-sizing: border-box;
padding: 16px 24px;
margin-bottom: 0;
max-height: 475px;
@media screen and (max-height: 650px) {
max-height: calc(65vh - 100px);
> *:last-child {
margin-bottom: 40px;
}
.input-group {
margin-bottom: 1em;
input {
width: 100%;
}
overflow-x: hidden;
overflow-y: auto;
}
form,
table {
width: 100%;
}
tr {
display: flex;
flex-direction: column;
margin-top: 0.15em;
&.password-confirmation {
display: none;
input:not(.value-entered):not(:focus) + label.alt-placeholder {
top: 12px;
}
}
}
tr.input {
input,
label {
margin-bottom: 0;
width: 100%;
.password-confirmation {
display: none;
}
.tip {
max-width: 340px;
scrollbar-color: rgba(var(--primary-rgb), 0.5) transparent;
&::-webkit-scrollbar {
width: 15px;
}
&::-webkit-scrollbar-track {
box-shadow: inset 0 0 14px 14px transparent;
border: 4px solid transparent;
}
&::-webkit-scrollbar-thumb {
box-shadow: inset 0 0 14px 14px rgba(var(--primary-rgb), 0.5);
border: 4px solid transparent;
border-radius: 14px;
}
/* set button(top and bottom of the scrollbar) */
&::-webkit-scrollbar-button {
display: none;
}
}
.user-fields {
margin-bottom: 20px;
margin-bottom: 1em;
}
.user-field {
display: flex;
flex-direction: column;
&.confirm {
margin-top: 5px;
}
> label {
width: auto;
}
.controls {
margin-left: 0;
}
}
.invites-show {
@ -266,11 +365,7 @@
.login-form {
.tip {
&:not(:empty) + td {
display: none;
}
&:not(:empty),
&:empty + td {
&:not(:empty) {
min-height: 1.75em;
&.bad .svg-icon {
vertical-align: middle;

View File

@ -1,77 +1,110 @@
// shared styles
// used in both login and
// create account modals
.login-modal,
.create-account {
.d-modal.login-modal:not(.hidden),
.d-modal.create-account {
.modal-body {
padding: 0;
}
&.fixed-modal .modal-inner-container {
max-width: 350px;
max-height: 90vh;
position: relative;
overflow: hidden;
}
.close {
padding: 0;
}
.login-welcome-header {
padding: 1.5em 1em;
margin: 0 auto;
}
#login-buttons:not(.hidden) {
display: flex;
flex: 1 0 auto;
flex-wrap: wrap;
justify-content: center;
width: 102%;
padding-bottom: 10px;
margin-left: -2%;
padding: 1em;
margin: 0;
button {
.btn {
padding: 0.53em 0.53em 0.53em 0.43em;
border: 1px solid var(--primary-low);
flex: 1 1 47%;
max-width: 70%;
margin: 1% 0 1% 2%;
font-size: $font-0;
margin: 0 0.5em 0.5em 0;
white-space: nowrap;
@media screen and (max-width: 360px) {
font-size: $font-0;
&:last-child {
margin-right: 0;
}
}
}
+ #login-form {
border-top: 1px solid var(--primary-low);
@media screen and (max-width: 750px) {
#login-buttons:not(.hidden) {
justify-content: center;
.btn {
&:nth-child(even) {
margin: 0 0 0.5em 0;
}
}
}
}
form {
display: flex;
justify-content: center;
flex-direction: column;
}
#login-form,
.login-form {
padding: 1em;
table {
width: 100%;
}
tr {
&.input td label {
margin-top: 0.75em;
}
td label {
max-width: 280px;
display: block;
overflow: hidden;
word-wrap: break-word;
}
&:not(.instructions) td {
padding: 2px 0 0 0;
}
&:not(.password-confirmation) {
form {
display: flex;
justify-content: center;
flex-direction: column;
}
.input-group {
input {
height: 2.25em;
margin-bottom: 0.25em;
}
margin-bottom: 1em;
label.alt-placeholder {
top: 8px;
}
input:focus + label,
input.value-entered + label.alt-placeholder {
top: -10px;
}
input.alt-placeholder:invalid {
color: var(--primary);
}
label.more-info {
color: var(--primary-medium);
}
}
}
.tip {
&:not(:empty) + td {
display: none;
.user-fields .input-group {
.text.user-field {
label.control-label {
top: 8px;
}
&.value-entered label.control-label,
label.control-label.value-entered {
top: -10px;
left: calc(1em - 0.25em);
background-color: var(--secondary);
padding: 0 0.25em 0 0.25em;
font-size: 14px;
color: var(--primary-medium);
}
}
.controls .checkbox-label {
align-items: center;
input[type="checkbox"].ember-checkbox {
height: 15px;
}
}
}
@ -105,38 +138,21 @@
.d-modal.login-modal {
#credentials {
width: 100%;
tr {
display: flex;
flex-direction: column;
}
}
#login-form {
margin-bottom: 0.75em;
td {
padding: 0;
width: 100%;
margin: 0 auto;
}
label {
float: left;
display: block;
}
textarea,
input,
select {
font-size: $font-up-1;
clear: left;
margin-top: 0;
}
}
}
tr {
td label {
margin-top: 0.75em;
padding: 4px 0;
}
.login-modal:not(.hidden).has-alt-auth.no-local-login {
#login-buttons:not(.hidden) {
width: 100%;
}
}
@ -146,24 +162,31 @@
#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 {
.create-account-form {
overflow-y: auto;
.login-welcome-header {
width: calc(100% - 2em);
position: sticky;
position: -webkit-sticky;
top: 0;
background-color: var(--secondary);
}
}
}
.create-account .modal-body {
display: flex;
flex-direction: column;
tr.instructions {
label {
color: var(--primary-med-or-secondary-med);
}
}
#login-buttons {
border-bottom: 1px solid var(--primary-low);
}
.login-form {
margin-bottom: 0.75em;
}
@ -178,16 +201,6 @@
.user-field {
display: flex;
flex-direction: column;
> label {
width: auto;
}
.controls {
margin-left: 0;
label {
line-height: $line-height-medium;
}
}
}
}

View File

@ -1674,8 +1674,10 @@ en:
search_hint: "username, email or IP address"
create_account:
header_title: "Welcome!"
subheader_title: "Let's create your account"
disclaimer: "By registering, you agree to the <a href='%{privacy_link}' target='blank'>privacy policy</a> and <a href='%{tos_link}' target='blank'>terms of service</a>."
title: "Create New Account"
title: "Create your account"
failed: "Something went wrong, perhaps this email is already registered, try the forgot password link"
forgot_password:
@ -1709,6 +1711,8 @@ en:
confirm_button: Finish Login
login:
header_title: "Welcome Back"
subheader_title: "Log in to your account"
title: "Log In"
username: "User"
password: "Password"
@ -1724,7 +1728,7 @@ en:
security_key_not_allowed_error: "The security key authentication process either timed out or was cancelled."
security_key_no_matching_credential_error: "No matching credentials could be found in the provided security key."
security_key_support_missing_error: "Your current device or browser does not support the use of security keys. Please use a different method."
email_placeholder: "email or username"
email_placeholder: "email / username"
caps_lock_warning: "Caps Lock is on"
error: "Unknown error"
cookies_error: "Your browser seems to have cookies disabled. You might not be able to log in without enabling them first."