UX: refactor password reset page (#30323)

This commit is contained in:
Jordan Vidrine 2024-12-17 12:11:02 -06:00 committed by GitHub
parent af8c98217a
commit 8f26ae7b7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 128 additions and 108 deletions

View File

@ -29,6 +29,7 @@ export default class PasswordResetController extends Controller.extend(
requiresApproval = false; requiresApproval = false;
redirected = false; redirected = false;
maskPassword = true; maskPassword = true;
passwordValidationVisible = false;
lockImageUrl = getURL("/images/lock.svg"); lockImageUrl = getURL("/images/lock.svg");
@ -65,6 +66,30 @@ export default class PasswordResetController extends Controller.extend(
return getURL(redirectTo || "/"); return getURL(redirectTo || "/");
} }
@discourseComputed(
"passwordValidation.ok",
"passwordValidation.reason",
"passwordValidationVisible"
)
showPasswordValidation(
passwordValidationOk,
passwordValidationReason,
passwordValidationVisible
) {
return (
passwordValidationOk ||
(passwordValidationReason && passwordValidationVisible)
);
}
@action
togglePasswordValidation() {
this.set(
"passwordValidationVisible",
Boolean(this.passwordValidation.reason)
);
}
@action @action
done(event) { done(event) {
if (wantsNewWindow(event)) { if (wantsNewWindow(event)) {

View File

@ -2,11 +2,7 @@
{{hide-application-sidebar}} {{hide-application-sidebar}}
{{hide-application-header-buttons "search" "login" "signup" "menu"}} {{hide-application-header-buttons "search" "login" "signup" "menu"}}
<div class="container password-reset clearfix"> <div class="container password-reset clearfix">
<div class="pull-left col-image"> <form class="change-password-form login-left-side">
<img src={{this.lockImageUrl}} class="password-reset-img" alt="" />
</div>
<div class="pull-left col-form">
{{#if this.successMessage}} {{#if this.successMessage}}
<p>{{this.successMessage}}</p> <p>{{this.successMessage}}</p>
@ -22,92 +18,95 @@
{{/unless}} {{/unless}}
{{/if}} {{/if}}
{{else}} {{else}}
<form class="change-password-form"> {{#if this.securityKeyOrSecondFactorRequired}}
{{#if this.securityKeyOrSecondFactorRequired}} <h2>{{i18n "user.change_password.title"}}</h2>
<h2>{{i18n "user.change_password.title"}}</h2> <p>
<p> {{i18n "user.change_password.verify_identity"}}
{{i18n "user.change_password.verify_identity"}} </p>
</p> {{#if this.errorMessage}}
{{#if this.errorMessage}} <div class="alert alert-error">{{this.errorMessage}}</div>
<div class="alert alert-error">{{this.errorMessage}}</div> <br />
<br /> {{/if}}
{{/if}}
{{#if this.displaySecurityKeyForm}} {{#if this.displaySecurityKeyForm}}
<SecurityKeyForm <SecurityKeyForm
@setSecondFactorMethod={{fn @setSecondFactorMethod={{fn (mut this.selectedSecondFactorMethod)}}
(mut this.selectedSecondFactorMethod) @backupEnabled={{this.backupEnabled}}
}} @totpEnabled={{this.secondFactorRequired}}
@backupEnabled={{this.backupEnabled}} @otherMethodAllowed={{this.otherMethodAllowed}}
@totpEnabled={{this.secondFactorRequired}} @action={{this.authenticateSecurityKey}}
@otherMethodAllowed={{this.otherMethodAllowed}} />
@action={{this.authenticateSecurityKey}}
/>
{{else}}
<SecondFactorForm
@secondFactorMethod={{this.selectedSecondFactorMethod}}
@secondFactorToken={{this.secondFactorToken}}
@backupEnabled={{this.backupEnabled}}
@totpEnabled={{this.secondFactorRequired}}
@isLogin={{false}}
>
<SecondFactorInput
{{on
"input"
(with-event-value (fn (mut this.secondFactorToken)))
}}
@secondFactorMethod={{this.selectedSecondFactorMethod}}
value={{this.secondFactorToken}}
id="second-factor"
/>
</SecondFactorForm>
{{/if}}
{{#unless this.displaySecurityKeyForm}}
<DButton
@action={{action "submit"}}
@label="submit"
type="submit"
class="btn-primary"
/>
{{/unless}}
{{else}} {{else}}
<h2>{{i18n "user.change_password.choose"}}</h2> <SecondFactorForm
{{#if this.errorMessage}} @secondFactorMethod={{this.selectedSecondFactorMethod}}
<div class="alert alert-error">{{this.errorMessage}}</div> @secondFactorToken={{this.secondFactorToken}}
<br /> @backupEnabled={{this.backupEnabled}}
{{/if}} @totpEnabled={{this.secondFactorRequired}}
@isLogin={{false}}
<div class="input"> >
<PasswordField <SecondFactorInput
@value={{this.accountPassword}} {{on
@capsLockOn={{this.capsLockOn}} "input"
type={{if this.maskPassword "password" "text"}} (with-event-value (fn (mut this.secondFactorToken)))
autofocus="autofocus" }}
autocomplete="new-password" @secondFactorMethod={{this.selectedSecondFactorMethod}}
id="new-account-password" value={{this.secondFactorToken}}
id="second-factor"
/> />
</SecondFactorForm>
{{/if}}
{{#unless this.displaySecurityKeyForm}}
<DButton
@action={{action "submit"}}
@label="submit"
type="submit"
class="btn-primary"
/>
{{/unless}}
{{else}}
<h2>{{i18n "user.change_password.choose_new"}}</h2>
{{#if this.errorMessage}}
<div class="alert alert-error">{{this.errorMessage}}</div>
<br />
{{/if}}
<div class="input">
<PasswordField
@value={{this.accountPassword}}
{{on "focusout" this.togglePasswordValidation}}
@capsLockOn={{this.capsLockOn}}
type={{if this.maskPassword "password" "text"}}
autofocus="autofocus"
autocomplete="new-password"
id="new-account-password"
/>
<div class="change-password__password-info">
<div class="change-password_tip-validation">
{{#if this.showPasswordValidation}}
<InputTip @validation={{this.passwordValidation}} />
{{/if}}
<div
class="caps-lock-warning {{unless this.capsLockOn 'hidden'}}"
>
{{d-icon "triangle-exclamation"}}
{{i18n "login.caps_lock_warning"}}
</div>
</div>
<TogglePasswordMask <TogglePasswordMask
@maskPassword={{this.maskPassword}} @maskPassword={{this.maskPassword}}
@togglePasswordMask={{this.togglePasswordMask}} @togglePasswordMask={{this.togglePasswordMask}}
/> />
<div class="caps-lock-warning {{unless this.capsLockOn 'hidden'}}">
{{d-icon "triangle-exclamation"}}
{{i18n "login.caps_lock_warning"}}
</div>
</div> </div>
</div>
<InputTip @validation={{this.passwordValidation}} /> <DButton
@action={{action "submit"}}
<DButton @label="user.change_password.set_password"
@action={{action "submit"}} type="submit"
@label="user.change_password.set_password" class="btn-primary"
type="submit" />
class="btn-primary" {{/if}}
/>
{{/if}}
</form>
{{/if}} {{/if}}
</div> </form>
</div> </div>

View File

@ -1,4 +1,4 @@
import { click, fillIn, visit } from "@ember/test-helpers"; import { blur, click, fillIn, visit } from "@ember/test-helpers";
import { test } from "qunit"; import { test } from "qunit";
import sinon from "sinon"; import sinon from "sinon";
import PreloadStore from "discourse/lib/preload-store"; import PreloadStore from "discourse/lib/preload-store";
@ -71,6 +71,8 @@ acceptance("Password Reset", function (needs) {
assert.dom(".password-reset .tip.good").exists("input looks good"); assert.dom(".password-reset .tip.good").exists("input looks good");
await fillIn(".password-reset input", "123"); await fillIn(".password-reset input", "123");
await blur(".password-reset input");
assert.dom(".password-reset .tip.bad").exists("input is not valid"); assert.dom(".password-reset .tip.bad").exists("input is not valid");
assert.dom(".password-reset .tip.bad").includesHtml( assert.dom(".password-reset .tip.bad").includesHtml(
i18n("user.password.too_short", { i18n("user.password.too_short", {

View File

@ -11,7 +11,8 @@
body.login-page, body.login-page,
body.signup-page, body.signup-page,
body.invite-page { body.invite-page,
body.password-reset-page {
& ~ .powered-by-discourse, & ~ .powered-by-discourse,
.above-main-container-outlet { .above-main-container-outlet {
display: none; display: none;
@ -25,7 +26,8 @@ body.signup-page {
.login-fullpage, .login-fullpage,
.signup-fullpage, .signup-fullpage,
.invites-show { .invites-show,
.password-reset-page {
.signup-body, .signup-body,
.login-body { .login-body {
display: flex; display: flex;
@ -241,8 +243,6 @@ body.signup-page {
.caps-lock-warning { .caps-lock-warning {
color: var(--danger); color: var(--danger);
font-size: var(--font-down-1); font-size: var(--font-down-1);
font-weight: bold;
margin-top: 0.5em;
} }
.create-account__password-info { .create-account__password-info {

View File

@ -33,28 +33,22 @@ body.invite-page {
// the second button can wrap in some locales, and this helps alignment // the second button can wrap in some locales, and this helps alignment
} }
.password-reset {
.instructions {
label {
color: var(--primary-medium);
}
}
#new-account-password {
width: 15em;
}
.tip {
margin: 0 0 0.5em;
}
.toggle-password-mask {
margin-left: 0.25em;
}
}
.password-reset-page { .password-reset-page {
.caps-lock-warning {
display: inline;
}
.change-password-form { .change-password-form {
margin: 0 auto;
display: flex;
flex-direction: column;
width: 400px;
input {
padding: 0.75em 0.77em;
min-width: 250px;
margin-bottom: 0.25em;
width: 100%;
}
.input {
position: relative;
margin-bottom: 1em;
}
.tip { .tip {
display: block; display: block;
} }