DEV: Convert second-factor-backup-edit modal to component-based API (#22366)

This PR converts the `second-factor-backup-edit` modal to make use of the new component-based API
This commit is contained in:
Isaac Janzen 2023-07-06 11:03:18 -05:00 committed by GitHub
parent f92ed88c52
commit c05e54e461
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 143 additions and 156 deletions

View File

@ -0,0 +1,62 @@
<DModal
@title={{i18n "user.second_factor_backup.title"}}
@closeModal={{@closeModal}}
class="second-factor-backup-edit-modal"
>
<:body>
{{#if this.successMessage}}
<div class="alert alert-success">
{{this.successMessage}}
</div>
{{/if}}
{{#if this.errorMessage}}
<div class="alert alert-error">
{{this.errorMessage}}
</div>
{{/if}}
<ConditionalLoadingSection @isLoading={{this.loading}}>
{{#if this.backupCodes}}
<h3>{{i18n "user.second_factor_backup.codes.title"}}</h3>
<p>{{i18n "user.second_factor_backup.codes.description"}}</p>
<BackupCodes
@copyBackupCode={{this.copyBackupCode}}
@backupCodes={{this.backupCodes}}
/>
{{/if}}
</ConditionalLoadingSection>
{{#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}}
</:body>
<:footer>
<div class="actions">
{{#if this.backupEnabled}}
<DButton
class="btn-primary"
@icon="redo"
@action={{this.generateSecondFactorCodes}}
@type="submit"
@isLoading={{this.loading}}
@label="user.second_factor_backup.regenerate"
/>
{{else}}
<DButton
class="btn-primary"
@action={{this.generateSecondFactorCodes}}
@type="submit"
@disabled={{this.loading}}
@label="user.second_factor_backup.enable"
/>
{{/if}}
</div>
</:footer>
</DModal>

View File

@ -0,0 +1,63 @@
import Component from "@glimmer/component";
import I18n from "I18n";
import { SECOND_FACTOR_METHODS } from "discourse/models/user";
import { debounce } from "discourse-common/utils/decorators";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
export default class SecondFactorBackupEdit extends Component {
@tracked loading = false;
@tracked errorMessage;
@tracked successMessage;
@tracked backupEnabled =
this.args.model.secondFactor.second_factor_backup_enabled;
@tracked remainingCodes =
this.args.model.secondFactor.second_factor_remaining_backup_codes;
@tracked backupCodes;
@tracked secondFactorMethod = SECOND_FACTOR_METHODS.TOTP;
@action
copyBackupCode(successful) {
if (successful) {
this.successMessage = I18n.t(
"user.second_factor_backup.copied_to_clipboard"
);
} else {
this.errorMessage = I18n.t(
"user.second_factor_backup.copy_to_clipboard_error"
);
}
this._hideCopyMessage();
}
@action
generateSecondFactorCodes() {
this.loading = true;
this.args.model.secondFactor
.generateSecondFactorCodes()
.then((response) => {
if (response.error) {
this.errorMessage = response.error;
return;
}
this.args.model.markDirty();
this.errorMessage = null;
this.backupCodes = response.backup_codes;
this.args.model.setBackupEnabled(true);
this.backupEnabled = true;
this.remainingCodes = response.backup_codes.length;
})
.catch((error) => {
this.args.closeModal();
this.args.model.onError(error);
})
.finally(() => (this.loading = false));
}
@debounce(2000)
_hideCopyMessage() {
this.successMessage = null;
this.errorMessage = null;
}
}

View File

@ -8,13 +8,13 @@ import { alias } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { findAll } from "discourse/models/login-method"; import { findAll } from "discourse/models/login-method";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import showModal from "discourse/lib/show-modal";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import SecondFactorConfirmPhrase from "discourse/components/dialog-messages/second-factor-confirm-phrase"; import SecondFactorConfirmPhrase from "discourse/components/dialog-messages/second-factor-confirm-phrase";
import SecondFactorAddSecurityKey from "discourse/components/modal/second-factor-add-security-key"; import SecondFactorAddSecurityKey from "discourse/components/modal/second-factor-add-security-key";
import SecondFactorEditSecurityKey from "discourse/components/modal/second-factor-edit-security-key"; import SecondFactorEditSecurityKey from "discourse/components/modal/second-factor-edit-security-key";
import SecondFactorEdit from "discourse/components/modal/second-factor-edit"; import SecondFactorEdit from "discourse/components/modal/second-factor-edit";
import SecondFactorAddTotp from "discourse/components/modal/second-factor-add-totp"; import SecondFactorAddTotp from "discourse/components/modal/second-factor-add-totp";
import SecondFactorBackupEdit from "discourse/components/modal/second-factor-backup-edit";
export default Controller.extend(CanCheckEmails, { export default Controller.extend(CanCheckEmails, {
dialog: service(), dialog: service(),
@ -63,6 +63,11 @@ export default Controller.extend(CanCheckEmails, {
} }
}, },
@action
setBackupEnabled(value) {
this.set("backupEnabled", value);
},
@action @action
loadSecondFactors() { loadSecondFactors() {
if (this.dirty === false) { if (this.dirty === false) {
@ -310,15 +315,14 @@ export default Controller.extend(CanCheckEmails, {
this.loadSecondFactors(); this.loadSecondFactors();
}, },
editSecondFactorBackup() { async editSecondFactorBackup() {
const controller = showModal("second-factor-backup-edit", { await this.modal.show(SecondFactorBackupEdit, {
model: this.model, model: {
title: "user.second_factor_backup.title", secondFactor: this.model,
}); markDirty: () => this.markDirty(),
controller.setProperties({ onError: (e) => this.handleError(e),
onClose: () => this.loadSecondFactors(), setBackupEnabled: (e) => this.setBackupEnabled(e),
markDirty: () => this.markDirty(), },
onError: (e) => this.handleError(e),
}); });
}, },
}, },

View File

@ -1,79 +0,0 @@
import Controller from "@ember/controller";
import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { SECOND_FACTOR_METHODS } from "discourse/models/user";
import { alias } from "@ember/object/computed";
import discourseLater from "discourse-common/lib/later";
export default Controller.extend(ModalFunctionality, {
loading: false,
errorMessage: null,
successMessage: null,
backupEnabled: alias("model.second_factor_backup_enabled"),
remainingCodes: alias("model.second_factor_remaining_backup_codes"),
backupCodes: null,
secondFactorMethod: SECOND_FACTOR_METHODS.TOTP,
onShow() {
this.setProperties({
loading: false,
errorMessage: null,
successMessage: null,
backupCodes: null,
});
},
actions: {
copyBackupCode(successful) {
if (successful) {
this.set(
"successMessage",
I18n.t("user.second_factor_backup.copied_to_clipboard")
);
} else {
this.set(
"errorMessage",
I18n.t("user.second_factor_backup.copy_to_clipboard_error")
);
}
this._hideCopyMessage();
},
generateSecondFactorCodes() {
this.set("loading", true);
this.model
.generateSecondFactorCodes()
.then((response) => {
if (response.error) {
this.set("errorMessage", response.error);
return;
}
this.markDirty();
this.setProperties({
errorMessage: null,
backupCodes: response.backup_codes,
backupEnabled: true,
remainingCodes: response.backup_codes.length,
});
})
.catch((error) => {
this.send("closeModal");
this.onError(error);
})
.finally(() => {
this.setProperties({
loading: false,
});
});
},
},
_hideCopyMessage() {
discourseLater(
() => this.setProperties({ successMessage: null, errorMessage: null }),
2000
);
},
});

View File

@ -1,61 +0,0 @@
<DModalBody>
{{#if this.successMessage}}
<div class="alert alert-success">
{{this.successMessage}}
</div>
{{/if}}
{{#if this.errorMessage}}
<div class="alert alert-error">
{{this.errorMessage}}
</div>
{{/if}}
<ConditionalLoadingSection @isLoading={{this.loading}}>
{{#if this.backupCodes}}
<h3>{{i18n "user.second_factor_backup.codes.title"}}</h3>
<p>
{{i18n "user.second_factor_backup.codes.description"}}
</p>
<BackupCodes
@copyBackupCode={{action "copyBackupCode"}}
@backupCodes={{this.backupCodes}}
/>
{{/if}}
</ConditionalLoadingSection>
{{#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}}
</DModalBody>
<div class="modal-footer">
<div class="actions">
{{#if this.backupEnabled}}
<DButton
@class="btn-primary"
@icon="redo"
@action={{action "generateSecondFactorCodes"}}
@type="submit"
@isLoading={{this.loading}}
@label="user.second_factor_backup.regenerate"
/>
{{else}}
<DButton
@class="btn-primary"
@action={{action "generateSecondFactorCodes"}}
@type="submit"
@disabled={{this.loading}}
@label="user.second_factor_backup.enable"
/>
{{/if}}
</div>
</div>

View File

@ -48,12 +48,10 @@ acceptance("User Preferences - Second Factor Backup", function (needs) {
updateCurrentUser({ second_factor_enabled: true }); updateCurrentUser({ second_factor_enabled: true });
await visit("/u/eviltrout/preferences/second-factor"); await visit("/u/eviltrout/preferences/second-factor");
if (exists(".new-second-factor-backup")) { // create backup codes
// if codes don't exist yet, create them await click(".new-second-factor-backup");
await click(".new-second-factor-backup"); await click(".second-factor-backup-edit-modal .btn-primary");
await click(".second-factor-backup-edit-modal .btn-primary"); await click(".second-factor-backup-edit-modal .modal-close");
await click(".modal-close");
}
await click(".two-factor-backup-dropdown .select-kit-header"); await click(".two-factor-backup-dropdown .select-kit-header");
await click("li[data-name='Disable'"); await click("li[data-name='Disable'");