mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Refactored auth pages for future flows (#10458)
no-issue * Used camelCase for gateway method calls * Added some components for building blocks of forms * Added input specific components * Added Form component This handles collecting the data to submit and sharing state between forms * Added Pages component to handle urls * Added the pages for the popup * Added MembersProvider component This is designed to give its children access to gateway methods * Added Modal component This wraps the pages and handles dispatching form submissions to the members gateway * Refactored index.js to use new components/pages * Fixed default page from Signup -> Signin
This commit is contained in:
parent
0755036926
commit
b1a1f61d5d
18 changed files with 488 additions and 433 deletions
18
ghost/members-auth-pages/components/EmailInput.js
Normal file
18
ghost/members-auth-pages/components/EmailInput.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import FormInput from './FormInput';
|
||||
import { IconEmail } from './icons';
|
||||
|
||||
export default ({value, error, children, onInput, className}) => (
|
||||
<FormInput
|
||||
type="email"
|
||||
name="email"
|
||||
label="Email"
|
||||
value={value}
|
||||
error={error}
|
||||
icon={IconEmail}
|
||||
placeholder="Email..."
|
||||
required={true}
|
||||
className={className}
|
||||
onInput={onInput}>
|
||||
{children}
|
||||
</FormInput>
|
||||
);
|
82
ghost/members-auth-pages/components/Form.js
Normal file
82
ghost/members-auth-pages/components/Form.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
import { Component } from 'preact';
|
||||
|
||||
const states = {};
|
||||
|
||||
export default class Form extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const includeData = props.includeData || {};
|
||||
|
||||
if (props.bindTo) {
|
||||
if (states[props.bindTo]) {
|
||||
this.state = states[props.bindTo]
|
||||
this.state.data = { ...this.state.data, ...includeData };
|
||||
} else {
|
||||
states[props.bindTo] = this.state = {
|
||||
submitted: false,
|
||||
data: {...includeData}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.state = {
|
||||
submitted: false,
|
||||
data: {...includeData}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrapChildren(children, data, onInput = () => {}) {
|
||||
return children.map(child => {
|
||||
const { bindTo } = child.attributes;
|
||||
if (bindTo) {
|
||||
child.attributes.value = data[bindTo];
|
||||
child.attributes.onInput = (e) => {
|
||||
// This is a hack
|
||||
// Preact keeps copy of the child attributes to know whether to rerender
|
||||
// The state change below will for a check, and the old attributes will be reused
|
||||
child.attributes.error = false;
|
||||
this.setState({
|
||||
submitted: false,
|
||||
data: Object.assign({}, this.state.data, {
|
||||
[bindTo]: e.target.value
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (this.state.submitted && !data[bindTo]) {
|
||||
child.attributes.error = true;
|
||||
}
|
||||
}
|
||||
return child;
|
||||
})
|
||||
}
|
||||
|
||||
wrapSubmit(onSubmit = () => {}, children, data) {
|
||||
return (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const requiredFields = children.map(c => c.attributes.bindTo).filter(x => !!x)
|
||||
if (!requiredFields.some(x => !data[x])) {
|
||||
onSubmit(this.state.data)
|
||||
}
|
||||
this.setState({
|
||||
submitted: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render({bindTo, children, onInput, onSubmit}, state) {
|
||||
if (bindTo) {
|
||||
states[bindTo] = state;
|
||||
}
|
||||
const data = state.data;
|
||||
return (
|
||||
<div className="flex flex-column mt3">
|
||||
<form className="gm-signup-form" onSubmit={this.wrapSubmit(onSubmit, children, data)} noValidate>
|
||||
{ this.wrapChildren(children, data, onInput) }
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
10
ghost/members-auth-pages/components/FormFooter.js
Normal file
10
ghost/members-auth-pages/components/FormFooter.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
export default ({title, hash, label}) => (
|
||||
<div className="gm-auth-footer">
|
||||
<div className="flex items-baseline">
|
||||
<h4>{ title }</h4>
|
||||
<a href={hash}>
|
||||
{ label }
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
16
ghost/members-auth-pages/components/FormHeader.js
Normal file
16
ghost/members-auth-pages/components/FormHeader.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { IconError } from './icons';
|
||||
|
||||
export default ({title, error, errorText, children}) => (
|
||||
<div>
|
||||
<div className="gm-logo"></div>
|
||||
<div className="gm-auth-header">
|
||||
<h1>{ title }</h1>
|
||||
{ children }
|
||||
</div>
|
||||
{(error ?
|
||||
<div class="gm-form-errortext"><i>{ IconError }</i>
|
||||
<span> {errorText} </span>
|
||||
</div> : "")
|
||||
}
|
||||
</div>
|
||||
);
|
8
ghost/members-auth-pages/components/FormHeaderCTA.js
Normal file
8
ghost/members-auth-pages/components/FormHeaderCTA.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
export default ({title, label, hash}) => (
|
||||
<div className="flex items-baseline mt2">
|
||||
<h4>{ title }</h4>
|
||||
<a href={hash}>
|
||||
{ label }
|
||||
</a>
|
||||
</div>
|
||||
);
|
23
ghost/members-auth-pages/components/FormInput.js
Normal file
23
ghost/members-auth-pages/components/FormInput.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
export default ({type, name, placeholder, value = '', error, onInput, required, className, children, icon}) => (
|
||||
<div className="gm-form-element">
|
||||
<div className="gm-input">
|
||||
<input
|
||||
type={ type }
|
||||
name={ name }
|
||||
key={ name }
|
||||
placeholder={ placeholder }
|
||||
value={ value }
|
||||
onInput={ (e) => onInput(e, name) }
|
||||
required={ required }
|
||||
className={[
|
||||
(value ? "gm-input-filled" : ""),
|
||||
(error ? "gm-error" : ""),
|
||||
className
|
||||
].join(' ')}
|
||||
/>
|
||||
<label for={ name }>{ placeholder }</label>
|
||||
<i>{ icon }</i>
|
||||
{ children }
|
||||
</div>
|
||||
</div>
|
||||
);
|
7
ghost/members-auth-pages/components/FormSubmit.js
Normal file
7
ghost/members-auth-pages/components/FormSubmit.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
export default ({onClick, label}) => (
|
||||
<div className="mt6">
|
||||
<button type="submit" className="gm-btn-blue" onClick={onClick}>
|
||||
{ label }
|
||||
</button>
|
||||
</div>
|
||||
);
|
54
ghost/members-auth-pages/components/MembersProvider.js
Normal file
54
ghost/members-auth-pages/components/MembersProvider.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { Component } from 'preact';
|
||||
|
||||
const layer0 = require('../layer0');
|
||||
|
||||
export default class MembersProvider extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.setGatewayFrame = gatewayFrame => this.gatewayFrame = gatewayFrame;
|
||||
this.gateway = null;
|
||||
}
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
members: {
|
||||
createSubscription: this.createMethod('createSubscription'),
|
||||
signin: this.createMethod('signin'),
|
||||
signup: this.createMethod('signup'),
|
||||
requestPasswordReset: this.createMethod('requestPasswordReset'),
|
||||
resetPassword: this.createMethod('resetPassword')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render({apiUrl, children}) {
|
||||
const src = `${apiUrl}/members/gateway`;
|
||||
return (
|
||||
<div>
|
||||
{ children }
|
||||
<iframe src={src} ref={this.setGatewayFrame} id="members-gateway" style="display: none;"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const gatewayFrame = this.gatewayFrame;
|
||||
gatewayFrame.addEventListener('load', () => {
|
||||
this.gateway = layer0(gatewayFrame)
|
||||
});
|
||||
}
|
||||
|
||||
createMethod(method) {
|
||||
return (options) => {
|
||||
return new Promise((resolve, reject) =>
|
||||
this.gateway.call(method, options, (err, successful) => {
|
||||
if (err || !successful) {
|
||||
reject(err || !successful);
|
||||
}
|
||||
resolve(successful);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
68
ghost/members-auth-pages/components/Modal.js
Normal file
68
ghost/members-auth-pages/components/Modal.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
import { Component } from 'preact';
|
||||
|
||||
import Pages from './Pages';
|
||||
|
||||
import SigninPage from '../pages/SigninPage';
|
||||
import SignupPage from '../pages/SignupPage';
|
||||
import RequestPasswordResetPage from '../pages/RequestPasswordResetPage';
|
||||
import PasswordResetSentPage from '../pages/PasswordResetSentPage';
|
||||
import ResetPasswordPage from '../pages/ResetPasswordPage';
|
||||
|
||||
export default class Modal extends Component {
|
||||
constructor(props, context) {
|
||||
super();
|
||||
this.state = {
|
||||
error: null,
|
||||
containerClass: 'gm-page-overlay'
|
||||
}
|
||||
}
|
||||
|
||||
handleAction(promise) {
|
||||
promise.then((success) => {
|
||||
this.close(success);
|
||||
}, (error) => {
|
||||
this.setState({error});
|
||||
});
|
||||
}
|
||||
|
||||
render(props, state) {
|
||||
const { queryToken } = props;
|
||||
const { containerClass, error } = state;
|
||||
const { members } = this.context;
|
||||
|
||||
const closeModal = () => this.close();
|
||||
const clearError = () => this.setState({error: null});
|
||||
|
||||
const signin = (data) => this.handleAction(members.signin(data));
|
||||
const signup = (data) => this.handleAction(members.signup(data));
|
||||
const requestReset = (data) => this.handleAction(members.requestPasswordReset(data));
|
||||
const resetPassword = (data) => this.handleAction(members.resetPassword(data));
|
||||
|
||||
return (
|
||||
<Pages className={containerClass} onChange={clearError} onClick={closeModal}>
|
||||
<SigninPage error={error} hash="" handleSubmit={signup} handleClose={closeModal}/>
|
||||
<SigninPage error={error} hash="signin" handleSubmit={signin} handleClose={closeModal}/>
|
||||
<SignupPage error={error} hash="signup" handleSubmit={signup} handleClose={closeModal}/>
|
||||
<RequestPasswordResetPage error={error} hash="request-password-reset" handleSubmit={requestReset} handleClose={closeModal}/>
|
||||
<PasswordResetSentPage error={error} hash="password-reset-sent" handleSubmit={requestReset} handleClose={closeModal}/>
|
||||
<ResetPasswordPage error={error} hash="reset-password" handleSubmit={resetPassword} handleClose={closeModal}/>
|
||||
</Pages>
|
||||
);
|
||||
}
|
||||
|
||||
close(success) {
|
||||
this.setState({
|
||||
containerClass: 'gm-page-overlay close'
|
||||
});
|
||||
|
||||
window.setTimeout(() => {
|
||||
this.setState({
|
||||
containerClass: 'gm-page-overlay'
|
||||
});
|
||||
window.parent.postMessage({
|
||||
msg: 'pls-close-auth-popup',
|
||||
success
|
||||
}, '*');
|
||||
}, 700);
|
||||
}
|
||||
}
|
18
ghost/members-auth-pages/components/NameInput.js
Normal file
18
ghost/members-auth-pages/components/NameInput.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import FormInput from './FormInput';
|
||||
import { IconName } from './icons';
|
||||
|
||||
export default ({value, error, children, onInput, className}) => (
|
||||
<FormInput
|
||||
type="text"
|
||||
name="name"
|
||||
label="Name"
|
||||
value={value}
|
||||
error={error}
|
||||
icon={IconName}
|
||||
placeholder="Name..."
|
||||
required={true}
|
||||
className={className}
|
||||
onInput={onInput}>
|
||||
{children}
|
||||
</FormInput>
|
||||
);
|
41
ghost/members-auth-pages/components/Pages.js
Normal file
41
ghost/members-auth-pages/components/Pages.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { Component } from 'preact';
|
||||
|
||||
export default class Pages extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = this.getStateFromBrowser();
|
||||
window.addEventListener("hashchange", () => this.onHashChange(), false);
|
||||
this.handleChange = props.onChange || (() => {});
|
||||
}
|
||||
|
||||
getStateFromBrowser() {
|
||||
const [fullMatch, hash, query] = window.location.hash.match(/^#([^?]+)\??(.*)$/) || ['#', '', ''];
|
||||
return {
|
||||
hash,
|
||||
query,
|
||||
fullMatch
|
||||
};
|
||||
}
|
||||
|
||||
onHashChange() {
|
||||
this.setState(this.getStateFromBrowser());
|
||||
this.handleChange();
|
||||
}
|
||||
|
||||
filterChildren(children, state) {
|
||||
return children.filter((child) => {
|
||||
return child.attributes.hash === state.hash;
|
||||
}).map((child) => {
|
||||
child.attributes.frameLocation = { ...this.state };
|
||||
return child;
|
||||
});
|
||||
}
|
||||
|
||||
render({children, className, onClick}, state) {
|
||||
return (
|
||||
<div className={className} onClick={onClick}>
|
||||
{ this.filterChildren(children, state) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
18
ghost/members-auth-pages/components/PasswordInput.js
Normal file
18
ghost/members-auth-pages/components/PasswordInput.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import FormInput from './FormInput';
|
||||
import { IconLock } from './icons';
|
||||
|
||||
export default ({value, error, children, onInput, className}) => (
|
||||
<FormInput
|
||||
type="password"
|
||||
name="password"
|
||||
label="Password"
|
||||
value={value}
|
||||
error={error}
|
||||
icon={IconLock}
|
||||
placeholder="Password..."
|
||||
required={true}
|
||||
className={className}
|
||||
onInput={onInput}>
|
||||
{ children }
|
||||
</FormInput>
|
||||
);
|
|
@ -1,449 +1,24 @@
|
|||
import './styles/members.css';
|
||||
import {IconEmail, IconLock, IconName, IconClose, IconError} from './components/icons';
|
||||
import { Component } from 'preact';
|
||||
const origin = new URL(window.location).origin;
|
||||
const membersApi = location.pathname.replace(/\/members\/auth\/?$/, '/ghost/api/v2/members');
|
||||
const storage = window.localStorage;
|
||||
var layer0 = require('./layer0');
|
||||
|
||||
function getFreshState() {
|
||||
const [hash, formType, query] = window.location.hash.match(/^#([^?]+)\??(.*)$/) || ['#signin?', 'signin', ''];
|
||||
return {
|
||||
formData: {},
|
||||
query,
|
||||
formType,
|
||||
parentContainerClass: 'gm-page-overlay',
|
||||
showError: false,
|
||||
submitFail: false
|
||||
};
|
||||
}
|
||||
import MembersProvider from './components/MembersProvider';
|
||||
import Modal from './components/Modal';
|
||||
|
||||
export default class App extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = getFreshState();
|
||||
this.gatewayFrame = '';
|
||||
window.addEventListener("hashchange", () => this.onHashChange(), false);
|
||||
}
|
||||
const apiUrl = window.location.href.substring(0, window.location.href.indexOf('/members/auth'));
|
||||
|
||||
loadGateway() {
|
||||
const blogUrl = window.location.href.substring(0, window.location.href.indexOf('/members/auth'));
|
||||
const frame = window.document.createElement('iframe');
|
||||
frame.id = 'member-gateway';
|
||||
frame.style.display = 'none';
|
||||
frame.src = `${blogUrl}/members/gateway`;
|
||||
frame.onload = () => {
|
||||
this.gatewayFrame = layer0(frame);
|
||||
this.state = {
|
||||
apiUrl
|
||||
};
|
||||
document.body.appendChild(frame);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadGateway();
|
||||
}
|
||||
|
||||
onHashChange() {
|
||||
this.setState(getFreshState());
|
||||
}
|
||||
|
||||
onInputChange(e, name) {
|
||||
let value = e.target.value;
|
||||
this.setState({
|
||||
formData: {
|
||||
...this.state.formData,
|
||||
[name]: value
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
submitForm(e) {
|
||||
e.preventDefault();
|
||||
if (this.hasFrontendError(this.state.formType)) {
|
||||
return false;
|
||||
}
|
||||
switch (this.state.formType) {
|
||||
case 'signin':
|
||||
this.signin(this.state.formData);
|
||||
break;
|
||||
case 'signup':
|
||||
this.signup(this.state.formData);
|
||||
break;
|
||||
case 'request-password-reset':
|
||||
this.requestPasswordReset(this.state.formData);
|
||||
break;
|
||||
case 'password-reset-sent':
|
||||
this.resendPasswordResetEmail(this.state.formData)
|
||||
break;
|
||||
case 'reset-password':
|
||||
this.resetPassword(this.state.formData)
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
signin({ email, password }) {
|
||||
this.gatewayFrame.call('signin', {email, password}, (err, successful) => {
|
||||
if (err || !successful) {
|
||||
this.setState({
|
||||
submitFail: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
signup({ name, email, password }) {
|
||||
this.gatewayFrame.call('signup', { name, email, password }, (err, successful) => {
|
||||
if (err || !successful) {
|
||||
this.setState({
|
||||
submitFail: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
requestPasswordReset({ email }) {
|
||||
this.gatewayFrame.call('request-password-reset', {email}, (err, successful) => {
|
||||
if (err || !successful) {
|
||||
this.setState({
|
||||
submitFail: true
|
||||
});
|
||||
} else {
|
||||
window.location.hash = 'password-reset-sent';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resendPasswordResetEmail({ email }) {
|
||||
this.gatewayFrame.call('request-password-reset', {email}, (err, successful) => {
|
||||
if (err || !successful) {
|
||||
this.setState({
|
||||
submitFail: true
|
||||
});
|
||||
} else {
|
||||
window.location.hash = 'password-reset-sent';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resetPassword({ password }) {
|
||||
const queryParams = new URLSearchParams(this.state.query);
|
||||
const token = queryParams.get('token') || '';
|
||||
this.gatewayFrame.call('reset-password', {password, token}, (err, successful) => {
|
||||
if (err || !successful) {
|
||||
this.setState({
|
||||
submitFail: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hasFrontendError(formType = this.state.formType) {
|
||||
switch(formType) {
|
||||
case 'signin':
|
||||
return (
|
||||
this.hasError({errorType: 'no-input', data: 'email'}) ||
|
||||
this.hasError({errorType: 'no-input', data: 'password'})
|
||||
);
|
||||
case 'signup':
|
||||
return (
|
||||
this.hasError({errorType: 'no-input', data: 'email'}) ||
|
||||
this.hasError({errorType: 'no-input', data: 'password'}) ||
|
||||
this.hasError({errorType: 'no-input', data: 'name'})
|
||||
);
|
||||
case 'request-password-reset':
|
||||
return (
|
||||
this.hasError({errorType: 'no-input', data: 'email'})
|
||||
);
|
||||
case 'reset-password':
|
||||
return (
|
||||
this.hasError({errorType: 'no-input', data: 'password'})
|
||||
);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
hasError({errorType, data}) {
|
||||
if (!this.state.showError) {
|
||||
return false;
|
||||
}
|
||||
let value = '';
|
||||
switch(errorType) {
|
||||
case 'no-input':
|
||||
value = this.state.formData[data];
|
||||
return (!value);
|
||||
case 'form-submit':
|
||||
return this.state.submitFail;
|
||||
}
|
||||
}
|
||||
|
||||
renderError({error, formType}) {
|
||||
if (this.hasError(error)) {
|
||||
let errorLabel = '';
|
||||
switch(error.errorType) {
|
||||
case 'no-input':
|
||||
errorLabel = `Enter ${error.data}`;
|
||||
break;
|
||||
case 'form-submit':
|
||||
switch(formType) {
|
||||
case 'signin':
|
||||
errorLabel = "Wrong email or password";
|
||||
break;
|
||||
case 'signup':
|
||||
errorLabel = "Email already registered"
|
||||
break;
|
||||
case 'request-password-reset':
|
||||
errorLabel = "Unable to send email"
|
||||
break;
|
||||
case 'password-reset-sent':
|
||||
errorLabel = "Unable to send email"
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<span>{ errorLabel }</span>
|
||||
)
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderFormHeaders(formType) {
|
||||
let mainTitle = '';
|
||||
let ctaTitle = '';
|
||||
let ctaLabel = '';
|
||||
let hash = '';
|
||||
switch (formType) {
|
||||
case 'signup':
|
||||
mainTitle = 'Sign up';
|
||||
ctaTitle = 'Already a member?';
|
||||
ctaLabel = 'Log in';
|
||||
hash = 'signin';
|
||||
break;
|
||||
case 'signin':
|
||||
mainTitle = 'Log in';
|
||||
ctaTitle = 'Not a member?';
|
||||
ctaLabel = 'Sign up';
|
||||
hash = 'signup';
|
||||
break;
|
||||
case 'request-password-reset':
|
||||
mainTitle = 'Reset password';
|
||||
break;
|
||||
case 'password-reset-sent':
|
||||
mainTitle = 'Reset password';
|
||||
break;
|
||||
case 'reset-password':
|
||||
mainTitle = 'Reset password';
|
||||
break;
|
||||
}
|
||||
let formError = this.renderError({ error: {errorType: "form-submit"}, formType });
|
||||
return (
|
||||
<div>
|
||||
<div className="gm-logo"></div>
|
||||
<div className="gm-auth-header">
|
||||
<h1>{ mainTitle }</h1>
|
||||
{(ctaTitle ?
|
||||
<div className="flex items-baseline mt2">
|
||||
<h4>{ ctaTitle }</h4>
|
||||
<a href="javascript:;"
|
||||
onClick={ (e) => { window.location.hash = hash } }
|
||||
>
|
||||
{ ctaLabel }
|
||||
</a>
|
||||
</div>
|
||||
: "")}
|
||||
</div>
|
||||
{(formError ? <div class="gm-form-errortext"><i>{ IconError }</i> { formError }</div> : "")}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderFormFooters(formType) {
|
||||
let mainTitle = '';
|
||||
let ctaTitle = '';
|
||||
let ctaLabel = '';
|
||||
let hash = '';
|
||||
switch (formType) {
|
||||
case 'request-password-reset':
|
||||
ctaTitle = 'Back to';
|
||||
ctaLabel = 'log in';
|
||||
hash = 'signin';
|
||||
break;
|
||||
case 'password-reset-sent':
|
||||
ctaTitle = 'Back to';
|
||||
ctaLabel = 'log in';
|
||||
hash = 'signin';
|
||||
break;
|
||||
case 'reset-password':
|
||||
ctaTitle = 'Back to';
|
||||
ctaLabel = 'log in';
|
||||
hash = 'signin';
|
||||
break;
|
||||
}
|
||||
if (ctaTitle) {
|
||||
return (
|
||||
<div className="gm-auth-footer">
|
||||
<div className="flex items-baseline">
|
||||
<h4>{ ctaTitle }</h4>
|
||||
<a href="javascript:;"
|
||||
onClick={ (e) => { window.location.hash = hash } }
|
||||
>
|
||||
{ ctaLabel }
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
renderFormInput({type, name, label, icon, placeholder, required, formType}) {
|
||||
let value = this.state.formData[name];
|
||||
let className = "";
|
||||
let forgot = (type === 'password' && formType === 'signin');
|
||||
let inputError = this.renderError({ error: {errorType: 'no-input', data: name}, formType });
|
||||
className += (value ? "gm-input-filled" : "") + (forgot ? " gm-forgot-input" : "") + (inputError ? " gm-error" : "");
|
||||
|
||||
return (
|
||||
<div className="gm-form-element">
|
||||
<div className="gm-input">
|
||||
<input
|
||||
type={ type }
|
||||
name={ name }
|
||||
key={ name }
|
||||
placeholder={ placeholder }
|
||||
value={ value || '' }
|
||||
onInput={ (e) => this.onInputChange(e, name) }
|
||||
required = {required}
|
||||
className={ className }
|
||||
/>
|
||||
<label for={ name }> { placeholder }</label>
|
||||
<i>{ icon }</i>
|
||||
{ (forgot ? <a href="javascript:;" className="gm-forgot-link" onClick={(e) => {window.location.hash = 'request-password-reset'}}>Forgot</a> : "") }
|
||||
</div>
|
||||
{/* { (inputError ? <div class="gm-input-errortext">{ inputError }</div> : "")} */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderFormText({formType}) {
|
||||
return (
|
||||
<div className="gm-reset-sent">
|
||||
<p>We’ve sent a recovery email to your inbox. Follow the link in the email to reset your password.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
onSubmitClick(e) {
|
||||
this.setState({
|
||||
showError: true,
|
||||
submitFail: false
|
||||
});
|
||||
}
|
||||
|
||||
renderFormSubmit({buttonLabel, formType}) {
|
||||
return (
|
||||
<div className="mt6">
|
||||
<button type="submit" name={ formType } className="gm-btn-blue" onClick={(e) => this.onSubmitClick(e)}>{ buttonLabel }</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderFormSection(formType) {
|
||||
const emailInput = this.renderFormInput({
|
||||
type: 'email',
|
||||
name: 'email',
|
||||
label: 'Email',
|
||||
icon: IconEmail,
|
||||
placeholder: 'Email...',
|
||||
required: true,
|
||||
formType: formType
|
||||
});
|
||||
const passwordInput = this.renderFormInput({
|
||||
type: 'password',
|
||||
name: 'password',
|
||||
label: 'Password',
|
||||
icon: IconLock,
|
||||
placeholder: 'Password...',
|
||||
required: true,
|
||||
formType: formType
|
||||
});
|
||||
const nameInput = this.renderFormInput({
|
||||
type: 'text',
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
icon: IconName,
|
||||
placeholder: 'Name...',
|
||||
required: true,
|
||||
formType: formType
|
||||
});
|
||||
const formText = this.renderFormText({formType});
|
||||
|
||||
let formElements = [];
|
||||
let buttonLabel = '';
|
||||
let formContainerClass = 'flex flex-column'
|
||||
switch (formType) {
|
||||
case 'signin':
|
||||
buttonLabel = 'Log in';
|
||||
formElements = [emailInput, passwordInput, this.renderFormSubmit({formType, buttonLabel})];
|
||||
formContainerClass += ' mt3'
|
||||
break;
|
||||
case 'signup':
|
||||
buttonLabel = 'Sign up';
|
||||
formElements = [nameInput, emailInput, passwordInput, this.renderFormSubmit({formType, buttonLabel})];
|
||||
formContainerClass += ' mt3'
|
||||
break;
|
||||
case 'request-password-reset':
|
||||
buttonLabel = 'Send reset password instructions';
|
||||
formElements = [emailInput, this.renderFormSubmit({formType, buttonLabel})];
|
||||
break;
|
||||
case 'password-reset-sent':
|
||||
buttonLabel = 'Resend instructions';
|
||||
formElements = [formText, this.renderFormSubmit({formType, buttonLabel})];
|
||||
break;
|
||||
case 'reset-password':
|
||||
buttonLabel = 'Set password';
|
||||
formElements = [passwordInput, this.renderFormSubmit({formType, buttonLabel})];
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<div className={formContainerClass}>
|
||||
<form className={ `gm-` + formType + `-form` } onSubmit={(e) => this.submitForm(e)} noValidate>
|
||||
{ formElements }
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderFormComponent(formType = this.state.formType) {
|
||||
return (
|
||||
<div className="gm-modal-container">
|
||||
<div className="gm-modal gm-auth-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<a className="gm-modal-close" onClick={ (e) => this.close(e)}>{ IconClose }</a>
|
||||
{this.renderFormHeaders(formType)}
|
||||
{this.renderFormSection(formType)}
|
||||
{this.renderFormFooters(formType)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={this.state.parentContainerClass} onClick={(e) => this.close(e)}>
|
||||
{this.renderFormComponent()}
|
||||
</div>
|
||||
<MembersProvider apiUrl={ this.state.apiUrl }>
|
||||
<Modal />
|
||||
</MembersProvider>
|
||||
);
|
||||
}
|
||||
|
||||
close(event) {
|
||||
this.setState({
|
||||
parentContainerClass: 'gm-page-overlay close'
|
||||
});
|
||||
|
||||
window.setTimeout(() => {
|
||||
this.setState({
|
||||
parentContainerClass: 'gm-page-overlay'
|
||||
});
|
||||
window.parent.postMessage('pls-close-auth-popup', '*');
|
||||
}, 700);
|
||||
}
|
||||
}
|
||||
|
|
18
ghost/members-auth-pages/pages/PasswordResetSentPage.js
Normal file
18
ghost/members-auth-pages/pages/PasswordResetSentPage.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import FormHeader from '../components/FormHeader';
|
||||
import FormSubmit from '../components/FormSubmit';
|
||||
import { IconClose } from '../components/icons';
|
||||
|
||||
export default ({error, handleClose, handleSubmit}) => (
|
||||
<div className="gm-modal-container">
|
||||
<div className="gm-modal gm-auth-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<a className="gm-modal-close" onClick={handleClose}>{ IconClose }</a>
|
||||
<FormHeader title="Sign up" error={error} errorText="Unable to send email"/>
|
||||
<Form bindTo="request-password-reset" onSubmit={handleSubmit}>
|
||||
<div className="gm-reset-sent">
|
||||
<p>We’ve sent a recovery email to your inbox. Follow the link in the email to reset your password.</p>
|
||||
</div>
|
||||
<FormSubmit label="Resend instructions" />
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
19
ghost/members-auth-pages/pages/RequestPasswordResetPage.js
Normal file
19
ghost/members-auth-pages/pages/RequestPasswordResetPage.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import FormHeader from '../components/FormHeader';
|
||||
import EmailInput from '../components/EmailInput';
|
||||
import FormSubmit from '../components/FormSubmit';
|
||||
import { IconClose } from '../components/icons';
|
||||
|
||||
import Form from '../components/Form';
|
||||
|
||||
export default ({error, handleClose, handleSubmit}) => (
|
||||
<div className="gm-modal-container">
|
||||
<div className="gm-modal gm-auth-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<a className="gm-modal-close" onClick={handleClose}>{ IconClose }</a>
|
||||
<FormHeader title="Reset password" error={error} errorText="Unable to send email"/>
|
||||
<Form bindTo="request-password-reset" onSubmit={handleSubmit}>
|
||||
<EmailInput bindTo="email"/>
|
||||
<FormSubmit label="Send reset password instructions"/>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
25
ghost/members-auth-pages/pages/ResetPasswordPage.js
Normal file
25
ghost/members-auth-pages/pages/ResetPasswordPage.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import FormHeader from '../components/FormHeader';
|
||||
import PasswordInput from '../components/PasswordInput';
|
||||
import FormSubmit from '../components/FormSubmit';
|
||||
import Form from '../components/Form';
|
||||
import { IconClose } from '../components/icons';
|
||||
|
||||
const getTokenData = frameLocation => {
|
||||
const params = new URLSearchParams(frameLocation.query);
|
||||
const token = params.get('token') || '';
|
||||
return { token };
|
||||
|
||||
};
|
||||
|
||||
export default ({error, frameLocation, handleClose, handleSubmit}) => (
|
||||
<div className="gm-modal-container">
|
||||
<div className="gm-modal gm-auth-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<a className="gm-modal-close" onClick={handleClose}>{ IconClose }</a>
|
||||
<FormHeader title="Reset password" error={error} errorText="Unable to reset password"/>
|
||||
<Form includeData={getTokenData(frameLocation)} onSubmit={handleSubmit}>
|
||||
<PasswordInput bindTo="password" />
|
||||
<FormSubmit label="Set password" />
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
29
ghost/members-auth-pages/pages/SigninPage.js
Normal file
29
ghost/members-auth-pages/pages/SigninPage.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import Form from '../components/Form';
|
||||
import FormHeader from '../components/FormHeader';
|
||||
import FormHeaderCTA from '../components/FormHeaderCTA';
|
||||
import FormSubmit from '../components/FormSubmit';
|
||||
|
||||
import EmailInput from '../components/EmailInput';
|
||||
import PasswordInput from '../components/PasswordInput';
|
||||
|
||||
import { IconClose } from '../components/icons';
|
||||
|
||||
export default ({error, handleClose, handleSubmit}) => (
|
||||
<div className="gm-modal-container">
|
||||
<div className="gm-modal gm-auth-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<a className="gm-modal-close" onClick={handleClose}>{ IconClose }</a>
|
||||
<FormHeader title="Log in" error={error} errorText="Wrong email or password">
|
||||
<FormHeaderCTA title="Not a member?" label="Sign up" hash="#signup" />
|
||||
</FormHeader>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<EmailInput bindTo="email"/>
|
||||
<PasswordInput bindTo="password" className="gm-forgot-input">
|
||||
<a href="#request-password-reset" className="gm-forgot-link">
|
||||
Forgot
|
||||
</a>
|
||||
</PasswordInput>
|
||||
<FormSubmit label="Log in"/>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
26
ghost/members-auth-pages/pages/SignupPage.js
Normal file
26
ghost/members-auth-pages/pages/SignupPage.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import Form from '../components/Form';
|
||||
import FormHeader from '../components/FormHeader';
|
||||
import FormHeaderCTA from '../components/FormHeaderCTA';
|
||||
import FormSubmit from '../components/FormSubmit';
|
||||
|
||||
import NameInput from '../components/NameInput';
|
||||
import EmailInput from '../components/EmailInput';
|
||||
import PasswordInput from '../components/PasswordInput';
|
||||
import { IconClose } from '../components/icons';
|
||||
|
||||
export default ({error, handleClose, handleSubmit}) => (
|
||||
<div className="gm-modal-container">
|
||||
<div className="gm-modal gm-auth-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<a className="gm-modal-close" onClick={handleClose}>{ IconClose }</a>
|
||||
<FormHeader title="Sign up" error={error} errorText="Email already registered">
|
||||
<FormHeaderCTA title="Already a member?" label="Log in" hash="#signin" />
|
||||
</FormHeader>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<NameInput bindTo="name" />
|
||||
<EmailInput bindTo="email" />
|
||||
<PasswordInput bindTo="password" />
|
||||
<FormSubmit label="Sign up" />
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
Loading…
Add table
Reference in a new issue