From 35612db851a8c388f49d4362f1a266b17cbe1078 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 17 May 2021 19:59:07 +0530 Subject: [PATCH] Wired membership tiers UI in new members setting no refs - Wired Premium membership UI to existing monthly/yearly prices in the default product - Wired free membership UI to redirect URI setting - Updated save to validate settings/errors --- .../app/controllers/settings/membership.js | 144 ++++++++++++++++++ ghost/admin/app/styles/layouts/settings.css | 24 ++- .../app/templates/settings/membership.hbs | 115 +++++++++----- ghost/admin/app/utils/currency.js | 31 ++++ 4 files changed, 273 insertions(+), 41 deletions(-) diff --git a/ghost/admin/app/controllers/settings/membership.js b/ghost/admin/app/controllers/settings/membership.js index dc35efd665..369417f334 100644 --- a/ghost/admin/app/controllers/settings/membership.js +++ b/ghost/admin/app/controllers/settings/membership.js @@ -1,18 +1,38 @@ import Controller from '@ember/controller'; import {action} from '@ember/object'; +import {getCurrencyOptions, getSymbol} from 'ghost-admin/utils/currency'; import {inject as service} from '@ember/service'; import {task} from 'ember-concurrency-decorators'; import {tracked} from '@glimmer/tracking'; export default class MembersAccessController extends Controller { @service settings; + @service store; + @service config; @tracked showLeavePortalModal = false; @tracked showLeaveRouteModal = false; @tracked showPortalSettings = false; + @tracked product = null; + @tracked stripePrices = []; + @tracked paidSignupRedirect; + @tracked freeSignupRedirect; + @tracked stripeMonthlyAmount = 5; + @tracked stripeYearlyAmount = 50; + @tracked currency = 'usd'; + @tracked stripePlanError = ''; + queryParams = ['showPortalSettings']; + constructor(...args) { + super(...args); + this.siteUrl = this.config.get('blogUrl'); + this.fetchDefaultProduct.perform(); + + this.allCurrencies = getCurrencyOptions(); + } + leaveRoute(transition) { if (this.settings.get('hasDirtyAttributes')) { transition.abort(); @@ -21,11 +41,77 @@ export default class MembersAccessController extends Controller { } } + _validateSignupRedirect(url, type) { + let errMessage = `Please enter a valid URL`; + this.settings.get('errors').remove(type); + this.settings.get('hasValidated').removeObject(type); + + if (url === null) { + this.settings.get('errors').add(type, errMessage); + this.settings.get('hasValidated').pushObject(type); + return false; + } + + if (url === undefined) { + // Not initialised + return; + } + + if (url.href.startsWith(this.siteUrl)) { + const path = url.href.replace(this.siteUrl, ''); + this.settings.set(type, path); + } else { + this.settings.set(type, url.href); + } + } + @action openPortalSettings() { this.saveSettingsTask.perform(); this.showPortalSettings = true; } + @action + setStripePlansCurrency(event) { + const newCurrency = event.value; + this.currency = newCurrency; + } + + @action + setPaidSignupRedirect(url) { + this.paidSignupRedirect = url; + } + + @action + setFreeSignupRedirect(url) { + this.freeSignupRedirect = url; + } + + @action + validatePaidSignupRedirect() { + return this._validateSignupRedirect(this.paidSignupRedirect, 'membersPaidSignupRedirect'); + } + + @action + validateFreeSignupRedirect() { + return this._validateSignupRedirect(this.freeSignupRedirect, 'membersFreeSignupRedirect'); + } + + @action + validateStripePlans() { + 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; + } + } + @action closePortalSettings() { const changedAttributes = this.settings.changedAttributes(); @@ -65,8 +151,66 @@ export default class MembersAccessController extends Controller { this.leaveSettingsTransition = null; } + saveProduct() { + if (this.product) { + const stripePrices = this.product.stripePrices || []; + if (this.stripeMonthlyAmount && this.stripeYearlyAmount) { + stripePrices.push( + { + nickname: 'Monthly', + amount: this.stripeMonthlyAmount * 100, + active: 1, + currency: this.currency, + interval: 'month', + type: 'recurring' + }, + { + nickname: 'Yearly', + amount: this.stripeYearlyAmount * 100, + active: 1, + currency: this.currency, + interval: 'year', + type: 'recurring' + } + ); + this.product.set('stripePrices', stripePrices); + return this.product; + } else { + return this.product; + } + } + } + + @task({drop: true}) + *fetchDefaultProduct() { + const products = yield this.store.query('product', {include: 'stripe_prices'}); + this.product = products.firstObject; + this.stripePrices = []; + if (this.product) { + this.stripePrices = this.product.get('stripePrices'); + const monthlyPrice = this.stripePrices.find(d => d.nickname === 'Monthly'); + const yearlyPrice = this.stripePrices.find(d => d.nickname === 'Yearly'); + if (monthlyPrice && monthlyPrice.amount) { + this.stripeMonthlyAmount = (monthlyPrice.amount / 100); + this.currency = monthlyPrice.currency; + } + if (yearlyPrice && yearlyPrice.amount) { + this.stripeYearlyAmount = (yearlyPrice.amount / 100); + } + } + } + @task({drop: true}) *saveSettingsTask() { + yield this.validateStripePlans(); + + if (this.stripePlanError) { + return; + } + + if (this.settings.get('errors').length !== 0) { + return; + } return yield this.settings.save(); } diff --git a/ghost/admin/app/styles/layouts/settings.css b/ghost/admin/app/styles/layouts/settings.css index 3c90410058..38fcfb5187 100644 --- a/ghost/admin/app/styles/layouts/settings.css +++ b/ghost/admin/app/styles/layouts/settings.css @@ -225,7 +225,7 @@ align-self: center; } -.gh-setting-action .for-checkbox label, +.gh-setting-action .for-checkbox label, .gh-setting-action .for-radio label { padding-bottom: 0; margin-bottom: 0; @@ -1465,6 +1465,26 @@ p.theme-validation-details { margin-bottom: 4px; } +.gh-settings-members-pricelabelcont .gh-select { + padding: 0; + width: 320px; + background: transparent; + border: none; +} + +.gh-settings-members-pricelabelcont .gh-select select { + background: transparent; + border: none; + padding: 0; +} + +.gh-settings-members-pricelabelcont .gh-select svg { + position: absolute; + bottom: unset; + width: 12px; + margin-right: unset; +} + .gh-setting-members-prices { display: grid; grid-template-columns: 1fr 1fr; @@ -1488,4 +1508,4 @@ p.theme-validation-details { height: 582px; margin-bottom: 60px; border-radius: 5px; -} \ No newline at end of file +} diff --git a/ghost/admin/app/templates/settings/membership.hbs b/ghost/admin/app/templates/settings/membership.hbs index 95733e5f34..2adcdd7b65 100644 --- a/ghost/admin/app/templates/settings/membership.hbs +++ b/ghost/admin/app/templates/settings/membership.hbs @@ -74,12 +74,18 @@
- + -

Redirect to this URL after signup for a free membership

@@ -99,43 +105,74 @@
{{#liquid-if this.paidOpen}}
- -
- - -
USD
-
-
-
- -
USD/month
+ {{#if this.fetchDefaultProduct.isRunning}} + Loading... + {{else}} + +
+ + +
+ + + {{svg-jar "arrow-down-small"}} + +
-
- -
USD/year
-
-
- - +
- - - - -

Redirect to this URL after signup for premium membership

-
+
+ + {{this.currency}}/month +
+
+ + {{this.currency}}/month +
+
+ {{#if this.stripePlanError}} +

{{this.stripePlanError}}

+ {{/if}} + + + + + + +

Redirect to this URL after signup for premium membership

+
+ {{/if}}
{{/liquid-if}}
diff --git a/ghost/admin/app/utils/currency.js b/ghost/admin/app/utils/currency.js index f460f1f8f3..db68c48522 100644 --- a/ghost/admin/app/utils/currency.js +++ b/ghost/admin/app/utils/currency.js @@ -128,3 +128,34 @@ export function getSymbol(currency) { export function getNonDecimal(amount/*, currency*/) { return amount / 100; } + +export function getCurrencyOptions() { + const noOfTopCurrencies = 5; + + const topCurrencies = currencies.slice(0, noOfTopCurrencies).map((currency) => { + return { + value: currency.isoCode.toLowerCase(), + label: `${currency.isoCode} - ${currency.name}`, + isoCode: currency.isoCode + }; + }); + + const otherCurrencies = currencies.slice(noOfTopCurrencies, currencies.length).map((currency) => { + return { + value: currency.isoCode.toLowerCase(), + label: `${currency.isoCode} - ${currency.name}`, + isoCode: currency.isoCode + }; + }); + + return [ + { + groupName: '—', + options: topCurrencies + }, + { + groupName: '—', + options: otherCurrencies + } + ]; +}