Updated usage of the Tiers API (#2388)

refs https://github.com/TryGhost/Team/issues/1575

- Update usage of Tier to read monthly & yearly price & currency from top level
- Updated usage of Tier to read benefit name from benefits[n], not from benefits[n].name

Co-authored-by: Fabien "egg" O'Carroll <fabien@allou.is>
This commit is contained in:
Rishabh Garg 2022-05-17 00:21:49 +05:30 committed by GitHub
parent 77484210ee
commit 8b5b3aa734
24 changed files with 64 additions and 468 deletions

View File

@ -1177,6 +1177,11 @@ remove|ember-template-lint|no-action|78|55|78|55|856c8ab1d5835ec424c83eb6b73888b
remove|ember-template-lint|no-action|82|59|82|59|e8badded2afd5d0c8426b85b6d0e7ac0bef55efb|1652054400000|1662422400000|1665014400000|app/templates/settings/integrations/slack.hbs
remove|ember-template-lint|no-passed-in-event-handlers|52|48|52|48|a893d9d149388aa9da024f59db6522bb9335bf8a|1652054400000|1662422400000|1665014400000|app/templates/settings/integrations/slack.hbs
remove|ember-template-lint|no-passed-in-event-handlers|78|48|78|48|1ab458a0888727af2b3a7a661a1e7d5f00ac0ad5|1652054400000|1662422400000|1665014400000|app/templates/settings/integrations/slack.hbs
remove|ember-template-lint|no-action|206|59|206|59|dcc09bb23a476d5b83b273b693cd8cb2aba68365|1652054400000|1662422400000|1665014400000|app/templates/settings/membership.hbs
remove|ember-template-lint|no-action|207|63|207|63|682c80ff5163c8e4a2bea89f2066dfd1248b5889|1652054400000|1662422400000|1665014400000|app/templates/settings/membership.hbs
remove|ember-template-lint|no-action|216|59|216|59|a80dd18e18dda6fb6f1f97d87bef2b8c2ce3d847|1652054400000|1662422400000|1665014400000|app/templates/settings/membership.hbs
remove|ember-template-lint|no-passed-in-event-handlers|206|52|206|52|dcb4785647a50814bcfce82f8d68ac8dd8f54ec2|1652054400000|1662422400000|1665014400000|app/templates/settings/membership.hbs
remove|ember-template-lint|no-passed-in-event-handlers|216|52|216|52|70487c008d7dda453fef82f0140699ee93c0055c|1652054400000|1662422400000|1665014400000|app/templates/settings/membership.hbs
remove|ember-template-lint|no-action|59|49|59|49|caf96338bbfda1006cc9251eacd9d9eee883df44|1652054400000|1662422400000|1665014400000|app/templates/editor.hbs
remove|ember-template-lint|no-action|60|44|60|44|1731ed4d2ac62556bb83fbc378686e26ca605687|1652054400000|1662422400000|1665014400000|app/templates/editor.hbs
add|ember-template-lint|table-groups|29|12|29|12|49e0fad85b643b6129963e49b6b5a37ccbd9796c|1652659200000|1663027200000|1668214800000|app/templates/offers.hbs

View File

@ -2,7 +2,6 @@ import ApplicationAdapter from 'ghost-admin/adapters/application';
import classic from 'ember-classic-decorator';
@classic
export default class Tier extends ApplicationAdapter {
queryRecord(store, type, query) {
if (query && query.id) {

View File

@ -165,25 +165,9 @@ export default class GhLaunchWizardConnectStripeComponent extends Component {
this.tier = tiers.firstObject;
if (this.tier) {
const yearlyDiscount = this.calculateDiscount(5, 50);
this.tier.set('monthlyPrice', {
nickname: 'Monthly',
amount: 500,
active: true,
description: 'Full access',
currency: 'usd',
interval: 'month',
type: 'recurring'
});
this.tier.set('yearlyPrice', {
nickname: 'Yearly',
amount: 5000,
active: true,
currency: 'usd',
description: yearlyDiscount > 0 ? `${yearlyDiscount}% discount` : 'Full access',
interval: 'year',
type: 'recurring'
});
this.tier.set('currency', 'usd');
this.tier.set('monthlyPrice', 500);
this.tier.set('yearlyPrice', 5000);
yield this.saveTier.perform();
this.settings.set('portalPlans', ['free', 'monthly', 'yearly']);
yield this.settings.save();

View File

@ -22,24 +22,9 @@ export default class GhLaunchWizardFinaliseComponent extends Component {
const monthlyAmount = Math.round(data.monthlyAmount * 100);
const yearlyAmount = Math.round(data.yearlyAmount * 100);
const currency = data.currency;
const monthlyPrice = {
nickname: 'Monthly',
amount: monthlyAmount,
active: true,
currency: currency,
interval: 'month',
type: 'recurring'
};
const yearlyPrice = {
nickname: 'Yearly',
amount: yearlyAmount,
active: true,
currency: currency,
interval: 'year',
type: 'recurring'
};
this.tier.set('monthlyPrice', monthlyPrice);
this.tier.set('yearlyPrice', yearlyPrice);
this.tier.set('monthlyPrice', monthlyAmount);
this.tier.set('yearlyPrice', yearlyAmount);
this.tier.set('currency', currency);
const savedTier = await this.tier.save();
return savedTier;
}

View File

@ -233,47 +233,13 @@ export default Component.extend({
this.onDisconnected?.();
}),
calculateDiscount(monthly, yearly) {
if (isNaN(monthly) || isNaN(yearly)) {
return 0;
}
return monthly ? 100 - Math.floor((yearly / 12 * 100) / monthly) : 0;
},
getActivePrice(prices, interval, amount, currency) {
return prices.find((price) => {
return (
price.active && price.amount === amount && price.type === 'recurring' &&
price.interval === interval && price.currency.toLowerCase() === currency.toLowerCase()
);
});
},
saveTier: task(function* () {
const tiers = yield this.store.query('tier', {filter: 'type:paid', include: 'monthly_price, yearly_price'});
this.tier = tiers.firstObject;
if (this.tier) {
const yearlyDiscount = this.calculateDiscount(5, 50);
this.tier.set('monthlyPrice', {
nickname: 'Monthly',
amount: 500,
active: true,
description: 'Full access',
currency: 'usd',
interval: 'month',
type: 'recurring'
});
this.tier.set('yearlyPrice', {
nickname: 'Yearly',
amount: 5000,
active: true,
currency: 'usd',
description: yearlyDiscount > 0 ? `${yearlyDiscount}% discount` : 'Full access',
interval: 'year',
type: 'recurring'
});
this.tier.set('monthlyPrice', 500);
this.tier.set('yearlyPrice', 5000);
this.tier.set('currency', 'usd');
let pollTimeout = 0;
/** To allow Stripe config to be ready in backend, we poll the save tier request */
while (pollTimeout < RETRY_PRODUCT_SAVE_MAX_POLL) {

View File

@ -41,14 +41,14 @@
<div class="gh-tier-card-price" data-test-monthly-price>
<div class="flex items-start">
<span class="currency">{{currency-symbol this.tierCurrency}}</span>
<span class="amount">{{gh-price-amount @tier.monthlyPrice.amount}}</span>
<span class="amount">{{gh-price-amount @tier.monthlyPrice}}</span>
</div>
<div class="period">Monthly</div>
</div>
<div class="gh-tier-card-price" data-test-yearly-price>
<div class="flex items-start">
<span class="currency">{{currency-symbol this.tierCurrency}}</span>
<span class="amount">{{gh-price-amount @tier.yearlyPrice.amount}}</span>
<span class="amount">{{gh-price-amount @tier.yearlyPrice}}</span>
</div>
<div class="period">Yearly</div>
</div>

View File

@ -26,9 +26,9 @@ export default class extends Component {
const firstPaidTier = this.args.tiers.find((tier) => {
return tier.type === 'paid';
});
return firstPaidTier?.monthlyPrice?.currency || 'usd';
return firstPaidTier?.currency || 'usd';
} else {
return this.tier?.monthlyPrice?.currency;
return this.tier?.currency;
}
}

View File

@ -1,89 +0,0 @@
<header class="modal-header" data-test-modal="webhook-form">
<h1 data-test-text="title">{{this.title}}</h1>
</header>
<button class="close" href title="Close" type="button" {{action "closeModal"}} {{action (optional this.noop) on="mouseDown"}}>
{{svg-jar "close"}}
</button>
<form>
<div class="modal-body">
<div class="gh-main-section-block">
<div class="gh-main-section-content grey gh-tier-priceform-block">
<GhFormGroup @errors={{this.errors}} @property="name">
<label for="name" class="fw6">Name</label>
<GhTextInput
@value={{readonly this.price.nickname}}
@input={{action (mut this.price.nickname) value="target.value"}}
@name="name"
@id="name"
@class="gh-input" />
<GhErrorMessage @errors={{this.errors}} @property="name" />
</GhFormGroup>
<GhFormGroup @errors={{this.errors}} @property="description">
<label for="description" class="fw6">Description</label>
<GhTextInput
@value={{readonly this.price.description}}
@input={{action (mut this.price.description) value="target.value"}}
@name="description"
@id="description"
@class="gh-input" />
<GhErrorMessage @errors={{this.errors}} @property="description" />
</GhFormGroup>
<div class="gh-tier-priceform-pricecurrency">
<GhFormGroup @errors={{this.errors}} @property="amount">
<label for="amount" class="fw6">Price</label>
<div class="flex items-center justify-center gh-labs-price-label">
<GhTextInput
@id="amount"
@value={{this.price.amount}}
@type="number"
@disabled={{this.isExistingPrice}}
@input={{action "setAmount" value="target.value"}}
/>
</div>
<GhErrorMessage @errors={{this.errors}} @property="amount" />
</GhFormGroup>
<GhFormGroup @class="for-select">
<label class="fw6 f8"for="currency">Plan currency</label>
<span class="gh-select mt1">
{{one-way-select this.selectedCurrencyObj
id="currency"
name="currency"
options=(readonly this.allCurrencies)
optionValuePath="value"
optionLabelPath="label"
disabled=this.isExistingPrice
update=(action "setCurrency")
}}
{{svg-jar "arrow-down-small"}}
</span>
</GhFormGroup>
</div>
<GhFormGroup @errors={{this.price.errors}} @hasValidated={{this.price.hasValidated}} @property="billing-period">
<label for="billing-period" class="fw6">Billing period</label>
<GhTiersPriceBillingperiod
@updatePeriod={{action "updatePeriod"}}
@triggerId="period-input"
@value={{this.price.interval}} @disabled={{this.isExistingPrice}}
/>
<GhErrorMessage @errors={{this.errors}} @property="interval" />
</GhFormGroup>
</div>
</div>
</div>
</form>
<div class="modal-footer">
<button
class="gh-btn" data-test-button="cancel-webhook" type="button" {{action "closeModal"}}
{{!-- disable mouseDown so it doesn't trigger focus-out validations --}}
{{action (optional this.noop) on="mouseDown"}}
>
<span>Cancel</span>
</button>
<GhTaskButton @buttonText="Save"
@successText={{this.successText}}
@task={{this.savePrice}}
@class="gh-btn gh-btn-black gh-btn-icon"
data-test-button="save-price" />
</div>

View File

@ -1,143 +0,0 @@
import EmberObject, {action} from '@ember/object';
import ModalBase from 'ghost-admin/components/modal-base';
import classic from 'ember-classic-decorator';
import {currencies} from 'ghost-admin/utils/currency';
import {isEmpty} from '@ember/utils';
import {task} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';
// TODO: update modals to work fully with Glimmer components
@classic
export default class ModalTierPrice extends ModalBase {
@tracked model;
@tracked price;
@tracked currencyVal;
@tracked periodVal;
@tracked errors = EmberObject.create();
init() {
super.init(...arguments);
this.price = {
...(this.model.price || {})
};
this.topCurrencies = currencies.slice(0, 5).map((currency) => {
return {
value: currency.isoCode.toLowerCase(),
label: `${currency.isoCode} - ${currency.name}`,
isoCode: currency.isoCode
};
});
this.currencies = currencies.slice(5, currencies.length).map((currency) => {
return {
value: currency.isoCode.toLowerCase(),
label: `${currency.isoCode} - ${currency.name}`,
isoCode: currency.isoCode
};
});
this.allCurrencies = [
{
groupName: '—',
options: this.topCurrencies
},
{
groupName: '—',
options: this.currencies
}
];
this.currencyVal = this.price.currency || 'usd';
this.periodVal = this.price.interval || 'month';
}
get title() {
if (this.isExistingPrice) {
return `Price - ${this.price.nickname || 'No Name'}`;
}
return 'New Price';
}
get isExistingPrice() {
return !!this.model.price;
}
get currency() {
return this.price.currency || 'usd';
}
get selectedCurrencyObj() {
return this.currencies.findBy('value', this.price.currency) || this.topCurrencies.findBy('value', this.price.currency);
}
// TODO: rename to confirm() when modals have full Glimmer support
@action
confirmAction() {
this.confirm(this.price);
this.close();
}
@action
close(event) {
event?.preventDefault?.();
this.closeModal();
}
@task({drop: true})
*savePrice() {
this.validatePriceData();
if (!isEmpty(this.errors) && Object.keys(this.errors).length > 0) {
return;
}
const priceObj = {
...this.price,
amount: (this.price.amount || 0) * 100
};
if (!priceObj.id) {
priceObj.active = 1;
priceObj.currency = priceObj.currency || 'usd';
priceObj.interval = priceObj.interval || 'month';
priceObj.type = 'recurring';
}
yield this.confirm(priceObj);
this.send('closeModal');
}
validatePriceData() {
this.errors = EmberObject.create();
if (!this.price.nickname) {
this.errors.set('name', [{
message: 'Please enter name'
}]);
}
if (isNaN(this.price.amount) || this.price.amount === '') {
this.errors.set('amount', [{
message: 'Please enter amount'
}]);
}
if (!this.price.interval || !['month', 'year'].includes(this.price.interval)) {
this.errors.set('interval', [{
message: 'Please enter billing interval'
}]);
}
}
actions = {
confirm() {
this.confirmAction(...arguments);
},
updatePeriod(oldPeriod, newPeriod) {
this.price.interval = newPeriod;
this.periodVal = newPeriod;
},
setAmount(amount) {
this.price.amount = !isNaN(amount) ? parseInt(amount) : 0;
},
setCurrency(currency) {
this.price.currency = currency.value;
this.currencyVal = currency.value;
},
// needed because ModalBase uses .send() for keyboard events
closeModal() {
this.close();
}
};
}

View File

@ -36,6 +36,8 @@ export default class ModalTierPrice extends ModalBase {
@tracked welcomePageURL;
@tracked previewCadence = 'yearly';
@tracked discountValue = 0;
@tracked hasSaved = false;
@tracked savedBenefits;
accentColorStyle = '';
@ -49,17 +51,6 @@ export default class ModalTierPrice extends ModalBase {
return getCurrencyOptions();
}
get tierCurrency() {
if (this.isFreeTier) {
const firstPaidTier = this.model.tiers?.find((tier) => {
return tier.type === 'paid';
});
return firstPaidTier?.monthlyPrice?.currency || 'usd';
} else {
return this.tier?.monthlyPrice?.currency;
}
}
get selectedCurrency() {
return CURRENCIES.findBy('value', this.currency);
}
@ -67,15 +58,16 @@ export default class ModalTierPrice extends ModalBase {
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.amount / 100);
this.stripeMonthlyAmount = (monthlyPrice / 100);
}
if (yearlyPrice) {
this.stripeYearlyAmount = (yearlyPrice.amount / 100);
this.stripeYearlyAmount = (yearlyPrice / 100);
}
this.currency = this.tierCurrency || 'usd';
this.currency = this.tier.get('currency') || 'usd';
this.benefits = this.tier.get('benefits') || emberA([]);
this.newBenefit = TierBenefitItem.create({
isNew: true,
@ -128,7 +120,9 @@ export default class ModalTierPrice extends ModalBase {
@action
close(event) {
this.reset();
if (!this.hasSaved) {
this.reset();
}
event?.preventDefault?.();
this.closeModal();
}
@ -144,8 +138,8 @@ export default class ModalTierPrice extends ModalBase {
reset() {
this.newBenefit = TierBenefitItem.create({isNew: true, name: ''});
const savedBenefits = this.tier.benefits?.filter(benefit => !!benefit.id) || emberA([]);
this.tier.set('benefits', savedBenefits);
const finalBenefits = this.savedBenefits || emberA([]);
this.tier.set('benefits', finalBenefits);
}
@task({drop: true})
@ -165,26 +159,13 @@ export default class ModalTierPrice extends ModalBase {
if (!this.isFreeTier) {
const monthlyAmount = Math.round(this.stripeMonthlyAmount * 100);
const yearlyAmount = Math.round(this.stripeYearlyAmount * 100);
this.tier.set('monthlyPrice', {
nickname: 'Monthly',
amount: monthlyAmount,
active: true,
currency: this.currency,
interval: 'month',
type: 'recurring'
});
this.tier.set('yearlyPrice', {
nickname: 'Yearly',
amount: yearlyAmount,
active: true,
currency: this.currency,
interval: 'year',
type: 'recurring'
});
this.tier.set('monthlyPrice', monthlyAmount);
this.tier.set('yearlyPrice', yearlyAmount);
this.tier.set('currency', this.currency);
}
this.tier.set('benefits', this.benefits.filter(benefit => !benefit.get('isBlank')));
yield this.tier.save();
this.hasSaved = true;
yield this.confirm();
this.send('closeModal');
}

View File

@ -85,10 +85,10 @@ export default class OffersController extends Controller {
get cadence() {
if (this.offer.tier && this.offer.cadence) {
const tier = this.tiers.findBy('id', this.offer.tier.id);
return `${this.offer.tier.id}-${this.offer.cadence}-${tier?.monthlyPrice?.currency}`;
return `${this.offer.tier.id}-${this.offer.cadence}-${tier?.currency}`;
} else if (this.defaultProps) {
const tier = this.tiers.findBy('id', this.defaultProps.tier.id);
return `${this.defaultProps.tier.id}-${this.defaultProps.cadence}-${tier?.monthlyPrice?.currency}`;
return `${this.defaultProps.tier.id}-${this.defaultProps.cadence}-${tier?.currency}`;
}
return '';
}
@ -109,10 +109,10 @@ export default class OffersController extends Controller {
this.tiers.forEach((tier) => {
let monthlyLabel;
let yearlyLabel;
const tierCurrency = tier.monthlyPrice.currency;
const tierCurrency = tier.currency;
const tierCurrencySymbol = tierCurrency.toUpperCase();
monthlyLabel = `${tier.name} - Monthly (${ghPriceAmount(tier.monthlyPrice.amount)} ${tierCurrencySymbol})`;
yearlyLabel = `${tier.name} - Yearly (${ghPriceAmount(tier.yearlyPrice.amount)} ${tierCurrencySymbol})`;
monthlyLabel = `${tier.name} - Monthly (${ghPriceAmount(tier.monthlyPrice)} ${tierCurrencySymbol})`;
yearlyLabel = `${tier.name} - Yearly (${ghPriceAmount(tier.yearlyPrice)} ${tierCurrencySymbol})`;
cadences.push({
label: monthlyLabel,
@ -374,7 +374,7 @@ export default class OffersController extends Controller {
return '$';
}
const tier = this.tiers.findBy('id', tierId);
const tierCurrency = tier?.monthlyPrice?.currency || 'usd';
const tierCurrency = tier?.currency || 'usd';
return getSymbol(tierCurrency);
}

View File

@ -42,9 +42,9 @@ export default class MembersController extends Controller {
return p.id === offer.tier.id;
});
const price = offer.cadence === 'month' ? tier.monthlyPrice : tier.yearlyPrice;
offer.finalCurrency = offer.currency || price.currency;
offer.originalPrice = price.amount;
offer.updatedPrice = offer.type === 'fixed' ? (price.amount - offer.amount) : (price.amount - ((price.amount * offer.amount) / 100));
offer.finalCurrency = offer.currency || tier.currency;
offer.originalPrice = price;
offer.updatedPrice = offer.type === 'fixed' ? (price - offer.amount) : (price - ((price * offer.amount) / 100));
return offer;
});
}

View File

@ -339,12 +339,12 @@ export default class MembersAccessController extends Controller {
if (tier) {
const monthlyPrice = tier.get('monthlyPrice');
const yearlyPrice = tier.get('yearlyPrice');
if (monthlyPrice && monthlyPrice.amount) {
this.stripeMonthlyAmount = (monthlyPrice.amount / 100);
this.currency = monthlyPrice.currency;
this.currency = tier.get('currency');
if (monthlyPrice) {
this.stripeMonthlyAmount = (monthlyPrice / 100);
}
if (yearlyPrice && yearlyPrice.amount) {
this.stripeYearlyAmount = (yearlyPrice.amount / 100);
if (yearlyPrice) {
this.stripeYearlyAmount = (yearlyPrice / 100);
}
this.updatePortalPreview();
}
@ -380,22 +380,8 @@ export default class MembersAccessController extends Controller {
const monthlyAmount = Math.round(this.stripeMonthlyAmount * 100);
const yearlyAmount = Math.round(this.stripeYearlyAmount * 100);
this.tier.set('monthlyPrice', {
nickname: 'Monthly',
amount: monthlyAmount,
active: true,
currency: this.currency,
interval: 'month',
type: 'recurring'
});
this.tier.set('yearlyPrice', {
nickname: 'Yearly',
amount: yearlyAmount,
active: true,
currency: this.currency,
interval: 'year',
type: 'recurring'
});
this.tier.set('monthlyPrice', monthlyAmount);
this.tier.set('yearlyPrice', yearlyAmount);
const savedTier = await this.tier.save();
return savedTier;

View File

@ -1,13 +0,0 @@
import EmberObject from '@ember/object';
export default EmberObject.extend({
id: 'ID in Ghost',
stripe_price_id: 'ID of the Stripe Price',
stripe_product_id: 'ID of the Stripe Product the Stripe Price is associated with',
nickname: 'price nickname e.g. "Monthly"',
description: 'price description e.g. "Full access"',
amount: 'amount in smallest denomination e.g. cents, so value for 5 dollars would be 500',
currency: 'e.g. usd',
type: 'either one_time or recurring',
interval: 'will be `null` if type is one_time, otherwise how often price charges e.g "month", "year"'
});

View File

@ -11,7 +11,8 @@ export default Model.extend(ValidationEngine, {
welcomePageURL: attr('string'),
visibility: attr('string', {defaultValue: 'none'}),
type: attr('string', {defaultValue: 'paid'}),
monthlyPrice: attr('stripe-price'),
yearlyPrice: attr('stripe-price'),
currency: attr('string'),
monthlyPrice: attr('number'),
yearlyPrice: attr('number'),
benefits: attr('tier-benefits')
});

View File

@ -4,12 +4,12 @@ export default class TierSerializer extends ApplicationSerializer {
serialize() {
let json = super.serialize(...arguments);
if (json?.monthly_price?.amount) {
json.monthly_price.amount = Math.round(json.monthly_price.amount);
if (json?.monthly_price) {
json.monthly_price = Math.round(json.monthly_price);
}
if (json?.yearly_price?.amount) {
json.yearly_price.amount = Math.round(json.yearly_price.amount);
if (json?.yearly_price) {
json.yearly_price = Math.round(json.yearly_price);
}
return json;

View File

@ -1,27 +0,0 @@
import StripePrice from 'ghost-admin/models/stripe-price';
import Transform from '@ember-data/serializer/transform';
import {A as emberA, isArray as isEmberArray} from '@ember/array';
export default class StripePriceTransform extends Transform {
deserialize(serialized) {
if (serialized === null || serialized === undefined) {
return null;
} else if (Array.isArray(serialized)) {
const stripePrices = serialized.map(itemDetails => StripePrice.create(itemDetails));
return emberA(stripePrices);
} else {
return StripePrice.create(serialized);
}
}
serialize(deserialized) {
if (isEmberArray(deserialized)) {
return deserialized.map((item) => {
return item;
}).compact();
} else {
return deserialized || null;
}
}
}

View File

@ -9,7 +9,7 @@ export default class TierBenefits extends Transform {
benefitsArray = serialized || [];
benefitsItems = benefitsArray.map((itemDetails) => {
return TierBenefitItem.create(itemDetails);
return TierBenefitItem.create({name: itemDetails});
});
return emberA(benefitsItems);
@ -21,7 +21,7 @@ export default class TierBenefits extends Transform {
if (isEmberArray(deserialized)) {
benefitsArray = deserialized.map((item) => {
let name = item.get('name').trim();
return {name};
return name;
}).compact();
} else {
benefitsArray = [];

View File

@ -17,26 +17,12 @@ export default function mockTiers(server) {
});
});
server.put('/tiers/:id/', function ({tiers, tierBenefits}, {params}) {
server.put('/tiers/:id/', function ({tiers}, {params}) {
const attrs = this.normalizedRequestAttrs();
const tier = tiers.find(params.id);
const benefitAttrs = attrs.benefits;
delete attrs.benefits;
tier.update(attrs);
benefitAttrs.forEach((benefit) => {
if (benefit.id) {
const tierBenefit = tierBenefits.find(benefit.id);
tierBenefit.tier = tier;
tierBenefit.save();
} else {
tier.createTierBenefit(benefit);
tier.save();
}
});
return tier.save();
});

View File

@ -7,20 +7,7 @@ export default Factory.extend({
slug(i) { return `tier-${i}`;},
type: 'paid',
visibility: 'none',
monthly_price() {
return {
interval: 'month',
nickname: 'Monthly',
currency: 'usd',
amount: 500
};
},
yearly_price() {
return {
interval: 'year',
nickname: 'Yearly',
currency: 'usd',
amount: 5000
};
}
currency: 'usd',
monthly_price: 500,
yearly_price: 5000
});

View File

@ -1,5 +0,0 @@
import {Model, belongsTo} from 'miragejs';
export default Model.extend({
tier: belongsTo('tier')
});

View File

@ -1,8 +1,5 @@
import {Model, hasMany} from 'miragejs';
export default Model.extend({
// ran into odd relationship bugs when called `benefits`
// serializer will rename to `benefits`
tierBenefits: hasMany(),
members: hasMany()
});

View File

@ -1,3 +0,0 @@
import BaseSerializer from './application';
export default BaseSerializer.extend({});

View File

@ -286,8 +286,7 @@ describe('Acceptance: Settings - Membership', function () {
const freeTier = this.server.db.tiers.findBy({slug: 'free'});
expect(freeTier.description).to.equal('Test description');
expect(freeTier.welcomePageUrl).to.equal('/not%20a%20url');
expect(freeTier.tierBenefitIds.length).to.equal(1);
const benefits = this.server.db.tierBenefits.find(freeTier.tierBenefitIds);
expect(benefits[0].name).to.equal('Second benefit');
expect(freeTier.benefits.length).to.equal(1);
expect(freeTier.benefits[0]).to.equal('Second benefit');
});
});