mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-17 23:44:39 -05:00
Added new component for membership plans
refs https://github.com/TryGhost/members.js/issues/10 Create common plans section UI component as its used in multiple places in app
This commit is contained in:
parent
9edb3d2e58
commit
886ee7e9ec
2 changed files with 188 additions and 0 deletions
148
ghost/portal/src/components/common/PlansSection.js
Normal file
148
ghost/portal/src/components/common/PlansSection.js
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Styles = ({style = {}}) => {
|
||||||
|
return {
|
||||||
|
input: {
|
||||||
|
display: 'block',
|
||||||
|
padding: '0 .6em',
|
||||||
|
width: '100%',
|
||||||
|
height: '44px',
|
||||||
|
outline: '0',
|
||||||
|
border: '1px solid #c5d2d9',
|
||||||
|
color: 'inherit',
|
||||||
|
textDecoration: 'none',
|
||||||
|
background: '#fff',
|
||||||
|
borderRadius: '9px',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginBottom: '12px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
...(style.input || {}) // Override any custom style
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
marginBottom: '3px',
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: '700',
|
||||||
|
...(style.label || {}) // Override any custom style
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function Checkbox({name, onPlanSelect, isChecked}) {
|
||||||
|
const style = {
|
||||||
|
width: '20px',
|
||||||
|
height: '20px',
|
||||||
|
border: 'solid 1px #cccccc'
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
name={name}
|
||||||
|
key={name}
|
||||||
|
type="checkbox"
|
||||||
|
checked={isChecked}
|
||||||
|
aria-label={name}
|
||||||
|
style={style}
|
||||||
|
onChange={e => onPlanSelect(e, name)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PriceLabel({name, currency, price}) {
|
||||||
|
if (name === 'Free') {
|
||||||
|
return (
|
||||||
|
<strong style={{
|
||||||
|
fontSize: '11px',
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: '18px',
|
||||||
|
color: '#929292',
|
||||||
|
fontWeight: 'normal'
|
||||||
|
}}> Access free members-only posts </strong>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const type = name === 'Monthly' ? 'month' : 'year';
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: 'inline',
|
||||||
|
verticalAlign: 'baseline'
|
||||||
|
}}>
|
||||||
|
<span style={{fontSize: '14px', color: '#929292', fontWeight: 'normal'}}> {currency} </span>
|
||||||
|
<strong style={{fontSize: '21px'}}> {price} </strong>
|
||||||
|
<span style={{fontSize: '12px', color: '#929292', fontWeight: 'normal'}}> {` / ${type}`}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PlanOptions({plans, selectedPlan, onPlanSelect}) {
|
||||||
|
const nameStyle = {
|
||||||
|
fontSize: '13px',
|
||||||
|
fontWeight: '500',
|
||||||
|
display: 'flex',
|
||||||
|
color: '#343F44',
|
||||||
|
justifyContent: 'center'
|
||||||
|
};
|
||||||
|
|
||||||
|
const priceStyle = {
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginBottom: '9px'
|
||||||
|
};
|
||||||
|
const checkboxStyle = {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center'
|
||||||
|
};
|
||||||
|
const boxStyle = ({isLast = false}) => {
|
||||||
|
const style = {
|
||||||
|
padding: '12px 12px',
|
||||||
|
flexBasis: '100%'
|
||||||
|
};
|
||||||
|
if (!isLast) {
|
||||||
|
style.borderRight = '1px solid #c5d2d9';
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
};
|
||||||
|
|
||||||
|
return plans.map(({name, currency, price}, i) => {
|
||||||
|
const isLast = i === plans.length - 1;
|
||||||
|
const isChecked = selectedPlan === name;
|
||||||
|
return (
|
||||||
|
<div style={boxStyle({isLast})} key={name} onClick={e => onPlanSelect(e, name)}>
|
||||||
|
<div style={checkboxStyle}>
|
||||||
|
<Checkbox name={name} isChecked={isChecked} onPlanSelect={onPlanSelect} />
|
||||||
|
</div>
|
||||||
|
<div style={nameStyle}> {name.toUpperCase()} </div>
|
||||||
|
<div style={priceStyle}>
|
||||||
|
<PriceLabel name={name} currency={currency} price={price} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function PlansSection({plans, selectedPlan, onPlanSelect, style}) {
|
||||||
|
const containerStyle = {
|
||||||
|
display: 'flex',
|
||||||
|
border: '1px solid #c5d2d9',
|
||||||
|
borderRadius: '9px',
|
||||||
|
marginBottom: '12px'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!plans || plans.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div style={{width: '100%'}}>
|
||||||
|
<label style={{marginBottom: '3px', fontSize: '12px', fontWeight: '700'}}> Plan </label>
|
||||||
|
<div style={containerStyle}>
|
||||||
|
<PlanOptions plans={[
|
||||||
|
{type: 'free', price: 'Decide later', name: 'Free'},
|
||||||
|
{type: 'month', price: plans.monthly, currency: plans.currency_symbol, name: 'Monthly'},
|
||||||
|
{type: 'year', price: plans.yearly, currency: plans.currency_symbol, name: 'Yearly'}
|
||||||
|
]} onPlanSelect={onPlanSelect} selectedPlan={selectedPlan} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PlansSection;
|
40
ghost/portal/src/components/common/PlansSection.test.js
Normal file
40
ghost/portal/src/components/common/PlansSection.test.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {render, fireEvent} from '@testing-library/react';
|
||||||
|
import PlansSection from './PlansSection';
|
||||||
|
|
||||||
|
const setup = (overrides = {}) => {
|
||||||
|
const mockOnPlanSelectFn = jest.fn();
|
||||||
|
const props = {
|
||||||
|
plans: {
|
||||||
|
monthly: 12,
|
||||||
|
yearly: 110,
|
||||||
|
currency: 'USD',
|
||||||
|
currency_symbol: '$'
|
||||||
|
},
|
||||||
|
selectedPlan: 'Monthly',
|
||||||
|
onPlanSelect: mockOnPlanSelectFn
|
||||||
|
};
|
||||||
|
const utils = render(
|
||||||
|
<PlansSection {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const freeCheckboxEl = utils.getByLabelText('Free');
|
||||||
|
const monthlyCheckboxEl = utils.getByLabelText('Monthly');
|
||||||
|
const yearlyCheckboxEl = utils.getByLabelText('Yearly');
|
||||||
|
return {
|
||||||
|
freeCheckboxEl,
|
||||||
|
monthlyCheckboxEl,
|
||||||
|
yearlyCheckboxEl,
|
||||||
|
mockOnPlanSelectFn,
|
||||||
|
...utils
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('InputField', () => {
|
||||||
|
test('renders', () => {
|
||||||
|
const {freeCheckboxEl, monthlyCheckboxEl, yearlyCheckboxEl} = setup();
|
||||||
|
expect(freeCheckboxEl).toBeInTheDocument();
|
||||||
|
expect(monthlyCheckboxEl).toBeInTheDocument();
|
||||||
|
expect(yearlyCheckboxEl).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Reference in a new issue