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

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"}}
</button>
@ -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}}
/>
<span class="gh-input-append"><span class="ttu">{{this.currency}}</span>/month</span>
</div>
@ -162,70 +162,100 @@
{{#if this.isFreeProduct}}
<h4 class="gh-main-section-header small bn" data-test-tierpreview-title>Free Membership Preview</h4>
{{else}}
<h4 class="gh-main-section-header small bn" data-test-tierpreview-title>Tier Preview</h4>
{{/if}}
<div class="gh-main-section-content" style="border-color: {{this.settings.accentColor}}">
<span class="checkmark" style="background-color: {{this.settings.accentColor}}"></span>
{{#if this.product.name}}
<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 class="gh-product-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-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>

View file

@ -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();

View file

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

View file

@ -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"]`;