mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-02 08:13:34 +03:00
076e3c02b2
fixes https://github.com/TryGhost/Team/issues/2160 - Adds a `batch_id` to both events that contain the same ID if they were created at the same time. - Removes duplicate signup/conversion events using the batch_id - Requires an update in mongo-knex to work (refs https://ghost.slack.com/archives/C02G9E68C/p1666773313272409?thread_ts=1666767872.375009&cid=C02G9E68C) - Some dependencies needed an update to load the latest mongo-knex - Added tiers to membersUtils, loaded on startup (we can start to use this instead of fetching it every time)
309 lines
8.7 KiB
JavaScript
309 lines
8.7 KiB
JavaScript
import ModalBase from 'ghost-admin/components/modal-base';
|
|
import TierBenefitItem from '../models/tier-benefit-item';
|
|
import classic from 'ember-classic-decorator';
|
|
import {action} from '@ember/object';
|
|
import {currencies, getCurrencyOptions, getSymbol} from 'ghost-admin/utils/currency';
|
|
import {A as emberA} from '@ember/array';
|
|
import {htmlSafe} from '@ember/template';
|
|
import {inject as service} from '@ember/service';
|
|
import {task} from 'ember-concurrency';
|
|
import {tracked} from '@glimmer/tracking';
|
|
|
|
const CURRENCIES = currencies.map((currency) => {
|
|
return {
|
|
value: currency.isoCode.toLowerCase(),
|
|
label: `${currency.isoCode} - ${currency.name}`,
|
|
isoCode: currency.isoCode
|
|
};
|
|
});
|
|
|
|
// TODO: update modals to work fully with Glimmer components
|
|
@classic
|
|
export default class ModalTierPrice extends ModalBase {
|
|
@service feature;
|
|
@service settings;
|
|
@service config;
|
|
@service membersUtils;
|
|
@tracked model;
|
|
@tracked tier;
|
|
@tracked periodVal;
|
|
@tracked stripeMonthlyAmount = 5;
|
|
@tracked stripeYearlyAmount = 50;
|
|
@tracked currency = 'usd';
|
|
@tracked stripePlanError = '';
|
|
@tracked benefits = emberA([]);
|
|
@tracked newBenefit = null;
|
|
@tracked welcomePageURL;
|
|
@tracked previewCadence = 'yearly';
|
|
@tracked discountValue = 0;
|
|
@tracked hasSaved = false;
|
|
@tracked freeTrialEnabled = false;
|
|
@tracked savedBenefits;
|
|
|
|
accentColorStyle = '';
|
|
|
|
confirm() {}
|
|
|
|
get isFreeTier() {
|
|
return this.tier.type === 'free';
|
|
}
|
|
|
|
get hasTrialDaysError() {
|
|
const trialDays = this.tier.get('trialDays');
|
|
return this.freeTrialEnabled && (!trialDays || trialDays < 1);
|
|
}
|
|
|
|
get allCurrencies() {
|
|
return getCurrencyOptions();
|
|
}
|
|
|
|
get selectedCurrency() {
|
|
return CURRENCIES.findBy('value', this.currency);
|
|
}
|
|
|
|
get isFreeTrialEnabled() {
|
|
return this.feature.get('freeTrial') && this.freeTrialEnabled && this.tier.get('trialDays') > 0;
|
|
}
|
|
|
|
init() {
|
|
super.init(...arguments);
|
|
this.tier = this.model.tier;
|
|
this.savedBenefits = this.model.tier?.get('benefits');
|
|
const monthlyPrice = this.tier.get('monthlyPrice');
|
|
const yearlyPrice = this.tier.get('yearlyPrice');
|
|
if (monthlyPrice) {
|
|
this.stripeMonthlyAmount = (monthlyPrice / 100);
|
|
}
|
|
if (yearlyPrice) {
|
|
this.stripeYearlyAmount = (yearlyPrice / 100);
|
|
}
|
|
this.currency = this.tier.get('currency') || 'usd';
|
|
this.benefits = this.tier.get('benefits') || emberA([]);
|
|
this.newBenefit = TierBenefitItem.create({
|
|
isNew: true,
|
|
name: ''
|
|
});
|
|
this.calculateDiscount();
|
|
if (this.tier.get('trialDays')) {
|
|
this.freeTrialEnabled = true;
|
|
}
|
|
this.accentColorStyle = htmlSafe(`color: ${this.settings.accentColor}`);
|
|
}
|
|
|
|
@action
|
|
validateWelcomePageURL() {
|
|
const siteUrl = this.siteUrl;
|
|
|
|
if (this.welcomePageURL === undefined) {
|
|
// Not initialised
|
|
return;
|
|
}
|
|
|
|
if (this.welcomePageURL.href.startsWith(siteUrl)) {
|
|
const path = this.welcomePageURL.href.replace(siteUrl, '');
|
|
this.model.tier.welcomePageURL = path;
|
|
} else {
|
|
this.model.tier.welcomePageURL = this.welcomePageURL.href;
|
|
}
|
|
}
|
|
|
|
get siteUrl() {
|
|
return this.config.blogUrl;
|
|
}
|
|
|
|
// eslint-disable-next-line no-dupe-class-members
|
|
get welcomePageURL() {
|
|
return this.model.tier.welcomePageURL;
|
|
}
|
|
|
|
get title() {
|
|
if (this.isExistingTier) {
|
|
if (this.isFreeTier) {
|
|
return `Edit free membership`;
|
|
}
|
|
return `Edit tier`;
|
|
}
|
|
return 'New tier';
|
|
}
|
|
|
|
get isExistingTier() {
|
|
return !this.model.tier.isNew;
|
|
}
|
|
|
|
@action
|
|
close(event) {
|
|
if (!this.hasSaved) {
|
|
this.reset();
|
|
}
|
|
event?.preventDefault?.();
|
|
this.closeModal();
|
|
}
|
|
@action
|
|
setCurrency(event) {
|
|
const newCurrency = event.value;
|
|
this.currency = newCurrency;
|
|
}
|
|
@action
|
|
setWelcomePageURL(url) {
|
|
this.welcomePageURL = url;
|
|
}
|
|
|
|
reset() {
|
|
this.newBenefit = TierBenefitItem.create({isNew: true, name: ''});
|
|
const finalBenefits = this.savedBenefits || emberA([]);
|
|
this.tier.set('benefits', finalBenefits);
|
|
this.tier.rollbackAttributes();
|
|
}
|
|
|
|
@task({drop: true})
|
|
*saveTier() {
|
|
this.validatePrices();
|
|
|
|
if (this.stripePlanError || this.hasTrialDaysError) {
|
|
return;
|
|
}
|
|
|
|
if (!this.newBenefit.get('isBlank')) {
|
|
yield this.send('addBenefit', this.newBenefit);
|
|
}
|
|
|
|
if (!this.isFreeTier) {
|
|
const monthlyAmount = Math.round(this.stripeMonthlyAmount * 100);
|
|
const yearlyAmount = Math.round(this.stripeYearlyAmount * 100);
|
|
this.tier.set('monthlyPrice', monthlyAmount);
|
|
this.tier.set('yearlyPrice', yearlyAmount);
|
|
this.tier.set('currency', this.currency);
|
|
}
|
|
|
|
if (!this.freeTrialEnabled) {
|
|
this.tier.set('trialDays', 0);
|
|
}
|
|
|
|
this.tier.set('benefits', this.benefits.filter(benefit => !benefit.get('isBlank')));
|
|
|
|
try {
|
|
yield this.tier.save();
|
|
this.hasSaved = true;
|
|
yield this.confirm();
|
|
this.send('closeModal');
|
|
|
|
// Reload in the background (no await here)
|
|
this.membersUtils.reload();
|
|
} catch (error) {
|
|
if (error === undefined) {
|
|
// Validation error
|
|
return;
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
validatePrices() {
|
|
this.stripePlanError = undefined;
|
|
|
|
try {
|
|
const yearlyAmount = this.stripeYearlyAmount;
|
|
const monthlyAmount = this.stripeMonthlyAmount;
|
|
const symbol = getSymbol(this.currency);
|
|
if (!yearlyAmount || yearlyAmount < 1 || !monthlyAmount || monthlyAmount < 1) {
|
|
throw new TypeError(`Subscription amount must be at least ${symbol}1.00`);
|
|
}
|
|
} catch (err) {
|
|
this.stripePlanError = err.message;
|
|
}
|
|
}
|
|
|
|
addNewBenefitItem(item) {
|
|
item.set('isNew', false);
|
|
this.benefits.pushObject(item);
|
|
|
|
this.newBenefit = TierBenefitItem.create({isNew: true, name: ''});
|
|
}
|
|
|
|
calculateDiscount() {
|
|
const discount = this.stripeMonthlyAmount ? 100 - Math.floor((this.stripeYearlyAmount / 12 * 100) / this.stripeMonthlyAmount) : 0;
|
|
this.discountValue = discount > 0 ? discount : 0;
|
|
}
|
|
|
|
@action
|
|
changeCadence(cadence) {
|
|
this.previewCadence = cadence;
|
|
}
|
|
|
|
@action
|
|
setTrialDays(event) {
|
|
const value = parseInt(event.target.value);
|
|
this.tier.set('trialDays', value);
|
|
}
|
|
|
|
@action
|
|
setFreeTrialEnabled(event) {
|
|
this.freeTrialEnabled = event.target.checked;
|
|
if (event.target.checked && !this.tier.get('trialDays')) {
|
|
this.tier.set('trialDays', 7);
|
|
}
|
|
}
|
|
|
|
@action
|
|
validateStripePlans() {
|
|
this.calculateDiscount();
|
|
this.stripePlanError = undefined;
|
|
|
|
try {
|
|
const yearlyAmount = this.stripeYearlyAmount;
|
|
const monthlyAmount = this.stripeMonthlyAmount;
|
|
const symbol = getSymbol(this.currency);
|
|
if (!yearlyAmount || yearlyAmount < 1 || !monthlyAmount || monthlyAmount < 1) {
|
|
throw new TypeError(`Subscription amount must be at least ${symbol}1.00`);
|
|
}
|
|
} catch (err) {
|
|
this.stripePlanError = err.message;
|
|
}
|
|
}
|
|
|
|
actions = {
|
|
addBenefit(item) {
|
|
return item.validate().then(() => {
|
|
this.addNewBenefitItem(item);
|
|
});
|
|
},
|
|
focusItem() {
|
|
// Focus on next benefit on enter
|
|
},
|
|
deleteBenefit(item) {
|
|
if (!item) {
|
|
return;
|
|
}
|
|
this.benefits.removeObject(item);
|
|
},
|
|
reorderItems() {
|
|
this.tier.set('benefits', this.benefits);
|
|
},
|
|
updateLabel(label, benefitItem) {
|
|
if (!benefitItem) {
|
|
return;
|
|
}
|
|
|
|
if (benefitItem.get('name') !== label) {
|
|
benefitItem.set('name', label);
|
|
}
|
|
},
|
|
// noop - we don't want the enter key doing anything
|
|
confirm() {},
|
|
setAmount(amount) {
|
|
this.price.amount = !isNaN(amount) ? parseInt(amount) : 0;
|
|
},
|
|
|
|
setCurrency(event) {
|
|
const newCurrency = event.value;
|
|
this.currency = newCurrency;
|
|
},
|
|
|
|
// needed because ModalBase uses .send() for keyboard events
|
|
closeModal() {
|
|
this.close();
|
|
}
|
|
};
|
|
}
|