UX: Improve second factor UI (#9526)

UX: Improve second factor UI (#9526)

This will make a few minor improvements to the second factor user interface. Highlights include:

  • Using the site’s title to prefix the backup code filename. If non-ascii characters are detected, then prefix “discourse” instead.
  • Add icons and change the text on some of the buttons for better clarity and consistency
  • Add an education link to the security key modal
diff --git a/app/assets/javascripts/discourse/app/components/backup-codes.js b/app/assets/javascripts/discourse/app/components/backup-codes.js
index 1109c4c..279a659 100644
--- a/app/assets/javascripts/discourse/app/components/backup-codes.js
+++ b/app/assets/javascripts/discourse/app/components/backup-codes.js
@@ -1,5 +1,6 @@
 import discourseComputed from "discourse-common/utils/decorators";
 import Component from "@ember/component";
+import { toAsciiPrintable, slugify } from "discourse/lib/utilities";
 
 // https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
 function b64EncodeUnicode(str) {
@@ -42,6 +43,14 @@ export default Component.extend({
     return backupCodes.join("\n").trim();
   },
 
+  @discourseComputed()
+  siteTitleSlug() {
+    const title = this.siteSettings.title;
+    const convertedTitle = toAsciiPrintable(title, "discourse");
+
+    return slugify(convertedTitle);
+  },
+
   actions: {
     copyToClipboard() {
       this._selectAllBackupCodes();
diff --git a/app/assets/javascripts/discourse/app/controllers/second-factor-backup-edit.js b/app/assets/javascripts/discourse/app/controllers/second-factor-backup-edit.js
index aa3e0c1..3ef9658 100644
--- a/app/assets/javascripts/discourse/app/controllers/second-factor-backup-edit.js
+++ b/app/assets/javascripts/discourse/app/controllers/second-factor-backup-edit.js
@@ -1,7 +1,6 @@
 import { alias } from "@ember/object/computed";
 import { later } from "@ember/runloop";
 import Controller from "@ember/controller";
-import discourseComputed from "discourse-common/utils/decorators";
 import { SECOND_FACTOR_METHODS } from "discourse/models/user";
 import ModalFunctionality from "discourse/mixins/modal-functionality";
 
@@ -14,13 +13,6 @@ export default Controller.extend(ModalFunctionality, {
   backupCodes: null,
   secondFactorMethod: SECOND_FACTOR_METHODS.TOTP,
 
-  @discourseComputed("backupEnabled")
-  generateBackupCodeBtnLabel(backupEnabled) {
-    return backupEnabled
-      ? "user.second_factor_backup.regenerate"
-      : "user.second_factor_backup.enable";
-  },
-
   onShow() {
     this.setProperties({
       loading: false,
diff --git a/app/assets/javascripts/discourse/app/lib/utilities.js b/app/assets/javascripts/discourse/app/lib/utilities.js
index b848ca7..f104c30 100644
--- a/app/assets/javascripts/discourse/app/lib/utilities.js
+++ b/app/assets/javascripts/discourse/app/lib/utilities.js
@@ -328,6 +328,28 @@ export function clipboardData(e, canUpload) {
   return { clipboard, types, canUpload, canPasteHtml };
 }
 
+// Replace any accented characters with their ASCII equivalent
+// Return the string if it only contains ASCII printable characters,
+// otherwise use the fallback
+export function toAsciiPrintable(string, fallback) {
+  if (typeof string.normalize === "function") {
+    string = string.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
+  }
+
+  return /^[/\040-\176/]*$/.test(string) ? string : fallback;
+}
+
+export function slugify(string) {
+  return string
+    .trim()
+    .toLowerCase()
+    .replace(/\s|_+/g, "-") // Replace spaces and underscores with dashes
+    .replace(/[^\w\-]+/g, "") // Remove non-word characters except for dashes
+    .replace(/\-\-+/g, "-") // Replace multiple dashes with a single dash
+    .replace(/^-+/, "") // Remove leading dashes
+    .replace(/-+$/, ""); // Remove trailing dashes
+}
+
 export function toNumber(input) {
   return typeof input === "number" ? input : parseFloat(input);
 }
diff --git a/app/assets/javascripts/discourse/app/templates/components/backup-codes.hbs b/app/assets/javascripts/discourse/app/templates/components/backup-codes.hbs
index 73b7ad4..95ae666 100644
--- a/app/assets/javascripts/discourse/app/templates/components/backup-codes.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/backup-codes.hbs
@@ -4,10 +4,14 @@
   {{d-button
     action=(action "copyToClipboard")
     class="backup-codes-copy-btn"
-    icon="copy"}}
+    icon="copy"
+    aria-label="user.second_factor_backup.copy_to_clipboard"
+    title="user.second_factor_backup.copy_to_clipboard"}}
 
-  <a download="backup_codes.txt"
+  <a download="{{siteTitleSlug}}-backup-codes.txt"
      class="btn no-text btn-icon backup-codes-download-btn"
+     aria-label={{i18n "user.second_factor_backup.download_backup_codes"}}
+     title={{i18n "user.second_factor_backup.download_backup_codes"}}
      rel="noopener noreferrer"
      target="_blank"
      href="data:application/octet-stream;charset=utf-8;base64,{{base64BackupCode}}">
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 8a9e433..1d3e961 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
@@ -18,18 +18,27 @@
       {{/if}}
 
       <div class="actions">
-        {{d-button
-          class="btn-primary"
-          action=(action "generateSecondFactorCodes")
-          type="submit"
-          disabled=loading
-          label=generateBackupCodeBtnLabel}}
         {{#if backupEnabled}}
           {{d-button
+            class="btn-primary"
+            icon="redo"
+            action=(action "generateSecondFactorCodes")
+            type="submit"
+            isLoading=loading
+            label="user.second_factor_backup.regenerate"}}
+          {{d-button
             class="btn-danger"
+            icon="ban"
             action=(action "disableSecondFactorBackup")
             disabled=loading
             label="user.second_factor_backup.disable"}}
+        {{else}}
+          {{d-button
+            class="btn-primary"
+            action=(action "generateSecondFactorCodes")
+            type="submit"
+            disabled=loading
+            label="user.second_factor_backup.enable"}}
         {{/if}}
       </div>
 
diff --git a/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit-security-key.hbs b/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit-security-key.hbs
index bd0d5a2..d0d1207 100644
--- a/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit-security-key.hbs
+++ b/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit-security-key.hbs
@@ -8,11 +8,13 @@
   {{d-button
     action=(action "editSecurityKey")
     class="btn-primary"
-    label="user.second_factor.security_key.edit"
+    label="user.second_factor.security_key.save"
   }}
   {{d-button
     action=(action "disableSecurityKey")
     class="btn-danger"
-    label="user.second_factor.security_key.delete"
+    icon="trash-alt"
+    aria-label="user.second_factor.disable"
+    title="user.second_factor.disable"
   }}
 {{/d-modal-body}}
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 2a77fce..a7a4f60 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
@@ -8,11 +8,13 @@
   {{d-button
     action=(action "editSecondFactor")
     class="btn-primary"
-    label="user.second_factor.edit"
+    label="user.second_factor.save"
   }}
   {{d-button
     action=(action "disableSecondFactor")
     class="btn-danger"
-    label="user.second_factor.disable"
+    icon="trash-alt"
+    aria-label="user.second_factor.disable"
+    title="user.second_factor.disable"
   }}
 {{/d-modal-body}}
diff --git a/app/assets/javascripts/discourse/app/templates/preferences-second-factor.hbs b/app/assets/javascripts/discourse/app/templates/preferences-second-factor.hbs
index d8b8d95..62cadd2 100644
--- a/app/assets/javascripts/discourse/app/templates/preferences-second-factor.hbs

[... diff too long, it was truncated ...]

GitHub sha: b8b1cbbf

This commit appears in #9526 which was approved by martin. It was merged by martin.