diff --git a/ghost/admin/app/controllers/member.js b/ghost/admin/app/controllers/member.js index bdaca12866..03a7ce06a6 100644 --- a/ghost/admin/app/controllers/member.js +++ b/ghost/admin/app/controllers/member.js @@ -20,12 +20,9 @@ export default class MemberController extends Controller { @tracked isLoading = false; @tracked showDeleteMemberModal = false; @tracked showImpersonateMemberModal = false; - @tracked showUnsavedChangesModal = false; @tracked modalLabel = null; @tracked showLabelModal = false; - leaveScreenTransition = null; - constructor() { super(...arguments); this._availableLabels = this.store.peekAll('label'); @@ -135,37 +132,6 @@ export default class MemberController extends Controller { }); } - @action - toggleUnsavedChangesModal(transition) { - let leaveTransition = this.leaveScreenTransition; - - if (!transition && this.showUnsavedChangesModal) { - this.leaveScreenTransition = null; - this.showUnsavedChangesModal = false; - return; - } - - if (!leaveTransition || transition.targetName === leaveTransition.targetName) { - this.leaveScreenTransition = transition; - - // if a save is running, wait for it to finish then transition - if (this.save.isRunning) { - return this.save.last.then(() => { - transition.retry(); - }); - } - - // we genuinely have unsaved data, show the modal - this.showUnsavedChangesModal = true; - } - } - - @action - leaveScreen() { - this.member.rollbackAttributes(); - return this.leaveScreenTransition.retry(); - } - // Tasks ------------------------------------------------------------------- @task({drop: true}) diff --git a/ghost/admin/app/routes/member.js b/ghost/admin/app/routes/member.js index 9d3dfd9437..905b70be4e 100644 --- a/ghost/admin/app/routes/member.js +++ b/ghost/admin/app/routes/member.js @@ -1,9 +1,11 @@ import AdminRoute from 'ghost-admin/routes/admin'; +import ConfirmUnsavedChangesModal from '../components/modals/confirm-unsaved-changes'; import {action} from '@ember/object'; import {inject as service} from '@ember/service'; export default class MembersRoute extends AdminRoute { @service feature; + @service modals; @service router; _requiresBackgroundRefresh = true; @@ -11,7 +13,6 @@ export default class MembersRoute extends AdminRoute { constructor() { super(...arguments); this.router.on('routeWillChange', (transition) => { - this.showUnsavedChangesModal(transition); this.closeImpersonateModal(transition); }); } @@ -36,10 +37,10 @@ export default class MembersRoute extends AdminRoute { } deactivate() { - super.deactivate(...arguments); - // clean up newly created records and revert unsaved changes to existing - this.controller.member.rollbackAttributes(); this._requiresBackgroundRefresh = true; + + this.confirmModal = null; + this.hasConfirmed = false; } @action @@ -47,27 +48,48 @@ export default class MembersRoute extends AdminRoute { this.controller.save(); } - titleToken() { - return this.controller.member.name; - } + @action + async willTransition(transition) { + if (this.hasConfirmed) { + return true; + } - showUnsavedChangesModal(transition) { - if (transition.from && transition.from.name === this.routeName && transition.targetName) { - let {controller} = this; + transition.abort(); - // member.changedAttributes is always true for new members but number of changed attrs is reliable - let isChanged = Object.keys(controller.member.changedAttributes()).length > 0; + // wait for any existing confirm modal to be closed before allowing transition + if (this.confirmModal) { + return; + } - if (!controller.member.isDeleted && isChanged) { - transition.abort(); - controller.toggleUnsavedChangesModal(transition); - return; - } + if (this.controller.saveTask?.isRunning) { + await this.controller.saveTask.last; + } + + const shouldLeave = await this.confirmUnsavedChanges(); + + if (shouldLeave) { + this.controller.model.rollbackAttributes(); + this.hasConfirmed = true; + return transition.retry(); } } + async confirmUnsavedChanges() { + if (this.controller.model?.hasDirtyAttributes) { + this.confirmModal = this.modals + .open(ConfirmUnsavedChangesModal) + .finally(() => { + this.confirmModal = null; + }); + + return this.confirmModal; + } + + return true; + } + closeImpersonateModal(transition) { - // If user navigates away with forward or back button, ensure returning to page + // If user navigates away with forward or back button, ensure returning to page // hides modal if (transition.from && transition.from.name === this.routeName && transition.targetName) { let {controller} = this; @@ -75,4 +97,8 @@ export default class MembersRoute extends AdminRoute { controller.closeImpersonateMemberModal(transition); } } + + titleToken() { + return this.controller.member.name; + } } diff --git a/ghost/admin/app/templates/member.hbs b/ghost/admin/app/templates/member.hbs index 12b6f5b6e1..2a958a48d7 100644 --- a/ghost/admin/app/templates/member.hbs +++ b/ghost/admin/app/templates/member.hbs @@ -68,14 +68,6 @@ -{{#if this.showUnsavedChangesModal}} - -{{/if}} - {{#if this.showDeleteMemberModal}}