Switched delete member modal to new modal pattern (#15467)

refs https://github.com/TryGhost/Team/issues/1734
refs https://github.com/TryGhost/Team/issues/559
refs https://github.com/TryGhost/Ghost/issues/14101

- switches to newer modal patterns ready for later Ember upgrades
This commit is contained in:
Kevin Ansfield 2022-09-24 18:18:34 +02:00 committed by GitHub
parent 63b1e4e8ad
commit ac67475605
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 100 additions and 119 deletions

View File

@ -0,0 +1,37 @@
<div class="modal-content" data-test-modal="delete-member">
<header class="modal-header">
<h1>Delete member account</h1>
</header>
<button type="button" class="close" title="Close" {{on "click" (fn @close false)}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
<div class="modal-body">
<p class="mb6">
Permanently delete <strong>{{@data.member.email}}</strong> from Ghost.
</p>
{{#if this.hasActiveStripeSubscriptions}}
<div class="flex justify-between">
<div class="form-group for-checkbox gh-member-cancelstripe-checkbox">
<label class="checkbox">
<input
class="gh-input"
type="checkbox"
checked={{this.shouldCancelSubscriptions}}
{{on "click" (toggle "toggleShouldCancelSubscriptions" this)}}
/>
<span class="input-toggle-component"></span>
<div>
<h4>Also cancel subscription in Stripe</h4>
<p>If disabled, the members premium subscription will continue</p>
</div>
</label>
</div>
</div>
{{/if}}
</div>
<div class="modal-footer">
<button class="gh-btn" type="button" {{on "click" (fn @close false)}}><span>Cancel</span></button>
<GhTaskButton @buttonText={{if this.shouldCancelSubscriptions "Delete member + Cancel subscription" "Delete member"}} @successText="Deleted" @task={{this.deleteMemberTask}} @class="gh-btn gh-btn-red gh-btn-icon" />
</div>
</div>

View File

@ -0,0 +1,47 @@
import Component from '@glimmer/component';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';
export default class DeleteMemberModal extends Component {
@service notifications;
@tracked shouldCancelSubscriptions = false;
get member() {
return this.args.data.member;
}
get hasActiveStripeMigrations() {
const subscriptions = this.member.get('subscriptions');
if (!subscriptions || subscriptions.length === 0) {
return false;
}
const firstActiveStripeSubscription = subscriptions.find((subscription) => {
return ['active', 'trialing', 'unpaid', 'past_due'].includes(subscription.status);
});
return firstActiveStripeSubscription !== undefined;
}
@task({drop: true})
*deleteMemberTask() {
const options = {
adapterOptions: {
cancel: this.shouldCancelSubscriptions
}
};
try {
yield this.member.destroyRecord(options);
this.args.data.afterDelete?.();
this.args.close(true);
} catch (e) {
this.notifications.showAPIError(e, {key: 'member.delete'});
this.args.close(false);
throw e;
}
}
}

View File

@ -1,35 +0,0 @@
<header class="modal-header">
<h1>Delete member account</h1>
</header>
<a class="close" href="" role="button" title="Close" {{action "closeModal"}}>{{svg-jar "close"}}<span class="hidden">Close</span></a>
<div class="modal-body">
<p class="mb6">
Permanently delete <strong>{{this.member.email}}</strong> from Ghost.
</p>
{{#if this.hasActiveStripeSubscriptions}}
<div class="flex justify-between">
<div class="form-group for-checkbox gh-member-cancelstripe-checkbox">
<label class="checkbox">
<input
class="gh-input"
type="checkbox"
checked={{this.shouldCancelSubscriptions}}
{{on "click" (action "toggleShouldCancelSubscriptions")}}
/>
<span class="input-toggle-component"></span>
<div>
<h4>Also cancel subscription in Stripe</h4>
<p>If disabled, the members premium subscription will continue</p>
</div>
</label>
</div>
</div>
{{/if}}
</div>
<div class="modal-footer">
<button class="gh-btn" type="button" {{action "closeModal"}}><span>Cancel</span></button>
<GhTaskButton @buttonText={{if this.shouldCancelSubscriptions "Delete member + Cancel subscription" "Delete member"}} @successText="Deleted" @task={{this.deleteMember}} @class="gh-btn gh-btn-red gh-btn-icon" />
</div>

View File

@ -1,51 +0,0 @@
import ModalComponent from 'ghost-admin/components/modal-base';
import {alias, reads} from '@ember/object/computed';
import {computed} from '@ember/object';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
export default ModalComponent.extend({
membersStats: service(),
shouldCancelSubscriptions: false,
// Allowed actions
confirm: () => {},
member: alias('model'),
cancelSubscriptions: reads('shouldCancelSubscriptions'),
hasActiveStripeSubscriptions: computed('member', function () {
let subscriptions = this.member.get('subscriptions');
if (!subscriptions || subscriptions.length === 0) {
return false;
}
let firstActiveStripeSubscription = subscriptions.find((subscription) => {
return ['active', 'trialing', 'unpaid', 'past_due'].includes(subscription.status);
});
return firstActiveStripeSubscription !== undefined;
}),
actions: {
confirm() {
this.deleteMember.perform();
},
toggleShouldCancelSubscriptions() {
this.set('shouldCancelSubscriptions', !this.shouldCancelSubscriptions);
}
},
deleteMember: task(function* () {
try {
yield this.confirm(this.shouldCancelSubscriptions);
this.membersStats.invalidate();
} finally {
this.send('closeModal');
}
}).drop()
});

View File

@ -1,4 +1,5 @@
import Controller, {inject as controller} from '@ember/controller';
import DeleteMemberModal from '../components/members/modals/delete-member';
import EmberObject, {action, defineProperty} from '@ember/object';
import boundOneWay from 'ghost-admin/utils/bound-one-way';
import moment from 'moment-timezone';
@ -13,12 +14,12 @@ export default class MemberController extends Controller {
@service session;
@service dropdown;
@service membersStats;
@service modals;
@service notifications;
@service router;
@service store;
@tracked isLoading = false;
@tracked showDeleteMemberModal = false;
@tracked showImpersonateMemberModal = false;
@tracked modalLabel = null;
@tracked showLabelModal = false;
@ -34,6 +35,10 @@ export default class MemberController extends Controller {
return this.model;
}
set member(member) {
this.model = member;
}
get labelModalData() {
let label = this.modalLabel;
let labels = this.availableLabels;
@ -56,10 +61,6 @@ export default class MemberController extends Controller {
return options;
}
set member(member) {
this.model = member;
}
get scratchMember() {
let scratchMember = EmberObject.create({member: this.member});
SCRATCH_PROPS.forEach(prop => defineProperty(scratchMember, prop, boundOneWay(`member.${prop}`)));
@ -97,8 +98,15 @@ export default class MemberController extends Controller {
}
@action
toggleDeleteMemberModal() {
this.showDeleteMemberModal = !this.showDeleteMemberModal;
confirmDeleteMember() {
this.modals.open(DeleteMemberModal, {
member: this.member,
afterDelete: () => {
this.membersStats.invalidate();
this.members.refreshData();
this.transitionToRoute('members');
}
});
}
@action
@ -116,22 +124,6 @@ export default class MemberController extends Controller {
return this.saveTask.perform();
}
@action
deleteMember(cancelSubscriptions = false) {
let options = {
adapterOptions: {
cancel: cancelSubscriptions
}
};
return this.member.destroyRecord(options).then(() => {
this.members.refreshData();
this.transitionToRoute('members');
return;
}, (error) => {
return this.notifications.showAPIError(error, {key: 'member.delete'});
});
}
// Tasks -------------------------------------------------------------------
@task({drop: true})

View File

@ -39,7 +39,7 @@
<button
type="button"
class="mr2"
{{on "click" this.toggleDeleteMemberModal}}
{{on "click" this.confirmDeleteMember}}
data-test-button="delete-member"
>
<span class="red">Delete member</span>
@ -68,15 +68,6 @@
</div>
</section>
{{#if this.showDeleteMemberModal}}
<GhFullscreenModal
@modal="delete-member"
@model={{this.member}}
@confirm={{this.deleteMember}}
@close={{this.toggleDeleteMemberModal}}
@modifier="action wide" />
{{/if}}
{{#if this.showImpersonateMemberModal}}
<GhFullscreenModal
@modal="impersonate-member"