diff --git a/ghost/portal/src/components/common/ProductsSection.js b/ghost/portal/src/components/common/ProductsSection.js
index 95d6abe8f5..9594af28cc 100644
--- a/ghost/portal/src/components/common/ProductsSection.js
+++ b/ghost/portal/src/components/common/ProductsSection.js
@@ -1,6 +1,7 @@
-import React from 'react';
+import React, {useContext, useState} from 'react';
import Switch from '../common/Switch';
-import {isCookiesDisabled} from '../../utils/helpers';
+import {getCurrencySymbol, getPriceString, getProducts, getStripeAmount, isCookiesDisabled} from '../../utils/helpers';
+import AppContext from '../../AppContext';
export const ProductsSectionStyles = `
.gh-portal-products {
@@ -28,7 +29,7 @@ export const ProductsSectionStyles = `
}
.gh-portal-products-priceswitch .gh-portal-for-switch .input-toggle-component,
- .gh-portal-products-priceswitch .gh-portal-for-switch label:hover input:not(:checked) + .input-toggle-component,
+ .gh-portal-products-priceswitch .gh-portal-for-switch label:hover input:not(:checked) + .input-toggle-component,
.gh-portal-products-priceswitch .gh-portal-for-switch .container:hover input:not(:checked) + .input-toggle-component {
background: var(--grey1);
border-color: var(--grey1);
@@ -60,7 +61,7 @@ export const ProductsSectionStyles = `
padding: 32px 5vw;
}
-
+
.gh-portal-product-card {
position: relative;
display: flex;
@@ -222,7 +223,7 @@ export const ProductsSectionStyles = `
grid-row: 1;
}
- .gh-portal-popup-container.fullscreen footer.gh-portal-signup-footer,
+ .gh-portal-popup-container.fullscreen footer.gh-portal-signup-footer,
.gh-portal-popup-container.fullscreen footer.gh-portal-signin-footer {
padding: 0 32px !important;
margin-top: 32px;
@@ -234,12 +235,18 @@ export const ProductsSectionStyles = `
}
`;
+const ProductsContext = React.createContext({
+ selectedInterval: 'month',
+ selectedProduct: 'free',
+ setSelectedProduct: null
+});
+
function productColumns() {
const noOfProducts = 4;
return noOfProducts > 5 ? 5 : noOfProducts;
}
-function Checkbox({name, id, onPlanSelect, isChecked, disabled = false}) {
+function Checkbox({name, id, onProductSelect, isChecked, disabled = false}) {
if (isCookiesDisabled()) {
disabled = true;
}
@@ -250,87 +257,169 @@ function Checkbox({name, id, onPlanSelect, isChecked, disabled = false}) {
key={id}
type="checkbox"
checked={isChecked}
+ onChange={(e) => {
+ onProductSelect(e, id);
+ }}
aria-label={name}
- onChange={e => onPlanSelect(e, id)}
disabled={disabled}
/>
-
+ {
+ if (!disabled) {
+ onProductSelect(e, id);
+ }
+ }}>
+
+ );
+}
+
+function ProductCardFooter({product}) {
+ const {selectedInterval} = useContext(ProductsContext);
+ const monthlyPrice = product.monthlyPrice;
+ const yearlyPrice = product.yearlyPrice;
+ const activePrice = selectedInterval === 'month' ? monthlyPrice : yearlyPrice;
+ const alternatePrice = selectedInterval === 'month' ? yearlyPrice : monthlyPrice;
+ if (!monthlyPrice || !yearlyPrice) {
+ return null;
+ }
+ return (
+
+
+ {getCurrencySymbol(activePrice.currency)}
+ {getStripeAmount(activePrice.amount)}
+ /{activePrice.interval}
+
+
{getPriceString(alternatePrice)}
+
+ );
+}
+
+function ProductCard({product}) {
+ const {selectedProduct, setSelectedProduct} = useContext(ProductsContext);
+
+ return (
+
+
+
{
+ setSelectedProduct(product.id);
+ }} />
+ {product.name}
+ {product.description}
+
+
+
+ );
+}
+
+function ProductCards({products}) {
+ return products.map((product) => {
+ return (
+
+ );
+ });
+}
+
+function DummyProductCards({products}) {
+ return (
+ <>
+
+
+
+
Bronze
+
Access to all members articles
+
+
+
+ $
+ 70
+ /year
+
+
$7/month
+
+
+
+
+
+
Silver
+
Access to all members articles and weekly podcast
+
+
+
+ $
+ 120
+ /year
+
+
$12/month
+
+
+
+
+
+
Gold
+
Access to all members articles, weekly podcast and exclusive interviews
+
+
+
+ $
+ 1000
+ /year
+
+
$12/month
+
+
+ >
+ );
+}
+
+function FreeProductCard() {
+ const {selectedProduct, setSelectedProduct} = useContext(ProductsContext);
+ return (
+
+
+
{
+ setSelectedProduct('free');
+ }} />
+ Free
+ Free preview
+
+
);
}
function ProductsSection() {
+ const {site} = useContext(AppContext);
+ const products = getProducts({site});
+ const [selectedInterval, setSelectedInterval] = useState('month');
+ const [selectedProduct, setSelectedProduct] = useState('free');
+ const checked = selectedInterval === 'year';
return (
-
+
+
+
+ Monthly
+ {
+ const interval = selectedInterval === 'month' ? 'year' : 'month';
+ setSelectedInterval(interval);
+ }} checked={checked} />
+ Yearly
+
-
- Monthly
-
- Yearly
-
-
-
-
-
-
-
Free
-
Free preview
-
-
+
-
-
-
-
Bronze
-
Access to all members articles
-
-
-
- $
- 70
- /year
-
-
$7/month
-
-
-
-
-
-
Silver
-
Access to all members articles and weekly podcast
-
-
-
- $
- 120
- /year
-
-
$12/month
-
-
-
-
-
-
Gold
-
Access to all members articles, weekly podcast and exclusive interviews
-
-
-
- $
- 1000
- /year
-
-
$12/month
-
-
-
-
+
+
);
}
diff --git a/ghost/portal/src/components/common/Switch.js b/ghost/portal/src/components/common/Switch.js
index 8b8a5197a1..19b2881962 100644
--- a/ghost/portal/src/components/common/Switch.js
+++ b/ghost/portal/src/components/common/Switch.js
@@ -74,7 +74,7 @@ export const SwitchStyles = `
}
`;
-function Switch({id, label, onToggle, checked = false}) {
+function Switch({id, label='', onToggle, checked = false}) {
const {action} = useContext(AppContext);
const [isChecked, setIsChecked] = useState(checked);
const isActionChanged = ['updateNewsletter:failed', 'updateNewsletter:success'].includes(action);
diff --git a/ghost/portal/src/utils/helpers.js b/ghost/portal/src/utils/helpers.js
index cb69d3cf11..2dfe35ebf2 100644
--- a/ghost/portal/src/utils/helpers.js
+++ b/ghost/portal/src/utils/helpers.js
@@ -316,6 +316,19 @@ export const getCurrencySymbol = (currency) => {
return Intl.NumberFormat('en', {currency, style: 'currency'}).format(0).replace(/[\d\s.]/g, '');
};
+export const getStripeAmount = (amount) => {
+ if (isNaN(amount)) {
+ return 0;
+ }
+ return (amount / 100);
+};
+
+export const getPriceString = (price = {}) => {
+ const symbol = getCurrencySymbol(price.currency);
+ const amount = getStripeAmount(price.amount);
+ return `${symbol}${amount}/${price.interval}`;
+};
+
export const formatNumber = (amount) => {
if (amount === undefined || amount === null) {
return '';