Ghost/ghost/admin/app/components/modal-tier.hbs
Hakim Razalan e376ef0242
🐛 Fixed Hidden error in "Add tier" modal (#15361)
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)
2022-09-13 09:23:54 +01:00

388 lines
24 KiB
Handlebars
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>