From 3cbe8d6f0c42802c7b5223cc2b0ae31b723a5500 Mon Sep 17 00:00:00 2001 From: Rish Date: Tue, 14 Apr 2020 12:32:35 +0530 Subject: [PATCH] Added new Signin/Signup/MagicLink pages Added new pages for different UI flows - Sign-in Page: Allow member to signin with magic link - Signup Page: Allow member to signup with name + email, also allow choosing plan in checkout - Signed-in Page: Default account page if member is signed in - Magic Link Page: Info page which shows magic link is sent to inbox --- ghost/portal/src/components/MagicLinkPage.js | 31 ++ ghost/portal/src/components/SignedInPage.js | 163 ++++++++++ ghost/portal/src/components/SigninPage.js | 158 ++++++++++ ghost/portal/src/components/SignupPage.js | 308 +++++++++++++++++++ 4 files changed, 660 insertions(+) create mode 100644 ghost/portal/src/components/MagicLinkPage.js create mode 100644 ghost/portal/src/components/SignedInPage.js create mode 100644 ghost/portal/src/components/SigninPage.js create mode 100644 ghost/portal/src/components/SignupPage.js diff --git a/ghost/portal/src/components/MagicLinkPage.js b/ghost/portal/src/components/MagicLinkPage.js new file mode 100644 index 0000000000..e638baf57f --- /dev/null +++ b/ghost/portal/src/components/MagicLinkPage.js @@ -0,0 +1,31 @@ +const React = require('react'); + +export default class MagicLinkPage extends React.Component { + renderFormHeader() { + return ( +
+
Check your Inbox!
+
We just sent you a login link, check your Inbox!
+
+ ); + } + + renderLoginMessage() { + return ( +
+
this.props.switchPage('signin')}> Back to Log in
+
+ ); + } + + render() { + return ( +
+
+ {this.renderFormHeader()} + {this.renderLoginMessage()} +
+
+ ); + } +} diff --git a/ghost/portal/src/components/SignedInPage.js b/ghost/portal/src/components/SignedInPage.js new file mode 100644 index 0000000000..c9e1463359 --- /dev/null +++ b/ghost/portal/src/components/SignedInPage.js @@ -0,0 +1,163 @@ +const React = require('react'); +const PropTypes = require('prop-types'); + +export default class SignedInPage 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 + }).isRequired, + onAction: PropTypes.func + }; + + handleSignout(e) { + e.preventDefault(); + this.props.onAction('signout'); + } + + handlePlanCheckout(e) { + e.preventDefault(); + const plan = e.target.name; + const email = this.getMemberEmail(); + this.props.onAction('checkoutPlan', {email, plan}); + } + + getMemberEmail() { + return this.props.data.member.email; + } + + renderPlanSelectButton({name}) { + const buttonStyle = { + display: 'inline-block', + height: '38px', + border: '0', + fontSize: '14px', + fontWeight: '300', + textAlign: 'center', + textDecoration: 'none', + whiteSpace: 'nowrap', + borderRadius: '5px', + cursor: 'pointer', + transition: '.4s ease', + color: '#fff', + backgroundColor: '#3eb0ef', + boxShadow: 'none', + userSelect: 'none', + width: '90px', + marginBottom: '12px' + }; + + return ( + + ); + } + + 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; + }; + + const nameStyle = { + fontSize: '14px', + fontWeight: 'bold', + display: 'flex', + justifyContent: 'center' + }; + + const priceStyle = { + fontSize: '12px', + fontWeight: 'bold', + display: 'flex', + justifyContent: 'center', + marginBottom: '9px' + }; + const checkboxStyle = { + display: 'flex', + justifyContent: 'center' + }; + + return ( +
+
{name}
+
+ {currency} {price} + {` / ${type}`} +
+
{this.renderPlanSelectButton({name})}
+
+ ); + } + + renderPlans() { + const containerStyle = { + display: 'flex', + border: '1px solid black', + marginBottom: '12px' + }; + const siteTitle = this.props.data.site && this.props.data.site.title; + const plans = this.props.data.member && this.props.data.member.plans; + return ( +
+
+ {`Hey there! You are subscribed to free updates from ${siteTitle}, but don't have a paid subscription to unlock full access`} +
+
Choose a Plan
+
+ {this.renderPlanBox({position: 'first', type: 'month', price: plans.monthly, currency: plans.currencySymbol, name: 'Monthly'})} + {this.renderPlanBox({position: 'last', type: 'year', price: plans.yearly, currency: plans.currencySymbol, name: 'Yearly'})} +
+
+ ); + } + + renderSignedInHeader() { + const memberEmail = this.getMemberEmail(); + + return ( + <> +
+ Signed in as +
+
+ {memberEmail} +
+ + ); + } + + renderLogoutButton() { + return ( +
+
{ + this.handleSignout(e); + }} style={{fontWeight: 'bold'}}> Logout
+
+ ); + } + + render() { + return ( +
+ {this.renderSignedInHeader()} + {this.renderPlans()} + {this.renderLogoutButton()} +
+ ); + } +} diff --git a/ghost/portal/src/components/SigninPage.js b/ghost/portal/src/components/SigninPage.js new file mode 100644 index 0000000000..cf3447663e --- /dev/null +++ b/ghost/portal/src/components/SigninPage.js @@ -0,0 +1,158 @@ +const React = require('react'); +const PropTypes = require('prop-types'); + +export default class SigninPage extends React.Component { + static propTypes = { + data: PropTypes.shape({ + site: PropTypes.shape({ + title: PropTypes.string, + description: PropTypes.string + }).isRequired + }).isRequired, + onAction: PropTypes.func.isRequired + }; + + constructor(props) { + super(props); + this.state = { + email: '' + }; + } + + handleSignin(e) { + e.preventDefault(); + const email = this.state.email; + + this.props.onAction('signin', {email}); + } + + handleInput(e, field) { + this.setState({ + [field]: e.target.value, + showSuccess: false, + isLoading: false + }); + } + + renderSubmitButton() { + 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: '#3eb0ef', + boxShadow: 'none', + userSelect: 'none', + width: '100%', + marginBottom: '12px' + }; + const isRunning = this.props.action && this.props.action.name === 'signin' && this.props.action.isRunning; + const label = this.state.isLoading ? 'Sending' : 'Continue'; + const disabled = isRunning ? true : false; + if (disabled) { + buttonStyle.backgroundColor = 'grey'; + } + + return ( + + ); + } + + renderInputField(fieldName) { + const inputStyle = { + display: 'block', + padding: '0 .6em', + width: '100%', + height: '44px', + outline: '0', + border: '1px solid #c5d2d9', + color: 'inherit', + textDecoration: 'none', + background: '#fff', + borderRadius: '5px', + fontSize: '14px', + marginBottom: '12px' + }; + + const fields = { + name: { + type: 'text', + value: this.state.name, + placeholder: 'Name...' + }, + email: { + type: 'email', + value: this.state.email, + placeholder: 'Email...' + } + }; + const field = fields[fieldName]; + return ( + { + this.handleInput(e, fieldName); + }} + style={inputStyle} + /> + ); + } + + renderSignupMessage() { + return ( +
+
Not a member ?
+
this.props.switchPage('signup')}> Signup
+
+ ); + } + + renderForm() { + return ( +
+ {this.renderInputField('email')} + {this.renderSubmitButton()} + {this.renderSignupMessage()} +
+ ); + } + + 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 ( +
+
Signin to {siteTitle}
+
{siteDescription}
+
+ ); + } + + render() { + return ( +
+
+ {this.renderFormHeader()} + {this.renderForm()} +
+
+ ); + } +} diff --git a/ghost/portal/src/components/SignupPage.js b/ghost/portal/src/components/SignupPage.js new file mode 100644 index 0000000000..fd88900042 --- /dev/null +++ b/ghost/portal/src/components/SignupPage.js @@ -0,0 +1,308 @@ +const React = require('react'); +const PropTypes = require('prop-types'); + +export default class SignupPage extends React.Component { + static propTypes = { + data: PropTypes.shape({ + site: PropTypes.shape({ + title: PropTypes.string, + description: PropTypes.string + }).isRequired + }).isRequired, + onAction: PropTypes.func.isRequired + }; + + constructor(props) { + super(props); + this.state = { + name: '', + email: '', + plan: 'Free', + isLoading: false, + showSuccess: false + }; + } + + handleSignin(e) { + e.preventDefault(); + const email = this.state.email; + const name = this.state.name; + const plan = this.state.plan; + this.props.onAction('signup', {name, email, plan}); + this.setState({ + isLoading: true, + showSuccess: false + }); + setTimeout(() => { + this.setState({ + isLoading: false, + showSuccess: true + }); + }, 3000); + } + + handleInput(e, field) { + this.setState({ + [field]: e.target.value, + showSuccess: false, + isLoading: false + }); + } + + renderSubmitButton() { + 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: '#3eb0ef', + boxShadow: 'none', + userSelect: 'none', + width: '100%', + marginBottom: '12px' + }; + const label = this.state.isLoading ? 'Sending' : 'Continue'; + const disabled = this.state.isLoading ? true : false; + return ( + + ); + } + + 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; + }; + + const nameStyle = { + fontSize: '14px', + fontWeight: 'bold', + display: 'flex', + justifyContent: 'center' + }; + + const priceStyle = { + fontSize: '12px', + fontWeight: 'bold', + display: 'flex', + justifyContent: 'center', + marginBottom: '9px' + }; + const checkboxStyle = { + display: 'flex', + justifyContent: 'center' + }; + const isChecked = this.state.plan === name; + if (name === 'Free') { + return ( +
+
{name}
+
+ {price} +
+
{this.renderCheckbox({name, isChecked})}
+
+ ); + } + return ( +
+
{name}
+
+ {currency} {price} + {` / ${type}`} +
+
{this.renderCheckbox({name, isChecked})}
+
+ ); + } + + renderPlanBoxOld({position, id, type, price, name}) { + const boxStyle = (position) => { + const style = { + padding: '12px 24px', + flexBasis: '100%' + }; + if (position !== 'last') { + style.borderRight = '1px solid black'; + } + return style; + }; + + const nameStyle = { + fontSize: '14px', + fontWeight: 'bold', + display: 'flex', + justifyContent: 'center' + }; + + const priceStyle = { + fontSize: '12px', + fontWeight: 'bold', + display: 'flex', + justifyContent: 'center' + }; + const checkboxStyle = { + display: 'flex', + justifyContent: 'center' + }; + + const isChecked = (this.state.plan === name); + return ( +
+
{type}
+
{price}
+
{this.renderCheckbox({name, isChecked})}
+
+ ); + } + + handleSelectPlan(e) { + const plan = e.target.name; + this.setState({ + plan + }); + } + + renderCheckbox({name, isChecked = false}) { + const style = { + width: '20px', + height: '20px', + border: 'solid 1px #cccccc' + }; + return ( + this.handleSelectPlan(e)} + /> + ); + } + + renderPlans() { + const containerStyle = { + display: 'flex', + border: '1px solid black', + borderRadius: '6px', + marginBottom: '12px' + }; + const plans = this.props.data.site && this.props.data.site.plans; + return ( +
+
Choose a Plan
+
+ {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'})} +
+
+ ); + } + + renderInputField(fieldName) { + const inputStyle = { + display: 'block', + padding: '0 .6em', + width: '100%', + height: '44px', + outline: '0', + border: '1px solid #c5d2d9', + color: 'inherit', + textDecoration: 'none', + background: '#fff', + borderRadius: '5px', + fontSize: '14px', + marginBottom: '12px' + }; + + const fields = { + name: { + type: 'text', + value: this.state.name, + placeholder: 'Name...' + }, + email: { + type: 'email', + value: this.state.email, + placeholder: 'Email...' + } + }; + const field = fields[fieldName]; + return ( + { + this.handleInput(e, fieldName); + }} + style={inputStyle} + /> + ); + } + + renderLoginMessage() { + return ( +
+
Already a member ?
+
this.props.switchPage('signin')}> Log in
+
+ ); + } + + renderForm() { + return ( +
+ {this.renderInputField('name')} + {this.renderInputField('email')} + {this.renderPlans()} + {this.renderSubmitButton()} + {this.renderLoginMessage()} +
+ ); + } + + 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 ( +
+
Signup to {siteTitle}
+
{siteDescription}
+
+ ); + } + + render() { + return ( +
+
+ {this.renderFormHeader()} + {this.renderForm()} +
+
+ ); + } +}