mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 14:43:08 +03:00
Refactored modal for bulk unsubscribing members
refs https://github.com/TryGhost/Team/issues/559 Members controller was becoming bloated and difficult to follow due to catering for many different concerns. - converted old modal to newer promise-modal style - pulled full bulk unsubscribing logic out of the members controller and into the modal so logic is contained in one place
This commit is contained in:
parent
8052166cbe
commit
f1c8af662f
@ -0,0 +1,80 @@
|
|||||||
|
<div class="modal-content">
|
||||||
|
<header class="modal-header" data-test-modal="unsubscribe-members">
|
||||||
|
<h1>Unsubscribe members from email?</h1>
|
||||||
|
</header>
|
||||||
|
<a class="close" href="" role="button" title="Close" {{on "click" @close}}>{{svg-jar "close"}}<span class="hidden">Close</span></a>
|
||||||
|
|
||||||
|
{{#if this.hasRun}}
|
||||||
|
<div class="gh-content-box pa" data-test-state="unsubscribe-complete">
|
||||||
|
{{#if this.error}}
|
||||||
|
<div class="flex items-center">
|
||||||
|
{{svg-jar "warning" class="w4 h4 fill-red mr2 nudge-top--3"}}
|
||||||
|
<div>
|
||||||
|
<p class="ma0 pa0">
|
||||||
|
<span class="fw5" data-test-text="unsubscribe-error">{{this.error}}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="flex items-center">
|
||||||
|
{{svg-jar "check-circle" class="w4 h4 stroke-green mr2"}}
|
||||||
|
<p class="ma0 pa0">
|
||||||
|
<span class="fw6" data-test-text="unsubscribe-count">{{gh-pluralize this.response.stats.successful "member"}}</span>
|
||||||
|
successfully unsubscribed
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{{#if this.response.stats.unsuccessful}}
|
||||||
|
<div class="flex items-start mt2" data-test-bulk-delete-errors>
|
||||||
|
{{svg-jar "warning" class="w4 h4 fill-red mr2 nudge-top--3"}}
|
||||||
|
<div>
|
||||||
|
<p class="ma0 pa0">
|
||||||
|
<span class="fw5" data-test-text="invalid-count">{{gh-pluralize this.response.stats.unsuccessful "member"}}</span>
|
||||||
|
failed to unsubscribe
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="modal-body" data-test-state="unsubscribe-unconfirmed">
|
||||||
|
{{#if @data.query}}
|
||||||
|
{{#let (members-count-fetcher query=@data.query) as |countFetcher|}}
|
||||||
|
{{#if countFetcher.isLoading}}
|
||||||
|
<GhLoadingSpinner />
|
||||||
|
{{else}}
|
||||||
|
<p>
|
||||||
|
You're about to unsubscribe
|
||||||
|
<strong data-test-text="unsubscribe-count">{{gh-pluralize countFetcher.count "member"}}</strong> from email newsletters.
|
||||||
|
Are you sure?
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
|
{{/let}}
|
||||||
|
{{else}}
|
||||||
|
<p>No members are selected.</p>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
{{#if this.hasRun}}
|
||||||
|
<button class="gh-btn gh-btn-black" data-test-button="close-modal" type="button" {{on "click" @close}}>
|
||||||
|
<span>Close</span>
|
||||||
|
</button>
|
||||||
|
{{else}}
|
||||||
|
<button class="gh-btn" data-test-button="cancel" type="button" {{on "click" @close}}>
|
||||||
|
<span>Cancel</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<GhTaskButton
|
||||||
|
@disabled={{this.isDisabled}}
|
||||||
|
@buttonText="Unsubscribe members"
|
||||||
|
@successText="Unsubscribed"
|
||||||
|
@task={{this.bulkUnsubscribeTask}}
|
||||||
|
@class="gh-btn gh-btn-red gh-btn-icon"
|
||||||
|
data-test-button="confirm"
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
@ -0,0 +1,55 @@
|
|||||||
|
import Component from '@glimmer/component';
|
||||||
|
import {action} from '@ember/object';
|
||||||
|
import {inject as service} from '@ember/service';
|
||||||
|
import {task} from 'ember-concurrency';
|
||||||
|
import {tracked} from '@glimmer/tracking';
|
||||||
|
|
||||||
|
export default class MembersBulkUnsubscribeModal extends Component {
|
||||||
|
@service ajax;
|
||||||
|
@service ghostPaths;
|
||||||
|
|
||||||
|
@tracked error;
|
||||||
|
@tracked response;
|
||||||
|
|
||||||
|
get isDisabled() {
|
||||||
|
return !this.args.data.query;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasRun() {
|
||||||
|
return !!(this.error || this.response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
setLabel(label) {
|
||||||
|
this.selectedLabel = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
@task({drop: true})
|
||||||
|
*bulkUnsubscribeTask() {
|
||||||
|
try {
|
||||||
|
const query = new URLSearchParams(this.args.data.query);
|
||||||
|
const removeLabelUrl = `${this.ghostPaths.url.api('members/bulk')}?${query}`;
|
||||||
|
const response = yield this.ajax.put(removeLabelUrl, {
|
||||||
|
data: {
|
||||||
|
bulk: {
|
||||||
|
action: 'unsubscribe',
|
||||||
|
meta: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.args.data.onComplete?.();
|
||||||
|
|
||||||
|
this.response = response?.bulk?.meta;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
if (e.payload?.errors) {
|
||||||
|
this.error = e.payload.errors[0].message;
|
||||||
|
} else {
|
||||||
|
this.error = 'An unknown error occurred. Please try again.';
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,7 +53,6 @@ export default class MembersController extends Controller {
|
|||||||
@tracked orderParam = null;
|
@tracked orderParam = null;
|
||||||
@tracked modalLabel = null;
|
@tracked modalLabel = null;
|
||||||
@tracked showLabelModal = false;
|
@tracked showLabelModal = false;
|
||||||
@tracked showUnsubscribeMembersModal = false;
|
|
||||||
@tracked filters = A([]);
|
@tracked filters = A([]);
|
||||||
@tracked softFilters = A([]);
|
@tracked softFilters = A([]);
|
||||||
|
|
||||||
@ -301,11 +300,7 @@ export default class MembersController extends Controller {
|
|||||||
bulkAddLabel() {
|
bulkAddLabel() {
|
||||||
this.modals.open('modals/members/bulk-add-label', {
|
this.modals.open('modals/members/bulk-add-label', {
|
||||||
query: this.getApiQueryObject(),
|
query: this.getApiQueryObject(),
|
||||||
onComplete: () => {
|
onComplete: this.resetAndReloadMembers
|
||||||
// reset and reload
|
|
||||||
this.store.unloadAll('member');
|
|
||||||
this.reload();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,20 +308,30 @@ export default class MembersController extends Controller {
|
|||||||
bulkRemoveLabel() {
|
bulkRemoveLabel() {
|
||||||
this.modals.open('modals/members/bulk-remove-label', {
|
this.modals.open('modals/members/bulk-remove-label', {
|
||||||
query: this.getApiQueryObject(),
|
query: this.getApiQueryObject(),
|
||||||
onComplete: () => {
|
onComplete: this.resetAndReloadMembers
|
||||||
// reset and reload
|
|
||||||
this.store.unloadAll('member');
|
|
||||||
this.reload();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
bulkUnsubscribe() {
|
||||||
|
this.modals.open('modals/members/bulk-unsubscribe', {
|
||||||
|
query: this.getApiQueryObject(),
|
||||||
|
onComplete: this.resetAndReloadMembers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
resetAndReloadMembers() {
|
||||||
|
this.store.unloadAll('member');
|
||||||
|
this.reload();
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
bulkDelete() {
|
bulkDelete() {
|
||||||
this.modals.open('modals/members/bulk-delete', {
|
this.modals.open('modals/members/bulk-delete', {
|
||||||
query: this.getApiQueryObject(),
|
query: this.getApiQueryObject(),
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
// reset and reload
|
// reset, clear filters, and reload list and counts
|
||||||
this.store.unloadAll('member');
|
this.store.unloadAll('member');
|
||||||
this.router.transitionTo('members.index', {queryParams: Object.assign(resetQueryParams('members.index'))});
|
this.router.transitionTo('members.index', {queryParams: Object.assign(resetQueryParams('members.index'))});
|
||||||
this.membersStats.invalidate();
|
this.membersStats.invalidate();
|
||||||
@ -340,16 +345,6 @@ export default class MembersController extends Controller {
|
|||||||
this.paidParam = paid.value;
|
this.paidParam = paid.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
|
||||||
toggleUnsubscribeMembersModal() {
|
|
||||||
this.showUnsubscribeMembersModal = !this.showUnsubscribeMembersModal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
unsubscribeMembers() {
|
|
||||||
return this.unsubscribeMembersTask.perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tasks -------------------------------------------------------------------
|
// Tasks -------------------------------------------------------------------
|
||||||
|
|
||||||
@task({restartable: true})
|
@task({restartable: true})
|
||||||
@ -416,30 +411,6 @@ export default class MembersController extends Controller {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@task({drop: true})
|
|
||||||
*unsubscribeMembersTask() {
|
|
||||||
const query = new URLSearchParams(this.getApiQueryObject());
|
|
||||||
const unsubscribeUrl = `${this.ghostPaths.url.api('members/bulk')}?${query}`;
|
|
||||||
// response contains details of which members failed to be unsubscribe
|
|
||||||
const response = yield this.ajax.put(unsubscribeUrl, {
|
|
||||||
data: {
|
|
||||||
bulk: {
|
|
||||||
action: 'unsubscribe',
|
|
||||||
meta: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// reset and reload
|
|
||||||
this.store.unloadAll('member');
|
|
||||||
this.reload();
|
|
||||||
|
|
||||||
this.membersStats.invalidate();
|
|
||||||
this.membersStats.fetchCounts();
|
|
||||||
|
|
||||||
return response?.bulk?.meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal ----------------------------------------------------------------
|
// Internal ----------------------------------------------------------------
|
||||||
|
|
||||||
resetFilters(params) {
|
resetFilters(params) {
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button class="mr2" data-test-button="remove-label-selected" type="button" {{on "click" this.toggleUnsubscribeMembersModal}}>
|
<button class="mr2" data-test-button="remove-label-selected" type="button" {{on "click" this.bulkUnsubscribe}}>
|
||||||
<span>Unsubscribe selected members ({{this.members.length}})</span>
|
<span>Unsubscribe selected members ({{this.members.length}})</span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
Loading…
Reference in New Issue
Block a user