mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-01 02:41:39 -05:00
Refactored UI structure and component naming
refs https://github.com/TryGhost/members.js/issues/10 - Added `/page` in components to structure all UI page flows for the app - Renamed components to highlight just actual purpose/utility - Cleaned up data loading flow
This commit is contained in:
parent
b50abb5ad2
commit
275655584e
17 changed files with 649 additions and 330 deletions
|
@ -1,31 +0,0 @@
|
|||
const React = require('react');
|
||||
|
||||
export default class MagicLinkPage extends React.Component {
|
||||
renderFormHeader() {
|
||||
return (
|
||||
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: '12px'}}>
|
||||
<div style={{fontSize: '18px', fontWeight: 'bold'}}> Awesome! </div>
|
||||
<div> We just sent you a login link, check your Inbox! </div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderLoginMessage() {
|
||||
return (
|
||||
<div style={{display: 'flex', justifyContent: 'center'}}>
|
||||
<div style={{color: '#3db0ef', fontWeight: 'bold', cursor: 'pointer'}} onClick={() => this.props.switchPage('signin')}> Back to Log in </div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{display: 'flex', flexDirection: 'column', color: '#313131'}}>
|
||||
<div style={{paddingLeft: '16px', paddingRight: '16px', paddingTop: '12px'}}>
|
||||
{this.renderFormHeader()}
|
||||
{this.renderLoginMessage()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import TriggerComponent from './TriggerComponent';
|
||||
import PopupMenuComponent from './PopupMenuComponent';
|
||||
import TriggerButton from './TriggerButton';
|
||||
import PopupMenu from './PopupMenu';
|
||||
import PopupModal from './PopupModal';
|
||||
import * as Fixtures from '../test/fixtures/data';
|
||||
const setupMembersApi = require('../utils/api');
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
|
@ -10,25 +12,27 @@ export default class ParentContainer extends React.Component {
|
|||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
console.log('Initialized script with data', props.data);
|
||||
|
||||
this.initialize();
|
||||
|
||||
this.state = {
|
||||
page: 'loading',
|
||||
page: 'magiclink',
|
||||
showPopup: false,
|
||||
action: {
|
||||
name: 'loading'
|
||||
}
|
||||
};
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Initialize site and members data
|
||||
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
initialize() {
|
||||
// Setup custom trigger button handling
|
||||
this.setupCustomTriggerButton();
|
||||
|
||||
// Initialize site and members data
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
async loadData() {
|
||||
|
@ -37,17 +41,16 @@ export default class ParentContainer extends React.Component {
|
|||
const siteUrl = window.location.origin;
|
||||
this.MembersAPI = setupMembersApi({siteUrl, adminUrl});
|
||||
try {
|
||||
// const {site} = await this.MembersAPI.getSiteData();
|
||||
// const member = await this.MembersAPI.getMemberData();
|
||||
const [{site}, member] = await Promise.all([this.MembersAPI.getSiteData(), this.MembersAPI.getMemberData()]);
|
||||
console.log('Setting state with', site, member);
|
||||
console.log('Initialized Members.js with', site, member);
|
||||
this.setState({
|
||||
site,
|
||||
member,
|
||||
page: member ? 'signedin' : 'signup',
|
||||
page: member ? 'accountHome' : 'signup',
|
||||
action: 'init:success'
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Failed state fetch', e);
|
||||
this.setState({
|
||||
action: {
|
||||
name: 'init:failed'
|
||||
|
@ -57,8 +60,9 @@ export default class ParentContainer extends React.Component {
|
|||
}
|
||||
|
||||
getData() {
|
||||
const member = this.state.member;
|
||||
const site = this.state.site;
|
||||
const member = process.env.REACT_APP_ADMIN_URL ? Fixtures.member.free : this.state.member;
|
||||
const site = process.env.REACT_APP_ADMIN_URL ? Fixtures.site : this.state.site;
|
||||
|
||||
return {site, member};
|
||||
}
|
||||
|
||||
|
@ -91,6 +95,10 @@ export default class ParentContainer extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
getBrandColor() {
|
||||
return this.getData().site && this.getData().site.brand && this.getData().site.brand.primaryColor;
|
||||
}
|
||||
|
||||
async onAction(action, data) {
|
||||
this.setState({
|
||||
action: {
|
||||
|
@ -101,7 +109,11 @@ export default class ParentContainer extends React.Component {
|
|||
}
|
||||
});
|
||||
try {
|
||||
if (action === 'signout') {
|
||||
if (action === 'closePopup') {
|
||||
this.setState({
|
||||
showPopup: false
|
||||
});
|
||||
} else if (action === 'signout') {
|
||||
await this.MembersAPI.signout();
|
||||
|
||||
this.setState({
|
||||
|
@ -128,7 +140,6 @@ export default class ParentContainer extends React.Component {
|
|||
if (action === 'checkoutPlan') {
|
||||
const checkoutSuccessUrl = (new URL('/account/?stripe=billing-update-success', window.location.href)).href;
|
||||
const checkoutCancelUrl = (new URL('/account/?stripe=billing-update-cancel', window.location.href)).href;
|
||||
console.log('Checkout URLs', checkoutSuccessUrl, checkoutCancelUrl);
|
||||
const {plan} = data;
|
||||
await this.MembersAPI.checkoutPlan({
|
||||
plan,
|
||||
|
@ -156,27 +167,43 @@ export default class ParentContainer extends React.Component {
|
|||
|
||||
renderPopupMenu() {
|
||||
if (this.state.showPopup) {
|
||||
if (this.state.page === 'accountHome') {
|
||||
return (
|
||||
<PopupMenu
|
||||
data={this.getData()}
|
||||
action={this.state.action}
|
||||
onToggle= {e => this.onTriggerToggle()}
|
||||
page={this.state.page}
|
||||
switchPage={page => this.switchPage(page)}
|
||||
onAction={(action, data) => this.onAction(action, data)}
|
||||
brandColor = {this.getBrandColor()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<PopupMenuComponent
|
||||
<PopupModal
|
||||
data={this.getData()}
|
||||
action={this.state.action}
|
||||
onToggle= {e => this.onTriggerToggle()}
|
||||
page={this.state.page}
|
||||
switchPage={page => this.switchPage(page)}
|
||||
onAction={(action, data) => this.onAction(action, data)}
|
||||
brandColor = {this.getBrandColor()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderTriggerComponent() {
|
||||
renderTriggerButton() {
|
||||
if (!this.customTriggerButton) {
|
||||
return (
|
||||
<TriggerComponent
|
||||
<TriggerButton
|
||||
name={this.props.name}
|
||||
onToggle= {e => this.onTriggerToggle()}
|
||||
isPopupOpen={this.state.showPopup}
|
||||
data={this.getData()}
|
||||
brandColor = {this.getBrandColor()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -186,10 +213,10 @@ export default class ParentContainer extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
{this.renderPopupMenu()}
|
||||
{this.renderTriggerComponent()}
|
||||
</div>
|
||||
{this.renderTriggerButton()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import FrameComponent from './FrameComponent';
|
||||
import SignupPage from './SignupPage';
|
||||
import SigninPage from './SigninPage';
|
||||
import SignedInPage from './SignedInPage';
|
||||
import MagicLinkPage from './MagicLinkPage';
|
||||
import LoadingPage from './LoadingPage';
|
||||
import Frame from './Frame';
|
||||
import SigninPage from './pages/SigninPage';
|
||||
import SignupPage from './pages/SignupPage';
|
||||
import AccountHomePage from './pages/AccountHomePage';
|
||||
import MagicLinkPage from './pages/MagicLinkPage';
|
||||
import LoadingPage from './pages/LoadingPage';
|
||||
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
|
@ -35,10 +35,10 @@ const Styles = {
|
|||
minHeight: '400px',
|
||||
maxHeight: '460px'
|
||||
},
|
||||
signedin: {
|
||||
width: '380px',
|
||||
minHeight: '350px',
|
||||
maxHeight: '410px'
|
||||
accountHome: {
|
||||
width: '280px',
|
||||
minHeight: '200px',
|
||||
maxHeight: '240px'
|
||||
},
|
||||
magiclink: {
|
||||
width: '400px',
|
||||
|
@ -80,12 +80,12 @@ const Styles = {
|
|||
const Pages = {
|
||||
signin: SigninPage,
|
||||
signup: SignupPage,
|
||||
signedin: SignedInPage,
|
||||
accountHome: AccountHomePage,
|
||||
magiclink: MagicLinkPage,
|
||||
loading: LoadingPage
|
||||
};
|
||||
|
||||
export default class PopupMenuComponent extends React.Component {
|
||||
export default class PopupMenu extends React.Component {
|
||||
static propTypes = {
|
||||
data: PropTypes.shape({
|
||||
site: PropTypes.shape({
|
||||
|
@ -103,6 +103,7 @@ export default class PopupMenuComponent extends React.Component {
|
|||
|
||||
renderCurrentPage(page) {
|
||||
const PageComponent = Pages[page];
|
||||
|
||||
return (
|
||||
<PageComponent
|
||||
data={this.props.data}
|
||||
|
@ -131,9 +132,9 @@ export default class PopupMenuComponent extends React.Component {
|
|||
};
|
||||
|
||||
return (
|
||||
<FrameComponent style={frameStyle} title="membersjs-popup">
|
||||
<Frame style={frameStyle} title="membersjs-popup">
|
||||
{this.renderPopupContent()}
|
||||
</FrameComponent>
|
||||
</Frame>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import React from 'react';
|
||||
import {render} from '@testing-library/react';
|
||||
import PopupMenuComponent from './PopupMenuComponent';
|
||||
import PopupMenu from './PopupMenu';
|
||||
import {site} from '../test/fixtures/data';
|
||||
|
||||
describe('PopupMenuComponentTest', () => {
|
||||
describe('Popup Menu', () => {
|
||||
test('renders', () => {
|
||||
const {getByTitle} = render(
|
||||
<PopupMenuComponent data={{site}} page='signin' action={{}} onAction={() => {}} />
|
||||
<PopupMenu data={{site}} page='signin' action={{}} onAction={() => {}} />
|
||||
);
|
||||
const popupFrame = getByTitle('membersjs-popup');
|
||||
|
168
ghost/portal/src/components/PopupModal.js
Normal file
168
ghost/portal/src/components/PopupModal.js
Normal file
|
@ -0,0 +1,168 @@
|
|||
import Frame from './Frame';
|
||||
import SigninPage from './pages/SigninPage';
|
||||
import SignupPage from './pages/SignupPage';
|
||||
import AccountHomePage from './pages/AccountHomePage';
|
||||
import MagicLinkPage from './pages/MagicLinkPage';
|
||||
import LoadingPage from './pages/LoadingPage';
|
||||
import {ReactComponent as CloseIcon} from '../images/icons/close.svg';
|
||||
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
|
||||
const Styles = {
|
||||
modalContainer: {
|
||||
zIndex: '1000',
|
||||
paddingTop: '100px',
|
||||
position: 'fixed',
|
||||
left: '0',
|
||||
top: '0',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'auto',
|
||||
backgroundColor: 'rgba(128,128,128,0.5)'
|
||||
},
|
||||
frame: {
|
||||
common: {
|
||||
margin: 'auto',
|
||||
position: 'relative',
|
||||
padding: '0',
|
||||
outline: '0',
|
||||
width: '500px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'rgba(0, 0, 0, 0.16) 0px 5px 40px',
|
||||
opacity: '1',
|
||||
overflow: 'hidden',
|
||||
height: '60%',
|
||||
backgroundColor: 'white'
|
||||
},
|
||||
signin: {
|
||||
minHeight: '200px',
|
||||
maxHeight: '330px'
|
||||
},
|
||||
signup: {
|
||||
minHeight: '580px',
|
||||
maxHeight: '620px'
|
||||
},
|
||||
accountHome: {
|
||||
minHeight: '350px',
|
||||
maxHeight: '510px'
|
||||
},
|
||||
magiclink: {
|
||||
minHeight: '230px',
|
||||
maxHeight: '230px'
|
||||
},
|
||||
loading: {
|
||||
minHeight: '130px'
|
||||
}
|
||||
},
|
||||
popup: {
|
||||
container: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
letterSpacing: '0',
|
||||
textRendering: 'optimizeLegibility',
|
||||
fontSize: '1.5rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-start',
|
||||
top: '0px',
|
||||
bottom: '0px',
|
||||
left: '0px',
|
||||
right: '0px',
|
||||
overflow: 'hidden',
|
||||
paddingTop: '18px',
|
||||
paddingBottom: '18px',
|
||||
textAlign: 'left',
|
||||
boxSizing: 'border-box'
|
||||
},
|
||||
closeIcon: {
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
color: 'grey',
|
||||
cursor: 'pointer'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Pages = {
|
||||
signin: SigninPage,
|
||||
signup: SignupPage,
|
||||
accountHome: AccountHomePage,
|
||||
magiclink: MagicLinkPage,
|
||||
loading: LoadingPage
|
||||
};
|
||||
|
||||
export default class PopupModal extends React.Component {
|
||||
static propTypes = {
|
||||
data: PropTypes.shape({
|
||||
site: PropTypes.shape({
|
||||
title: PropTypes.string,
|
||||
description: PropTypes.string
|
||||
}).isRequired,
|
||||
member: PropTypes.shape({
|
||||
email: PropTypes.string
|
||||
})
|
||||
}).isRequired,
|
||||
action: PropTypes.object,
|
||||
page: PropTypes.string.isRequired,
|
||||
onAction: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
renderCurrentPage(page) {
|
||||
const PageComponent = Pages[page];
|
||||
|
||||
return (
|
||||
<PageComponent
|
||||
data={this.props.data}
|
||||
action={this.props.action}
|
||||
onAction={(action, data) => this.props.onAction(action, data)}
|
||||
brandColor={this.props.brandColor}
|
||||
switchPage={page => this.props.switchPage(page)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderPopupClose() {
|
||||
return (
|
||||
<div style={{display: 'flex', justifyContent: 'flex-end', padding: '0 20px'}}>
|
||||
<CloseIcon style={Styles.popup.closeIcon} onClick = {() => this.props.onToggle()} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderPopupContent() {
|
||||
return (
|
||||
<div style={Styles.popup.container}>
|
||||
{this.renderPopupClose()}
|
||||
{this.renderCurrentPage(this.props.page)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handlePopupClose(e) {
|
||||
e.preventDefault();
|
||||
if (e.target === e.currentTarget) {
|
||||
this.props.onToggle();
|
||||
}
|
||||
}
|
||||
|
||||
renderFrameContainer() {
|
||||
const page = this.props.page;
|
||||
const frameStyle = {
|
||||
...Styles.frame.common,
|
||||
...Styles.frame[page]
|
||||
};
|
||||
return (
|
||||
<div style={Styles.modalContainer} onClick = {e => this.handlePopupClose(e)}>
|
||||
<Frame style={frameStyle} title="membersjs-popup">
|
||||
{this.renderPopupContent()}
|
||||
</Frame>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.renderFrameContainer();
|
||||
}
|
||||
}
|
98
ghost/portal/src/components/TriggerButton.js
Normal file
98
ghost/portal/src/components/TriggerButton.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
import Frame from './Frame';
|
||||
import {ReactComponent as UserIcon} from '../images/icons/user.svg';
|
||||
import {ReactComponent as CloseIcon} from '../images/icons/close.svg';
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
|
||||
const Styles = {
|
||||
frame: {
|
||||
zIndex: '2147483000',
|
||||
position: 'fixed',
|
||||
bottom: '20px',
|
||||
right: '20px',
|
||||
width: '60px',
|
||||
height: '60px',
|
||||
boxShadow: 'rgba(0, 0, 0, 0.06) 0px 1px 6px 0px, rgba(0, 0, 0, 0.16) 0px 2px 32px 0px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: '#3EB0EF',
|
||||
animation: '250ms ease 0s 1 normal none running animation-bhegco',
|
||||
transition: 'opacity 0.3s ease 0s'
|
||||
},
|
||||
launcher: {
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
left: '0px',
|
||||
width: '60px',
|
||||
height: '60px',
|
||||
cursor: 'pointer',
|
||||
transformOrigin: 'center center',
|
||||
backfaceVisibility: 'hidden',
|
||||
WebkitFontSmoothing: 'antialiased',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden'
|
||||
},
|
||||
button: {
|
||||
display: 'flex',
|
||||
WebkitBoxAlign: 'center',
|
||||
alignItems: 'center',
|
||||
WebkitBoxPack: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
bottom: '0px',
|
||||
width: '100%',
|
||||
opacity: '1',
|
||||
transform: 'rotate(0deg) scale(1)',
|
||||
transition: 'transform 0.16s linear 0s, opacity 0.08s linear 0s'
|
||||
},
|
||||
userIcon: {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
color: '#fff'
|
||||
},
|
||||
|
||||
closeIcon: {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
color: '#fff'
|
||||
}
|
||||
};
|
||||
|
||||
export default class TriggerButton extends React.Component {
|
||||
static propTypes = {
|
||||
name: PropTypes.string
|
||||
};
|
||||
|
||||
onToggle() {
|
||||
this.props.onToggle();
|
||||
}
|
||||
|
||||
renderTriggerIcon() {
|
||||
if (this.props.isPopupOpen) {
|
||||
return (
|
||||
<CloseIcon style={Styles.closeIcon} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<UserIcon style={Styles.userIcon} />
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const frameStyle = {
|
||||
...Styles.frame,
|
||||
backgroundColor: this.props.brandColor || '#3EB0EF'
|
||||
};
|
||||
|
||||
return (
|
||||
<Frame style={frameStyle} title="membersjs-trigger">
|
||||
<div style={Styles.launcher} onClick={e => this.onToggle(e)}>
|
||||
<div style={Styles.button}>
|
||||
{this.renderTriggerIcon()}
|
||||
</div>
|
||||
</div>
|
||||
</Frame>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
import React from 'react';
|
||||
import {render} from '@testing-library/react';
|
||||
import TriggerComponent from './TriggerComponent';
|
||||
import TriggerButton from './TriggerButton';
|
||||
|
||||
describe('TriggerComponentTest', () => {
|
||||
describe('Trigger Button', () => {
|
||||
test('renders', () => {
|
||||
const {getByTitle} = render(
|
||||
<TriggerComponent />
|
||||
<TriggerButton />
|
||||
);
|
||||
const triggerFrame = getByTitle('membersjs-trigger');
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
import FrameComponent from './FrameComponent';
|
||||
import {ReactComponent as UserIcon} from '../images/icons/user.svg';
|
||||
import {ReactComponent as CloseIcon} from '../images/icons/close.svg';
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
|
||||
export default class TriggerComponent extends React.Component {
|
||||
static propTypes = {
|
||||
name: PropTypes.string
|
||||
};
|
||||
|
||||
onToggle() {
|
||||
this.props.onToggle();
|
||||
}
|
||||
|
||||
renderTriggerIcon() {
|
||||
const userIconStyle = {
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
color: '#fff'
|
||||
};
|
||||
|
||||
const closeIconStyle = {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
color: '#fff'
|
||||
};
|
||||
|
||||
if (this.props.isPopupOpen) {
|
||||
return (
|
||||
<CloseIcon style={closeIconStyle} />
|
||||
// <img src={closeIcon} className="trigger-icon" alt="Close" style={{ height: '30px', userSelect: 'none' }} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<UserIcon style={userIconStyle} />
|
||||
// <img src={userIcon} className="trigger-icon" alt="Account" style={{ height: '20px', userSelect: 'none' }} />
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const backgroundColor = this.props.isPopupOpen ? '#3EB0EF' : '#3EB0EF';
|
||||
const hoverStyle = {
|
||||
zIndex: '2147483000',
|
||||
position: 'fixed',
|
||||
bottom: '20px',
|
||||
right: '20px',
|
||||
width: '60px',
|
||||
height: '60px',
|
||||
boxShadow: 'rgba(0, 0, 0, 0.06) 0px 1px 6px 0px, rgba(0, 0, 0, 0.16) 0px 2px 32px 0px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: backgroundColor,
|
||||
animation: '250ms ease 0s 1 normal none running animation-bhegco',
|
||||
transition: 'opacity 0.3s ease 0s'
|
||||
};
|
||||
|
||||
const launcherStyle = {
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
left: '0px',
|
||||
width: '60px',
|
||||
height: '60px',
|
||||
cursor: 'pointer',
|
||||
transformOrigin: 'center center',
|
||||
backfaceVisibility: 'hidden',
|
||||
WebkitFontSmoothing: 'antialiased',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden'
|
||||
};
|
||||
|
||||
const buttonStyle = {
|
||||
display: 'flex',
|
||||
WebkitBoxAlign: 'center',
|
||||
alignItems: 'center',
|
||||
WebkitBoxPack: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
bottom: '0px',
|
||||
width: '100%',
|
||||
opacity: '1',
|
||||
transform: 'rotate(0deg) scale(1)',
|
||||
transition: 'transform 0.16s linear 0s, opacity 0.08s linear 0s'
|
||||
};
|
||||
|
||||
return (
|
||||
<FrameComponent style={hoverStyle} title="membersjs-trigger">
|
||||
<div style={launcherStyle} onClick={e => this.onToggle(e)} id="membersjs-trigger-component">
|
||||
<div style={buttonStyle}>
|
||||
{this.renderTriggerIcon()}
|
||||
</div>
|
||||
</div>
|
||||
</FrameComponent>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
|
||||
export default class SignedInPage extends React.Component {
|
||||
export default class AccountHomePage extends React.Component {
|
||||
static propTypes = {
|
||||
data: PropTypes.shape({
|
||||
site: PropTypes.shape({
|
||||
|
@ -45,7 +45,7 @@ export default class SignedInPage extends React.Component {
|
|||
cursor: 'pointer',
|
||||
transition: '.4s ease',
|
||||
color: '#fff',
|
||||
backgroundColor: '#3eb0ef',
|
||||
backgroundColor: this.props.brandColor || '#3eb0ef',
|
||||
boxShadow: 'none',
|
||||
userSelect: 'none',
|
||||
width: '90px',
|
||||
|
@ -126,7 +126,7 @@ export default class SignedInPage extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
renderSignedInHeader() {
|
||||
renderHeader() {
|
||||
const memberEmail = this.getMemberEmail();
|
||||
|
||||
return (
|
||||
|
@ -141,21 +141,61 @@ export default class SignedInPage extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
renderUserAvatar() {
|
||||
const avatarImg = (this.props.data.member && this.props.data.member.avatar_image);
|
||||
|
||||
const logoStyle = {
|
||||
position: 'relative',
|
||||
display: 'block',
|
||||
width: '64px',
|
||||
height: '64px',
|
||||
marginBottom: '6px',
|
||||
backgroundPosition: '50%',
|
||||
backgroundSize: 'cover',
|
||||
borderRadius: '100%',
|
||||
boxShadow: '0 0 0 3px #fff',
|
||||
border: '1px solid gray'
|
||||
};
|
||||
|
||||
if (avatarImg) {
|
||||
logoStyle.backgroundImage = `url(${avatarImg})`;
|
||||
return (
|
||||
<span style={logoStyle}> </span>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderUserHeader() {
|
||||
const memberEmail = this.getMemberEmail();
|
||||
const memberName = this.props.data.member.name;
|
||||
|
||||
return (
|
||||
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: '24px'}}>
|
||||
{this.renderUserAvatar()}
|
||||
{memberName ? <div style={{fontSize: '16px', fontWeight: '400'}}> {memberName}</div> : null}
|
||||
<div style={{fontSize: '14px', fontWeight: '400', color: '#929292', lineHeight: '12px'}}> {memberEmail}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderLogoutButton() {
|
||||
return (
|
||||
<div style={{paddingLeft: '16px', paddingRight: '16px', paddingTop: '12px', borderTop: '1px solid #EFEFEF', cursor: 'pointer'}}>
|
||||
<div style={{paddingLeft: '21px', paddingRight: '16px', paddingTop: '12px', borderTop: '1px solid #EFEFEF', cursor: 'pointer'}}>
|
||||
<div role="button" onClick={(e) => {
|
||||
this.handleSignout(e);
|
||||
}} style={{fontWeight: 'bold'}}> Logout </div>
|
||||
}} style={{marginBottom: '3px'}}> Account </div>
|
||||
<div role="button" onClick={(e) => {
|
||||
this.handleSignout(e);
|
||||
}}> Log out </div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{display: 'flex', flexDirection: 'column', color: '#313131'}}>
|
||||
{this.renderSignedInHeader()}
|
||||
{this.renderPlans()}
|
||||
<div style={{display: 'flex', flexDirection: 'column', color: '#313131', paddingTop: '9px'}}>
|
||||
{this.renderUserHeader()}
|
||||
{this.renderLogoutButton()}
|
||||
</div>
|
||||
);
|
|
@ -1,14 +1,14 @@
|
|||
import React from 'react';
|
||||
import {render, fireEvent} from '@testing-library/react';
|
||||
import SignedInPage from './SignedInPage';
|
||||
import {site, member} from '../test/fixtures/data';
|
||||
import AccountHomePage from './AccountHomePage';
|
||||
import {site, member} from '../../test/fixtures/data';
|
||||
|
||||
const setup = (overrides) => {
|
||||
const mockOnActionFn = jest.fn();
|
||||
const mockSwitchPageFn = jest.fn();
|
||||
|
||||
const freeMember = member.free;
|
||||
const utils = render(
|
||||
<SignedInPage data={{site, member}} onAction={mockOnActionFn} switchPage={mockSwitchPageFn} />
|
||||
<AccountHomePage data={{site, member: freeMember}} onAction={mockOnActionFn} switchPage={mockSwitchPageFn} />
|
||||
);
|
||||
const memberEmail = utils.getByText(member.email);
|
||||
const logoutButton = utils.queryByRole('button', {name: 'Logout'});
|
||||
|
@ -21,7 +21,7 @@ const setup = (overrides) => {
|
|||
};
|
||||
};
|
||||
|
||||
describe('SignedInPage', () => {
|
||||
describe('Account Home Page', () => {
|
||||
test('renders', () => {
|
||||
const {memberEmail, logoutButton} = setup();
|
||||
|
67
ghost/portal/src/components/pages/MagicLinkPage.js
Normal file
67
ghost/portal/src/components/pages/MagicLinkPage.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
const React = require('react');
|
||||
|
||||
export default class MagicLinkPage extends React.Component {
|
||||
renderFormHeader() {
|
||||
return (
|
||||
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: '18px'}}>
|
||||
<div style={{fontSize: '24px', fontWeight: 'bold', marginBottom: '18px'}}> Check your inbox! </div>
|
||||
<div> Check your inbox and click on the login link to complete the signin. </div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderLoginMessage() {
|
||||
return (
|
||||
<div style={{display: 'flex', justifyContent: 'center'}}>
|
||||
<div style={{color: '#3db0ef', fontWeight: 'bold', cursor: 'pointer'}} onClick={() => this.props.switchPage('signin')}> Back to Log in </div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleClose(e) {
|
||||
this.props.onAction('closePopup');
|
||||
}
|
||||
|
||||
renderCloseButton() {
|
||||
|
||||
const buttonStyle = {
|
||||
display: 'inline-block',
|
||||
padding: '0 1.8rem',
|
||||
height: '44px',
|
||||
border: '0',
|
||||
fontSize: '1.5rem',
|
||||
lineHeight: '42px',
|
||||
fontWeight: '600',
|
||||
textAlign: 'center',
|
||||
textDecoration: 'none',
|
||||
whiteSpace: 'nowrap',
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer',
|
||||
transition: '.4s ease',
|
||||
color: '#fff',
|
||||
backgroundColor: this.props.brandColor || '#3eb0ef',
|
||||
boxShadow: 'none',
|
||||
userSelect: 'none',
|
||||
width: '100%',
|
||||
marginBottom: '12px'
|
||||
};
|
||||
return (
|
||||
<button onClick={(e) => {
|
||||
this.handleClose(e);
|
||||
}} style={buttonStyle}>
|
||||
Close
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{display: 'flex', flexDirection: 'column', color: '#313131', padding: '0 24px'}}>
|
||||
<div style={{paddingLeft: '16px', paddingRight: '16px', paddingTop: '12px'}}>
|
||||
{this.renderFormHeader()}
|
||||
{this.renderCloseButton()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -50,7 +50,7 @@ export default class SigninPage extends React.Component {
|
|||
cursor: 'pointer',
|
||||
transition: '.4s ease',
|
||||
color: '#fff',
|
||||
backgroundColor: '#3eb0ef',
|
||||
backgroundColor: this.props.brandColor || '#3eb0ef',
|
||||
boxShadow: 'none',
|
||||
userSelect: 'none',
|
||||
width: '100%',
|
||||
|
@ -83,46 +83,53 @@ export default class SigninPage extends React.Component {
|
|||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
background: '#fff',
|
||||
borderRadius: '5px',
|
||||
borderRadius: '9px',
|
||||
fontSize: '14px',
|
||||
marginBottom: '12px'
|
||||
marginBottom: '12px',
|
||||
boxSizing: 'border-box'
|
||||
};
|
||||
|
||||
const fields = {
|
||||
email: {
|
||||
type: 'email',
|
||||
value: this.state.email,
|
||||
placeholder: 'Email...',
|
||||
label: 'email'
|
||||
placeholder: 'Your email address',
|
||||
label: 'Email',
|
||||
name: 'email'
|
||||
}
|
||||
};
|
||||
const field = fields[fieldName];
|
||||
return (
|
||||
<input
|
||||
type={field.type}
|
||||
placeholder={field.placeholder}
|
||||
value={field.value}
|
||||
onChange={(e) => {
|
||||
this.handleInput(e, fieldName);
|
||||
}}
|
||||
aria-label={field.label}
|
||||
style={inputStyle}
|
||||
/>
|
||||
<>
|
||||
<label htmlFor={field.name} style={{marginBottom: '3px', fontSize: '12px', fontWeight: '700'}}> {field.label} </label>
|
||||
<input
|
||||
type={field.type}
|
||||
name={field.name}
|
||||
placeholder={field.placeholder}
|
||||
value={field.value}
|
||||
onChange={(e) => {
|
||||
this.handleInput(e, fieldName);
|
||||
}}
|
||||
style={inputStyle}
|
||||
aria-label={field.label}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderSignupMessage() {
|
||||
const color = this.props.brandColor || '#3db0ef';
|
||||
return (
|
||||
<div style={{display: 'flex'}}>
|
||||
<div style={{marginRight: '6px'}}> Not a member ? </div>
|
||||
<div style={{color: '#3db0ef', fontWeight: 'bold', cursor: 'pointer'}} role="button" onClick={() => this.props.switchPage('signup')}> Subscribe </div>
|
||||
<div style={{display: 'flex', justifyContent: 'center'}}>
|
||||
<div style={{marginRight: '6px', color: '#929292'}}> Don't have an account ? </div>
|
||||
<div style={{color, fontWeight: 'bold', cursor: 'pointer'}} role="button" onClick={() => this.props.switchPage('signup')}> Subscribe </div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderForm() {
|
||||
return (
|
||||
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: '12px'}}>
|
||||
<div style={{display: 'flex', flexDirection: 'column', marginBottom: '12px', padding: '0 18px'}}>
|
||||
{this.renderInputField('email')}
|
||||
{this.renderSubmitButton()}
|
||||
{this.renderSignupMessage()}
|
||||
|
@ -130,14 +137,37 @@ export default class SigninPage extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
renderSiteLogo() {
|
||||
const siteLogo = (this.props.data.site && this.props.data.site.logo);
|
||||
|
||||
const logoStyle = {
|
||||
position: 'relative',
|
||||
display: 'block',
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
marginBottom: '12px',
|
||||
backgroundPosition: '50%',
|
||||
backgroundSize: 'cover',
|
||||
borderRadius: '100%',
|
||||
boxShadow: '0 0 0 3px #fff'
|
||||
};
|
||||
|
||||
if (siteLogo) {
|
||||
logoStyle.backgroundImage = `url(${siteLogo})`;
|
||||
return (
|
||||
<span style={logoStyle}> </span>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderFormHeader() {
|
||||
const siteTitle = (this.props.data.site && this.props.data.site.title) || 'Site Title';
|
||||
const siteDescription = (this.props.data.site && this.props.data.site.description) || 'Site Description';
|
||||
|
||||
return (
|
||||
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: '12px'}}>
|
||||
<div style={{fontSize: '18px', fontWeight: 'bold'}}> Signin to {siteTitle}</div>
|
||||
<div> {siteDescription} </div>
|
||||
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: '18px'}}>
|
||||
{this.renderSiteLogo()}
|
||||
<div style={{fontSize: '21px', fontWeight: '400'}}> Sign in to {siteTitle}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -145,7 +175,7 @@ export default class SigninPage extends React.Component {
|
|||
render() {
|
||||
return (
|
||||
<div style={{display: 'flex', flexDirection: 'column', color: '#313131'}}>
|
||||
<div style={{paddingLeft: '16px', paddingRight: '16px', paddingTop: '12px'}}>
|
||||
<div style={{paddingLeft: '16px', paddingRight: '16px'}}>
|
||||
{this.renderFormHeader()}
|
||||
{this.renderForm()}
|
||||
</div>
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import {render, fireEvent} from '@testing-library/react';
|
||||
import SigninPage from './SigninPage';
|
||||
import {site} from '../test/fixtures/data';
|
||||
import {site} from '../../test/fixtures/data';
|
||||
|
||||
const setup = (overrides) => {
|
||||
const mockOnActionFn = jest.fn();
|
|
@ -1,6 +1,10 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
|
||||
const Styles = {
|
||||
|
||||
};
|
||||
|
||||
export default class SignupPage extends React.Component {
|
||||
static propTypes = {
|
||||
data: PropTypes.shape({
|
||||
|
@ -18,7 +22,7 @@ export default class SignupPage extends React.Component {
|
|||
this.state = {
|
||||
name: '',
|
||||
email: '',
|
||||
plan: 'Free',
|
||||
plan: 'FREE',
|
||||
isLoading: false,
|
||||
showSuccess: false
|
||||
};
|
||||
|
@ -66,7 +70,7 @@ export default class SignupPage extends React.Component {
|
|||
cursor: 'pointer',
|
||||
transition: '.4s ease',
|
||||
color: '#fff',
|
||||
backgroundColor: '#3eb0ef',
|
||||
backgroundColor: this.props.brandColor || '#3eb0ef',
|
||||
boxShadow: 'none',
|
||||
userSelect: 'none',
|
||||
width: '100%',
|
||||
|
@ -83,22 +87,38 @@ export default class SignupPage extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
renderPlanBox({position, id, type, price, currency, name}) {
|
||||
const boxStyle = (position) => {
|
||||
const style = {
|
||||
padding: '12px 24px',
|
||||
flexBasis: '100%'
|
||||
};
|
||||
if (position !== 'last') {
|
||||
style.borderRight = '1px solid black';
|
||||
}
|
||||
return style;
|
||||
handleSelectPlan(e, name) {
|
||||
this.setState({
|
||||
plan: name
|
||||
});
|
||||
}
|
||||
|
||||
renderCheckbox({name, isChecked = false}) {
|
||||
const style = {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
border: 'solid 1px #cccccc'
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
name={name}
|
||||
type="checkbox"
|
||||
style={style}
|
||||
checked={this.state.plan === name}
|
||||
onChange={e => e.preventDefault()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderPlanOptions(plans) {
|
||||
const nameStyle = {
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold',
|
||||
fontSize: '13px',
|
||||
fontWeight: '500',
|
||||
display: 'flex',
|
||||
color: '#343F44',
|
||||
justifyContent: 'center'
|
||||
};
|
||||
|
||||
|
@ -113,99 +133,62 @@ export default class SignupPage extends React.Component {
|
|||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
};
|
||||
const isChecked = this.state.plan === name;
|
||||
if (name === 'Free') {
|
||||
return (
|
||||
<div style={boxStyle(position)}>
|
||||
<div style={nameStyle}> {name} </div>
|
||||
<div style={priceStyle}>
|
||||
<strong style={{fontSize: '14px'}}> {price} </strong>
|
||||
</div>
|
||||
<div style={checkboxStyle}> {this.renderCheckbox({name, isChecked})} </div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div style={boxStyle(position)}>
|
||||
<div style={nameStyle}> {name} </div>
|
||||
<div style={priceStyle}>
|
||||
<strong style={{fontSize: '14px'}}> {currency} {price} </strong>
|
||||
<span> {` / ${type}`}</span>
|
||||
</div>
|
||||
<div style={checkboxStyle}> {this.renderCheckbox({name, isChecked})} </div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderPlanBoxOld({position, id, type, price, name}) {
|
||||
const boxStyle = (position) => {
|
||||
const boxStyle = ({isLast = false}) => {
|
||||
const style = {
|
||||
padding: '12px 24px',
|
||||
padding: '12px 12px',
|
||||
flexBasis: '100%'
|
||||
};
|
||||
if (position !== 'last') {
|
||||
style.borderRight = '1px solid black';
|
||||
if (!isLast) {
|
||||
style.borderRight = '1px solid #c5d2d9';
|
||||
}
|
||||
return style;
|
||||
};
|
||||
|
||||
const nameStyle = {
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold',
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
const 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>
|
||||
);
|
||||
};
|
||||
|
||||
const priceStyle = {
|
||||
fontSize: '12px',
|
||||
fontWeight: 'bold',
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
};
|
||||
const checkboxStyle = {
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
};
|
||||
|
||||
const isChecked = (this.state.plan === name);
|
||||
return (
|
||||
<div style={boxStyle(position)}>
|
||||
<div style={nameStyle}> {type} </div>
|
||||
<div style={priceStyle}> {price} </div>
|
||||
<div style={checkboxStyle}> {this.renderCheckbox({name, isChecked})} </div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleSelectPlan(e) {
|
||||
const plan = e.target.name;
|
||||
this.setState({
|
||||
plan
|
||||
return plans.map(({name, currency, price}, i) => {
|
||||
const isLast = i === plans.length - 1;
|
||||
const isChecked = this.state.plan === name;
|
||||
return (
|
||||
<div style={boxStyle({isLast})} key={name} onClick={e => this.handleSelectPlan(e, name)}>
|
||||
<div style={checkboxStyle}> {this.renderCheckbox({name, isChecked})} </div>
|
||||
<div style={nameStyle}> {name} </div>
|
||||
<div style={priceStyle}>
|
||||
{PriceLabel({name, currency, price})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
renderCheckbox({name, isChecked = false}) {
|
||||
const style = {
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
border: 'solid 1px #cccccc'
|
||||
};
|
||||
return (
|
||||
<input
|
||||
name={name}
|
||||
type="checkbox"
|
||||
style={style}
|
||||
checked={isChecked}
|
||||
onChange = {e => this.handleSelectPlan(e)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderPlans() {
|
||||
const containerStyle = {
|
||||
display: 'flex',
|
||||
border: '1px solid black',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid #c5d2d9',
|
||||
borderRadius: '9px',
|
||||
marginBottom: '12px'
|
||||
};
|
||||
const plans = this.props.data.site && this.props.data.site.plans;
|
||||
|
@ -214,11 +197,13 @@ export default class SignupPage extends React.Component {
|
|||
}
|
||||
return (
|
||||
<div style={{width: '100%'}}>
|
||||
<div style={{fontWeight: 'bold', marginBottom: '9px'}}> Choose a Plan </div>
|
||||
<label style={{marginBottom: '3px', fontSize: '12px', fontWeight: '700'}}> Plan </label>
|
||||
<div style={containerStyle}>
|
||||
{this.renderPlanBox({position: 'first', type: 'free', price: 'Decide later', name: 'Free'})}
|
||||
{this.renderPlanBox({position: 'middle', type: 'month', price: plans.monthly, currency: plans.currencySymbol, name: 'Monthly'})}
|
||||
{this.renderPlanBox({position: 'last', type: 'year', price: plans.yearly, currency: plans.currencySymbol, name: 'Yearly'})}
|
||||
{this.renderPlanOptions([
|
||||
{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'}
|
||||
])}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -235,52 +220,60 @@ export default class SignupPage extends React.Component {
|
|||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
background: '#fff',
|
||||
borderRadius: '5px',
|
||||
borderRadius: '9px',
|
||||
fontSize: '14px',
|
||||
marginBottom: '12px'
|
||||
marginBottom: '12px',
|
||||
boxSizing: 'border-box'
|
||||
};
|
||||
|
||||
const fields = {
|
||||
name: {
|
||||
type: 'text',
|
||||
value: this.state.name,
|
||||
placeholder: 'Name',
|
||||
label: 'name'
|
||||
placeholder: 'Name...',
|
||||
label: 'Name',
|
||||
name: 'name'
|
||||
},
|
||||
email: {
|
||||
type: 'email',
|
||||
value: this.state.email,
|
||||
placeholder: 'Email',
|
||||
label: 'email'
|
||||
placeholder: 'Email...',
|
||||
label: 'Email',
|
||||
name: 'email'
|
||||
}
|
||||
};
|
||||
const field = fields[fieldName];
|
||||
return (
|
||||
<input
|
||||
type={field.type}
|
||||
placeholder={field.placeholder}
|
||||
value={field.value}
|
||||
onChange={(e) => {
|
||||
this.handleInput(e, fieldName);
|
||||
}}
|
||||
style={inputStyle}
|
||||
aria-label={field.label}
|
||||
/>
|
||||
<>
|
||||
<label htmlFor={field.name} style={{marginBottom: '3px', fontSize: '12px', fontWeight: '700'}}> {field.label} </label>
|
||||
<input
|
||||
type={field.type}
|
||||
name={field.name}
|
||||
placeholder={field.placeholder}
|
||||
value={field.value}
|
||||
onChange={(e) => {
|
||||
this.handleInput(e, fieldName);
|
||||
}}
|
||||
style={inputStyle}
|
||||
aria-label={field.label}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderLoginMessage() {
|
||||
const color = this.props.brandColor || '#3db0ef';
|
||||
return (
|
||||
<div style={{display: 'flex'}}>
|
||||
<div style={{marginRight: '6px'}}> Already a member ? </div>
|
||||
<div style={{color: '#3db0ef', fontWeight: 'bold', cursor: 'pointer'}} role="button" onClick={() => this.props.switchPage('signin')}> Log in </div>
|
||||
<div style={{display: 'flex', justifyContent: 'center'}}>
|
||||
<div style={{marginRight: '6px', color: '#929292'}}> Already a member ? </div>
|
||||
<div style={{color, fontWeight: 'bold', cursor: 'pointer'}} role="button" onClick={() => this.props.switchPage('signin')}> Log in </div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderForm() {
|
||||
return (
|
||||
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: '12px'}}>
|
||||
<div style={{display: 'flex', flexDirection: 'column', marginBottom: '12px', padding: '0 18px'}}>
|
||||
{this.renderInputField('name')}
|
||||
{this.renderInputField('email')}
|
||||
{this.renderPlans()}
|
||||
|
@ -290,14 +283,37 @@ export default class SignupPage extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
renderSiteLogo() {
|
||||
const siteLogo = (this.props.data.site && this.props.data.site.logo);
|
||||
|
||||
const logoStyle = {
|
||||
position: 'relative',
|
||||
display: 'block',
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
marginBottom: '12px',
|
||||
backgroundPosition: '50%',
|
||||
backgroundSize: 'cover',
|
||||
borderRadius: '100%',
|
||||
boxShadow: '0 0 0 3px #fff'
|
||||
};
|
||||
|
||||
if (siteLogo) {
|
||||
logoStyle.backgroundImage = `url(${siteLogo})`;
|
||||
return (
|
||||
<span style={logoStyle}> </span>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderFormHeader() {
|
||||
const siteTitle = (this.props.data.site && this.props.data.site.title) || 'Site Title';
|
||||
const siteDescription = (this.props.data.site && this.props.data.site.description) || 'Site Description';
|
||||
|
||||
return (
|
||||
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: '12px'}}>
|
||||
<div style={{fontSize: '18px', fontWeight: 'bold'}}> Signup to {siteTitle}</div>
|
||||
<div> {siteDescription} </div>
|
||||
<div style={{display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: '18px'}}>
|
||||
{this.renderSiteLogo()}
|
||||
<div style={{fontSize: '21px', fontWeight: '400'}}> Subscribe to {siteTitle}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -305,7 +321,7 @@ export default class SignupPage extends React.Component {
|
|||
render() {
|
||||
return (
|
||||
<div style={{display: 'flex', flexDirection: 'column', color: '#313131'}}>
|
||||
<div style={{paddingLeft: '16px', paddingRight: '16px', paddingTop: '12px'}}>
|
||||
<div style={{paddingLeft: '16px', paddingRight: '16px'}}>
|
||||
{this.renderFormHeader()}
|
||||
{this.renderForm()}
|
||||
</div>
|
Loading…
Add table
Reference in a new issue