0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-17 23:44:39 -05:00

Updated Portal mock for offers (#2319)

Updated the preview block of offer creation screen to match the new style of Portal.
This commit is contained in:
Peter Zimon 2022-04-01 13:22:41 +02:00 committed by GitHub
parent 0023695a29
commit 626634246f
5 changed files with 301 additions and 186 deletions

View file

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

View file

@ -1,4 +1,4 @@
<button class="close" href title="Close" type="button" {{action "closeModal"}} {{action (optional this.noop) on="mouseDown"}}> <button class="close" href title="Close" type="button" {{on "click" this.closeModal}}>
{{svg-jar "close"}} {{svg-jar "close"}}
</button> </button>
@ -83,7 +83,7 @@
@value={{readonly this.stripeMonthlyAmount}} @value={{readonly this.stripeMonthlyAmount}}
@type="number" @type="number"
@input={{action (mut this.stripeMonthlyAmount) value="target.value"}} @input={{action (mut this.stripeMonthlyAmount) value="target.value"}}
@focus-out={{action "validateStripePlans"}} @focus-out={{this.validateStripePlans}}
/> />
<span class="gh-input-append"><span class="ttu">{{this.currency}}</span>/month</span> <span class="gh-input-append"><span class="ttu">{{this.currency}}</span>/month</span>
</div> </div>
@ -162,70 +162,100 @@
{{#if this.isFreeProduct}} {{#if this.isFreeProduct}}
<h4 class="gh-main-section-header small bn" data-test-tierpreview-title>Free Membership Preview</h4> <h4 class="gh-main-section-header small bn" data-test-tierpreview-title>Free Membership Preview</h4>
{{else}} {{else}}
<h4 class="gh-main-section-header small bn" data-test-tierpreview-title>Tier Preview</h4> <div class="gh-product-form-tierpreivew-cadence">
{{/if}} <h4 class="gh-main-section-header small bn" data-test-tierpreview-title>Tier Preview</h4>
<div class="gh-main-section-content" style="border-color: {{this.settings.accentColor}}"> <div>
<span class="checkmark" style="background-color: {{this.settings.accentColor}}"></span> <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>
{{#if this.product.name}} </div>
<h4 data-test-tierpreview-name>{{this.product.name}}</h4>
{{else}}
<h4 class="placeholder" data-test-tierpreview-name>Bronze</h4>
{{/if}}
{{#if this.product.description}}
<p data-test-tierpreview-description>{{this.product.description}}</p>
{{else}}
{{#if this.isFreeProduct}}
<p class="placeholder" data-test-tierpreview-description>Free preview of {{this.settings.title}}</p>
{{else}}
<p class="placeholder" data-test-tierpreview-description>Full access to premium content</p>
{{/if}}
{{/if}}
{{#if this.benefits}}
<ul data-test-tierpreview-benefits>
{{#each this.benefits as |benefitItem index|}}
<li>{{svg-jar "check-2"}} <span>{{benefitItem.name}}</span></li>
{{/each}}
</ul>
{{else}}
<ul class="placeholder" data-test-tierpreview-benefits>
{{#if this.isFreeProduct}}
<li>{{svg-jar "check-2"}} <span>Access to all public posts</span></li>
{{else}}
<li>{{svg-jar "check-2"}} <span>Expert analysis</span></li>
{{/if}}
</ul>
{{/if}}
<div class="price" data-test-tierpreview-price>
{{#if this.isFreeProduct}}
<span class="monthly-price">
<span class="currency">{{currency-symbol this.currency}}</span>
0
</span>
{{else}}
{{#if this.stripeMonthlyAmount}}
<span class="monthly-price">
<span class="currency">{{currency-symbol this.currency}}</span>
{{format-number this.stripeMonthlyAmount}}
<span class="period">/month</span>
</span>
{{else}}
<span class="monthly-price placeholder">
<span class="currency">{{currency-symbol this.currency}}</span>
0
<span class="period">/month</span>
</span>
{{/if}}
{{#if this.stripeYearlyAmount}}
<span class="yearly-price">{{currency-symbol this.currency}}{{format-number this.stripeYearlyAmount}}/year</span>
{{else}}
<span class="yearly-price placeholder">0<span class="currency">{{this.currency}}</span>/year</span>
{{/if}}
{{/if}}
</div> </div>
{{/if}}
<div class="gh-main-section-content">
<div class="gh-portal-product-card-header">
{{#if this.product.name}}
<h4 class="gh-portal-product-name" data-test-tierpreview-name style={{this.accentColorStyle}}>{{this.product.name}}</h4>
{{else}}
<h4 class="placeholder gh-portal-product-name" style={{this.accentColorStyle}} data-test-tierpreview-name>Bronze</h4>
{{/if}}
<div class="gh-portal-product-card-pricecontainer" data-test-tierpreview-price>
{{#if this.isFreeProduct}}
<div class="gh-portal-product-price">
<span class="currency-sign">{{currency-symbol this.currency}}</span>
<span class="amount">0</span>
</div>
{{else}}
{{#if this.stripeYearlyAmount}}
<div class="gh-portal-product-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 (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}}
{{else}}
<div class="gh-portal-product-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>
</div>
<div class="gh-portal-product-card-details">
<div class="gh-portal-product-card-detaildata">
{{#if this.product.description}}
<div class="gh-portal-product-description" data-test-tierpreview-description>{{this.product.description}}</div>
{{else}}
{{#if this.isFreeProduct}}
<div class="placeholder gh-portal-product-description" data-test-tierpreview-description>Free preview of {{this.settings.title}}</div>
{{else}}
<div class="placeholder gh-portal-product-description" data-test-tierpreview-description>Full access to premium content</div>
{{/if}}
{{/if}}
{{#if this.benefits}}
<div class="gh-portal-product-benefits" data-test-tierpreview-benefits>
{{#each this.benefits as |benefitItem|}}
<div class="gh-portal-product-benefit">{{svg-jar "check-2"}}
<div class="gh-portal-benefit-title">{{benefitItem.name}}</div>
</div>
{{/each}}
</div>
{{else}}
<div class="placeholder gh-portal-product-benefits" data-test-tierpreview-benefits>
{{#if this.isFreeProduct}}
<div class="gh-portal-product-benefit">{{svg-jar "check-2"}}
<div class="gh-portal-benefit-title">
Access to all public posts
</div>
</div>
{{else}}
<div class="gh-portal-product-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> </div>

View file

@ -4,6 +4,7 @@ import ProductBenefitItem from '../models/product-benefit-item';
import classic from 'ember-classic-decorator'; import classic from 'ember-classic-decorator';
import {currencies, getCurrencyOptions, getSymbol} from 'ghost-admin/utils/currency'; import {currencies, getCurrencyOptions, getSymbol} from 'ghost-admin/utils/currency';
import {A as emberA} from '@ember/array'; import {A as emberA} from '@ember/array';
import {htmlSafe} from '@ember/template';
import {isEmpty} from '@ember/utils'; import {isEmpty} from '@ember/utils';
import {inject as service} from '@ember/service'; import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency'; import {task} from 'ember-concurrency';
@ -33,6 +34,10 @@ export default class ModalProductPrice extends ModalBase {
@tracked benefits = emberA([]); @tracked benefits = emberA([]);
@tracked newBenefit = null; @tracked newBenefit = null;
@tracked welcomePageURL; @tracked welcomePageURL;
@tracked previewCadence = 'yearly';
@tracked discountValue = 0;
accentColorStyle = '';
confirm() {} confirm() {}
@ -76,6 +81,9 @@ export default class ModalProductPrice extends ModalBase {
isNew: true, isNew: true,
name: '' name: ''
}); });
this.calculateDiscount();
this.accentColorStyle = htmlSafe(`color: ${this.settings.get('accentColor')}`);
} }
@action @action
@ -203,6 +211,33 @@ export default class ModalProductPrice extends ModalBase {
this.newBenefit = ProductBenefitItem.create({isNew: true, name: ''}); 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 = { actions = {
addBenefit(item) { addBenefit(item) {
return item.validate().then(() => { return item.validate().then(() => {
@ -240,20 +275,6 @@ export default class ModalProductPrice extends ModalBase {
const newCurrency = event.value; const newCurrency = event.value;
this.currency = newCurrency; 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 // needed because ModalBase uses .send() for keyboard events
closeModal() { closeModal() {
this.close(); this.close();

View file

@ -181,7 +181,6 @@
line-height: 1; line-height: 1;
margin-right: 2px; margin-right: 2px;
font-size: 2.2rem; font-size: 2.2rem;
font-weight: 500;
letter-spacing: 0.1px; letter-spacing: 0.1px;
} }
@ -190,7 +189,6 @@
position: relative; position: relative;
top: 2px; top: 2px;
font-size: 1.4rem; font-size: 1.4rem;
font-weight: 500;
letter-spacing: 0.4px; letter-spacing: 0.4px;
line-height: 1; line-height: 1;
} }
@ -436,7 +434,7 @@
/* Add/edit product modal */ /* Add/edit product modal */
.fullscreen-modal-edit-product { .fullscreen-modal-edit-product {
max-width: 900px; max-width: 1080px;
} }
.gh-product-modal-content { .gh-product-modal-content {
@ -448,7 +446,7 @@
.gh-form-edit-product .gh-main-section { .gh-form-edit-product .gh-main-section {
margin-bottom: 32px; 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 { .gh-form-edit-product .gh-main-section-block {
@ -568,66 +566,109 @@
} }
.gh-product-form-tierpreview .gh-main-section-content { .gh-product-form-tierpreview .gh-main-section-content {
display: flex; flex: 1;
flex-direction: column; max-width: 420px;
align-items: center; min-width: 320px;
background: #fff;
border: 2px solid var(--blackgrey);
border-radius: 4px;
padding: 24px;
}
.gh-product-form-tierpreview .gh-main-section-content .checkmark {
position: relative; 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; display: flex;
flex-direction: column; 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 { .gh-product-form-tierpreview .monthly-price {
@ -639,67 +680,84 @@
color: #3d3d3d; color: #3d3d3d;
} }
.gh-product-form-tierpreview .monthly-price .period { .gh-product-form-tierpreview .currency-sign {
font-size: 1.3rem; align-self: flex-start;
line-height: 1.6em; font-size: 2.7rem;
color: #515151; font-weight: 700;
letter-spacing: 0.3px; line-height: 1.115em;
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 {
text-transform: uppercase; text-transform: uppercase;
} }
.gh-product-form-tierpreview .monthly-price .currency { .gh-product-form-tierpreview .billing-period {
align-self: flex-start; align-self: flex-end;
font-size: 2.0rem; font-size: 1.5rem;
font-weight: 500; line-height: 1.4em;
line-height: 1.3em; color: #686868;
letter-spacing: 0.3px;
margin-left: 5px;
font-weight: 400;
} }
.gh-product-form-tierpreview ul { .gh-portal-discount-label {
list-style: none; position: relative;
margin: 0; font-size: 1.25rem;
padding: 0; 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%; width: 100%;
min-height: 100px; margin-top: 16px;
margin-bottom: 12px;
} }
.gh-product-form-tierpreview li { .gh-product-form-tierpreview .gh-portal-product-benefit {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
font-size: 1.3rem; margin-bottom: 10px;
line-height: 1.45em; color: #3d3d3d;
color: #686868;
margin: 0 0 12px;
padding: 0;
} }
.gh-product-form-tierpreview li svg { .gh-portal-product-benefit svg {
width: 13px; width: 14px;
height: 13px; height: 14px;
min-width: 12px; min-width: 14px;
margin: 3px 6px 0 0; margin: 3px 10px 0 0;
overflow: visible; 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; stroke-width: 3px;
} }
.gh-product-form-tierpreview .gh-portal-benefit-title {
letter-spacing: normal;
}
.gh-product-form-tierpreview .placeholder { .gh-product-form-tierpreview .placeholder {
opacity: 0.35; opacity: 0.35;
} }

View file

@ -260,19 +260,19 @@ describe('Acceptance: Settings - Membership', function () {
await click(`${newBenefit} [data-test-button="add-benefit"]`); await click(`${newBenefit} [data-test-button="add-benefit"]`);
expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.contain.text('Second 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"]`); 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(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 // Add a new benefit that we will later rename to an empty name
await fillIn(`${newBenefit} [data-test-input="benefit-label"]`, 'Third benefit'); await fillIn(`${newBenefit} [data-test-input="benefit-label"]`, 'Third benefit');
await click(`${newBenefit} [data-test-button="add-benefit"]`); await click(`${newBenefit} [data-test-button="add-benefit"]`);
expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.contain.text('Third 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) // Clear the second benefit's name (it should get removed after saving)
const secondBenefitItem = `${modal} [data-test-benefit-item="1"]`; const secondBenefitItem = `${modal} [data-test-benefit-item="1"]`;