Ghost/ghost/members-auth-pages/index.js

450 lines
15 KiB
JavaScript
Raw Normal View History

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
};
}
export default class App extends Component {
constructor() {
super();
this.state = getFreshState();
this.gatewayFrame = '';
window.addEventListener("hashchange", () => this.onHashChange(), false);
}
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);
};
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>Weve 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>
);
}
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);
}
}