Updated delete member UI to add toggle to cancel subscriptions (#1647)

no-issue

* Supported cancellation of subscriptions on delete

This makes the cancellation of subscriptions much more obvious to the
user, and we err on the side of caution by *not* cancelling by default.

* Updated base adapter to handle urls with query params

After creating the member adapter and overriding the urlForDeleteRecord
method the flow would take the url from that and it would get passed
into the buildUrl method of the base adapter. At this point it would
append a "/" _after_ the query param.

It would ouput http://admin.com/ghost/api?query=blah/
rather than http://admin.com/ghost/api/?query=blah
This commit is contained in:
Fabien 'egg' O'Carroll 2020-07-24 16:02:42 +02:00 committed by GitHub
parent a0cd857da1
commit 28a2caec98
5 changed files with 67 additions and 6 deletions

View File

@ -28,11 +28,12 @@ export default RESTAdapter.extend(DataAdapterMixin, AjaxServiceSupport, {
buildURL() {
// Ensure trailing slashes
let url = this._super(...arguments);
let parsedUrl = new URL(url);
if (url.slice(-1) !== '/') {
url += '/';
if (!parsedUrl.pathname.endsWith('/')) {
parsedUrl.pathname += '/';
}
return url;
return parsedUrl.toString();
}
});

View File

@ -0,0 +1,14 @@
import ApplicationAdapter from 'ghost-admin/adapters/application';
export default ApplicationAdapter.extend({
urlForDeleteRecord(id, modelName, snapshot) {
let url = this._super(...arguments);
let parsedUrl = new URL(url);
if (snapshot && snapshot.adapterOptions && snapshot.adapterOptions.cancel) {
parsedUrl.searchParams.set('cancel', 'true');
}
return parsedUrl.toString();
}
});

View File

@ -7,6 +7,26 @@
<p>
You're about to delete "<strong>{{or this.member.name this.member.email}}</strong>". This is permanent! We warned you, k?
</p>
{{#if this.hasActiveStripeSubscriptions}}
<div class="flex justify-between">
<div>
<h4>Cancel Stripe subscription</h4>
<p class="pa0 ma0">If disabled, member's Stripe subscription will continue</p>
</div>
<div class="for-switch">
<label class="switch">
<input
class="gh-input"
type="checkbox"
checked={{this.shouldCancelSubscriptions}}
{{on "click" (action "toggleShouldCancelSubscriptions")}}
/>
<span class="input-toggle-component mt1"></span>
</label>
</div>
</div>
{{/if}}
</div>
<div class="modal-footer">

View File

@ -1,25 +1,46 @@
import ModalComponent from 'ghost-admin/components/modal-base';
import {alias} 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'),
hasActiveStripeSubscriptions: computed('member', function () {
let subscriptions = this.member.get('stripe');
if (!subscriptions || subscriptions.length === 0) {
return false;
}
let firstActiveStripeSubscription = subscriptions.find((subscription) => {
return subscription.status === 'active' || subscription.status === 'trialing';
});
return firstActiveStripeSubscription !== undefined;
}),
actions: {
confirm() {
this.deleteMember.perform();
},
toggleShouldCancelSubscriptions() {
this.shouldCancelSubscriptions = !this.shouldCancelSubscriptions;
}
},
deleteMember: task(function* () {
try {
yield this.confirm();
yield this.confirm(this.shouldCancelSubscriptions);
this.membersStats.invalidate();
} finally {
this.send('closeModal');

View File

@ -65,8 +65,13 @@ export default class MemberController extends Controller {
}
@action
deleteMember() {
return this.member.destroyRecord().then(() => {
deleteMember(cancelSubscriptions = false) {
let options = {
adapterOptions: {
cancel: cancelSubscriptions
}
};
return this.member.destroyRecord(options).then(() => {
this.members.refreshData();
return this.transitionToRoute('members');
}, (error) => {