Skip to content

Commit

Permalink
🐛 Fixed missing unsaved changes modal for member newsletters (#15564)
Browse files Browse the repository at this point in the history
closes: #15507

- manually handle relationship changes detection labels and newsletters
- add `dirtyAttributes` controller property - return newsletters and labels dirty attributes status
  • Loading branch information
h2akim authored Oct 21, 2022
1 parent b88a54f commit 48b033f
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 23 deletions.
57 changes: 57 additions & 0 deletions ghost/admin/app/controllers/member.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export default class MemberController extends Controller {
@tracked modalLabel = null;
@tracked showLabelModal = false;

_previousLabels = null;
_previousNewsletters = null;

constructor() {
super(...arguments);
this._availableLabels = this.store.peekAll('label');
Expand All @@ -39,6 +42,18 @@ export default class MemberController extends Controller {
this.model = member;
}

get dirtyAttributes() {
return this._hasDirtyAttributes();
}

get _labels() {
return this.member.get('labels').map(label => label.name);
}

get _newsletters() {
return this.member.get('newsletters').map(newsletter => newsletter.id);
}

get labelModalData() {
let label = this.modalLabel;
let labels = this.availableLabels;
Expand Down Expand Up @@ -75,6 +90,12 @@ export default class MemberController extends Controller {

// Actions -----------------------------------------------------------------

@action
setInitialRelationshipValues() {
this._previousLabels = this._labels;
this._previousNewsletters = this._newsletters;
}

@action
toggleLabelModal() {
this.showLabelModal = !this.showLabelModal;
Expand Down Expand Up @@ -139,6 +160,8 @@ export default class MemberController extends Controller {
member.updateLabels();
this.members.refreshData();

this.setInitialRelationshipValues();

// replace 'member.new' route with 'member' route
this.replaceRoute('member', member);

Expand Down Expand Up @@ -171,6 +194,8 @@ export default class MemberController extends Controller {
include: 'tiers'
});

this.setInitialRelationshipValues();

this.isLoading = false;
}

Expand All @@ -190,4 +215,36 @@ export default class MemberController extends Controller {

this.member[propKey] = newValue;
}

_hasDirtyAttributes() {
let member = this.member;

if (!member) {
return false;
}

// member.labels is an array so hasDirtyAttributes doesn't pick up
// changes unless the array ref is changed.
// use sort() to sort of detect same item is re-added
let currentLabels = (this._labels.sort() || []).join(', ');
let previousLabels = (this._previousLabels.sort() || []).join(', ');
if (currentLabels !== previousLabels) {
return true;
}

// member.newsletters is an array so hasDirtyAttributes doesn't pick up
// changes unless the array ref is changed
// use sort() to sort of detect same item is re-enabled
let currentNewsletters = (this._newsletters.sort() || []).join(', ');
let previousNewsletters = (this._previousNewsletters.sort() || []).join(', ');
if (currentNewsletters !== previousNewsletters) {
return true;
}

// we've covered all the non-tracked cases we care about so fall
// back on Ember Data's default dirty attribute checks
let {hasDirtyAttributes} = member;

return hasDirtyAttributes;
}
}
43 changes: 20 additions & 23 deletions ghost/admin/app/routes/member.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,42 +48,39 @@ export default class MembersRoute extends AdminRoute {

@action
async willTransition(transition) {
if (this.hasConfirmed) {
return true;
}

transition.abort();
let hasDirtyAttributes = this.controller.dirtyAttributes;

// wait for any existing confirm modal to be closed before allowing transition
if (this.confirmModal) {
return;
}

if (this.controller.saveTask?.isRunning) {
await this.controller.saveTask.last;
}
if (!this.hasConfirmed && hasDirtyAttributes) {
transition.abort();

const shouldLeave = await this.confirmUnsavedChanges();
if (this.controller.saveTask?.isRunning) {
await this.controller.saveTask.last;
transition.retry();
}

if (shouldLeave) {
this.controller.model.rollbackAttributes();
this.hasConfirmed = true;
return transition.retry();
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;
}
this.confirmModal = this.modals
.open(ConfirmUnsavedChangesModal)
.finally(() => {
this.confirmModal = null;
});

return true;
return this.confirmModal;
}

closeImpersonateModal(transition) {
Expand Down

0 comments on commit 48b033f

Please sign in to comment.