Alpha version of multiple products

- added Products grid component to signup page with static data
- separate signup design between single vs. multiple products
- alpha!
This commit is contained in:
Peter Zimon 2021-06-10 17:21:10 +02:00
parent 859043e22e
commit 5516e348af
7 changed files with 449 additions and 23 deletions

View File

@ -12,6 +12,7 @@ import {AccountPlanPageStyles} from './pages/AccountPlanPage';
import {InputFieldStyles} from './common/InputField';
import {SignupPageStyles} from './pages/SignupPage';
import {PlanSectionStyles} from './common/PlansSection';
import {ProductsSectionStyles} from './common/ProductsSection';
import {AvatarStyles} from './common/MemberGravatar';
import {MagicLinkStyles} from './pages/MagicLinkPage';
import {LinkPageStyles} from './pages/LinkPage';
@ -233,6 +234,10 @@ const FrameStyles = `
height: 100%;
}
.gh-portal-popup-wrapper.fullscreen {
padding: 0;
}
.gh-portal-popup-container {
outline: none;
position: relative;
@ -254,6 +259,16 @@ const FrameStyles = `
z-index: 9999;
}
.gh-portal-popup-container.fullscreen {
align-items: center;
width: 100% !important;
height: 100% !important;
border-radius: 0px;
box-shadow: none !important;
overflow-y: scroll;
padding-bottom: 6vmin;
}
@keyframes popup {
0% {
transform: scale(0.9) translateY(20px);
@ -303,6 +318,10 @@ const FrameStyles = `
margin: 0 6px 0 0;
}
.gh-portal-popup-wrapper.fullscreen .gh-portal-powered {
z-index: 10000;
}
.gh-portal-container-wide {
width: 440px;
}
@ -348,7 +367,12 @@ const FrameStyles = `
.gh-portal-content {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
}
.gh-portal-popup-container.fullscreen .gh-portal-content {
overflow-y: visible;
max-height: unset !important;
}
.gh-portal-popup-container footer {
padding: 0 32px 32px;
@ -371,6 +395,11 @@ const FrameStyles = `
transition: all 0.2s ease-in-out;
}
.gh-portal-popup-container.fullscreen .gh-portal-closeicon {
width: 24px;
height: 24px;
}
.gh-portal-closeicon:hover {
color: var(--grey5);
}
@ -675,6 +704,10 @@ const MobileStyles = `
.gh-portal-popup-wrapper.account-home .gh-portal-powered a {
box-shadow: none;
}
.gh-portal-popup-container.fullscreen {
padding: 0;
}
}
@media (max-width: 414px) {
@ -703,7 +736,7 @@ const MobileStyles = `
border-bottom: 1px solid var(--grey10);
}
.gh-portal-plan-checkbox {
div:not(.gh-portal-product-card-header) > .gh-portal-plan-checkbox {
grid-column: 1 / 2;
grid-row: 1 / 3;
margin: 0 12px;
@ -801,6 +834,7 @@ const FrameStyle =
AccountPlanPageStyles +
InputFieldStyles +
PlanSectionStyles +
ProductsSectionStyles +
SwitchStyles +
ActionButtonStyles +
BackButtonStyles +

View File

@ -4,7 +4,7 @@ import AppContext from '../AppContext';
import FrameStyle from './Frame.styles';
import Pages, {getActivePage} from '../pages';
import PopupNotification from './common/PopupNotification';
import {isCookiesDisabled, getSitePrices, isInviteOnlySite} from '../utils/helpers';
import {hasMultipleProducts, isCookiesDisabled, getSitePrices, isInviteOnlySite} from '../utils/helpers';
const React = require('react');
@ -165,6 +165,11 @@ class PopupContent extends React.Component {
pageClass = page;
break;
}
if (hasMultipleProducts({site}) && (page === 'signup' || page === 'signin')) {
pageClass += ' fullscreen';
}
const className = (hasMode(['preview', 'dev'], {customSiteUrl}) && !site.disableBackground) ? 'gh-portal-popup-container preview' : 'gh-portal-popup-container';
const containerClassName = `${className} ${popupWidthStyle} ${pageClass}`;
return (

View File

@ -454,7 +454,7 @@ function PlansSection({plans, showLabel = true, selectedPlan, onPlanSelect, chan
}
const className = getPlanClassNames({cookiesDisabled, changePlan, plans});
return (
<section>
<section className="gh-portal-plans">
<PlanLabel showLabel={showLabel} />
<div className={className}>
<PlanOptions plans={plans} onPlanSelect={onPlanSelect} selectedPlan={selectedPlan} changePlan={changePlan} />

View File

@ -0,0 +1,337 @@
import React from 'react';
import Switch from '../common/Switch';
import {isCookiesDisabled} from '../../utils/helpers';
export const ProductsSectionStyles = `
.gh-portal-products {
background: var(--grey13);
margin: 40px -32px;
padding: 0 32px;
}
.gh-portal-products-priceswitch {
display: flex;
justify-content: center;
padding-top: 32px;
}
.gh-portal-priceoption-label {
font-size: 1.3rem;
font-weight: 600;
letter-spacing: 0.3px;
text-transform: uppercase;
margin: 0 6px;
}
.gh-portal-products-priceswitch .gh-portal-for-switch label, .gh-portal-for-switch .container {
width: 43px !important;
}
.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 .container:hover input:not(:checked) + .input-toggle-component {
background: var(--grey1);
border-color: var(--grey1);
box-shadow: none;
width: 43px !important;
height: 24px !important;
}
.gh-portal-products-priceswitch .gh-portal-for-switch .input-toggle-component:before {
height: 18px !important;
width: 18px !important;
}
.gh-portal-products-priceswitch .gh-portal-for-switch input:checked + .input-toggle-component {
background: var(--grey1);
}
.gh-portal-products-priceswitch .gh-portal-for-switch input:checked + .input-toggle-component:before {
transform: translateX(19px);
}
.gh-portal-products-grid {
display: grid;
grid-template-columns: repeat(${productColumns()}, minmax(0, 1fr));
grid-gap: 32px;
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 32px 5vw;
}
.gh-portal-product-card {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
background: white;
padding: 24px 24px 18px;
border-radius: 3px;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
min-height: 240px;
cursor: pointer;
}
.gh-portal-product-card.checked::before {
position: absolute;
display: block;
top: -1px;
right: -1px;
bottom: -1px;
left: -1px;
content: "";
z-index: 999;
border: 2px solid var(--brandcolor);
pointer-events: none;
border-radius: 3px;
}
.gh-portal-product-card.checked {
box-shadow: none;
}
.gh-portal-product-card-header {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.gh-portal-product-name {
font-size: 1.2rem;
font-weight: 500;
line-height: 1.0em;
letter-spacing: 0.5px;
text-transform: uppercase;
margin-top: 7px;
text-align: center;
min-height: 24px;
word-break: break-word;
width: 100%;
border-bottom: 1px solid var(--grey12);
padding: 8px 0 16px;
margin-bottom: 12px;
}
.gh-portal-product-description {
font-size: 1.4rem;
line-height: 1.5em;
text-align: center;
color: var(--grey5);
margin-bottom: 24px;
}
.gh-portal-product-price {
display: flex;
}
.gh-portal-product-price .currency-sign {
align-self: flex-start;
font-size: 2.0rem;
font-weight: 500;
line-height: 1.3em;
}
.gh-portal-product-price .amount {
font-size: 3.3rem;
font-weight: 500;
line-height: 1em;
}
.gh-portal-product-price .billing-period {
align-self: flex-end;
font-size: 1.3rem;
line-height: 1.6em;
color: var(--grey7);
letter-spacing: 0.2px;
}
.gh-portal-product-alternative-price {
font-size: 1.15rem;
line-height: 1.6em;
color: var(--grey7);
text-align: center;
margin-top: 4px;
letter-spacing: 0.2px;
height: 18px;
}
@media (max-width: 480px) {
.gh-portal-products {
margin: 0 -32px;
}
.gh-portal-products-grid {
grid-template-columns: unset;
grid-gap: 20px;
padding: 32px 0;
}
.gh-portal-product-card {
display: grid;
grid-template-columns: 1fr auto;
grid-gap: 12px;
align-items: start;
min-height: unset;
padding: 20px;
}
.gh-portal-product-card-header {
grid-row: 1;
display: grid;
grid-template-columns: 20px auto;
}
.gh-portal-product-name {
margin: 0;
padding: 0;
text-align: left;
border-bottom: none;
min-height: unset;
}
.gh-portal-product-description {
grid-column: 2 / 3;
margin-bottom: 0px;
text-align: left;
}
.gh-portal-product-price {
position: relative;
}
.gh-portal-product-price .currency-sign {
font-size: 1.5rem;
}
.gh-portal-product-price .amount {
font-size: 2.6rem;
}
.gh-portal-product-price .billing-period {
position: absolute;
right: 0;
top: 24px;
font-size: 1.2rem;
}
.gh-portal-product-card-footer {
grid-row: 1;
}
.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;
}
.gh-portal-product-alternative-price {
display: none;
}
}
`;
function productColumns() {
const noOfProducts = 4;
return noOfProducts > 5 ? 5 : noOfProducts;
}
function Checkbox({name, id, onPlanSelect, isChecked, disabled = false}) {
if (isCookiesDisabled()) {
disabled = true;
}
return (
<div className='gh-portal-plan-checkbox'>
<input
name={name}
key={id}
type="checkbox"
checked={isChecked}
aria-label={name}
onChange={e => onPlanSelect(e, id)}
disabled={disabled}
/>
<span className='checkmark'></span>
</div>
);
}
function ProductsSection() {
return (
<section className="gh-portal-products">
<div className="gh-portal-products-priceswitch">
<span className="gh-portal-priceoption-label">Monthly</span>
<Switch onToggle='' checked={false} />
<span className="gh-portal-priceoption-label">Yearly</span>
</div>
<div className="gh-portal-products-grid">
<div className="gh-portal-product-card">
<div className="gh-portal-product-card-header">
<Checkbox name='x' id='x' isChecked={false} onPlanSelect='' />
<h4 className="gh-portal-product-name">Free</h4>
<div className="gh-portal-product-description">Free preview</div>
</div>
<div className="gh-portal-product-card-footer">
<div className="gh-portal-product-price">
<span className="currency-sign">$</span>
<span className="amount">0</span>
</div>
<div className="gh-portal-product-alternative-price"></div>
</div>
</div>
<div className="gh-portal-product-card checked">
<div className="gh-portal-product-card-header">
<Checkbox name='x' id='x' isChecked={true} onPlanSelect='' />
<h4 className="gh-portal-product-name">Bronze</h4>
<div className="gh-portal-product-description">Access to all members articles</div>
</div>
<div className="gh-portal-product-card-footer">
<div className="gh-portal-product-price">
<span className="currency-sign">$</span>
<span className="amount">70</span>
<span className="billing-period">/year</span>
</div>
<div className="gh-portal-product-alternative-price">$7/month</div>
</div>
</div>
<div className="gh-portal-product-card">
<div className="gh-portal-product-card-header">
<Checkbox name='x' id='x' isChecked={false} onPlanSelect='' />
<h4 className="gh-portal-product-name">Silver</h4>
<div className="gh-portal-product-description">Access to all members articles and weekly podcast</div>
</div>
<div className="gh-portal-product-card-footer">
<div className="gh-portal-product-price">
<span className="currency-sign">$</span>
<span className="amount">120</span>
<span className="billing-period">/year</span>
</div>
<div className="gh-portal-product-alternative-price">$12/month</div>
</div>
</div>
<div className="gh-portal-product-card">
<div className="gh-portal-product-card-header">
<Checkbox name='x' id='x' isChecked={false} onPlanSelect='' />
<h4 className="gh-portal-product-name">Gold</h4>
<div className="gh-portal-product-description">Access to all members articles, weekly podcast and exclusive interviews</div>
</div>
<div className="gh-portal-product-card-footer">
<div className="gh-portal-product-price">
<span className="currency-sign">$</span>
<span className="amount">1000</span>
<span className="billing-period">/year</span>
</div>
<div className="gh-portal-product-alternative-price">$12/month</div>
</div>
</div>
</div>
</section>
);
}
export default ProductsSection;

View File

@ -93,10 +93,6 @@ export const AccountHomePageStyles = `
opacity: 0.6;
}
.gh-portal-products:hover {
cursor: pointer;
}
.gh-portal-product-icon {
width: 52px;
margin-right: 12px;

View File

@ -2,6 +2,7 @@ import ActionButton from '../common/ActionButton';
import CloseButton from '../common/CloseButton';
import AppContext from '../../AppContext';
import PlansSection from '../common/PlansSection';
import ProductsSection from '../common/ProductsSection';
import InputForm from '../common/InputForm';
import {ValidateInputForm} from '../../utils/form';
import {getSitePrices, hasMultipleProducts, hasOnlyFreePlan, isInviteOnlySite} from '../../utils/helpers';
@ -51,6 +52,14 @@ export const SignupPageStyles = `
margin-bottom: 0;
}
.gh-portal-popup-container.fullscreen .gh-portal-signup-header {
margin-top: 6vmin;
}
.gh-portal-popup-container.fullscreen .gh-portal-signin-header {
margin-top: 22vmin;
}
.gh-portal-signup-message {
display: flex;
justify-content: center;
@ -83,6 +92,16 @@ export const SignupPageStyles = `
background-attachment: local,local,scroll,scroll;
}
.gh-portal-popup-container.fullscreen .gh-portal-content.signup,
.gh-portal-popup-container.fullscreen .gh-portal-content.signin {
width: 100%;
}
.gh-portal-popup-container.fullscreen .gh-portal-input-section {
max-width: 420px;
margin: 0 auto;
}
.gh-portal-content.signup.invite-only {
background: none;
}
@ -93,6 +112,17 @@ export const SignupPageStyles = `
height: 132px;
}
.gh-portal-popup-container.fullscreen footer.gh-portal-signup-footer,
.gh-portal-popup-container.fullscreen footer.gh-portal-signin-footer {
padding: 0;
width: 100%;
max-width: 420px;
}
.gh-portal-popup-container.fullscreen footer.gh-portal-signin-footer {
padding-top: 24px;
}
.gh-portal-content.signup,
.gh-portal-content.signin {
max-height: calc(100vh - 12vw - 140px);
@ -350,6 +380,14 @@ class SignupPage extends React.Component {
);
}
renderProducts() {
return (
<>
<ProductsSection />
</>
);
}
renderLoginMessage() {
const {brandColor, onAction} = this.context;
return (
@ -379,18 +417,34 @@ class SignupPage extends React.Component {
</section>
);
}
return (
<section>
<div className='gh-portal-section'>
<InputForm
fields={fields}
onChange={(e, field) => this.handleInputChange(e, field)}
onKeyDown={(e, field) => this.onKeyDown(e, field)}
/>
{this.renderPlans()}
</div>
</section>
);
if (hasMultipleProducts({site})) {
return (
<section>
<div className='gh-portal-section'>
<InputForm
fields={fields}
onChange={(e, field) => this.handleInputChange(e, field)}
onKeyDown={(e, field) => this.onKeyDown(e, field)}
/>
{this.renderProducts()}
</div>
</section>
);
} else {
return (
<section>
<div className='gh-portal-section'>
<InputForm
fields={fields}
onChange={(e, field) => this.handleInputChange(e, field)}
onKeyDown={(e, field) => this.onKeyDown(e, field)}
/>
{this.renderPlans()}
</div>
</section>
);
}
}
renderSiteLogo() {

View File

@ -85,8 +85,8 @@ const products = [
export const site = {
title: 'A Ghost site',
description: 'Thoughts, stories and ideas.',
logo: 'https://pbs.twimg.com/profile_images/1111773508231667713/mf2N0uqc_400x400.png',
icon: 'https://pbs.twimg.com/profile_images/1111773508231667713/mf2N0uqc_400x400.png',
logo: 'https://static.ghost.org/v4.0.0/images/ghost-orb-1.png',
icon: 'https://static.ghost.org/v4.0.0/images/ghost-orb-1.png',
accent_color: '#45C32E',
url: 'http://localhost:2368/',
plans: {
@ -96,7 +96,7 @@ export const site = {
},
products,
prices: prices,
allow_self_signup: true,
allow_self_signup: false,
members_signup_access: 'all',
free_price_name: 'Free',
free_price_description: 'Free preview',