mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-26 04:13:30 +03:00
Added pricing setup to launch site wizard
refs https://github.com/TryGhost/Team/issues/450 - duplicated and refactored/updated members pricing settings UI from labs to the "set pricing" step of the launch site wizard
This commit is contained in:
parent
26f4e79ac7
commit
f6a82b0099
@ -1,2 +1,83 @@
|
|||||||
<h2>Set subscription pricing</h2>
|
<h2>Set subscription pricing</h2>
|
||||||
<button type="button" class="right gh-btn gh-btn-green" {{on "click" @afterComplete}} data-test-button="wizard-next"><span>Finish & launch my site</span></button>
|
|
||||||
|
<div class="flex mb4 br3 bg-white">
|
||||||
|
<div class="pa4">
|
||||||
|
<div class="w-100 w-50-l flex flex-column flex-row-ns">
|
||||||
|
<div class="w-100">
|
||||||
|
<GhFormGroup @class="for-select">
|
||||||
|
<label class="fw6 f8"for="currency">Plan currency</label>
|
||||||
|
<span class="gh-select mt1">
|
||||||
|
<OneWaySelect
|
||||||
|
@value={{this.selectedCurrency}}
|
||||||
|
id="currency"
|
||||||
|
name="currency"
|
||||||
|
@options={{this.currencies}}
|
||||||
|
@optionValuePath="value"
|
||||||
|
@optionLabelPath="label"
|
||||||
|
@update={{this.setStripePlansCurrency}}
|
||||||
|
/>
|
||||||
|
{{svg-jar "arrow-down-small"}}
|
||||||
|
</span>
|
||||||
|
</GhFormGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-100 w-50-l flex flex-column flex-row-ns">
|
||||||
|
<div class="w-100 w-50-ns mr3-ns">
|
||||||
|
<GhFormGroup @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="stripePlans">
|
||||||
|
<label class="fw6 f8">Monthly price</label>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-center mt1 gh-input-group gh-labs-price-label">
|
||||||
|
<GhTextInput
|
||||||
|
@value={{readonly this.stripePlans.monthly.amount}}
|
||||||
|
@type="number"
|
||||||
|
@input={{action (mut this.stripeMonthlyAmount) value="target.value"}}
|
||||||
|
@focus-out={{action "validateStripePlans"}}
|
||||||
|
/>
|
||||||
|
<span class="gh-input-append"><span class="ttu">{{this.stripePlans.monthly.currency}}</span>/month</span>
|
||||||
|
</div>
|
||||||
|
</GhFormGroup>
|
||||||
|
</div>
|
||||||
|
<div class="w-100 w-50-ns ml2-ns">
|
||||||
|
<GhFormGroup @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="stripePlans">
|
||||||
|
<label class="fw6 f8">Yearly price</label>
|
||||||
|
<div class="flex items-center justify-center mt1 gh-input-group gh-labs-price-label">
|
||||||
|
<GhTextInput
|
||||||
|
@value={{readonly this.stripePlans.yearly.amount}}
|
||||||
|
@type="number"
|
||||||
|
@input={{action (mut this.stripeYearlyAmount) value="target.value"}}
|
||||||
|
@focus-out={{action "validateStripePlans"}}
|
||||||
|
/>
|
||||||
|
<span class="gh-input-append"><span class="ttu">{{this.stripePlans.yearly.currency}}</span>/year</span>
|
||||||
|
</div>
|
||||||
|
</GhFormGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-100 w-50-l flex flex-column flex-row-ns">
|
||||||
|
<GhErrorMessage @errors={{settings.errors}} @property="stripePlans" class="w-100 red"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gh-setting-first">
|
||||||
|
<div class="gh-setting-content">
|
||||||
|
<h4 class="gh-setting-title">Allow free member signup</h4>
|
||||||
|
<p class="gh-setting-desc pa0 ma0">If disabled, members can only be signed up via payment checkout or API integration</p>
|
||||||
|
</div>
|
||||||
|
<div class="gh-setting-action">
|
||||||
|
<div class="for-switch">
|
||||||
|
<label class="switch" for="members-allow-self-signup" {{action "toggleSelfSignup" bubbles="false"}}>
|
||||||
|
<input type="checkbox" checked={{this.settings.membersAllowFreeSignup}} class="gh-input" {{on "click" this.toggleSelfSignup}} data-test-checkbox="members-allow-self-signup">
|
||||||
|
<span class="input-toggle-component mt1"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{!-- TODO: reset "failed" state automatically --}}
|
||||||
|
<GhTaskButton
|
||||||
|
@buttonText="Launch my site"
|
||||||
|
@task={{this.saveAndContinue}}
|
||||||
|
@runningText="Launching"
|
||||||
|
@class="right gh-btn gh-btn-green gh-btn-icon"
|
||||||
|
data-test-button="wizard-next"
|
||||||
|
/>
|
@ -1,4 +1,130 @@
|
|||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
|
import {CURRENCIES} from 'ghost-admin/components/gh-members-lab-setting';
|
||||||
|
import {action} from '@ember/object';
|
||||||
|
import {inject as service} from '@ember/service';
|
||||||
|
import {task} from 'ember-concurrency-decorators';
|
||||||
|
import {tracked} from '@glimmer/tracking';
|
||||||
|
|
||||||
export default class GhLaunchWizardSetPricingComponent extends Component {
|
export default class GhLaunchWizardSetPricingComponent extends Component {
|
||||||
|
@service settings;
|
||||||
|
|
||||||
|
currencies = CURRENCIES;
|
||||||
|
|
||||||
|
@tracked stripeMonthlyAmount = null;
|
||||||
|
@tracked stripeYearlyAmount = null;
|
||||||
|
|
||||||
|
get stripePlans() {
|
||||||
|
const plans = this.settings.get('stripePlans') || [];
|
||||||
|
const monthly = plans.find(plan => plan.interval === 'month');
|
||||||
|
const yearly = plans.find(plan => plan.interval === 'year' && plan.name !== 'Complimentary');
|
||||||
|
|
||||||
|
return {
|
||||||
|
monthly: {
|
||||||
|
amount: (parseInt(monthly?.amount) || 0) / 100 || 5,
|
||||||
|
currency: monthly?.currency || this.currencies[0].value
|
||||||
|
},
|
||||||
|
yearly: {
|
||||||
|
amount: (parseInt(yearly?.amount) || 0) / 100 || 50,
|
||||||
|
currency: yearly?.currency || this.currencies[0].value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedCurrency() {
|
||||||
|
return this.currencies.findBy('value', this.stripePlans.monthly.currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
willDestroy() {
|
||||||
|
// clear any unsaved settings changes when going back/forward/closing
|
||||||
|
this.settings.rollbackAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
setStripePlansCurrency(event) {
|
||||||
|
const newCurrency = event.value;
|
||||||
|
|
||||||
|
const updatedPlans = this.settings.get('stripePlans').map((plan) => {
|
||||||
|
if (plan.name !== 'Complimentary') {
|
||||||
|
return Object.assign({}, plan, {
|
||||||
|
currency: newCurrency
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return plan;
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentComplimentaryPlan = updatedPlans.find((plan) => {
|
||||||
|
return plan.name === 'Complimentary' && plan.currency === event.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!currentComplimentaryPlan) {
|
||||||
|
updatedPlans.push({
|
||||||
|
name: 'Complimentary',
|
||||||
|
currency: event.value,
|
||||||
|
interval: 'year',
|
||||||
|
amount: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.settings.set('stripePlans', updatedPlans);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
toggleSelfSignup() {
|
||||||
|
this.settings.set('membersAllowFreeSignup', !this.settings.get('membersAllowFreeSignup'));
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
validateStripePlans() {
|
||||||
|
this.settings.errors.remove('stripePlans');
|
||||||
|
this.settings.hasValidated.removeObject('stripePlans');
|
||||||
|
|
||||||
|
if (this.stripeYearlyAmount === null) {
|
||||||
|
this.stripeYearlyAmount = this.stripePlans.yearly.amount;
|
||||||
|
}
|
||||||
|
if (this.stripeMonthlyAmount === null) {
|
||||||
|
this.stripeMonthlyAmount = this.stripePlans.monthly.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const selectedCurrency = this.selectedCurrency;
|
||||||
|
const yearlyAmount = parseInt(this.stripeYearlyAmount);
|
||||||
|
const monthlyAmount = parseInt(this.stripeMonthlyAmount);
|
||||||
|
if (!yearlyAmount || yearlyAmount < 1 || !monthlyAmount || monthlyAmount < 1) {
|
||||||
|
throw new TypeError(`Subscription amount must be at least ${selectedCurrency.symbol}1.00`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedPlans = this.settings.get('stripePlans').map((plan) => {
|
||||||
|
if (plan.name !== 'Complimentary') {
|
||||||
|
let newAmount;
|
||||||
|
if (plan.interval === 'year') {
|
||||||
|
newAmount = yearlyAmount * 100;
|
||||||
|
} else if (plan.interval === 'month') {
|
||||||
|
newAmount = monthlyAmount * 100;
|
||||||
|
}
|
||||||
|
return Object.assign({}, plan, {
|
||||||
|
amount: newAmount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return plan;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.settings.set('stripePlans', updatedPlans);
|
||||||
|
} catch (err) {
|
||||||
|
this.settings.errors.add('stripePlans', err.message);
|
||||||
|
} finally {
|
||||||
|
this.settings.hasValidated.pushObject('stripePlans');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@task
|
||||||
|
*saveAndContinue() {
|
||||||
|
yield this.validateStripePlans();
|
||||||
|
|
||||||
|
if (this.settings.errors.length > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield this.settings.save();
|
||||||
|
this.args.afterComplete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import {task} from 'ember-concurrency';
|
|||||||
const US = {flag: '🇺🇸', name: 'US', baseUrl: 'https://api.mailgun.net/v3'};
|
const US = {flag: '🇺🇸', name: 'US', baseUrl: 'https://api.mailgun.net/v3'};
|
||||||
const EU = {flag: '🇪🇺', name: 'EU', baseUrl: 'https://api.eu.mailgun.net/v3'};
|
const EU = {flag: '🇪🇺', name: 'EU', baseUrl: 'https://api.eu.mailgun.net/v3'};
|
||||||
|
|
||||||
const CURRENCIES = [
|
export const CURRENCIES = [
|
||||||
{
|
{
|
||||||
label: 'USD - US Dollar', value: 'usd', symbol: '$'
|
label: 'USD - US Dollar', value: 'usd', symbol: '$'
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user