mirror of
https://github.com/discourse/discourse.git
synced 2026-08-07 11:45:21 -05:00
UX/UI: various d-otp improvements (#35993)
- auto submit after otp is filled on login form - ensures input is not taking full width and we render the 1password helper outside of the input - ensures there's enough spacing between otp input and submit button - changes autofocus option from autofocus to autoFocus, this is to have parity with our autoFocus modifier - prevents a white rectangle to appear while 1password is filling the field This is how it should look now: <img width="638" height="206" alt="Screenshot 2025-11-12 at 16 48 16" src="https://github.com/user-attachments/assets/bb367458-9959-4cae-a045-d9887ab60e71" />
This commit is contained in:
@@ -588,22 +588,6 @@ body.signup-page {
|
||||
}
|
||||
}
|
||||
|
||||
// Login page
|
||||
.login-fullpage {
|
||||
#second-factor {
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 0.75em 0.5em;
|
||||
min-width: 250px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Signup and invite page
|
||||
.invite-page,
|
||||
.signup-fullpage {
|
||||
|
||||
@@ -131,3 +131,10 @@ body.invite-page {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.totp-token {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
.d-otp-group {
|
||||
@@ -55,7 +56,7 @@
|
||||
.d-otp-input {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
width: calc(100% + 40px);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
text-align: left;
|
||||
@@ -72,6 +73,11 @@
|
||||
font-size: var(--root-height);
|
||||
font-family: monospace;
|
||||
font-variant-numeric: tabular-nums;
|
||||
clip-path: inset(0 40px 0 0);
|
||||
|
||||
&[data-com-onepassword-filled] {
|
||||
background-clip: text;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes otp-cursor-blink {
|
||||
|
||||
@@ -19,7 +19,7 @@ const DEFAULT_SLOTS = 6;
|
||||
* @property {object} Args
|
||||
*
|
||||
* @property {number} [Args.slots] - Number of OTP input slots to display (defaults to 6)
|
||||
* @property {boolean} [Args.autofocus] - Whether to autofocus the input on mount (defaults to true)
|
||||
* @property {boolean} [Args.autoFocus] - Whether to autoFocus the input on mount (defaults to true)
|
||||
* @property {function(string): void} [Args.onChange] - Callback invoked whenever the OTP value changes
|
||||
* @property {function(string): void} [Args.onFill] - Callback invoked when all OTP slots are filled
|
||||
*
|
||||
@@ -36,8 +36,8 @@ export default class DOTP extends Component {
|
||||
return this.args.slots ?? DEFAULT_SLOTS;
|
||||
}
|
||||
|
||||
get autofocus() {
|
||||
return this.args.autofocus ?? true;
|
||||
get autoFocus() {
|
||||
return this.args.autoFocus ?? true;
|
||||
}
|
||||
|
||||
get isFilled() {
|
||||
@@ -163,7 +163,7 @@ export default class DOTP extends Component {
|
||||
{{on "blur" this.onBlur}}
|
||||
{{on "paste" this.onPaste}}
|
||||
aria-label={{i18n "d_otp.screen_reader" count=this.slots}}
|
||||
{{(if this.autofocus (modifier autoFocus))}}
|
||||
{{(if this.autoFocus (modifier autoFocus))}}
|
||||
...attributes
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -125,6 +125,12 @@ export default class LocalLoginForm extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
filledSecondFactorToken(otp) {
|
||||
this.args.secondFactorTokenChanged(otp);
|
||||
this.args.login();
|
||||
}
|
||||
|
||||
@action
|
||||
handleForgotPassword(event) {
|
||||
event?.preventDefault();
|
||||
@@ -260,6 +266,7 @@ export default class LocalLoginForm extends Component {
|
||||
{{on "keydown" this.loginOnEnter}}
|
||||
{{on "focusin" this.scrollInputIntoView}}
|
||||
@onChange={{fn (mut @secondFactorToken)}}
|
||||
@onFill={{this.filledSecondFactorToken}}
|
||||
@secondFactorMethod={{@secondFactorMethod}}
|
||||
value={{@secondFactorToken}}
|
||||
id="login-second-factor"
|
||||
|
||||
@@ -178,6 +178,11 @@ export default class LoginPageController extends Controller {
|
||||
this.loginPassword = event.target.value;
|
||||
}
|
||||
|
||||
@action
|
||||
secondFactorTokenChanged(token) {
|
||||
this.secondFactorToken = token;
|
||||
}
|
||||
|
||||
@action
|
||||
showNotActivated(props) {
|
||||
this.modal.show(NotActivatedModal, { model: props });
|
||||
|
||||
@@ -96,6 +96,7 @@ export default <template>
|
||||
@loginPasswordChanged={{@controller.loginPasswordChanged}}
|
||||
@secondFactorMethod={{@controller.secondFactorMethod}}
|
||||
@secondFactorToken={{@controller.secondFactorToken}}
|
||||
@secondFactorTokenChanged={{@controller.secondFactorTokenChanged}}
|
||||
@backupEnabled={{@controller.backupEnabled}}
|
||||
@totpEnabled={{@controller.totpEnabled}}
|
||||
@securityKeyAllowedCredentialIds={{@controller.securityKeyAllowedCredentialIds}}
|
||||
|
||||
@@ -98,7 +98,6 @@ acceptance("Signing In", function () {
|
||||
.isEnabled("enables the login button");
|
||||
|
||||
await fillIn("#login-second-factor", "123456");
|
||||
await click(".login-fullpage .btn-primary");
|
||||
|
||||
assert
|
||||
.dom(".login-fullpage .btn-primary")
|
||||
|
||||
@@ -80,12 +80,12 @@ module("Integration | Component | DOTP", function (hooks) {
|
||||
assert.strictEqual(this.value, "12");
|
||||
});
|
||||
|
||||
test("@autofocus", async function (assert) {
|
||||
test("@autoFocus", async function (assert) {
|
||||
await render(<template><DOTP /></template>);
|
||||
|
||||
assert.dom(".d-otp-slot[data-index='0'].--is-focused").exists();
|
||||
|
||||
await render(<template><DOTP @autofocus={{false}} /></template>);
|
||||
await render(<template><DOTP @autoFocus={{false}} /></template>);
|
||||
|
||||
assert.dom(".d-otp-slot[data-index='0'].--is-focused").doesNotExist();
|
||||
});
|
||||
|
||||
@@ -53,7 +53,6 @@ describe "Discourse Connect Provider", type: :system do
|
||||
|
||||
totp = ROTP::TOTP.new(user_second_factor.data).now
|
||||
find("#login-second-factor").fill_in(with: totp)
|
||||
login_form.click_login
|
||||
|
||||
expect(page).to have_current_path(
|
||||
/#{Regexp.escape(return_url)}\?sso=.*&sig=[0-9a-f]+/,
|
||||
|
||||
@@ -327,7 +327,6 @@ shared_examples "login scenarios" do
|
||||
|
||||
totp = ROTP::TOTP.new(user_second_factor.data).now
|
||||
find("#login-second-factor").fill_in(with: totp)
|
||||
login_form.click_login
|
||||
|
||||
expect(page).to have_css(".header-dropdown-toggle.current-user")
|
||||
end
|
||||
@@ -342,7 +341,6 @@ shared_examples "login scenarios" do
|
||||
|
||||
totp = ROTP::TOTP.new(user_second_factor.data).now
|
||||
find("#login-second-factor").fill_in(with: totp)
|
||||
login_form.click_login
|
||||
|
||||
expect(page).to have_current_path("/about")
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user