mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 14:43:08 +03:00
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>
This commit is contained in:
parent
eb2109499a
commit
86b55b0f81
@ -166,8 +166,9 @@ export default class GhLaunchWizardConnectStripeComponent extends Component {
|
||||
try {
|
||||
yield this.settings.save();
|
||||
|
||||
const products = yield this.store.query('product', {include: 'monthly_price,yearly_price'});
|
||||
const products = yield this.store.query('product', {filter: 'type:paid', include: 'monthly_price,yearly_price'});
|
||||
this.product = products.firstObject;
|
||||
|
||||
if (this.product) {
|
||||
const yearlyDiscount = this.calculateDiscount(5, 50);
|
||||
this.product.set('monthlyPrice', {
|
||||
|
@ -173,8 +173,9 @@ export default class GhLaunchWizardSetPricingComponent extends Component {
|
||||
this.stripeMonthlyAmount = storedData.monthlyAmount;
|
||||
this.stripeYearlyAmount = storedData.yearlyAmount;
|
||||
} else {
|
||||
const products = yield this.store.query('product', {include: 'monthly_price,yearly_price'});
|
||||
const products = yield this.store.query('product', {filter: 'type:paid', include: 'monthly_price,yearly_price'});
|
||||
this.product = products.firstObject;
|
||||
|
||||
let portalPlans = this.settings.get('portalPlans') || [];
|
||||
|
||||
this.isMonthlyChecked = portalPlans.includes('monthly');
|
||||
|
@ -251,7 +251,7 @@ export default Component.extend({
|
||||
},
|
||||
|
||||
saveProduct: task(function* () {
|
||||
const products = yield this.store.query('product', {include: 'monthly_price, yearly_price'});
|
||||
const products = yield this.store.query('product', {filter: 'type:paid', include: 'monthly_price, yearly_price'});
|
||||
this.product = products.firstObject;
|
||||
if (this.product) {
|
||||
const yearlyDiscount = this.calculateDiscount(5, 50);
|
||||
|
@ -148,7 +148,7 @@ export default class GhMembersRecipientSelect extends Component {
|
||||
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', {limit: 'all'});
|
||||
const products = yield this.store.query('product', {filter: 'type:paid', limit: 'all'});
|
||||
|
||||
if (products.length > 1) {
|
||||
const productsGroup = {
|
||||
|
@ -97,7 +97,7 @@ export default class GhMembersSegmentSelect extends Component {
|
||||
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', {limit: 'all', include: 'monthly_price,yearly_price,benefits'});
|
||||
const products = yield this.store.query('product', {filter: 'type:paid', limit: 'all', include: 'monthly_price,yearly_price,benefits'});
|
||||
|
||||
if (products.length > 0) {
|
||||
const productsGroup = {
|
||||
|
@ -1,54 +1,14 @@
|
||||
<label>Tiers</label>
|
||||
<div class="gh-product-cards">
|
||||
{{#each this.products as |product productIdx|}}
|
||||
<div class="gh-main-content-card gh-product-card">
|
||||
<button class="gh-product-card-editbutton gh-btn gh-btn-text gh-btn-link green" {{action "openEditProduct" product}}>
|
||||
<span>Edit</span>
|
||||
</button>
|
||||
<div class="gh-product-card-block">
|
||||
<h3 class="gh-product-card-name">
|
||||
{{product.name}}
|
||||
</h3>
|
||||
<p class="gh-product-card-description">
|
||||
{{product.description}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="gh-product-card-block benefits-block">
|
||||
<h4>Benefits <span class="counter">({{if product.benefits.length product.benefits.length "0"}})</span></h4>
|
||||
{{#if product.benefits.length}}
|
||||
<ul class="benefits">
|
||||
{{#each product.benefits as |benefit|}}
|
||||
<li>{{svg-jar "check"}} {{benefit.name}} </li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p class="gh-product-card-description">No benefits added for this tier.</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="gh-product-card-block">
|
||||
<div class="gh-product-price-container">
|
||||
<div class="gh-product-card-price">
|
||||
<div class="flex items-baseline">
|
||||
<span class="amount">{{gh-price-amount product.monthlyPrice.amount}}</span>
|
||||
<span class="currency">{{product.monthlyPrice.currency}}</span>
|
||||
</div>
|
||||
<div class="period">Monthly</div>
|
||||
</div>
|
||||
<div class="gh-product-card-price">
|
||||
<div class="flex items-baseline">
|
||||
<span class="amount">{{gh-price-amount product.yearlyPrice.amount}}</span>
|
||||
<span class="currency">{{product.monthlyPrice.currency}}</span>
|
||||
</div>
|
||||
<div class="period">Yearly</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<GhProductCard
|
||||
@product={{product}}
|
||||
@openEditProduct={{this.openEditProduct}}
|
||||
/>
|
||||
{{/each}}
|
||||
|
||||
<div class="gh-product-cards-footer">
|
||||
<button class="gh-btn gh-btn-link gh-btn-text gh-btn-icon gh-btn-add-product green" {{action "openNewProduct" product}}><span>{{svg-jar "add-stroke" class="stroke-green"}}Add tier</span></button>
|
||||
{{!-- <span>– Advanced (<a href="javascript:">learn more</a>)</span> --}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -57,7 +57,7 @@ export default Component.extend({
|
||||
}
|
||||
},
|
||||
fetchProducts: task(function* () {
|
||||
const products = yield this.store.query('product', {include: 'monthly_price,yearly_price'}) || [];
|
||||
const products = yield this.store.query('product', {filter: 'type:paid', include: 'monthly_price,yearly_price'}) || [];
|
||||
this.set('products', products);
|
||||
if (products.length > 0) {
|
||||
this.set('selectedProduct', {
|
||||
|
57
ghost/admin/app/components/gh-product-card.hbs
Normal file
57
ghost/admin/app/components/gh-product-card.hbs
Normal file
@ -0,0 +1,57 @@
|
||||
<div class="gh-main-content-card gh-product-card">
|
||||
<button class="gh-product-card-editbutton gh-btn gh-btn-text gh-btn-link green" {{action "openEditProduct" this.product}}>
|
||||
<span>Edit</span>
|
||||
</button>
|
||||
<div class="gh-product-card-block title-block">
|
||||
<h3 class="gh-product-card-name">
|
||||
{{this.product.name}}
|
||||
</h3>
|
||||
<p class="gh-product-card-description">
|
||||
{{this.product.description}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="gh-product-card-block benefits-block">
|
||||
<h4>Benefits <span class="counter">({{if this.product.benefits.length product.benefits.length "0"}})</span></h4>
|
||||
{{#if this.product.benefits.length}}
|
||||
<ul class="benefits">
|
||||
{{#each this.product.benefits as |benefit|}}
|
||||
<li>{{svg-jar "check"}} {{benefit.name}} </li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p class="gh-product-card-description">No benefits added for this tier.</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if (eq this.product.type "free" )}}
|
||||
<div class="gh-product-card-block">
|
||||
<div class="gh-product-price-container">
|
||||
<div class="gh-product-card-price">
|
||||
<div class="flex items-start">
|
||||
<span class="currency">{{currency-symbol this.product.monthlyPrice.currency}}</span>
|
||||
<span class="amount">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if (eq this.product.type "paid" )}}
|
||||
<div class="gh-product-card-block">
|
||||
<div class="gh-product-price-container">
|
||||
<div class="gh-product-card-price">
|
||||
<div class="flex items-start">
|
||||
<span class="currency">{{currency-symbol this.product.monthlyPrice.currency}}</span>
|
||||
<span class="amount">{{gh-price-amount this.product.monthlyPrice.amount}}</span>
|
||||
</div>
|
||||
<div class="period">Monthly</div>
|
||||
</div>
|
||||
<div class="gh-product-card-price">
|
||||
<div class="flex items-start">
|
||||
<span class="currency">{{currency-symbol this.product.monthlyPrice.currency}}</span>
|
||||
<span class="amount">{{gh-price-amount this.product.yearlyPrice.amount}}</span>
|
||||
</div>
|
||||
<div class="period">Yearly</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
37
ghost/admin/app/components/gh-product-card.js
Normal file
37
ghost/admin/app/components/gh-product-card.js
Normal file
@ -0,0 +1,37 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {getSymbol} from 'ghost-admin/utils/currency';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
export default class extends Component {
|
||||
@service membersUtils;
|
||||
@service ghostPaths;
|
||||
@service ajax;
|
||||
@service store;
|
||||
@service config;
|
||||
|
||||
@tracked showProductModal = false;
|
||||
@tracked productModel = null;
|
||||
|
||||
get product() {
|
||||
return this.args.product;
|
||||
}
|
||||
|
||||
get isPaidProduct() {
|
||||
return this.product.type === 'paid';
|
||||
}
|
||||
|
||||
get hasCurrencySymbol() {
|
||||
const currencySymbol = getSymbol(this.product?.monthlyPrice?.currency);
|
||||
return currencySymbol?.length !== 3;
|
||||
}
|
||||
|
||||
get isFreeProduct() {
|
||||
return this.product.type === 'free';
|
||||
}
|
||||
|
||||
@action
|
||||
async openEditProduct(product) {
|
||||
this.args.openEditProduct(product);
|
||||
}
|
||||
}
|
@ -17,7 +17,8 @@ export default class ModalMemberProduct extends ModalComponent {
|
||||
|
||||
@task({drop: true})
|
||||
*fetchProducts() {
|
||||
this.products = yield this.store.query('product', {include: 'monthly_price,yearly_price,benefits'});
|
||||
this.products = yield this.store.query('product', {filter: 'type:paid', include: 'monthly_price,yearly_price,benefits'});
|
||||
|
||||
this.loadingProducts = false;
|
||||
if (this.products.length > 0) {
|
||||
this.selectedProduct = this.products.firstObject.id;
|
||||
|
@ -72,11 +72,12 @@ export default ModalComponent.extend({
|
||||
return (this.membersUtils.isStripeEnabled && allowedPlans.includes('yearly'));
|
||||
}),
|
||||
products: computed('model.products.[]', 'settings.portalProducts.[]', 'isPreloading', function () {
|
||||
if (this.isPreloading || !this.model.products) {
|
||||
const paidProducts = this.model.products?.filter(product => product.type === 'paid');
|
||||
if (this.isPreloading || !paidProducts?.length) {
|
||||
return [];
|
||||
}
|
||||
const portalProducts = this.settings.get('portalProducts') || [];
|
||||
const products = this.model.products.map((product) => {
|
||||
const products = paidProducts.map((product) => {
|
||||
return {
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
|
@ -13,17 +13,19 @@
|
||||
<div class="gh-main-section-block span-2">
|
||||
<h4 class="gh-main-section-header small bn">Basic</h4>
|
||||
<div class="gh-main-section-content grey gh-product-priceform-block">
|
||||
<GhFormGroup @errors={{this.errors}} @property="name">
|
||||
<label for="name" class="fw6">Name</label>
|
||||
<GhTextInput
|
||||
@value={{readonly this.product.name}}
|
||||
@input={{action (mut this.product.name) value="target.value"}}
|
||||
@name="name"
|
||||
@placeholder="Bronze"
|
||||
@id="name"
|
||||
@class="gh-input" />
|
||||
<GhErrorMessage @errors={{this.errors}} @property="name" />
|
||||
</GhFormGroup>
|
||||
{{#if (not this.isFreeProduct)}}
|
||||
<GhFormGroup @errors={{this.errors}} @property="name">
|
||||
<label for="name" class="fw6">Name</label>
|
||||
<GhTextInput
|
||||
@value={{readonly this.product.name}}
|
||||
@input={{action (mut this.product.name) value="target.value"}}
|
||||
@name="name"
|
||||
@placeholder="Bronze"
|
||||
@id="name"
|
||||
@class="gh-input" />
|
||||
<GhErrorMessage @errors={{this.errors}} @property="name" />
|
||||
</GhFormGroup>
|
||||
{{/if}}
|
||||
<GhFormGroup @errors={{this.errors}} @property="description">
|
||||
<label for="description" class="fw6">Description</label>
|
||||
<GhTextInput
|
||||
@ -35,57 +37,59 @@
|
||||
@class="gh-input" />
|
||||
<GhErrorMessage @errors={{this.errors}} @property="description" />
|
||||
</GhFormGroup>
|
||||
<GhFormGroup @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="prices">
|
||||
<div class="gh-settings-members-pricelabelcont">
|
||||
<label for="monthlyPrice">Prices</label>
|
||||
<span>–</span>
|
||||
<div>
|
||||
<span class="gh-setting-members-currency gh-select">
|
||||
<div class="gh-setting-members-currencylabel">
|
||||
<span>{{this.currency}}</span>
|
||||
{{svg-jar "arrow-down-small"}}
|
||||
</div>
|
||||
<OneWaySelect
|
||||
@value={{this.selectedCurrency}}
|
||||
id="currency"
|
||||
name="currency"
|
||||
@options={{readonly this.allCurrencies}}
|
||||
@optionValuePath="value"
|
||||
@optionLabelPath="label"
|
||||
@update={{action "setCurrency"}}
|
||||
/>
|
||||
</span>
|
||||
{{#if (not this.isFreeProduct)}}
|
||||
<GhFormGroup @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="prices">
|
||||
<div class="gh-settings-members-pricelabelcont">
|
||||
<label for="monthlyPrice">Prices</label>
|
||||
<span>–</span>
|
||||
<div>
|
||||
<span class="gh-setting-members-currency gh-select">
|
||||
<div class="gh-setting-members-currencylabel">
|
||||
<span>{{this.currency}}</span>
|
||||
{{svg-jar "arrow-down-small"}}
|
||||
</div>
|
||||
<OneWaySelect
|
||||
@value={{this.selectedCurrency}}
|
||||
id="currency"
|
||||
name="currency"
|
||||
@options={{readonly this.allCurrencies}}
|
||||
@optionValuePath="value"
|
||||
@optionLabelPath="label"
|
||||
@update={{action "setCurrency"}}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-setting-members-prices">
|
||||
<div class="gh-setting-members-prices">
|
||||
|
||||
<div class="gh-input-group">
|
||||
<GhTextInput
|
||||
@id="monthlyPrice"
|
||||
@value={{readonly this.stripeMonthlyAmount}}
|
||||
@type="number"
|
||||
@input={{action (mut this.stripeMonthlyAmount) value="target.value"}}
|
||||
@focus-out={{action "validateStripePlans"}}
|
||||
/>
|
||||
<span class="gh-input-append"><span class="ttu">{{this.currency}}</span>/month</span>
|
||||
<div class="gh-input-group">
|
||||
<GhTextInput
|
||||
@id="monthlyPrice"
|
||||
@value={{readonly this.stripeMonthlyAmount}}
|
||||
@type="number"
|
||||
@input={{action (mut this.stripeMonthlyAmount) value="target.value"}}
|
||||
@focus-out={{action "validateStripePlans"}}
|
||||
/>
|
||||
<span class="gh-input-append"><span class="ttu">{{this.currency}}</span>/month</span>
|
||||
</div>
|
||||
<div class="gh-input-group">
|
||||
<GhTextInput
|
||||
@id="yearlyPrice"
|
||||
@value={{readonly this.stripeYearlyAmount}}
|
||||
@type="number"
|
||||
@input={{action (mut this.stripeYearlyAmount) value="target.value"}}
|
||||
@focus-out={{this.validateStripePlans}}
|
||||
@placeholder=''
|
||||
data-test-title-input={{true}}
|
||||
/>
|
||||
<span class="gh-input-append"><span class="ttu">{{this.currency}}</span>/year</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-input-group">
|
||||
<GhTextInput
|
||||
@id="yearlyPrice"
|
||||
@value={{readonly this.stripeYearlyAmount}}
|
||||
@type="number"
|
||||
@input={{action (mut this.stripeYearlyAmount) value="target.value"}}
|
||||
@focus-out={{this.validateStripePlans}}
|
||||
@placeholder=''
|
||||
data-test-title-input={{true}}
|
||||
/>
|
||||
<span class="gh-input-append"><span class="ttu">{{this.currency}}</span>/year</span>
|
||||
</div>
|
||||
</div>
|
||||
{{#if this.stripePlanError}}
|
||||
<p class="response w-100"><span class="red">{{this.stripePlanError}}</span></p>
|
||||
{{/if}}
|
||||
</GhFormGroup>
|
||||
{{#if this.stripePlanError}}
|
||||
<p class="response w-100"><span class="red">{{this.stripePlanError}}</span></p>
|
||||
{{/if}}
|
||||
</GhFormGroup>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<h4 class="gh-main-section-header small bn">Benefits</h4>
|
||||
@ -125,7 +129,7 @@
|
||||
<h4 class="gh-main-section-header small bn">Tier Preview</h4>
|
||||
<div class="gh-main-section-content" style="border-color: {{this.settings.accentColor}}">
|
||||
<span class="checkmark" style="background-color: {{this.settings.accentColor}}"></span>
|
||||
|
||||
|
||||
{{#if this.product.name}}
|
||||
<h4>{{this.product.name}}</h4>
|
||||
{{else}}
|
||||
@ -149,27 +153,32 @@
|
||||
<li>{{svg-jar "check-2"}} <span>Expert analysis</span></li>
|
||||
</ul>
|
||||
{{/if}}
|
||||
|
||||
<div class="price">
|
||||
|
||||
{{#if this.stripeMonthlyAmount}}
|
||||
<span class="monthly-price">
|
||||
<span class="currency">{{currency-symbol this.currency}}</span>
|
||||
{{this.stripeMonthlyAmount}}
|
||||
<span class="period">/month</span>
|
||||
</span>
|
||||
{{else}}
|
||||
<span class="monthly-price placeholder">
|
||||
<span class="currency">{{currency-symbol this.currency}}</span>
|
||||
0
|
||||
<span class="period">/month</span>
|
||||
</span>
|
||||
{{/if}}
|
||||
{{#if (not this.isFreeProduct)}}
|
||||
{{#if this.stripeMonthlyAmount}}
|
||||
<span class="monthly-price">
|
||||
<span class="currency">{{currency-symbol this.currency}}</span>
|
||||
{{format-number this.stripeMonthlyAmount}}
|
||||
<span class="period">/month</span>
|
||||
</span>
|
||||
{{else}}
|
||||
<span class="monthly-price placeholder">
|
||||
<span class="currency">{{currency-symbol this.currency}}</span>
|
||||
0
|
||||
<span class="period">/month</span>
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.stripeYearlyAmount}}
|
||||
<span class="yearly-price">{{currency-symbol this.currency}}{{this.stripeYearlyAmount}}/year</span>
|
||||
{{#if this.stripeYearlyAmount}}
|
||||
<span class="yearly-price">{{currency-symbol this.currency}}{{format-number this.stripeYearlyAmount}}/year</span>
|
||||
{{else}}
|
||||
<span class="yearly-price placeholder">0<span class="currency">{{this.currency}}</span>/year</span>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<span class="yearly-price placeholder">0<span class="currency">{{this.currency}}</span>/year</span>
|
||||
<span class="monthly-price">
|
||||
<span class="currency">{{currency-symbol this.currency}}</span>
|
||||
0
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -34,6 +34,10 @@ export default class ModalProductPrice extends ModalBase {
|
||||
|
||||
confirm() {}
|
||||
|
||||
get isFreeProduct() {
|
||||
return this.product.type === 'free';
|
||||
}
|
||||
|
||||
get allCurrencies() {
|
||||
return getCurrencyOptions();
|
||||
}
|
||||
@ -104,24 +108,26 @@ export default class ModalProductPrice extends ModalBase {
|
||||
yield this.send('addBenefit', this.newBenefit);
|
||||
}
|
||||
|
||||
const monthlyAmount = this.stripeMonthlyAmount * 100;
|
||||
const yearlyAmount = this.stripeYearlyAmount * 100;
|
||||
this.product.set('monthlyPrice', {
|
||||
nickname: 'Monthly',
|
||||
amount: monthlyAmount,
|
||||
active: true,
|
||||
currency: this.currency,
|
||||
interval: 'month',
|
||||
type: 'recurring'
|
||||
});
|
||||
this.product.set('yearlyPrice', {
|
||||
nickname: 'Yearly',
|
||||
amount: yearlyAmount,
|
||||
active: true,
|
||||
currency: this.currency,
|
||||
interval: 'year',
|
||||
type: 'recurring'
|
||||
});
|
||||
if (!this.isFreeProduct) {
|
||||
const monthlyAmount = this.stripeMonthlyAmount * 100;
|
||||
const yearlyAmount = this.stripeYearlyAmount * 100;
|
||||
this.product.set('monthlyPrice', {
|
||||
nickname: 'Monthly',
|
||||
amount: monthlyAmount,
|
||||
active: true,
|
||||
currency: this.currency,
|
||||
interval: 'month',
|
||||
type: 'recurring'
|
||||
});
|
||||
this.product.set('yearlyPrice', {
|
||||
nickname: 'Yearly',
|
||||
amount: yearlyAmount,
|
||||
active: true,
|
||||
currency: this.currency,
|
||||
interval: 'year',
|
||||
type: 'recurring'
|
||||
});
|
||||
}
|
||||
this.product.set('benefits', this.benefits);
|
||||
yield this.product.save();
|
||||
|
||||
|
@ -54,7 +54,9 @@ export default class DashboardController extends Controller {
|
||||
}
|
||||
|
||||
async loadMRRStats() {
|
||||
const products = await this.store.query('product', {include: 'monthly_price,yearly_price', limit: 'all'});
|
||||
const products = await this.store.query('product', {
|
||||
filter: 'type:paid', include: 'monthly_price,yearly_price', limit: 'all'
|
||||
});
|
||||
const defaultProduct = products?.firstObject;
|
||||
|
||||
this.mrrStatsLoading = true;
|
||||
|
@ -99,7 +99,7 @@ export default class OffersController extends Controller {
|
||||
|
||||
@task({drop: true})
|
||||
*fetchProducts() {
|
||||
this.products = yield this.store.query('product', {include: 'monthly_price,yearly_price'});
|
||||
this.products = yield this.store.query('product', {filter: 'type:paid', include: 'monthly_price,yearly_price'});
|
||||
this.products = this.products.filter((d) => {
|
||||
return d.monthlyPrice && d.yearlyPrice;
|
||||
});
|
||||
|
@ -70,7 +70,9 @@ export default class MembersController extends Controller {
|
||||
|
||||
@task({restartable: true})
|
||||
*fetchOffersTask() {
|
||||
this.products = yield this.store.query('product', {include: 'monthly_price,yearly_price'});
|
||||
this.products = yield this.store.query('product', {
|
||||
filter: 'type:paid', include: 'monthly_price,yearly_price'
|
||||
});
|
||||
this.offers = yield this.store.query('offer', {limit: 'all'});
|
||||
return this.offers;
|
||||
}
|
||||
|
@ -44,6 +44,14 @@ export default class MembersAccessController extends Controller {
|
||||
|
||||
queryParams = ['showPortalSettings'];
|
||||
|
||||
get freeProduct() {
|
||||
return this.products?.find(product => product.type === 'free');
|
||||
}
|
||||
|
||||
get paidProducts() {
|
||||
return this.products?.filter(product => product.type === 'paid');
|
||||
}
|
||||
|
||||
get allCurrencies() {
|
||||
return getCurrencyOptions();
|
||||
}
|
||||
@ -311,7 +319,9 @@ export default class MembersAccessController extends Controller {
|
||||
|
||||
@task({drop: true})
|
||||
*fetchProducts() {
|
||||
this.products = yield this.store.query('product', {include: 'monthly_price,yearly_price,benefits'});
|
||||
this.products = yield this.store.query('product', {
|
||||
filter: 'type:paid', include: 'monthly_price,yearly_price,benefits'
|
||||
});
|
||||
this.product = this.products.firstObject;
|
||||
this.setupPortalProduct(this.product);
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
import {formatNumber} from './format-number';
|
||||
import {helper} from '@ember/component/helper';
|
||||
|
||||
export function ghPriceAmount(amount) {
|
||||
if (amount) {
|
||||
let price = amount / 100;
|
||||
if (price % 1 === 0) {
|
||||
return price;
|
||||
return formatNumber(price);
|
||||
} else {
|
||||
return (Math.round(price * 100) / 100).toFixed(2);
|
||||
return formatNumber(Math.round(price * 100) / 100).toFixed(2);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
@ -7,6 +7,7 @@ export default Model.extend(ValidationEngine, {
|
||||
name: attr('string'),
|
||||
description: attr('string'),
|
||||
slug: attr('string'),
|
||||
type: attr('string', {defaultValue: 'paid'}),
|
||||
monthlyPrice: attr('stripe-price'),
|
||||
yearlyPrice: attr('stripe-price'),
|
||||
benefits: attr('product-benefits')
|
||||
|
@ -35,7 +35,7 @@
|
||||
}
|
||||
|
||||
.gh-product-card-block {
|
||||
flex-basis: 50%;
|
||||
flex-basis: 30%;
|
||||
}
|
||||
|
||||
.gh-product-card-block:not(:first-of-type) {
|
||||
@ -65,6 +65,10 @@
|
||||
color: var(--midgrey);
|
||||
}
|
||||
|
||||
.gh-product-card-block.title-block {
|
||||
flex-basis: 40%;
|
||||
}
|
||||
|
||||
.gh-product-card-block.benefits-block .gh-product-card-description {
|
||||
margin-top: 9px;
|
||||
}
|
||||
@ -99,13 +103,15 @@
|
||||
|
||||
.gh-product-price-container {
|
||||
display: flex;
|
||||
margin: 0 40px 0 20px;
|
||||
margin: 0 60px 0 20px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.gh-product-card-price {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 12px 2px 0;
|
||||
font-size: 1.3rem;
|
||||
color: var(--midgrey);
|
||||
@ -113,11 +119,13 @@
|
||||
border: 1px solid var(--whitegrey);
|
||||
border-radius: 3px;
|
||||
min-width: 90px;
|
||||
min-height: 66px;
|
||||
}
|
||||
|
||||
.gh-product-card-price .currency-symbol,
|
||||
.gh-product-card-price .amount,
|
||||
.gh-product-card-price .currency {
|
||||
.gh-product-card-price .currency,
|
||||
.gh-product-card-price .currency-code {
|
||||
font-weight: 600;
|
||||
color: var(--darkgrey);
|
||||
}
|
||||
@ -127,22 +135,39 @@
|
||||
}
|
||||
|
||||
.gh-product-card-price .amount {
|
||||
font-size: 2.1rem;
|
||||
letter-spacing: -0.2px;
|
||||
letter-spacing: -.2px;
|
||||
line-height: 1;
|
||||
margin-right: 2px;
|
||||
font-size: 2.2rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.1px;
|
||||
}
|
||||
|
||||
.gh-product-card-price .currency {
|
||||
font-size: 1.25rem;
|
||||
letter-spacing: -0.2px;
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.4px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.gh-product-card-price .currency-code {
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
top: 0;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
font-size: 1.25rem;
|
||||
letter-spacing: -.2px;
|
||||
}
|
||||
|
||||
.gh-product-card-price .period {
|
||||
font-size: 1.25rem;
|
||||
text-transform: lowercase;
|
||||
line-height: 1.2em;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.gh-product-cards-footer {
|
||||
|
@ -81,7 +81,7 @@
|
||||
<div class="gh-expandable-block">
|
||||
<div class="gh-expandable-header">
|
||||
<div>
|
||||
<h4 class="gh-expandable-title">Free</h4>
|
||||
<h4 class="gh-expandable-title">Default</h4>
|
||||
<p class="gh-expandable-description">Free member sign up settings</p>
|
||||
</div>
|
||||
<button type="button" class="gh-btn" {{on "click" (toggle "freeOpen" this)}} data-test-toggle-pub-info><span>{{if this.freeOpen "Close" "Expand"}}</span></button>
|
||||
@ -89,6 +89,10 @@
|
||||
<div class="gh-expandable-content">
|
||||
{{#liquid-if this.freeOpen}}
|
||||
<div class="gh-setting-content-extended">
|
||||
<GhProductCard
|
||||
@product={{this.freeProduct}}
|
||||
@openEditProduct={{this.openEditProduct}}
|
||||
/>
|
||||
<GhFormGroup @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="free-welcome-page">
|
||||
<label for="freeWelcomePage">Welcome page</label>
|
||||
<GhUrlInput
|
||||
@ -143,7 +147,7 @@
|
||||
{{else}}
|
||||
{{#if (feature "multipleProducts")}}
|
||||
<GhMembershipProductsAlpha
|
||||
@products={{this.products}}
|
||||
@products={{this.paidProducts}}
|
||||
@confirmProductSave={{this.confirmProductSave}}
|
||||
/>
|
||||
{{else}}
|
||||
@ -259,4 +263,14 @@
|
||||
@close={{this.closeStripeConnect}}
|
||||
@modifier="action wide stripe-connect" />
|
||||
{{/if}}
|
||||
{{#if this.showProductModal}}
|
||||
<GhFullscreenModal
|
||||
@modal="product"
|
||||
@model={{hash
|
||||
product=this.productModel
|
||||
}}
|
||||
@confirm={{this.confirmProductSave}}
|
||||
@close={{this.closeProductModal}}
|
||||
@modifier="edit-product action wide" />
|
||||
{{/if}}
|
||||
</section>
|
||||
|
@ -121,6 +121,9 @@ export const currencies = [
|
||||
];
|
||||
|
||||
export function getSymbol(currency) {
|
||||
if (!currency) {
|
||||
return '';
|
||||
}
|
||||
return Intl.NumberFormat('en', {currency, style: 'currency'}).format(0).replace(/[\d\s.]/g, '');
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user