From 4eb7d2d79b6ec06d8f4a67c660c112a4cd45a7b5 Mon Sep 17 00:00:00 2001 From: Kris Date: Wed, 19 Apr 2023 09:41:02 -0400 Subject: [PATCH] UX: improve layout and styles for solo preferences (#21094) --- .../discourse/app/components/backup-codes.hbs | 38 ++--- .../app/components/security-key-dropdown.js | 43 +++++ .../components/token-based-auth-dropdown.js | 40 +++++ .../components/two-factor-backup-dropdown.js | 45 ++++++ .../modal/second-factor-backup-edit.hbs | 111 +++++++------ .../templates/modal/second-factor-edit.hbs | 18 +-- .../app/templates/preferences-email.hbs | 65 +++----- .../templates/preferences-second-factor.hbs | 116 ++++++------- ...r-preferences-second-factor-backup-test.js | 20 ++- .../user-preferences-second-factor-test.js | 8 +- .../stylesheets/common/base/discourse.scss | 1 + app/assets/stylesheets/common/base/user.scss | 152 +++++++++--------- app/assets/stylesheets/desktop/discourse.scss | 5 - app/assets/stylesheets/mobile/user.scss | 4 + config/locales/client.en.yml | 10 +- 15 files changed, 389 insertions(+), 287 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/components/security-key-dropdown.js create mode 100644 app/assets/javascripts/discourse/app/components/token-based-auth-dropdown.js create mode 100644 app/assets/javascripts/discourse/app/components/two-factor-backup-dropdown.js diff --git a/app/assets/javascripts/discourse/app/components/backup-codes.hbs b/app/assets/javascripts/discourse/app/components/backup-codes.hbs index 37b1cd141fb..2df8855621e 100644 --- a/app/assets/javascripts/discourse/app/components/backup-codes.hbs +++ b/app/assets/javascripts/discourse/app/components/backup-codes.hbs @@ -6,23 +6,25 @@ readonly >{{this.formattedBackupCodes}} - + \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/security-key-dropdown.js b/app/assets/javascripts/discourse/app/components/security-key-dropdown.js new file mode 100644 index 00000000000..847317bf53f --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/security-key-dropdown.js @@ -0,0 +1,43 @@ +import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; +import I18n from "I18n"; +import { computed } from "@ember/object"; + +export default DropdownSelectBoxComponent.extend({ + classNames: ["security-key-dropdown"], + + selectKitOptions: { + icon: "wrench", + showFullTitle: false, + }, + + content: computed(function () { + const content = []; + + content.push({ + id: "edit", + icon: "pencil-alt", + name: I18n.t("user.second_factor.edit"), + }); + + content.push({ + id: "disable", + icon: "trash-alt", + name: I18n.t("user.second_factor.disable"), + }); + + return content; + }), + + actions: { + onChange(id) { + switch (id) { + case "edit": + this.editSecurityKey(this.securityKey); + break; + case "disable": + this.disableSingleSecondFactor(this.securityKey); + break; + } + }, + }, +}); diff --git a/app/assets/javascripts/discourse/app/components/token-based-auth-dropdown.js b/app/assets/javascripts/discourse/app/components/token-based-auth-dropdown.js new file mode 100644 index 00000000000..3d13eeb23aa --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/token-based-auth-dropdown.js @@ -0,0 +1,40 @@ +import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; +import I18n from "I18n"; +import { computed } from "@ember/object"; + +export default DropdownSelectBoxComponent.extend({ + classNames: ["token-based-auth-dropdown"], + + selectKitOptions: { + icon: "wrench", + showFullTitle: false, + }, + + content: computed(function () { + return [ + { + id: "edit", + icon: "pencil-alt", + name: I18n.t("user.second_factor.edit"), + }, + { + id: "disable", + icon: "trash-alt", + name: I18n.t("user.second_factor.disable"), + }, + ]; + }), + + actions: { + onChange(id) { + switch (id) { + case "edit": + this.editSecondFactor(this.totp); + break; + case "disable": + this.disableSingleSecondFactor(this.totp); + break; + } + }, + }, +}); diff --git a/app/assets/javascripts/discourse/app/components/two-factor-backup-dropdown.js b/app/assets/javascripts/discourse/app/components/two-factor-backup-dropdown.js new file mode 100644 index 00000000000..56233cb8af7 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/two-factor-backup-dropdown.js @@ -0,0 +1,45 @@ +import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; +import I18n from "I18n"; +import { computed } from "@ember/object"; + +export default DropdownSelectBoxComponent.extend({ + classNames: ["two-factor-backup-dropdown"], + + selectKitOptions: { + icon: "wrench", + showFullTitle: false, + }, + + content: computed(function () { + const content = []; + + content.push({ + id: "edit", + icon: "pencil-alt", + name: I18n.t("user.second_factor.edit"), + }); + + if (this.secondFactorBackupEnabled) { + content.push({ + id: "disable", + icon: "trash-alt", + name: I18n.t("user.second_factor.disable"), + }); + } + + return content; + }), + + actions: { + onChange(id) { + switch (id) { + case "edit": + this.editSecondFactorBackup(); + break; + case "disable": + this.disableSecondFactorBackup(); + break; + } + }, + }, +}); diff --git a/app/assets/javascripts/discourse/app/templates/modal/second-factor-backup-edit.hbs b/app/assets/javascripts/discourse/app/templates/modal/second-factor-backup-edit.hbs index 0501f11bad3..1e1633ef87e 100644 --- a/app/assets/javascripts/discourse/app/templates/modal/second-factor-backup-edit.hbs +++ b/app/assets/javascripts/discourse/app/templates/modal/second-factor-backup-edit.hbs @@ -1,64 +1,61 @@ -
-
- {{#if this.successMessage}} -
- {{this.successMessage}} -
- {{/if}} + {{#if this.successMessage}} +
+ {{this.successMessage}} +
+ {{/if}} - {{#if this.errorMessage}} -
- {{this.errorMessage}} -
- {{/if}} + {{#if this.errorMessage}} +
+ {{this.errorMessage}} +
+ {{/if}} - {{#if this.backupEnabled}} - {{html-safe - (i18n - "user.second_factor_backup.remaining_codes" - count=this.remainingCodes - ) - }} - {{/if}} + + {{#if this.backupCodes}} +

{{i18n "user.second_factor_backup.codes.title"}}

-
- {{#if this.backupEnabled}} - - {{else}} - - {{/if}} -
+

+ {{i18n "user.second_factor_backup.codes.description"}} +

- - {{#if this.backupCodes}} -

{{i18n "user.second_factor_backup.codes.title"}}

+ + {{/if}} +
-

- {{i18n "user.second_factor_backup.codes.description"}} -

+ {{#if this.backupEnabled}} + {{html-safe + (i18n + "user.second_factor_backup.remaining_codes" count=this.remainingCodes + ) + }} + {{else}} + {{html-safe (i18n "user.second_factor_backup.not_enabled")}} + {{/if}} + - - {{/if}} -
-
-
-
\ No newline at end of file + \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit.hbs b/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit.hbs index 7aa3f44dd67..1b53501d6e2 100644 --- a/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit.hbs +++ b/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit.hbs @@ -1,16 +1,10 @@ -
-
- - -
-
+
+ + +
- {{#if this.isCurrentUser}}
- -
{{/if}} @@ -65,7 +46,7 @@ {{/each}} - - {{/if}} @@ -112,7 +82,7 @@ {{/each}} {{/if}} - {{#if this.isCurrentUser}} + {{#if + (and + this.model.second_factor_backup_enabled this.isCurrentUser + ) + }}
- - - {{#if this.model.second_factor_backup_enabled}} - - {{/if}}
{{/if}} + {{else}} {{i18n "user.second_factor_backup.enable_prerequisites"}} {{/if}} @@ -170,7 +142,7 @@ {{#if this.model.second_factor_enabled}} {{#unless this.showEnforcedNotice}}
-
+
+
{{/unless}} diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-second-factor-backup-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-second-factor-backup-test.js index ebd73c0bc44..71642244377 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-second-factor-backup-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-second-factor-backup-test.js @@ -32,14 +32,14 @@ acceptance("User Preferences - Second Factor Backup", function (needs) { test("second factor backup", async function (assert) { updateCurrentUser({ second_factor_enabled: true }); await visit("/u/eviltrout/preferences/second-factor"); - await click(".edit-2fa-backup"); + await click(".new-second-factor-backup"); assert.ok( - exists(".second-factor-backup-preferences"), + exists(".second-factor-backup-edit-modal"), "shows the 2fa backup panel" ); - await click(".second-factor-backup-preferences .btn-primary"); + await click(".second-factor-backup-edit-modal .btn-primary"); assert.ok(exists(".backup-codes-area"), "shows backup codes"); }); @@ -47,10 +47,16 @@ acceptance("User Preferences - Second Factor Backup", function (needs) { test("delete backup codes", async function (assert) { updateCurrentUser({ second_factor_enabled: true }); await visit("/u/eviltrout/preferences/second-factor"); - await click(".edit-2fa-backup"); - await click(".second-factor-backup-preferences .btn-primary"); - await click(".modal-close"); - await click(".pref-second-factor-backup .btn-danger"); + + if (exists(".new-second-factor-backup")) { + // if codes don't exist yet, create them + await click(".new-second-factor-backup"); + await click(".second-factor-backup-edit-modal .btn-primary"); + await click(".modal-close"); + } + + await click(".two-factor-backup-dropdown .select-kit-header"); + await click("li[data-name='Disable'"); assert.strictEqual( query("#dialog-title").innerText.trim(), "Deleting backup codes" diff --git a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-second-factor-test.js b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-second-factor-test.js index 4150b25d0d7..16aa5b2cdbf 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/user-preferences-second-factor-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/user-preferences-second-factor-test.js @@ -108,7 +108,9 @@ acceptance("User Preferences - Second Factor", function (needs) { await fillIn("#password", "secrets"); await click(".user-preferences .btn-primary"); - await click(".totp .btn-danger"); + await click(".token-based-auth-dropdown .select-kit-header"); + await click("li[data-name='Disable']"); + assert.strictEqual( query("#dialog-title").innerText.trim(), "Deleting an authenticator" @@ -120,7 +122,9 @@ acceptance("User Preferences - Second Factor", function (needs) { "User has a physical security key" ); - await click(".security-key .btn-danger"); + await click(".security-key-dropdown .select-kit-header"); + await click("li[data-name='Disable'"); + assert.strictEqual( query("#dialog-title").innerText.trim(), "Deleting an authenticator" diff --git a/app/assets/stylesheets/common/base/discourse.scss b/app/assets/stylesheets/common/base/discourse.scss index 15041981b4e..7aac037d920 100644 --- a/app/assets/stylesheets/common/base/discourse.scss +++ b/app/assets/stylesheets/common/base/discourse.scss @@ -570,6 +570,7 @@ table { display: inline-block; margin-bottom: 0; flex: 0 0 auto; + max-width: 100%; } .control-group { diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss index df610ca08cd..9ebbd2a256c 100644 --- a/app/assets/stylesheets/common/base/user.scss +++ b/app/assets/stylesheets/common/base/user.scss @@ -580,59 +580,6 @@ .second-factor-token-input { margin-right: 10px; } - - .form-horizontal { - .instructions { - margin-left: 0; - } - .actions { - margin-top: 5px; - } - } - - .backup-codes { - margin: 2em 0; - - .wrapper { - display: inline-block; - position: relative; - border-radius: 3px; - border: 1px solid var(--primary-low); - width: 100%; - } - - .backup-codes-area { - resize: none; - padding: 0; - height: auto; - text-align: center; - width: 100%; - background: var(--secondary); - border: 0; - cursor: auto; - outline: none; - font-family: monospace; - - &:focus { - box-shadow: none; - border-color: var(--primary-low); - } - } - - .backup-codes-copy-btn, - .backup-codes-download-btn { - right: 5px; - position: absolute; - } - - .backup-codes-copy-btn { - top: 5px; - } - - .backup-codes-download-btn { - top: 40px; - } - } } .pref-associated-accounts table { @@ -661,9 +608,16 @@ } .instructions { - clear: both; - display: inline-block; - margin-top: 4px; + display: block; + margin-top: 0.25em; + } + + .success-back { + display: flex; + align-items: center; + .d-icon { + margin-right: 0.25em; + } } @mixin inactiveMode() { @@ -687,6 +641,11 @@ .undo-preview { margin-bottom: 1em; } + + .save-button { + display: flex; + align-items: center; + } } .paginated-topics-list { @@ -729,30 +688,77 @@ } .second-factor { - &.instructions { - color: var(--primary-medium); - margin-top: 5px; - margin-bottom: 10px; - font-size: var(--font-down-1); - } .second-factor-item { - margin-top: 0.75em; - width: 500px; + width: 100%; display: flex; justify-content: space-between; - border-bottom: 1px solid #ddd; - margin: 5px 0px; + border-top: 1px solid var(--primary-low); + margin: 0.25em 0; + padding: 0.25em 0; align-items: center; - &:last-child { - border-bottom: 0; - } - .btn-danger .d-icon { - color: var(--danger); + .select-kit { + .select-kit-header { + background: transparent; + &:hover .d-icon { + color: var(--primary-high); + } + } + + &.is-expanded { + .select-kit-header .d-icon { + color: var(--primary-high); + } + } } } - .btn.edit { - min-height: auto; + + .-actions { + display: flex; + align-items: center; + } +} + +.d-modal[class*="second-factor-"] { + .modal-inner-container { + max-width: 24em; + } +} + +.backup-codes { + margin: 1em 0; + + .wrapper { + display: flex; + border: 1px solid var(--primary-low); + width: 100%; + } + + textarea.backup-codes-area { + flex: 1 1 100%; + height: 100%; + resize: none; + margin: 0; + padding: 0.5em; + height: auto; + background: var(--secondary); + border: 0; + cursor: auto; + outline: none; + font-family: monospace; + &:focus { + box-shadow: none; + border-color: var(--primary-low); + } + } + + .controls { + padding: 0.5em; + flex: 1 1 2em; + margin-left: auto; + .btn { + margin-bottom: 0.5em; + } } } diff --git a/app/assets/stylesheets/desktop/discourse.scss b/app/assets/stylesheets/desktop/discourse.scss index d2bff7998d2..90c36632750 100644 --- a/app/assets/stylesheets/desktop/discourse.scss +++ b/app/assets/stylesheets/desktop/discourse.scss @@ -156,11 +156,6 @@ input { .controls { margin-left: 160px; - - &.-actions { - display: flex; - align-items: center; - } } } diff --git a/app/assets/stylesheets/mobile/user.scss b/app/assets/stylesheets/mobile/user.scss index d8135a4f696..04f5809fd70 100644 --- a/app/assets/stylesheets/mobile/user.scss +++ b/app/assets/stylesheets/mobile/user.scss @@ -330,6 +330,10 @@ .apps .controls button { float: right; } + + #change-email { + width: 100%; + } } .user-right { diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index d2c7bed7ba0..9d86c7f762a 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1419,11 +1419,12 @@ en: title: "Two-Factor Backup Codes" regenerate: "Regenerate" disable: "Disable" - enable: "Enable" - enable_long: "Enable backup codes" + enable: "Create backup codes" + enable_long: "Add backup codes" + not_enabled: "You haven't created any backup codes yet." manage: - one: "Manage backup codes. You have %{count} backup code remaining." - other: "Manage backup codes. You have %{count} backup codes remaining." + one: "You have %{count} backup code remaining." + other: "You have %{count} backup codes remaining." copy_to_clipboard: "Copy to Clipboard" copy_to_clipboard_error: "Error copying data to Clipboard" copied_to_clipboard: "Copied to Clipboard" @@ -1512,6 +1513,7 @@ en: success: "We've sent an email to that address. Please follow the confirmation instructions." success_via_admin: "We've sent an email to that address. The user will need to follow the confirmation instructions in the email." success_staff: "We've sent an email to your current address. Please follow the confirmation instructions." + back_to_preferences: "Back to preferences" change_avatar: title: "Change your profile picture"