UX: More improvements to login/signup forms (#29417)

This commit is contained in:
Jan Cernik 2024-10-30 13:33:06 -03:00 committed by GitHub
parent 852ff18fb9
commit 81396467d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 175 additions and 22 deletions

View File

@ -2,6 +2,7 @@
<div id="credentials" class={{this.credentialsClass}}> <div id="credentials" class={{this.credentialsClass}}>
<div class="input-group" {{did-insert this.passkeyConditionalLogin}}> <div class="input-group" {{did-insert this.passkeyConditionalLogin}}>
<Input <Input
{{on "focusin" this.scrollInputIntoView}}
@value={{@loginName}} @value={{@loginName}}
@type="email" @type="email"
id="login-account-name" id="login-account-name"
@ -32,6 +33,7 @@
</div> </div>
<div class="input-group"> <div class="input-group">
<PasswordField <PasswordField
{{on "focusin" this.scrollInputIntoView}}
{{on "keydown" this.loginOnEnter}} {{on "keydown" this.loginOnEnter}}
@value={{@loginPassword}} @value={{@loginPassword}}
@capsLockOn={{this.capsLockOn}} @capsLockOn={{this.capsLockOn}}
@ -90,6 +92,7 @@
<SecondFactorInput <SecondFactorInput
{{on "keydown" this.loginOnEnter}} {{on "keydown" this.loginOnEnter}}
{{on "input" (with-event-value (fn (mut @secondFactorToken)))}} {{on "input" (with-event-value (fn (mut @secondFactorToken)))}}
{{on "focusin" this.scrollInputIntoView}}
@secondFactorMethod={{@secondFactorMethod}} @secondFactorMethod={{@secondFactorMethod}}
value={{@secondFactorToken}} value={{@secondFactorToken}}
id="login-second-factor" id="login-second-factor"

View File

@ -39,6 +39,14 @@ export default class LocalLoginForm extends Component {
} }
} }
@action
scrollInputIntoView(event) {
event.target.scrollIntoView({
behavior: "smooth",
block: "center",
});
}
@action @action
togglePasswordMask() { togglePasswordMask() {
this.maskPassword = !this.maskPassword; this.maskPassword = !this.maskPassword;

View File

@ -20,7 +20,7 @@ const LoginPageCta = <template>
{{/unless}} {{/unless}}
{{#if @showSignupLink}} {{#if @showSignupLink}}
<span class="signup-page-cta__no-account-yet"> <span class="login-page-cta__no-account-yet">
{{i18n "create_account.no_account_yet"}} {{i18n "create_account.no_account_yet"}}
</span> </span>
<DButton <DButton

View File

@ -41,6 +41,7 @@
<div class="input-group create-account-email"> <div class="input-group create-account-email">
<Input <Input
{{on "focusout" this.checkEmailAvailability}} {{on "focusout" this.checkEmailAvailability}}
{{on "focusin" this.scrollInputIntoView}}
@type="email" @type="email"
@value={{this.model.accountEmail}} @value={{this.model.accountEmail}}
disabled={{this.emailDisabled}} disabled={{this.emailDisabled}}
@ -54,7 +55,12 @@
<label class="alt-placeholder" for="new-account-email"> <label class="alt-placeholder" for="new-account-email">
{{i18n "user.email.title"}} {{i18n "user.email.title"}}
</label> </label>
{{#if this.emailValidation.reason}} {{#if
(or
this.emailValidation.ok
(and this.emailValidationVisible this.emailValidation.reason)
)
}}
<InputTip <InputTip
@validation={{this.emailValidation}} @validation={{this.emailValidation}}
id="account-email-validation" id="account-email-validation"
@ -68,6 +74,7 @@
<div class="input-group create-account__username"> <div class="input-group create-account__username">
<Input <Input
{{on "focusin" this.scrollInputIntoView}}
@value={{this.model.accountUsername}} @value={{this.model.accountUsername}}
disabled={{this.usernameDisabled}} disabled={{this.usernameDisabled}}
maxlength={{this.maxUsernameLength}} maxlength={{this.maxUsernameLength}}
@ -102,6 +109,8 @@
<div class="input-group create-account__password"> <div class="input-group create-account__password">
{{#if this.passwordRequired}} {{#if this.passwordRequired}}
<PasswordField <PasswordField
{{on "focusout" this.showPasswordValidation}}
{{on "focusin" this.scrollInputIntoView}}
@value={{this.accountPassword}} @value={{this.accountPassword}}
@capsLockOn={{this.capsLockOn}} @capsLockOn={{this.capsLockOn}}
type={{if this.maskPassword "password" "text"}} type={{if this.maskPassword "password" "text"}}
@ -117,10 +126,20 @@
<div class="create-account__password-info"> <div class="create-account__password-info">
<div class="create-account__password-tip-validation"> <div class="create-account__password-tip-validation">
<InputTip {{#if
@validation={{this.passwordValidation}} (or
id="password-validation" this.passwordValidation.ok
/> (and
this.passwordValidationVisible
this.passwordValidation.reason
)
)
}}
<InputTip
@validation={{this.passwordValidation}}
id="password-validation"
/>
{{/if}}
<div <div
class={{concat-class class={{concat-class
"caps-lock-warning" "caps-lock-warning"
@ -157,6 +176,7 @@
{{#if this.requireInviteCode}} {{#if this.requireInviteCode}}
<div class="input-group create-account__invite-code"> <div class="input-group create-account__invite-code">
<Input <Input
{{on "focusin" this.scrollInputIntoView}}
@value={{this.inviteCode}} @value={{this.inviteCode}}
id="inviteCode" id="inviteCode"
class={{value-entered this.inviteCode}} class={{value-entered this.inviteCode}}
@ -189,6 +209,7 @@
> >
{{#if this.fullnameRequired}} {{#if this.fullnameRequired}}
<TextField <TextField
{{on "focusin" this.scrollInputIntoView}}
@disabled={{this.nameDisabled}} @disabled={{this.nameDisabled}}
@value={{this.model.accountName}} @value={{this.model.accountName}}
@id="new-account-name" @id="new-account-name"
@ -212,6 +233,7 @@
{{#each this.userFields as |f|}} {{#each this.userFields as |f|}}
<div class="input-group"> <div class="input-group">
<UserField <UserField
{{on "focusin" this.scrollInputIntoView}}
@field={{f.field}} @field={{f.field}}
@value={{f.value}} @value={{f.value}}
@validation={{f.validation}} @validation={{f.validation}}

View File

@ -39,6 +39,8 @@ export default class CreateAccount extends Component.extend(
userFields = null; userFields = null;
isDeveloper = false; isDeveloper = false;
maskPassword = true; maskPassword = true;
passwordValidationVisible = false;
emailValidationVisible = false;
@notEmpty("model.authOptions") hasAuthOptions; @notEmpty("model.authOptions") hasAuthOptions;
@setting("enable_local_logins") canCreateLocal; @setting("enable_local_logins") canCreateLocal;
@ -215,8 +217,18 @@ export default class CreateAccount extends Component.extend(
}); });
} }
@action
showPasswordValidation() {
this.set(
"passwordValidationVisible",
Boolean(this.passwordValidation.reason)
);
}
@action @action
checkEmailAvailability() { checkEmailAvailability() {
this.set("emailValidationVisible", Boolean(this.emailValidation.reason));
if ( if (
!this.emailValidation.ok || !this.emailValidation.ok ||
this.serverAccountEmail === this.model.accountEmail this.serverAccountEmail === this.model.accountEmail
@ -438,6 +450,14 @@ export default class CreateAccount extends Component.extend(
}); });
} }
@action
scrollInputIntoView(event) {
event.target.scrollIntoView({
behavior: "smooth",
block: "center",
});
}
@action @action
togglePasswordMask() { togglePasswordMask() {
this.toggleProperty("maskPassword"); this.toggleProperty("maskPassword");
@ -453,6 +473,8 @@ export default class CreateAccount extends Component.extend(
createAccount() { createAccount() {
this.set("flash", ""); this.set("flash", "");
this.set("forceValidationReason", true); this.set("forceValidationReason", true);
this.set("emailValidationVisible", true);
this.set("passwordValidationVisible", true);
const validation = [ const validation = [
this.emailValidation, this.emailValidation,

View File

@ -35,7 +35,7 @@ export default class LoginPageController extends Controller {
@tracked loggingIn = false; @tracked loggingIn = false;
@tracked loggedIn = false; @tracked loggedIn = false;
@tracked showLoginButtons = true; @tracked showLoginButtons = true;
@tracked showLogin = false; @tracked showLogin = true;
@tracked showSecondFactor = false; @tracked showSecondFactor = false;
@tracked loginPassword = ""; @tracked loginPassword = "";
@tracked loginName = ""; @tracked loginName = "";

View File

@ -39,6 +39,8 @@ export default class SignupPageController extends Controller.extend(
userFields = null; userFields = null;
isDeveloper = false; isDeveloper = false;
maskPassword = true; maskPassword = true;
passwordValidationVisible = false;
emailValidationVisible = false;
@notEmpty("authOptions") hasAuthOptions; @notEmpty("authOptions") hasAuthOptions;
@setting("enable_local_logins") canCreateLocal; @setting("enable_local_logins") canCreateLocal;
@ -204,8 +206,23 @@ export default class SignupPageController extends Controller.extend(
}); });
} }
@action
showPasswordValidation() {
if (this.passwordValidation.reason) {
this.set("passwordValidationVisible", true);
} else {
this.set("passwordValidationVisible", false);
}
}
@action @action
checkEmailAvailability() { checkEmailAvailability() {
if (this.emailValidation.reason) {
this.set("emailValidationVisible", true);
} else {
this.set("emailValidationVisible", false);
}
if ( if (
!this.emailValidation.ok || !this.emailValidation.ok ||
this.serverAccountEmail === this.accountEmail this.serverAccountEmail === this.accountEmail
@ -423,6 +440,14 @@ export default class SignupPageController extends Controller.extend(
}); });
} }
@action
scrollInputIntoView(event) {
event.target.scrollIntoView({
behavior: "smooth",
block: "center",
});
}
@action @action
togglePasswordMask() { togglePasswordMask() {
this.toggleProperty("maskPassword"); this.toggleProperty("maskPassword");
@ -438,6 +463,8 @@ export default class SignupPageController extends Controller.extend(
createAccount() { createAccount() {
this.set("flash", ""); this.set("flash", "");
this.set("forceValidationReason", true); this.set("forceValidationReason", true);
this.set("emailValidationVisible", true);
this.set("passwordValidationVisible", true);
const validation = [ const validation = [
this.emailValidation, this.emailValidation,

View File

@ -300,6 +300,9 @@ export default class ApplicationRoute extends DiscourseRoute {
} else if (this.siteSettings.experimental_full_page_login) { } else if (this.siteSettings.experimental_full_page_login) {
this.router.transitionTo("login").then((login) => { this.router.transitionTo("login").then((login) => {
login.controller.set("canSignUp", this.controller.canSignUp); login.controller.set("canSignUp", this.controller.canSignUp);
if (this.siteSettings.login_required) {
login.controller.set("showLogin", true);
}
}); });
} else { } else {
this.modal.show(LoginModal, { this.modal.show(LoginModal, {

View File

@ -21,7 +21,9 @@ export default class LoginRoute extends DiscourseRoute {
} }
model() { model() {
return StaticPage.find("login"); if (this.siteSettings.login_required) {
return StaticPage.find("login");
}
} }
setupController(controller) { setupController(controller) {
@ -31,5 +33,9 @@ export default class LoginRoute extends DiscourseRoute {
controller.set("canSignUp", canSignUp); controller.set("canSignUp", canSignUp);
controller.set("flashType", ""); controller.set("flashType", "");
controller.set("flash", ""); controller.set("flash", "");
if (this.siteSettings.login_required) {
controller.set("showLogin", false);
}
} }
} }

View File

@ -70,7 +70,7 @@ export default RouteTemplate(
} else if (response.needs_approval) { } else if (response.needs_approval) {
this.needsApproval = true; this.needsApproval = true;
} else { } else {
setTimeout(this.loadHomepage, 2000); setTimeout(this.loadHomepage, 3000);
} }
} catch (error) { } catch (error) {
this.errorMessage = i18n("user.activate_account.already_done"); this.errorMessage = i18n("user.activate_account.already_done");

View File

@ -7,8 +7,9 @@
{{hide-application-header-buttons "search" "login" "signup" "menu"}} {{hide-application-header-buttons "search" "login" "signup" "menu"}}
{{hide-application-sidebar}} {{hide-application-sidebar}}
<FlashMessage @flash={{this.flash}} @type={{this.flashType}} />
<div class="login-fullpage"> <div class="login-fullpage">
<FlashMessage @flash={{this.flash}} @type={{this.flashType}} />
<div class={{concat-class "login-body" this.bodyClasses}}> <div class={{concat-class "login-body" this.bodyClasses}}>
<PluginOutlet @name="login-before-modal-body" @connectorTagName="div" /> <PluginOutlet @name="login-before-modal-body" @connectorTagName="div" />

View File

@ -2,9 +2,9 @@
{{hide-application-header-buttons "search" "login" "signup" "menu"}} {{hide-application-header-buttons "search" "login" "signup" "menu"}}
{{hide-application-sidebar}} {{hide-application-sidebar}}
<FlashMessage @flash={{this.flash}} @type={{this.flashType}} />
<div class="signup-fullpage"> <div class="signup-fullpage">
<FlashMessage @flash={{this.flash}} @type={{this.flashType}} />
<div class={{concat-class "signup-body" this.bodyClasses}}> <div class={{concat-class "signup-body" this.bodyClasses}}>
<PluginOutlet <PluginOutlet
@name="create-account-before-modal-body" @name="create-account-before-modal-body"
@ -37,6 +37,7 @@
<div class="input-group create-account-email"> <div class="input-group create-account-email">
<Input <Input
{{on "focusout" this.checkEmailAvailability}} {{on "focusout" this.checkEmailAvailability}}
{{on "focusin" this.scrollInputIntoView}}
@type="email" @type="email"
@value={{this.accountEmail}} @value={{this.accountEmail}}
disabled={{this.emailDisabled}} disabled={{this.emailDisabled}}
@ -50,7 +51,12 @@
<label class="alt-placeholder" for="new-account-email"> <label class="alt-placeholder" for="new-account-email">
{{i18n "user.email.title"}} {{i18n "user.email.title"}}
</label> </label>
{{#if this.emailValidation.reason}} {{#if
(or
this.emailValidation.ok
(and this.emailValidationVisible this.emailValidation.reason)
)
}}
<InputTip <InputTip
@validation={{this.emailValidation}} @validation={{this.emailValidation}}
id="account-email-validation" id="account-email-validation"
@ -64,6 +70,7 @@
<div class="input-group create-account__username"> <div class="input-group create-account__username">
<Input <Input
{{on "focusin" this.scrollInputIntoView}}
@value={{this.accountUsername}} @value={{this.accountUsername}}
disabled={{this.usernameDisabled}} disabled={{this.usernameDisabled}}
maxlength={{this.maxUsernameLength}} maxlength={{this.maxUsernameLength}}
@ -98,6 +105,8 @@
<div class="input-group create-account__password"> <div class="input-group create-account__password">
{{#if this.passwordRequired}} {{#if this.passwordRequired}}
<PasswordField <PasswordField
{{on "focusout" this.showPasswordValidation}}
{{on "focusin" this.scrollInputIntoView}}
@value={{this.accountPassword}} @value={{this.accountPassword}}
@capsLockOn={{this.capsLockOn}} @capsLockOn={{this.capsLockOn}}
type={{if this.maskPassword "password" "text"}} type={{if this.maskPassword "password" "text"}}
@ -113,10 +122,20 @@
<div class="create-account__password-info"> <div class="create-account__password-info">
<div class="create-account__password-tip-validation"> <div class="create-account__password-tip-validation">
<InputTip {{#if
@validation={{this.passwordValidation}} (or
id="password-validation" this.passwordValidation.ok
/> (and
this.passwordValidationVisible
this.passwordValidation.reason
)
)
}}
<InputTip
@validation={{this.passwordValidation}}
id="password-validation"
/>
{{/if}}
<div <div
class={{concat-class class={{concat-class
"caps-lock-warning" "caps-lock-warning"
@ -153,6 +172,7 @@
{{#if this.requireInviteCode}} {{#if this.requireInviteCode}}
<div class="input-group create-account__invite-code"> <div class="input-group create-account__invite-code">
<Input <Input
{{on "focusin" this.scrollInputIntoView}}
@value={{this.inviteCode}} @value={{this.inviteCode}}
id="inviteCode" id="inviteCode"
class={{value-entered this.inviteCode}} class={{value-entered this.inviteCode}}
@ -185,6 +205,7 @@
> >
{{#if this.fullnameRequired}} {{#if this.fullnameRequired}}
<TextField <TextField
{{on "focusin" this.scrollInputIntoView}}
@disabled={{this.nameDisabled}} @disabled={{this.nameDisabled}}
@value={{this.accountName}} @value={{this.accountName}}
@id="new-account-name" @id="new-account-name"
@ -208,6 +229,7 @@
{{#each this.userFields as |f|}} {{#each this.userFields as |f|}}
<div class="input-group"> <div class="input-group">
<UserField <UserField
{{on "focusin" this.scrollInputIntoView}}
@field={{f.field}} @field={{f.field}}
@value={{f.value}} @value={{f.value}}
@validation={{f.validation}} @validation={{f.validation}}

View File

@ -9,6 +9,9 @@
padding: 0; padding: 0;
height: 100%; height: 100%;
} }
.above-main-container-outlet {
display: none;
}
} }
.activate-account-page .alert-error { .activate-account-page .alert-error {
@ -20,7 +23,6 @@
max-width: 500px; max-width: 500px;
padding: 2rem 3rem; padding: 2rem 3rem;
background: var(--secondary); background: var(--secondary);
box-shadow: var(--shadow-menu-panel);
margin: 10vh auto 1em auto; margin: 10vh auto 1em auto;
@media screen and (max-height: 700px) { @media screen and (max-height: 700px) {
margin: 1em auto 1em auto; margin: 1em auto 1em auto;

View File

@ -9,6 +9,17 @@
flex-direction: column; flex-direction: column;
} }
#main-outlet:has(.login-fullpage, .signup-fullpage, .invites-show) {
& ~ .powered-by-discourse,
.above-main-container-outlet {
display: none;
}
}
body:has(.login-fullpage, .signup-fullpage) {
background-color: var(--secondary);
}
.login-fullpage, .login-fullpage,
.signup-fullpage, .signup-fullpage,
.invites-show { .invites-show {
@ -18,6 +29,14 @@
justify-content: center; justify-content: center;
width: 100%; width: 100%;
max-width: 800px; max-width: 800px;
box-sizing: border-box;
}
.alert {
width: 100%;
max-width: 800px;
box-sizing: border-box;
margin: 0;
} }
.login-page-cta, .login-page-cta,

View File

@ -217,11 +217,14 @@
/* end shared styles */ /* end shared styles */
.d-modal.create-account { .d-modal.create-account,
.d-modal.login-modal {
&:not(:has(.login-right-side)) .d-modal__container { &:not(:has(.login-right-side)) .d-modal__container {
max-width: 500px; max-width: 500px;
} }
}
.d-modal.create-account {
.d-modal { .d-modal {
&__container { &__container {
width: 100%; width: 100%;

View File

@ -7,7 +7,6 @@
max-width: 500px; max-width: 500px;
padding: 2rem 3rem; padding: 2rem 3rem;
background: var(--secondary); background: var(--secondary);
box-shadow: var(--shadow-menu-panel);
margin: 10vh auto 1em auto; margin: 10vh auto 1em auto;
@media screen and (max-height: 700px) { @media screen and (max-height: 700px) {
margin: 1em auto 1em auto; margin: 1em auto 1em auto;

View File

@ -3,6 +3,10 @@
.login-fullpage, .login-fullpage,
.signup-fullpage, .signup-fullpage,
.invites-show { .invites-show {
&:not(:has(.has-alt-auth)) .alert {
max-width: 500px;
}
.login-page-cta, .login-page-cta,
.signup-page-cta { .signup-page-cta {
&__buttons { &__buttons {
@ -71,3 +75,9 @@
} }
} }
} }
.invites-show.container {
box-sizing: border-box;
box-shadow: none;
max-width: 550px;
}

View File

@ -158,9 +158,12 @@
} }
&__signup { &__signup {
background: none !important; background: none !important;
font-size: var(--font-down); font-size: var(--font-up-1);
padding: 0; padding: 0;
} }
&__no-account-yet {
font-size: var(--font-up-1);
}
} }
} }
@ -176,9 +179,12 @@
} }
&__login { &__login {
background: none !important; background: none !important;
font-size: var(--font-down); font-size: var(--font-up-1);
padding: 0; padding: 0;
} }
&__existing-account {
font-size: var(--font-up-1);
}
} }
.login-right-side::before { .login-right-side::before {