diff --git a/ghost/admin/.lint-todo b/ghost/admin/.lint-todo index 3d5fe0ee58..6fb7a85743 100644 --- a/ghost/admin/.lint-todo +++ b/ghost/admin/.lint-todo @@ -1277,3 +1277,9 @@ remove|ember-template-lint|no-action|228|83|228|83|259aa927a526d3449245a05298d5c remove|ember-template-lint|no-action|229|123|229|123|259aa927a526d3449245a05298d5cb8b1961de4e|1648684800000|1651276800000|1653868800000|app/components/settings/members-email-labs.hbs remove|ember-template-lint|no-passed-in-event-handlers|192|48|192|48|a302b99c2a5682fd9186a16dce373578fe74a18b|1648684800000|1651276800000|1653868800000|app/components/settings/members-email-labs.hbs remove|ember-template-lint|no-passed-in-event-handlers|207|40|207|40|7ca43fc52964fe52050eacf376e788422c34cf7a|1648684800000|1651276800000|1653868800000|app/components/settings/members-email-labs.hbs +remove|ember-template-lint|no-unused-block-params|189|74|189|74|1ae2f6f18de70d2f2a881a48c048eb10ef38fe6d|1646611200000|1649199600000|1651791600000|app/components/modal-product.hbs +remove|ember-template-lint|style-concatenation|168|57|168|57|665a1558cb4618790daff51f9c99fcb62948b414|1646611200000|1649199600000|1651791600000|app/components/modal-product.hbs +remove|ember-template-lint|no-action|240|71|240|71|141d456b03124abca146e58e4ae15825fdd040bb|1646611200000|1649199600000|1651791600000|app/components/modal-product.hbs +remove|ember-template-lint|no-action|242|8|242|8|d465b362b15b90cf42a093e72895155f49cdf6f2|1646611200000|1649199600000|1651791600000|app/components/modal-product.hbs +remove|ember-template-lint|no-down-event-binding|242|41|242|41|b3e87879e52edc06bb07f1ad0becc6ec762bbb39|1646611200000|1649199600000|1651791600000|app/components/modal-product.hbs +remove|ember-template-lint|no-action|86|51|86|51|682c80ff5163c8e4a2bea89f2066dfd1248b5889|1646611200000|1649199600000|1651791600000|app/components/modal-product.hbs diff --git a/ghost/admin/app/components/modal-product.hbs b/ghost/admin/app/components/modal-product.hbs index 179283a367..c8c2689c41 100644 --- a/ghost/admin/app/components/modal-product.hbs +++ b/ghost/admin/app/components/modal-product.hbs @@ -1,4 +1,4 @@ - @@ -83,7 +83,7 @@ @value={{readonly this.stripeMonthlyAmount}} @type="number" @input={{action (mut this.stripeMonthlyAmount) value="target.value"}} - @focus-out={{action "validateStripePlans"}} + @focus-out={{this.validateStripePlans}} /> {{this.currency}}/month @@ -162,70 +162,100 @@ {{#if this.isFreeProduct}}

Free Membership Preview

{{else}} -

Tier Preview

- {{/if}} -
- - - {{#if this.product.name}} -

{{this.product.name}}

- {{else}} -

Bronze

- {{/if}} - - {{#if this.product.description}} -

{{this.product.description}}

- {{else}} - {{#if this.isFreeProduct}} -

Free preview of {{this.settings.title}}

- {{else}} -

Full access to premium content

- {{/if}} - {{/if}} - - {{#if this.benefits}} - - {{else}} - - {{/if}} -
- {{#if this.isFreeProduct}} - - {{currency-symbol this.currency}} - 0 - - {{else}} - {{#if this.stripeMonthlyAmount}} - - {{currency-symbol this.currency}} - {{format-number this.stripeMonthlyAmount}} - /month - - {{else}} - - {{currency-symbol this.currency}} - 0 - /month - - {{/if}} - - {{#if this.stripeYearlyAmount}} - {{currency-symbol this.currency}}{{format-number this.stripeYearlyAmount}}/year - {{else}} - 0{{this.currency}}/year - {{/if}} - {{/if}} +
+

Tier Preview

+
+ + +
+ {{/if}} +
+
+ {{#if this.product.name}} +

{{this.product.name}}

+ {{else}} +

Bronze

+ {{/if}} + +
+ {{#if this.isFreeProduct}} +
+ {{currency-symbol this.currency}} + 0 +
+ {{else}} + {{#if this.stripeYearlyAmount}} +
+ {{currency-symbol this.currency}} + {{#if (eq this.previewCadence "monthly")}} + + {{format-number this.stripeMonthlyAmount}} + + /month + {{else}} + + {{format-number this.stripeYearlyAmount}} + + /year + {{/if}} +
+ {{#if (and (eq this.previewCadence "yearly") (gt this.discountValue 0))}} + + + {{this.discountValue}}% discount + {{/if}} + {{else}} +
+ {{currency-symbol this.currency}} + 0 + /year +
+ {{/if}} + + {{/if}} +
+
+ +
+
+ {{#if this.product.description}} +
{{this.product.description}}
+ {{else}} + {{#if this.isFreeProduct}} +
Free preview of {{this.settings.title}}
+ {{else}} +
Full access to premium content
+ {{/if}} + {{/if}} + + {{#if this.benefits}} +
+ {{#each this.benefits as |benefitItem|}} +
{{svg-jar "check-2"}} +
{{benefitItem.name}}
+
+ {{/each}} +
+ {{else}} +
+ {{#if this.isFreeProduct}} +
{{svg-jar "check-2"}} +
+ Access to all public posts +
+
+ {{else}} +
+ {{svg-jar "check-2"}} +
Expert analysis
+
+ {{/if}} +
+ {{/if}} +
+
+
diff --git a/ghost/admin/app/components/modal-product.js b/ghost/admin/app/components/modal-product.js index e9e0f280e0..a644785687 100644 --- a/ghost/admin/app/components/modal-product.js +++ b/ghost/admin/app/components/modal-product.js @@ -4,6 +4,7 @@ import ProductBenefitItem from '../models/product-benefit-item'; import classic from 'ember-classic-decorator'; import {currencies, getCurrencyOptions, getSymbol} from 'ghost-admin/utils/currency'; import {A as emberA} from '@ember/array'; +import {htmlSafe} from '@ember/template'; import {isEmpty} from '@ember/utils'; import {inject as service} from '@ember/service'; import {task} from 'ember-concurrency'; @@ -33,6 +34,10 @@ export default class ModalProductPrice extends ModalBase { @tracked benefits = emberA([]); @tracked newBenefit = null; @tracked welcomePageURL; + @tracked previewCadence = 'yearly'; + @tracked discountValue = 0; + + accentColorStyle = ''; confirm() {} @@ -76,6 +81,9 @@ export default class ModalProductPrice extends ModalBase { isNew: true, name: '' }); + this.calculateDiscount(); + + this.accentColorStyle = htmlSafe(`color: ${this.settings.get('accentColor')}`); } @action @@ -203,6 +211,33 @@ export default class ModalProductPrice extends ModalBase { this.newBenefit = ProductBenefitItem.create({isNew: true, name: ''}); } + calculateDiscount() { + const discount = this.stripeMonthlyAmount ? 100 - Math.floor((this.stripeYearlyAmount / 12 * 100) / this.stripeMonthlyAmount) : 0; + this.discountValue = discount > 0 ? discount : 0; + } + + @action + changeCadence(cadence) { + this.previewCadence = cadence; + } + + @action + validateStripePlans() { + this.calculateDiscount(); + 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; + } + } + actions = { addBenefit(item) { return item.validate().then(() => { @@ -240,20 +275,6 @@ export default class ModalProductPrice extends ModalBase { const newCurrency = event.value; this.currency = newCurrency; }, - 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; - } - }, // needed because ModalBase uses .send() for keyboard events closeModal() { this.close(); diff --git a/ghost/admin/app/styles/layouts/products.css b/ghost/admin/app/styles/layouts/products.css index 85a9674dbf..6adca3d030 100644 --- a/ghost/admin/app/styles/layouts/products.css +++ b/ghost/admin/app/styles/layouts/products.css @@ -181,7 +181,6 @@ line-height: 1; margin-right: 2px; font-size: 2.2rem; - font-weight: 500; letter-spacing: 0.1px; } @@ -190,7 +189,6 @@ position: relative; top: 2px; font-size: 1.4rem; - font-weight: 500; letter-spacing: 0.4px; line-height: 1; } @@ -436,7 +434,7 @@ /* Add/edit product modal */ .fullscreen-modal-edit-product { - max-width: 900px; + max-width: 1080px; } .gh-product-modal-content { @@ -448,7 +446,7 @@ .gh-form-edit-product .gh-main-section { margin-bottom: 32px; - grid-template-columns: 1fr 0.9fr 1.1fr; + grid-template-columns: 1fr 0.8fr 1.2fr; } .gh-form-edit-product .gh-main-section-block { @@ -568,66 +566,109 @@ } .gh-product-form-tierpreview .gh-main-section-content { - display: flex; - flex-direction: column; - align-items: center; - background: #fff; - border: 2px solid var(--blackgrey); - border-radius: 4px; - padding: 24px; -} - -.gh-product-form-tierpreview .gh-main-section-content .checkmark { + flex: 1; + max-width: 420px; + min-width: 320px; position: relative; - background-color: var(--blackgrey); - border-radius: 999px; - height: 18px; - width: 18px; -} - -.gh-product-form-tierpreview .gh-main-section-content .checkmark::after { - display: block; - position: absolute; - content: ""; - left: 6.5px; - top: 2.5px; - width: 5px; - height: 11px; - border: solid #fff; - border-width: 0 2px 2px 0; - transform: rotate(45deg); -} - -.gh-product-form-tierpreview .gh-main-section-content h4 { - font-size: 1.3rem; - font-weight: 500; - line-height: 1.45em; - letter-spacing: 0.5px; - text-transform: uppercase; - margin-top: 7px; - text-align: center; - min-height: 24px; - word-break: break-word; - color: #333; -} - -.gh-product-form-tierpreview .gh-main-section-content p { - font-size: 1.35rem; - font-weight: 500; - line-height: 1.45em; - text-align: center; - color: #474747; - padding: 16px 0; - margin: 8px 0 20px; - width: 100%; - border-top: 1px solid #eaeaea; - border-bottom: 1px solid #eaeaea; -} - -.gh-product-form-tierpreview .price { display: flex; flex-direction: column; - align-items: center; + align-items: flex-start; + justify-content: stretch; + background: white; + padding: 32px; + border-radius: 7px; + border: 1px solid #e1e1e1; + min-height: 200px; + letter-spacing: normal; +} + +.gh-portal-product-card-header { + width: 100%; + min-height: 56px; +} + +.gh-product-form-tierpreview .gh-main-section-content .gh-portal-product-name { + font-size: 1.8rem; + font-weight: 600; + line-height: 1.3em; + letter-spacing: 0px; + margin-top: -4px; + margin-bottom: 0; + word-break: break-word; + width: 100%; +} + +.gh-product-form-tierpreview .gh-main-section-content .gh-portal-product-description { + font-size: 1.55rem; + font-weight: 600; + line-height: 1.4em; + width: 100%; + margin-top: 16px; + color: #3d3d3d; +} + +.gh-portal-product-card-pricecontainer { + display: flex; + flex-direction: row; + align-items: flex-end; + justify-content: space-between; + flex-wrap: wrap; + row-gap: 10px; + column-gap: 4px; + width: 100%; + margin-top: 16px; +} + +.gh-portal-product-price { + display: flex; + justify-content: center; + color: #1d1d1d; +} + +.gh-portal-product-card-details { + flex: 1; + display: flex; + flex-direction: column; + width: 100%; +} + +.gh-portal-product-card-detaildata { + flex: 1; +} + +.gh-portal-product-price .amount { + font-size: 3.4rem; + font-weight: 700; + line-height: 1em; + letter-spacing: -1.3px; +} + +.gh-product-form-tierpreivew-cadence { + display: flex; + align-items: baseline; + justify-content: space-between; +} + +.gh-product-form-tierpreivew-cadence .gh-btn, +.gh-product-form-tierpreivew-cadence .gh-btn span { + background: transparent !important; + padding: 0; + line-height: 1em; + height: auto; + font-size: 1.3rem; + color: var(--midlightgrey); + margin-left: 4px; + font-weight: 400; + overflow: unset; +} + +.gh-product-form-tierpreivew-cadence .gh-btn:hover span { + color: var(--middarkgrey); +} + +.gh-product-form-tierpreivew-cadence .gh-btn.selected span { + font-weight: 500; + color: var(--darkgrey); } .gh-product-form-tierpreview .monthly-price { @@ -639,67 +680,84 @@ color: #3d3d3d; } -.gh-product-form-tierpreview .monthly-price .period { - font-size: 1.3rem; - line-height: 1.6em; - color: #515151; - letter-spacing: 0.3px; - margin-left: 2px; -} - -.gh-product-form-tierpreview .yearly-price { - font-size: 1.2rem; - line-height: 1.6em; - color: #aeaeae; - text-align: center; - margin-top: 4px; - letter-spacing: 0.3px; - height: 18px; - margin-top: -1px; -} - -.gh-product-form-tierpreview .currency { +.gh-product-form-tierpreview .currency-sign { + align-self: flex-start; + font-size: 2.7rem; + font-weight: 700; + line-height: 1.115em; text-transform: uppercase; } -.gh-product-form-tierpreview .monthly-price .currency { - align-self: flex-start; - font-size: 2.0rem; - font-weight: 500; - line-height: 1.3em; +.gh-product-form-tierpreview .billing-period { + align-self: flex-end; + font-size: 1.5rem; + line-height: 1.4em; + color: #686868; + letter-spacing: 0.3px; + margin-left: 5px; + font-weight: 400; } -.gh-product-form-tierpreview ul { - list-style: none; - margin: 0; - padding: 0; +.gh-portal-discount-label { + position: relative; + font-size: 1.25rem; + line-height: 1em; + font-weight: 600; + letter-spacing: 0.3px; + color: #1d1d1d; + padding: 6px 9px; + text-align: center; + white-space: nowrap; + border-radius: 999px; + margin-right: -4px; + margin-top: -4px; + max-height: 24.5px; +} + +.gh-portal-discount-label span { + position: absolute; + content: ""; + display: block; + top: 0; + right: 0; + bottom: 0; + left: 0; + border-radius: 999px; + opacity: 0.2; +} + +.gh-product-form-tierpreview .gh-portal-product-benefits { + font-size: 1.5rem; + line-height: 1.4em; width: 100%; - min-height: 100px; - margin-bottom: 12px; + margin-top: 16px; } -.gh-product-form-tierpreview li { +.gh-product-form-tierpreview .gh-portal-product-benefit { display: flex; align-items: flex-start; - font-size: 1.3rem; - line-height: 1.45em; - color: #686868; - margin: 0 0 12px; - padding: 0; + margin-bottom: 10px; + color: #3d3d3d; } -.gh-product-form-tierpreview li svg { - width: 13px; - height: 13px; - min-width: 12px; - margin: 3px 6px 0 0; +.gh-portal-product-benefit svg { + width: 14px; + height: 14px; + min-width: 14px; + margin: 3px 10px 0 0; overflow: visible; } -.gh-product-form-tierpreview li svg path { +.gh-product-form-tierpreview .gh-portal-product-benefit polyline, +.gh-product-form-tierpreview .gh-portal-product-benefit g, +.gh-product-form-tierpreview .gh-portal-product-benefit path { stroke-width: 3px; } +.gh-product-form-tierpreview .gh-portal-benefit-title { + letter-spacing: normal; +} + .gh-product-form-tierpreview .placeholder { opacity: 0.35; } \ No newline at end of file diff --git a/ghost/admin/tests/acceptance/settings/membership-test.js b/ghost/admin/tests/acceptance/settings/membership-test.js index 4a5ad6d664..1290cac709 100644 --- a/ghost/admin/tests/acceptance/settings/membership-test.js +++ b/ghost/admin/tests/acceptance/settings/membership-test.js @@ -260,19 +260,19 @@ describe('Acceptance: Settings - Membership', function () { await click(`${newBenefit} [data-test-button="add-benefit"]`); expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.contain.text('Second benefit'); - expect(findAll(`${modal} [data-test-tierpreview-benefits] li`).length).to.equal(2); + expect(findAll(`${modal} [data-test-tierpreview-benefits] div`).length).to.equal(4); await click(`${modal} [data-test-benefit-item="0"] [data-test-button="delete-benefit"]`); expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.not.contain.text('First benefit'); - expect(findAll(`${modal} [data-test-tierpreview-benefits] li`).length).to.equal(1); + expect(findAll(`${modal} [data-test-tierpreview-benefits] div`).length).to.equal(2); // Add a new benefit that we will later rename to an empty name await fillIn(`${newBenefit} [data-test-input="benefit-label"]`, 'Third benefit'); await click(`${newBenefit} [data-test-button="add-benefit"]`); expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.contain.text('Third benefit'); - expect(findAll(`${modal} [data-test-tierpreview-benefits] li`).length).to.equal(2); + expect(findAll(`${modal} [data-test-tierpreview-benefits] div`).length).to.equal(4); // Clear the second benefit's name (it should get removed after saving) const secondBenefitItem = `${modal} [data-test-benefit-item="1"]`;