mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-23 19:02:29 +03:00
e376ef0242
closes https://github.com/TryGhost/Ghost/issues/15290 - Capture error from model errors - Add hasValidated for name property to properly mark field as error/success - Add property to hasValidated after each failed validation - Wrap saving on try-catch to suppress uncaught exception (validation error)
388 lines
24 KiB
Handlebars
388 lines
24 KiB
Handlebars
<button class="close" href title="Close" type="button" {{on "click" this.closeModal}}>
|
||
{{svg-jar "close"}}
|
||
</button>
|
||
|
||
<div class="gh-tier-modal-content" data-test-modal="edit-tier">
|
||
<header class="modal-header">
|
||
<h1 data-test-text="title">{{this.title}}</h1>
|
||
</header>
|
||
|
||
<form>
|
||
<div class="modal-body gh-form-edit-tier gh-tier-settings">
|
||
<div class="gh-main-section columns-3">
|
||
<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-tier-priceform-block">
|
||
{{#unless this.isFreeTier}}
|
||
<GhFormGroup @errors={{this.tier.errors}} @hasValidated={{this.tier.hasValidated}} @property="name" data-test-formgroup="name">
|
||
<label for="name" class="fw6">Name</label>
|
||
<GhTextInput
|
||
@value={{readonly this.tier.name}}
|
||
@input={{action (mut this.tier.name) value="target.value"}}
|
||
@name="name"
|
||
@autofocus={{true}}
|
||
@placeholder="Bronze"
|
||
@id="name"
|
||
@class="gh-input"
|
||
data-test-input="tier-name" />
|
||
<GhErrorMessage @errors={{this.tier.errors}} @property="name" />
|
||
</GhFormGroup>
|
||
{{/unless}}
|
||
|
||
<GhFormGroup @errors={{this.tier.errors}} @property="description" data-test-formgroup="description">
|
||
<label for="description" class="fw6">Description</label>
|
||
{{#if this.isFreeTier}}
|
||
<GhTextInput
|
||
@value={{readonly this.tier.description}}
|
||
@input={{action (mut this.tier.description) value="target.value"}}
|
||
@name="description"
|
||
@placeholder="Free preview of {{this.settings.title}}"
|
||
@id="description"
|
||
@class="gh-input"
|
||
data-test-input="free-tier-description" />
|
||
{{else}}
|
||
<GhTextInput
|
||
@value={{readonly this.tier.description}}
|
||
@input={{action (mut this.tier.description) value="target.value"}}
|
||
@name="description"
|
||
@placeholder="Full access to premium content"
|
||
@id="description"
|
||
@class="gh-input"
|
||
data-test-input="tier-description" />
|
||
{{/if}}
|
||
<GhErrorMessage @errors={{this.tier.errors}} @property="description" />
|
||
</GhFormGroup>
|
||
|
||
{{#unless this.isFreeTier}}
|
||
{{#if (feature 'freeTrial')}}
|
||
<div class="gh-settings-members-pricetrialcont">
|
||
<GhFormGroup @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="prices" data-test-formgroup="prices">
|
||
<div class="gh-settings-members-pricelabelcont free-trial-enabled">
|
||
<label for="monthlyPrice" class="fw6">Prices</label>
|
||
<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 class="gh-setting-members-prices free-trial-enabled">
|
||
|
||
<div class="gh-input-group">
|
||
<GhTextInput
|
||
@id="monthlyPrice"
|
||
@value={{readonly this.stripeMonthlyAmount}}
|
||
@type="number"
|
||
@input={{action (mut this.stripeMonthlyAmount) value="target.value"}}
|
||
@focus-out={{this.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>
|
||
{{#if this.stripePlanError}}
|
||
<p class="response w-100"><span class="red">{{this.stripePlanError}}</span></p>
|
||
{{/if}}
|
||
</GhFormGroup>
|
||
|
||
<GhFormGroup @errors={{this.tier.errors}} @property="trialDays" data-test-formgroup="trialDays">
|
||
<div class="flex justify-between items-center mb2">
|
||
<div>
|
||
<h4 class="gh-tier-setting-title">Add a free trial</h4>
|
||
</div>
|
||
<div class="for-switch small">
|
||
<label class="switch" for="free-trial">
|
||
<Input
|
||
@checked={{this.freeTrialEnabled}}
|
||
@type="checkbox"
|
||
id="free-trial"
|
||
name="free-trial"
|
||
{{on "click" this.setFreeTrialEnabled}}
|
||
/>
|
||
<span class="input-toggle-component"></span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="gh-input-group {{unless this.freeTrialEnabled "gh-input-group-tier-trial-disabled"}}">
|
||
<GhTextInput
|
||
@value={{readonly this.tier.trialDays}}
|
||
@type="number"
|
||
{{on "input" this.setTrialDays}}
|
||
@name="trial"
|
||
@id="trial"
|
||
@class="gh-input"
|
||
@disabled={{unless this.freeTrialEnabled "true"}}
|
||
data-test-input="tier-trial" />
|
||
<span class="gh-input-append">days</span>
|
||
</div>
|
||
<p>Members will be subscribed at full price once the trial ends. <a class="trial-docs-link" href="https://ghost.org/help/free-trials">Learn more</a></p>
|
||
{{#if this.hasTrialDaysError}}
|
||
<p class="response w-100"><span class="red">Free trial must be at least 1 day</span></p>
|
||
{{/if}}
|
||
</GhFormGroup>
|
||
</div>
|
||
{{else}}
|
||
<GhFormGroup @errors={{this.settings.errors}} @hasValidated={{this.settings.hasValidated}} @property="prices" data-test-formgroup="prices">
|
||
<div class="gh-settings-members-pricelabelcont">
|
||
<label for="monthlyPrice" class="fw6">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 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={{this.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>
|
||
{{#if this.stripePlanError}}
|
||
<p class="response w-100"><span class="red">{{this.stripePlanError}}</span></p>
|
||
{{/if}}
|
||
</GhFormGroup>
|
||
{{/if}}
|
||
|
||
<GhFormGroup>
|
||
<label for="welcomePage" class="fw6">Welcome page</label>
|
||
<GhUrlInput
|
||
@id="welcomePage"
|
||
@value={{readonly this.model.tier.welcomePageURL}}
|
||
@baseUrl={{readonly this.siteUrl}}
|
||
@setResult={{this.setWelcomePageURL}}
|
||
@validateUrl={{this.validateWelcomePageURL}}
|
||
@placeholder={{readonly this.siteUrl}}
|
||
/>
|
||
{{#if this.isFreeTier}}
|
||
<p>Redirect to this URL after signup for a free membership</p>
|
||
{{else}}
|
||
<p>Redirect to this URL after signup for premium membership</p>
|
||
{{/if}}
|
||
</GhFormGroup>
|
||
{{/unless}}
|
||
</div>
|
||
|
||
<h4 class="gh-main-section-header small bn">Benefits</h4>
|
||
<div class="gh-main-section-content grey gh-tier-form-benefits">
|
||
<div class="gh-tier-benefits">
|
||
<div class="gh-blognav">
|
||
<SortableObjects
|
||
@sortableObjectList={{this.benefits}}
|
||
@useSwap={{false}}
|
||
@sortEndAction={{action "reorderItems"}}
|
||
>
|
||
{{#each this.benefits as |benefitItem index|}}
|
||
<DraggableObject @content={{benefitItem}} @dragHandle=".gh-blognav-grab" @isSortable={{true}}>
|
||
<GhBenefitItem
|
||
@benefitItem={{benefitItem}}
|
||
@addItem={{action "addBenefit"}}
|
||
@focusItem={{action "focusItem"}}
|
||
@deleteItem={{action "deleteBenefit"}}
|
||
@updateLabel={{action "updateLabel"}}
|
||
data-test-benefit-item={{index}} />
|
||
</DraggableObject>
|
||
{{/each}}
|
||
</SortableObjects>
|
||
<GhBenefitItem
|
||
@isFreeTier={{this.isFreeTier}}
|
||
@benefitItem={{this.newBenefit}}
|
||
@addItem={{action "addBenefit"}}
|
||
@deleteItem={{action "deleteBenefit"}}
|
||
@updateLabel={{action "updateLabel"}}
|
||
data-test-benefit-item="new" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="gh-main-section-block gh-tier-form-tierpreview" data-test-tierpreview>
|
||
<div class="gh-tier-form-tierpreview-content">
|
||
{{#if this.isFreeTier}}
|
||
<h4 class="gh-main-section-header small bn" data-test-tierpreview-title>Free Membership Preview</h4>
|
||
{{else}}
|
||
<div class="gh-tier-form-tierpreivew-cadence">
|
||
<h4 class="gh-main-section-header small bn" data-test-tierpreview-title>Tier Preview</h4>
|
||
<div>
|
||
<button class="gh-btn {{if (eq this.previewCadence "monthly") "selected"}}" type="button" {{on "click" (fn this.changeCadence "monthly")}}><span>Monthly</span></button>
|
||
<button class="gh-btn {{if (eq this.previewCadence "yearly") "selected"}}" type="button" {{on "click" (fn this.changeCadence "yearly")}}><span>Yearly</span></button>
|
||
</div>
|
||
</div>
|
||
{{/if}}
|
||
<div class="gh-main-section-content">
|
||
<div class="gh-portal-tier-card-header">
|
||
{{#if this.tier.name}}
|
||
<h4 class="gh-portal-tier-name" data-test-tierpreview-name style={{this.accentColorStyle}}>{{this.tier.name}}</h4>
|
||
{{else}}
|
||
<h4 class="placeholder gh-portal-tier-name" style={{this.accentColorStyle}} data-test-tierpreview-name>Bronze</h4>
|
||
{{/if}}
|
||
|
||
<div class="gh-portal-tier-card-pricecontainer" data-test-tierpreview-price>
|
||
{{#if this.isFreeTier}}
|
||
<div class="gh-portal-tier-price">
|
||
<span class="currency-sign">{{currency-symbol this.currency}}</span>
|
||
<span class="amount">0</span>
|
||
</div>
|
||
{{else}}
|
||
{{#if this.stripeYearlyAmount}}
|
||
<div class="gh-portal-tier-price">
|
||
<span class="currency-sign">{{currency-symbol this.currency}}</span>
|
||
{{#if (eq this.previewCadence "monthly")}}
|
||
<span class="amount">
|
||
{{format-number this.stripeMonthlyAmount}}
|
||
</span>
|
||
<span class="billing-period">/month</span>
|
||
{{else}}
|
||
<span class="amount">
|
||
{{format-number this.stripeYearlyAmount}}
|
||
</span>
|
||
<span class="billing-period">/year</span>
|
||
{{/if}}
|
||
</div>
|
||
{{#if (feature 'freeTrial')}}
|
||
{{#if this.isFreeTrialEnabled}}
|
||
<span class="gh-portal-discount-label">
|
||
<span style="background-color: {{this.settings.accentColor}}"></span>
|
||
{{ this.tier.trialDays }} days free</span>
|
||
{{/if}}
|
||
{{else}}
|
||
{{#if (and (eq this.previewCadence "yearly") (gt this.discountValue 0))}}
|
||
<span class="gh-portal-discount-label">
|
||
<span style="background-color: {{this.settings.accentColor}}"></span>
|
||
{{this.discountValue}}% discount
|
||
</span>
|
||
{{/if}}
|
||
{{/if}}
|
||
{{else}}
|
||
<div class="gh-portal-tier-price placeholder">
|
||
<span class="currency-sign">{{currency-symbol this.currency}}</span>
|
||
<span class="amount">0</span>
|
||
<span class="billing-period">/year</span>
|
||
</div>
|
||
{{/if}}
|
||
{{/if}}
|
||
</div>
|
||
{{#if (feature 'freeTrial')}}
|
||
{{#if (and (eq this.previewCadence "yearly") (gt this.discountValue 0))}}
|
||
<span class="gh-portal-discount-label-trial" style={{this.accentColorStyle}}>
|
||
{{this.discountValue}}% discount
|
||
</span>
|
||
{{/if}}
|
||
{{/if}}
|
||
</div>
|
||
|
||
<div class="gh-portal-tier-card-details">
|
||
<div class="gh-portal-tier-card-detaildata">
|
||
{{#if this.tier.description}}
|
||
<div class="gh-portal-tier-description" data-test-tierpreview-description>{{this.tier.description}}</div>
|
||
{{else}}
|
||
{{#if this.isFreeTier}}
|
||
<div class="placeholder gh-portal-tier-description" data-test-tierpreview-description>Free preview of {{this.settings.title}}</div>
|
||
{{else}}
|
||
<div class="placeholder gh-portal-tier-description" data-test-tierpreview-description>Full access to premium content</div>
|
||
{{/if}}
|
||
{{/if}}
|
||
|
||
{{#if this.benefits}}
|
||
<div class="gh-portal-tier-benefits" data-test-tierpreview-benefits>
|
||
{{#each this.benefits as |benefitItem|}}
|
||
<div class="gh-portal-tier-benefit">{{svg-jar "check-2"}}
|
||
<div class="gh-portal-benefit-title">{{benefitItem.name}}</div>
|
||
</div>
|
||
{{/each}}
|
||
</div>
|
||
{{else}}
|
||
<div class="placeholder gh-portal-tier-benefits" data-test-tierpreview-benefits>
|
||
{{#if this.isFreeTier}}
|
||
<div class="gh-portal-tier-benefit">{{svg-jar "check-2"}}
|
||
<div class="gh-portal-benefit-title">
|
||
Access to all public posts
|
||
</div>
|
||
</div>
|
||
{{else}}
|
||
<div class="gh-portal-tier-benefit">
|
||
{{svg-jar "check-2"}}
|
||
<div class="gh-portal-benefit-title">Expert analysis</div>
|
||
</div>
|
||
{{/if}}
|
||
</div>
|
||
{{/if}}
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="modal-footer top-shadow items-center">
|
||
<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="{{if this.isExistingTier "Save" "Add tier"}}"
|
||
@successText={{this.successText}}
|
||
@task={{this.saveTier}}
|
||
@idleClass="gh-btn-primary"
|
||
@class="gh-btn {{if this.isExistingTier "gh-btn-black" "gh-btn-green"}} gh-btn-icon"
|
||
data-test-button="save-tier" />
|
||
</div>
|