mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-17 23:44:39 -05:00
Refactored modal for bulk adding a label to 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 label-adding logic out of the members controller and into the modal so logic is contained in one place - added `{{members-count-fetcher}}` resource that allows for member counts to be fetched directly from templates avoiding duplicated code
This commit is contained in:
parent
510b115329
commit
24b222e927
7 changed files with 194 additions and 149 deletions
|
@ -1,65 +0,0 @@
|
|||
<header class="modal-header" data-test-modal="add-label-members">
|
||||
<h1>Add Label</h1>
|
||||
</header>
|
||||
<a class="close" href="" role="button" title="Close" {{action "closeModal"}}>{{svg-jar "close"}}<span class="hidden">Close</span></a>
|
||||
|
||||
{{#if this.confirmed}}
|
||||
<div class="gh-content-box pa" data-test-state="add-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="add-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">
|
||||
Label added to <span class="fw6" data-test-text="add-count">{{gh-pluralize this.response.stats.successful "member"}}</span>
|
||||
successfully
|
||||
</p>
|
||||
</div>
|
||||
{{#if this.response.stats.unsuccessful}}
|
||||
<div class="flex items-start mt2" data-test-bulk-label-add-errors>
|
||||
{{svg-jar "warning" class="w4 h4 fill-red mr2 nudge-top--3"}}
|
||||
<div>
|
||||
<p class="ma0 pa0">
|
||||
Failed to add label to <span class="fw5" data-test-text="invalid-count"> {{gh-pluralize this.response.stats.unsuccessful "member"}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="modal-body" data-test-state="add-label-unconfirmed">
|
||||
<GhMemberSingleLabelInput @onChange={{action "setLabel"}} @triggerId="label-input" data-test-input="" />
|
||||
<p class="mt2 ml1">
|
||||
Will be added to the currently selected <span class="fw6" data-test-text="member-count">{{gh-pluralize this.model.memberCount "member"}}</span>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="modal-footer">
|
||||
{{#if this.confirmed}}
|
||||
<button class="gh-btn gh-btn-black" data-test-button="close-modal" type="button" {{action "closeModal"}}>
|
||||
<span>Close</span>
|
||||
</button>
|
||||
{{else}}
|
||||
<button class="gh-btn" data-test-button="cancel" type="button" {{action "closeModal"}}>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
|
||||
<GhTaskButton
|
||||
@buttonText="Add Label"
|
||||
@successText="Added"
|
||||
@disabled={{this.isDisabled}}
|
||||
@task={{this.addLabelTask}}
|
||||
@class="gh-btn gh-btn-green gh-btn-icon"
|
||||
data-test-button="confirm"
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -1,38 +0,0 @@
|
|||
import ModalComponent from 'ghost-admin/components/modal-base';
|
||||
import {alias, not} from '@ember/object/computed';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
export default ModalComponent.extend({
|
||||
membersStats: service(),
|
||||
selectedLabel: null,
|
||||
// Allowed actions
|
||||
confirm: () => {},
|
||||
|
||||
isDisabled: not('selectedLabel'),
|
||||
member: alias('model'),
|
||||
|
||||
actions: {
|
||||
confirm() {
|
||||
this.addLabelTask.perform();
|
||||
},
|
||||
|
||||
setLabel(label) {
|
||||
this.set('selectedLabel', label);
|
||||
}
|
||||
},
|
||||
|
||||
addLabelTask: task(function* () {
|
||||
try {
|
||||
const response = yield this.confirm(this.selectedLabel);
|
||||
this.set('response', response);
|
||||
this.set('confirmed', true);
|
||||
} catch (e) {
|
||||
if (e.payload?.errors) {
|
||||
this.set('confirmed', true);
|
||||
this.set('error', e.payload.errors[0].message);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}).drop()
|
||||
});
|
81
ghost/admin/app/components/modals/members/bulk-add-label.hbs
Normal file
81
ghost/admin/app/components/modals/members/bulk-add-label.hbs
Normal file
|
@ -0,0 +1,81 @@
|
|||
<div class="modal-content">
|
||||
<header class="modal-header" data-test-modal="add-label-members">
|
||||
<h1>Add Label</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="add-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="add-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">
|
||||
Label added to
|
||||
<span class="fw6" data-test-text="add-count">{{gh-pluralize this.response.stats.successful "member"}}</span>
|
||||
successfully
|
||||
</p>
|
||||
</div>
|
||||
{{#if this.response.stats.unsuccessful}}
|
||||
<div class="flex items-start mt2" data-test-bulk-label-add-errors>
|
||||
{{svg-jar "warning" class="w4 h4 fill-red mr2 nudge-top--3"}}
|
||||
<div>
|
||||
<p class="ma0 pa0">
|
||||
Failed to add label to <span class="fw5" data-test-text="invalid-count"> {{gh-pluralize this.response.stats.unsuccessful "member"}}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="modal-body" data-test-state="add-label-unconfirmed">
|
||||
{{#if @data.query}}
|
||||
{{#let (members-count-fetcher query=@data.query) as |countFetcher|}}
|
||||
{{#if countFetcher.isLoading}}
|
||||
<GhLoadingSpinner />
|
||||
{{else}}
|
||||
<GhMemberSingleLabelInput
|
||||
@onChange={{this.setLabel}}
|
||||
@triggerId="label-input"
|
||||
/>
|
||||
<p class="mt2 ml1">
|
||||
Will be added to the currently selected <span class="fw6" data-test-text="member-count">{{gh-pluralize countFetcher.count "member"}}</span>
|
||||
</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
|
||||
@buttonText="Add Label"
|
||||
@successText="Added"
|
||||
@disabled={{this.isDisabled}}
|
||||
@task={{this.addLabelTask}}
|
||||
@class="gh-btn gh-btn-green gh-btn-icon"
|
||||
data-test-button="confirm"
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
60
ghost/admin/app/components/modals/members/bulk-add-label.js
Normal file
60
ghost/admin/app/components/modals/members/bulk-add-label.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
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 MembersBulkAddLabelModal extends Component {
|
||||
@service ajax;
|
||||
@service ghostPaths;
|
||||
|
||||
@tracked error;
|
||||
@tracked response;
|
||||
@tracked selectedLabel;
|
||||
|
||||
get isDisabled() {
|
||||
return !this.args.data.query || !this.selectedLabel;
|
||||
}
|
||||
|
||||
get hasRun() {
|
||||
return !!(this.error || this.response);
|
||||
}
|
||||
|
||||
@action
|
||||
setLabel(label) {
|
||||
this.selectedLabel = label;
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*addLabelTask() {
|
||||
try {
|
||||
const query = new URLSearchParams(this.args.data.query);
|
||||
const addLabelUrl = `${this.ghostPaths.url.api('members/bulk')}?${query}`;
|
||||
const response = yield this.ajax.put(addLabelUrl, {
|
||||
data: {
|
||||
bulk: {
|
||||
action: 'addLabel',
|
||||
meta: {
|
||||
label: {
|
||||
id: this.selectedLabel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ export default class MembersController extends Controller {
|
|||
@service feature;
|
||||
@service ghostPaths;
|
||||
@service membersStats;
|
||||
@service modals;
|
||||
@service router;
|
||||
@service store;
|
||||
@service utils;
|
||||
|
@ -55,7 +56,6 @@ export default class MembersController extends Controller {
|
|||
@tracked showLabelModal = false;
|
||||
@tracked showDeleteMembersModal = false;
|
||||
@tracked showUnsubscribeMembersModal = false;
|
||||
@tracked showAddMembersLabelModal = false;
|
||||
@tracked showRemoveMembersLabelModal = false;
|
||||
@tracked filters = A([]);
|
||||
@tracked softFilters = A([]);
|
||||
|
@ -311,6 +311,18 @@ export default class MembersController extends Controller {
|
|||
this.showLabelModal = !this.showLabelModal;
|
||||
}
|
||||
|
||||
@action
|
||||
bulkAddLabel() {
|
||||
this.modals.open('modals/members/bulk-add-label', {
|
||||
query: this.getApiQueryObject(),
|
||||
onComplete: () => {
|
||||
// reset and reload
|
||||
this.store.unloadAll('member');
|
||||
this.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
changePaidParam(paid) {
|
||||
this.paidParam = paid.value;
|
||||
|
@ -326,11 +338,6 @@ export default class MembersController extends Controller {
|
|||
this.showUnsubscribeMembersModal = !this.showUnsubscribeMembersModal;
|
||||
}
|
||||
|
||||
@action
|
||||
toggleAddMembersLabelModal() {
|
||||
this.showAddMembersLabelModal = !this.showAddMembersLabelModal;
|
||||
}
|
||||
|
||||
@action
|
||||
toggleRemoveMembersLabelModal() {
|
||||
this.showRemoveMembersLabelModal = !this.showRemoveMembersLabelModal;
|
||||
|
@ -346,11 +353,6 @@ export default class MembersController extends Controller {
|
|||
return this.unsubscribeMembersTask.perform();
|
||||
}
|
||||
|
||||
@action
|
||||
addLabelToMembers(selectedLabel) {
|
||||
return this.addLabelToMembersTask.perform(selectedLabel);
|
||||
}
|
||||
|
||||
@action
|
||||
removeLabelFromMembers(selectedLabel) {
|
||||
return this.removeLabelFromMembersTask.perform(selectedLabel);
|
||||
|
@ -489,30 +491,6 @@ export default class MembersController extends Controller {
|
|||
return response?.bulk?.meta;
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*addLabelToMembersTask(selectedLabel) {
|
||||
const query = new URLSearchParams(this.getApiQueryObject());
|
||||
const addLabelUrl = `${this.ghostPaths.url.api('members/bulk')}?${query}`;
|
||||
const response = yield this.ajax.put(addLabelUrl, {
|
||||
data: {
|
||||
bulk: {
|
||||
action: 'addLabel',
|
||||
meta: {
|
||||
label: {
|
||||
id: selectedLabel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// reset and reload
|
||||
this.store.unloadAll('member');
|
||||
this.reload();
|
||||
|
||||
return response?.bulk?.meta;
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*removeLabelFromMembersTask(selectedLabel) {
|
||||
const query = new URLSearchParams(this.getApiQueryObject());
|
||||
|
|
39
ghost/admin/app/helpers/members-count-fetcher.js
Normal file
39
ghost/admin/app/helpers/members-count-fetcher.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import {Resource} from 'ember-could-get-used-to-this';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class MembersCount extends Resource {
|
||||
@service store;
|
||||
|
||||
@tracked count = null;
|
||||
|
||||
get value() {
|
||||
return {
|
||||
isLoading: this.fetchMembersTask.isRunning,
|
||||
count: this.count
|
||||
};
|
||||
}
|
||||
|
||||
setup() {
|
||||
const query = this.args.named.query || {};
|
||||
this._query = query;
|
||||
this.fetchMembersTask.perform({query});
|
||||
}
|
||||
|
||||
update() {
|
||||
// required due to a weird invalidation issue when using Ember Data with ember-could-get-used-to-this
|
||||
// TODO: re-test after upgrading to ember-resources
|
||||
if (this.args.named.query !== this._query) {
|
||||
const query = this.args.named.query || {};
|
||||
this._query = query;
|
||||
this.fetchMembersTask.perform({query});
|
||||
}
|
||||
}
|
||||
|
||||
@task
|
||||
*fetchMembersTask({query} = {}) {
|
||||
const result = yield this.store.query('member', {...query, limit: 1});
|
||||
this.count = result.meta.pagination.total;
|
||||
}
|
||||
}
|
|
@ -69,7 +69,7 @@
|
|||
{{#if (and this.members.length this.isFiltered)}}
|
||||
<li class="divider"></li>
|
||||
<li>
|
||||
<button class="mr2" data-test-button="add-label-selected" type="button" {{on "click" this.toggleAddMembersLabelModal}}>
|
||||
<button class="mr2" data-test-button="add-label-selected" type="button" {{on "click" this.bulkAddLabel}}>
|
||||
<span>Add label for selected members ({{this.members.length}})</span>
|
||||
</button>
|
||||
</li>
|
||||
|
@ -178,16 +178,6 @@
|
|||
|
||||
{{outlet}}
|
||||
|
||||
{{#if this.showAddMembersLabelModal}}
|
||||
<GhFullscreenModal
|
||||
@modal="add-label-members"
|
||||
@model={{hash memberCount=this.members.length}}
|
||||
@confirm={{this.addLabelToMembers}}
|
||||
@close={{this.toggleAddMembersLabelModal}}
|
||||
@modifier="action wide"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showRemoveMembersLabelModal}}
|
||||
<GhFullscreenModal
|
||||
@modal="remove-label-members"
|
||||
|
|
Loading…
Add table
Reference in a new issue