Ghost/ghost/admin/app/components/gh-members-recipient-select.js
Rishabh Garg 86b55b0f81 Added new free tier card with custom description/benefits (#2203)
refs https://github.com/TryGhost/Team/issues/1037

Adds new free tier card with option to add custom description and benefits for free tier, behind the tiers beta flag. Also:

- updates formatting of tier prices
- changes "Free" section to "Default"
- updates price formatting of membership tiers in admin
- updates currency code handling for product card
- updates default paid product handling

Co-authored-by: Djordje Vlaisavljevic <dzvlais@gmail.com>
Co-authored-by: Peter Zimon <peter.zimon@gmail.com>
2022-01-18 00:23:43 +05:30

193 lines
5.6 KiB
JavaScript

import Component from '@glimmer/component';
import flattenGroupedOptions from 'ghost-admin/utils/flatten-grouped-options';
import {Promise} from 'rsvp';
import {TrackedSet} from 'tracked-built-ins';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency-decorators';
import {tracked} from '@glimmer/tracking';
const BASE_FILTERS = ['status:free', 'status:-free'];
export default class GhMembersRecipientSelect extends Component {
@service membersUtils;
@service session;
@service store;
@service feature;
baseFilters = new TrackedSet();
specificFilters = new TrackedSet();
@tracked isSpecificChecked = false;
@tracked specificOptions = [];
@tracked freeMemberCount;
@tracked paidMemberCount;
constructor() {
super(...arguments);
this.fetchSpecificOptionsTask.perform();
this.fetchMemberCountsTask.perform();
this.baseFilters.clear();
this.specificFilters.clear();
(this.args.filter || '').split(',').forEach((filter) => {
if (filter?.trim()) {
if (BASE_FILTERS.includes(filter)) {
this.baseFilters.add(filter);
} else {
this.isSpecificChecked = true;
this.specificFilters.add(filter);
}
}
});
}
get isPaidAvailable() {
return this.membersUtils.isStripeEnabled;
}
get isFreeChecked() {
return this.baseFilters.has('status:free');
}
get isPaidChecked() {
return this.baseFilters.has('status:-free');
}
get selectedSpecificOptions() {
return flattenGroupedOptions(this.specificOptions)
.filter(o => this.specificFilters.has(o.segment));
}
get freeMemberCountLabel() {
if (this.freeMemberCount !== undefined) {
return `(${this.freeMemberCount})`;
}
return '';
}
get paidMemberCountLabel() {
if (this.paidMemberCount !== undefined) {
return `(${this.paidMemberCount})`;
}
return '';
}
get filterString() {
const selectedFilters = !this.isSpecificChecked ?
new Set([...this.baseFilters.values()]) :
new Set([...this.baseFilters.values(), ...this.specificFilters.values()]);
if (!this.isPaidAvailable) {
selectedFilters.delete('status:-free');
}
return Array.from(selectedFilters).join(',') || null;
}
@action
toggleFilter(filter, event) {
event?.preventDefault();
if (this.args.disabled) {
return;
}
this.baseFilters.has(filter) ? this.baseFilters.delete(filter) : this.baseFilters.add(filter);
this.args.onChange?.(this.filterString);
}
@action
toggleSpecificFilter(event) {
event?.preventDefault();
if (this.args.disabled) {
return;
}
this.isSpecificChecked = !this.isSpecificChecked;
this.args.onChange?.(this.filterString);
}
@action
selectSpecificOptions(selectedOptions) {
if (this.args.disabled) {
return;
}
this.specificFilters.clear();
selectedOptions.forEach(o => this.specificFilters.add(o.segment));
if (this.isSpecificChecked) {
this.args.onChange?.(this.filterString);
}
}
@task
*fetchSpecificOptionsTask() {
const options = [];
// fetch all labels w̶i̶t̶h̶ c̶o̶u̶n̶t̶s̶
// TODO: add `include: 'count.members` to query once API is fixed
const labels = yield this.store.query('label', {limit: 'all'});
if (labels.length > 0) {
const labelsGroup = {
groupName: 'Labels',
options: []
};
labels.forEach((label) => {
labelsGroup.options.push({
name: label.name,
segment: `label:${label.slug}`,
count: label.count?.members,
class: 'segment-label'
});
});
options.push(labelsGroup);
}
if (this.feature.get('multipleProducts')) {
// fetch all products w̶i̶t̶h̶ c̶o̶u̶n̶t̶s̶
// TODO: add `include: 'count.members` to query once API supports
const products = yield this.store.query('product', {filter: 'type:paid', limit: 'all'});
if (products.length > 1) {
const productsGroup = {
groupName: 'Tiers',
options: []
};
products.forEach((product) => {
productsGroup.options.push({
name: product.name,
segment: `product:${product.slug}`,
count: product.count?.members,
class: 'segment-product'
});
});
options.push(productsGroup);
}
}
this.specificOptions = options;
}
@task
*fetchMemberCountsTask() {
const user = yield this.session.user;
if (!user.isAdmin) {
return;
}
yield Promise.all([
this.store.query('member', {filter: 'subscribed:true+status:free', limit: 1}).then((res) => {
this.freeMemberCount = res.meta.pagination.total;
}),
this.store.query('member', {filter: 'subscribed:true+status:-free', limit: 1}).then((res) => {
this.paidMemberCount = res.meta.pagination.total;
})
]);
}
}