Added basic signup/signin UI flow

refs https://github.com/TryGhost/membersjs/issues/3

Adds signup/signin flow to theme when no member is logged in, uses api utils to send magic-link to member's email which can be used to signup/signin
This commit is contained in:
Rish 2020-04-08 22:36:28 +05:30
parent f7f683bfc0
commit 4310a18150
2 changed files with 208 additions and 47 deletions

View File

@ -1,8 +1,8 @@
import TriggerComponent from './TriggerComponent';
import PopupMenuComponent from './PopupMenuComponent';
const setupMembersApi = require('../utils/api');
const React = require("react");
const PropTypes = require("prop-types");
export default class ParentContainer extends React.Component {
static propTypes = {
name: PropTypes.string,
@ -13,33 +13,35 @@ export default class ParentContainer extends React.Component {
this.state = {
showPopup: false
};
console.log("Initialized script with data", props.data);
const {blogUrl, adminUrl} = props.data.site;
this.MembersAPI = setupMembersApi({blogUrl, adminUrl});
}
componentDidMount() {
console.log("Loaded Members Data", this.props.data);
onSignout() {
this.MembersAPI.signout();
}
onSignin(data) {
this.MembersAPI.sendMagicLink(data);
}
onTriggerToggle() {
let showPopup = !this.state.showPopup;
this.setState({
showPopup
}, () => {
setTimeout(() => {
if (showPopup) {
// Trigger member signout method
const querySelector = document.querySelectorAll('iframe')[0] && document.querySelectorAll('iframe')[0].contentWindow.document.body.querySelectorAll('[data-members-signout]')
if (querySelector) {
window.handleMembersSignout && window.handleMembersSignout(querySelector);
}
}
}, 500 )
});
}
renderPopupMenu() {
if (this.state.showPopup) {
return (
<PopupMenuComponent name={this.props.name} data={this.props.data} />
<PopupMenuComponent
name={this.props.name}
data={this.props.data}
onSignout={(e) => this.onSignout()}
onSignin={(data) => this.onSignin(data)}
/>
);
}
return null;
@ -47,7 +49,12 @@ export default class ParentContainer extends React.Component {
renderTriggerComponent() {
return (
<TriggerComponent name={this.props.name} onToggle= {(e) => this.onTriggerToggle()} isPopupOpen={this.state.showPopup} data={this.props.data} />
<TriggerComponent
name={this.props.name}
onToggle= {(e) => this.onTriggerToggle()}
isPopupOpen={this.state.showPopup}
data={this.props.data}
/>
)
}

View File

@ -7,23 +7,156 @@ export default class PopupMenuComponent extends React.Component {
name: PropTypes.string,
};
render() {
const hoverStyle = {
zIndex: '2147483000',
position: 'fixed',
bottom: '100px',
right: '20px',
width: '250px',
minHeight: '50px',
maxHeight: '116px',
boxShadow: 'rgba(0, 0, 0, 0.16) 0px 5px 40px',
opacity: '1',
height: 'calc(100% - 120px)',
borderRadius: '8px',
overflow: 'hidden',
backgroundColor: 'white'
constructor(props) {
super(props);
this.state = {
inputVal: '',
isLoading: false,
showSuccess: false
}
}
handleSignout(e) {
e.preventDefault();
this.props.onSignout();
}
handleSignin(e) {
e.preventDefault();
const email = this.state.inputVal;
this.props.onSignin({email});
this.setState({
isLoading: true,
showSuccess: false
});
setTimeout(() => {
this.setState({
isLoading: false,
showSuccess: true
})
}, 3000)
}
handleInput(e) {
this.setState({
inputVal: e.target.value,
showSuccess: false,
isLoading: false
})
}
isMemberLoggedIn() {
return !!this.props.data.member;
}
getMemberEmail() {
if (this.isMemberLoggedIn()) {
return this.props.data.member.email;
}
return '';
}
renderSignedOutContent() {
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 blogTitle = (this.props.data.site && this.props.data.site.title) || 'Site Title';
const blogDescription = (this.props.data.site && this.props.data.site.description) || 'Site Description';
return (
<div style={{ display: 'flex', flexDirection: 'column', color: '#313131' }}>
<div style={{ paddingLeft: '16px', paddingRight: '16px', paddingTop: '12px', cursor: 'pointer' }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: '12px' }}>
<div style={{fontSize: '18px', fontWeight: 'bold'}}> Signup to {blogTitle}</div>
<div>{blogDescription} </div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: '12px' }}>
<input
type="email"
placeholder="Type your email..."
value={this.state.inputVal}
onChange={(e) => { this.handleInput(e) }}
style={inputStyle}
/>
{this.renderSubmitButton()}
{this.renderSuccessMessage()}
</div>
</div>
</div>
);
}
renderSuccessMessage() {
if (!this.state.isLoading && this.state.showSuccess) {
return (
<div> Please check your email for magic link! </div>
)
}
return null;
}
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'
}
const label = this.state.isLoading ? 'Sending' : 'Continue';
const disabled = this.state.isLoading ? true : false;
return (
<button onClick={(e) => { this.handleSignin(e) }} style={buttonStyle} disabled={disabled}>
{label}
</button>
)
}
renderSignedinContent() {
const memberEmail = (this.props.data.member && this.props.data.member.email) || "test@test.com";
return (
<div style={{ display: 'flex', flexDirection: 'column', color: '#313131' }}>
<div style={{ paddingLeft: '16px', paddingRight: '16px', color: '#A6A6A6', fontSize: '1.2rem', lineHeight: '1.0em' }}>
Signed in as
</div>
<div style={{ paddingLeft: '16px', paddingRight: '16px', paddingBottom: '9px' }}>
{memberEmail}
</div>
<div style={{ paddingLeft: '16px', paddingRight: '16px', paddingTop: '12px', borderTop: '1px solid #EFEFEF', cursor: 'pointer' }}>
<div onClick={(e) => { this.handleSignout(e) }}> Logout </div>
</div>
</div>
);
}
renderPopupContent() {
const launcherStyle = {
width: '100%',
height: '100%',
@ -47,24 +180,45 @@ export default class PopupMenuComponent extends React.Component {
paddingBottom: '18px',
textAlign: 'left'
};
const memberEmail = (this.props.data && this.props.data.email) || "test@test.com";
return (
<div style={launcherStyle}>
<div style={buttonStyle}>
{this.isMemberLoggedIn() ? this.renderSignedinContent() : this.renderSignedOutContent()}
</div>
</div>
);
}
render() {
let hoverStyle = {
zIndex: '2147483000',
position: 'fixed',
bottom: '100px',
right: '20px',
width: '250px',
minHeight: '50px',
maxHeight: '116px',
boxShadow: 'rgba(0, 0, 0, 0.16) 0px 5px 40px',
opacity: '1',
height: 'calc(100% - 120px)',
borderRadius: '8px',
overflow: 'hidden',
backgroundColor: 'white'
};
if (!this.isMemberLoggedIn()) {
hoverStyle = {
...hoverStyle,
width: '450px',
minHeight: '200px',
maxHeight: '220px',
}
}
return (
<FrameComponent style={hoverStyle}>
<div style={launcherStyle}>
<div style={buttonStyle}>
<div style={{display: 'flex', flexDirection: 'column', color: '#313131'}}>
<div style={{paddingLeft: '16px', paddingRight: '16px', color: '#A6A6A6', fontSize: '1.2rem', lineHeight: '1.0em'}}>
Signed in as
</div>
<div style={{paddingLeft: '16px', paddingRight: '16px', paddingBottom: '9px'}}>
{memberEmail}
</div>
<div style={{paddingLeft: '16px', paddingRight: '16px', paddingTop: '12px', borderTop: '1px solid #EFEFEF', cursor: 'pointer'}}>
<div data-members-signout> Logout </div>
</div>
</div>
</div>
</div>
{this.renderPopupContent()}
</FrameComponent>
);
}