b216aa0628
fixes PROD-325 fixes https://github.com/TryGhost/Product/issues/3489 - a canceled subscription that is still valid till the end of the current period now correctly shows "Has access until x", instead of "Ended x"
251 lines
7.8 KiB
JavaScript
251 lines
7.8 KiB
JavaScript
import Component from '@glimmer/component';
|
|
import moment from 'moment-timezone';
|
|
import {action} from '@ember/object';
|
|
import {getNonDecimal, getSymbol} from 'ghost-admin/utils/currency';
|
|
import {inject as service} from '@ember/service';
|
|
import {task} from 'ember-concurrency';
|
|
import {tracked} from '@glimmer/tracking';
|
|
|
|
export default class extends Component {
|
|
@service membersUtils;
|
|
@service ghostPaths;
|
|
@service ajax;
|
|
@service store;
|
|
@service feature;
|
|
@service settings;
|
|
|
|
constructor(...args) {
|
|
super(...args);
|
|
this.member = this.args.member;
|
|
this.scratchMember = this.args.scratchMember;
|
|
}
|
|
|
|
@tracked showMemberTierModal = false;
|
|
@tracked tiersList;
|
|
@tracked newslettersList;
|
|
|
|
get isAddComplimentaryAllowed() {
|
|
if (!this.membersUtils.paidMembersEnabled) {
|
|
return false;
|
|
}
|
|
|
|
if (this.member.get('isNew')) {
|
|
return false;
|
|
}
|
|
|
|
if (this.member.get('tiers')?.length > 0) {
|
|
return false;
|
|
}
|
|
|
|
// complimentary subscriptions are assigned to tiers so it only
|
|
// makes sense to show the "add complimentary" buttons when there's a
|
|
// tier to assign the complimentary subscription to
|
|
const hasAnActivePaidTier = !!this.tiersList?.length;
|
|
|
|
return hasAnActivePaidTier;
|
|
}
|
|
|
|
get isCreatingComplimentary() {
|
|
return this.args.isSaveRunning;
|
|
}
|
|
|
|
get tiers() {
|
|
let subscriptions = this.member.get('subscriptions') || [];
|
|
|
|
// Create the tiers from `subscriptions.price.tier`
|
|
let tiers = subscriptions
|
|
.map(subscription => (subscription.tier || subscription.price.tier))
|
|
.filter((value, index, self) => {
|
|
// Deduplicate by taking the first object by `id`
|
|
return typeof value.id !== 'undefined' && self.findIndex(element => (element.tier_id || element.id) === (value.tier_id || value.id)) === index;
|
|
});
|
|
|
|
let subscriptionData = subscriptions.filter((sub) => {
|
|
return !!sub.price;
|
|
}).map((sub) => {
|
|
const periodEnded = sub.current_period_end && new Date(sub.current_period_end) < new Date();
|
|
const data = {
|
|
...sub,
|
|
attribution: {
|
|
...sub.attribution,
|
|
referrerSource: sub.attribution?.referrer_source || 'Unknown',
|
|
referrerMedium: sub.attribution?.referrer_medium || '-'
|
|
},
|
|
startDate: sub.start_date ? moment(sub.start_date).format('D MMM YYYY') : '-',
|
|
validUntil: sub.current_period_end ? moment(sub.current_period_end).format('D MMM YYYY') : '-',
|
|
hasEnded: sub.status === 'canceled' && periodEnded,
|
|
willEndSoon: sub.cancel_at_period_end || (sub.status === 'canceled' && !periodEnded),
|
|
cancellationReason: sub.cancellation_reason,
|
|
price: {
|
|
...sub.price,
|
|
currencySymbol: getSymbol(sub.price.currency),
|
|
nonDecimalAmount: getNonDecimal(sub.price.amount)
|
|
},
|
|
isComplimentary: !sub.id
|
|
};
|
|
if (sub.trial_end_at) {
|
|
const inTrialMode = moment(sub.trial_end_at).isAfter(new Date(), 'day');
|
|
if (inTrialMode) {
|
|
data.trialUntil = moment(sub.trial_end_at).format('D MMM YYYY');
|
|
}
|
|
}
|
|
|
|
if (!sub.id && sub.tier?.expiry_at) {
|
|
data.compExpiry = moment(sub.tier.expiry_at).utc().format('D MMM YYYY');
|
|
}
|
|
return data;
|
|
});
|
|
return tiers.map((tier) => {
|
|
let tierSubscriptions = subscriptionData.filter((subscription) => {
|
|
return subscription?.price?.tier?.tier_id === (tier.tier_id || tier.id);
|
|
});
|
|
return {
|
|
...tier,
|
|
subscriptions: tierSubscriptions
|
|
};
|
|
});
|
|
}
|
|
|
|
get customer() {
|
|
let firstSubscription = this.member.get('subscriptions').firstObject;
|
|
let customer = firstSubscription?.customer;
|
|
|
|
if (customer) {
|
|
return {
|
|
...customer,
|
|
startDate: firstSubscription?.startDate
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
get canShowMultipleNewsletters() {
|
|
return (
|
|
this.settings.editorDefaultEmailRecipients !== 'disabled'
|
|
);
|
|
}
|
|
|
|
@action
|
|
updateNewsletterPreference(event) {
|
|
if (!event.target.checked) {
|
|
this.member.set('newsletters', []);
|
|
} else if (this.newslettersList.firstObject) {
|
|
const newsletter = this.newslettersList.firstObject;
|
|
this.member.set('newsletters', [newsletter]);
|
|
}
|
|
}
|
|
|
|
@action
|
|
setup() {
|
|
this.fetchTiers.perform();
|
|
this.fetchNewsletters.perform();
|
|
}
|
|
|
|
@action
|
|
setProperty(property, value) {
|
|
this.args.setProperty(property, value);
|
|
}
|
|
|
|
@action
|
|
updateProperty(event){
|
|
this.args.setProperty(event.currentTarget.name, event.target.value);
|
|
}
|
|
|
|
@action
|
|
setLabels(labels) {
|
|
this.member.set('labels', labels);
|
|
}
|
|
|
|
@action
|
|
setMemberNewsletters(newsletters) {
|
|
this.member.set('newsletters', newsletters);
|
|
}
|
|
|
|
@action
|
|
closeMemberTierModal() {
|
|
this.showMemberTierModal = false;
|
|
}
|
|
|
|
@action
|
|
cancelSubscription(subscriptionId) {
|
|
this.cancelSubscriptionTask.perform(subscriptionId);
|
|
}
|
|
|
|
@action
|
|
removeComplimentary(tierId) {
|
|
this.removeComplimentaryTask.perform(tierId);
|
|
}
|
|
|
|
@action
|
|
continueSubscription(subscriptionId) {
|
|
this.continueSubscriptionTask.perform(subscriptionId);
|
|
}
|
|
|
|
@task({drop: true})
|
|
*cancelSubscriptionTask(subscriptionId) {
|
|
let url = this.ghostPaths.url.api('members', this.member.get('id'), 'subscriptions', subscriptionId);
|
|
|
|
let response = yield this.ajax.put(url, {
|
|
data: {
|
|
cancel_at_period_end: true
|
|
}
|
|
});
|
|
|
|
this.store.pushPayload('member', response);
|
|
return response;
|
|
}
|
|
|
|
@task({drop: true})
|
|
*removeComplimentaryTask(tierId) {
|
|
let url = this.ghostPaths.url.api(`members/${this.member.get('id')}`);
|
|
let tiers = this.member.get('tiers') || [];
|
|
|
|
const updatedTiers = tiers
|
|
.filter(tier => tier.id !== tierId)
|
|
.map(tier => ({id: tier.id}));
|
|
|
|
let response = yield this.ajax.put(url, {
|
|
data: {
|
|
members: [{
|
|
id: this.member.get('id'),
|
|
email: this.member.get('email'),
|
|
tiers: updatedTiers
|
|
}]
|
|
}
|
|
});
|
|
|
|
this.store.pushPayload('member', response);
|
|
return response;
|
|
}
|
|
|
|
@task({drop: true})
|
|
*continueSubscriptionTask(subscriptionId) {
|
|
let url = this.ghostPaths.url.api('members', this.member.get('id'), 'subscriptions', subscriptionId);
|
|
|
|
let response = yield this.ajax.put(url, {
|
|
data: {
|
|
cancel_at_period_end: false
|
|
}
|
|
});
|
|
|
|
this.store.pushPayload('member', response);
|
|
return response;
|
|
}
|
|
|
|
@task({drop: true})
|
|
*fetchTiers() {
|
|
this.tiersList = yield this.store.query('tier', {filter: 'type:paid+active:true', include: 'monthly_price,yearly_price'});
|
|
}
|
|
|
|
@task({drop: true})
|
|
*fetchNewsletters() {
|
|
this.newslettersList = yield this.store.query('newsletter', {filter: 'status:active'});
|
|
if (this.member.get('isNew')) {
|
|
const defaultNewsletters = this.newslettersList.filter((newsletter) => {
|
|
return newsletter.subscribeOnSignup && newsletter.visibility === 'members';
|
|
});
|
|
this.setMemberNewsletters(defaultNewsletters);
|
|
}
|
|
}
|
|
}
|