2019-07-29 22:29:04 -04:00
|
|
|
const bindAll = require('lodash.bindall');
|
2019-08-01 15:40:46 -04:00
|
|
|
const classNames = require('classnames');
|
2019-07-29 22:29:04 -04:00
|
|
|
const React = require('react');
|
|
|
|
const PropTypes = require('prop-types');
|
|
|
|
import {Formik} from 'formik';
|
|
|
|
const {injectIntl, intlShape} = require('react-intl');
|
2019-08-13 12:16:42 -04:00
|
|
|
const FormattedMessage = require('react-intl').FormattedMessage;
|
2019-07-29 22:29:04 -04:00
|
|
|
|
2019-08-25 11:31:07 -04:00
|
|
|
const validate = require('../../lib/validate');
|
2019-07-29 22:29:04 -04:00
|
|
|
const JoinFlowStep = require('./join-flow-step.jsx');
|
2019-08-01 15:40:46 -04:00
|
|
|
const FormikInput = require('../../components/formik-forms/formik-input.jsx');
|
2019-08-16 17:13:24 -04:00
|
|
|
const FormikCheckbox = require('../../components/formik-forms/formik-checkbox.jsx');
|
2019-09-15 17:05:47 -04:00
|
|
|
const InfoButton = require('../info-button/info-button.jsx');
|
2019-07-29 22:29:04 -04:00
|
|
|
|
|
|
|
require('./join-flow-steps.scss');
|
|
|
|
|
|
|
|
class EmailStep extends React.Component {
|
|
|
|
constructor (props) {
|
|
|
|
super(props);
|
|
|
|
bindAll(this, [
|
2019-08-26 16:02:07 -04:00
|
|
|
'handleSetEmailRef',
|
2019-07-29 22:29:04 -04:00
|
|
|
'handleValidSubmit',
|
2019-08-27 15:42:33 -04:00
|
|
|
'validateEmail',
|
2019-09-27 12:26:25 -04:00
|
|
|
'validateEmailRemotelyWithCache',
|
2019-08-20 17:05:43 -04:00
|
|
|
'validateForm',
|
|
|
|
'setCaptchaRef',
|
2019-08-27 15:42:33 -04:00
|
|
|
'captchaSolved',
|
|
|
|
'onCaptchaLoad',
|
|
|
|
'onCaptchaError'
|
2019-07-29 22:29:04 -04:00
|
|
|
]);
|
2019-08-27 16:29:54 -04:00
|
|
|
this.state = {
|
|
|
|
captchaIsLoading: true
|
|
|
|
};
|
2019-09-27 12:26:25 -04:00
|
|
|
// simple object to memoize remote requests for email addresses.
|
|
|
|
// keeps us from submitting multiple requests for same data.
|
|
|
|
this.emailRemoteCache = {};
|
2019-07-29 22:29:04 -04:00
|
|
|
}
|
2019-08-26 16:02:07 -04:00
|
|
|
componentDidMount () {
|
|
|
|
// automatically start with focus on username field
|
|
|
|
if (this.emailInput) this.emailInput.focus();
|
2019-08-29 11:33:50 -04:00
|
|
|
|
2019-10-17 22:22:44 -04:00
|
|
|
if (window.grecaptcha) {
|
|
|
|
this.onCaptchaLoad();
|
|
|
|
} else {
|
|
|
|
// If grecaptcha doesn't exist on window, we havent loaded the captcha js yet. Load it.
|
2019-08-27 16:36:10 -04:00
|
|
|
// ReCaptcha calls a callback when the grecatpcha object is usable. That callback
|
|
|
|
// needs to be global so set it on the window.
|
|
|
|
window.grecaptchaOnLoad = this.onCaptchaLoad;
|
|
|
|
// Load Google ReCaptcha script.
|
|
|
|
const script = document.createElement('script');
|
|
|
|
script.async = true;
|
|
|
|
script.onerror = this.onCaptchaError;
|
|
|
|
script.src = `https://www.recaptcha.net/recaptcha/api.js?onload=grecaptchaOnLoad&render=explicit&hl=${window._locale}`;
|
|
|
|
document.body.appendChild(script);
|
|
|
|
}
|
2019-08-27 16:29:54 -04:00
|
|
|
}
|
|
|
|
componentWillUnmount () {
|
|
|
|
window.grecaptchaOnLoad = null;
|
|
|
|
}
|
2019-08-29 11:33:50 -04:00
|
|
|
handleSetEmailRef (emailInputRef) {
|
|
|
|
this.emailInput = emailInputRef;
|
|
|
|
}
|
2019-08-27 16:29:54 -04:00
|
|
|
onCaptchaError () {
|
2019-09-19 13:40:09 -04:00
|
|
|
this.props.onRegistrationError(
|
2019-09-18 10:26:37 -04:00
|
|
|
this.props.intl.formatMessage({
|
2019-10-31 23:21:21 -04:00
|
|
|
id: 'registration.troubleReload'
|
2019-09-18 10:26:37 -04:00
|
|
|
})
|
|
|
|
);
|
2019-08-27 16:29:54 -04:00
|
|
|
}
|
|
|
|
onCaptchaLoad () {
|
|
|
|
this.setState({captchaIsLoading: false});
|
2019-08-20 17:05:43 -04:00
|
|
|
this.grecaptcha = window.grecaptcha;
|
|
|
|
if (!this.grecaptcha) {
|
2019-08-27 15:42:33 -04:00
|
|
|
// According to the reCaptcha documentation, this callback shouldn't get
|
|
|
|
// called unless window.grecaptcha exists. This is just here to be extra defensive.
|
2019-09-18 10:26:37 -04:00
|
|
|
this.onCaptchaError();
|
|
|
|
return;
|
2019-08-20 17:05:43 -04:00
|
|
|
}
|
|
|
|
this.widgetId = this.grecaptcha.render(this.captchaRef,
|
|
|
|
{
|
|
|
|
callback: this.captchaSolved,
|
2019-08-21 13:59:49 -04:00
|
|
|
sitekey: process.env.RECAPTCHA_SITE_KEY
|
2019-08-20 17:05:43 -04:00
|
|
|
},
|
|
|
|
true);
|
2019-08-26 16:02:07 -04:00
|
|
|
}
|
2019-09-27 12:26:25 -04:00
|
|
|
// simple function to memoize remote requests for usernames
|
|
|
|
validateEmailRemotelyWithCache (email) {
|
|
|
|
if (this.emailRemoteCache.hasOwnProperty(email)) {
|
|
|
|
return Promise.resolve(this.emailRemoteCache[email]);
|
|
|
|
}
|
|
|
|
// email is not in our cache
|
|
|
|
return validate.validateEmailRemotely(email).then(
|
|
|
|
remoteResult => {
|
|
|
|
this.emailRemoteCache[email] = remoteResult;
|
|
|
|
return remoteResult;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2019-08-16 16:22:33 -04:00
|
|
|
validateEmail (email) {
|
|
|
|
if (!email) return this.props.intl.formatMessage({id: 'general.required'});
|
2019-08-25 11:31:07 -04:00
|
|
|
const localResult = validate.validateEmailLocally(email);
|
|
|
|
if (!localResult.valid) return this.props.intl.formatMessage({id: localResult.errMsgId});
|
2019-09-27 12:26:25 -04:00
|
|
|
return this.validateEmailRemotelyWithCache(email).then(
|
2019-08-25 11:31:07 -04:00
|
|
|
remoteResult => {
|
|
|
|
if (remoteResult.valid === true) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return this.props.intl.formatMessage({id: remoteResult.errMsgId});
|
|
|
|
}
|
|
|
|
);
|
2019-08-01 15:40:46 -04:00
|
|
|
}
|
2019-07-29 22:29:04 -04:00
|
|
|
validateForm () {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
handleValidSubmit (formData, formikBag) {
|
2019-08-20 17:05:43 -04:00
|
|
|
this.formData = formData;
|
|
|
|
this.formikBag = formikBag;
|
2019-08-27 15:51:48 -04:00
|
|
|
// Change set submitting to false so that if the user clicks out of
|
|
|
|
// the captcha, the button is clickable again (instead of a disabled button with a spinner).
|
|
|
|
this.formikBag.setSubmitting(false);
|
2019-08-20 17:05:43 -04:00
|
|
|
this.grecaptcha.execute(this.widgetId);
|
|
|
|
}
|
|
|
|
captchaSolved (token) {
|
2019-08-29 11:33:50 -04:00
|
|
|
// Now thatcaptcha is done, we can tell Formik we're submitting.
|
2019-08-27 15:51:48 -04:00
|
|
|
this.formikBag.setSubmitting(true);
|
2019-08-20 17:05:43 -04:00
|
|
|
this.formData['g-recaptcha-response'] = token;
|
|
|
|
this.props.onNextStep(this.formData);
|
|
|
|
}
|
|
|
|
setCaptchaRef (ref) {
|
|
|
|
this.captchaRef = ref;
|
2019-07-29 22:29:04 -04:00
|
|
|
}
|
|
|
|
render () {
|
|
|
|
return (
|
|
|
|
<Formik
|
|
|
|
initialValues={{
|
2019-08-21 08:25:21 -04:00
|
|
|
email: '',
|
|
|
|
subscribe: false
|
2019-07-29 22:29:04 -04:00
|
|
|
}}
|
|
|
|
validate={this.validateForm}
|
|
|
|
validateOnBlur={false}
|
|
|
|
validateOnChange={false}
|
|
|
|
onSubmit={this.handleValidSubmit}
|
|
|
|
>
|
|
|
|
{props => {
|
|
|
|
const {
|
2019-08-01 15:40:46 -04:00
|
|
|
errors,
|
2019-07-29 22:29:04 -04:00
|
|
|
handleSubmit,
|
2019-08-01 15:40:46 -04:00
|
|
|
isSubmitting,
|
2019-08-16 16:22:33 -04:00
|
|
|
setFieldError,
|
2019-08-25 11:31:29 -04:00
|
|
|
setFieldTouched,
|
|
|
|
setFieldValue,
|
2019-08-01 15:40:46 -04:00
|
|
|
validateField
|
2019-07-29 22:29:04 -04:00
|
|
|
} = props;
|
|
|
|
return (
|
|
|
|
<JoinFlowStep
|
2019-08-13 12:16:42 -04:00
|
|
|
footerContent={(
|
|
|
|
<FormattedMessage
|
|
|
|
id="registration.acceptTermsOfUse"
|
|
|
|
values={{
|
|
|
|
touLink: (
|
|
|
|
<a
|
|
|
|
className="join-flow-link"
|
|
|
|
href="/terms_of_use"
|
|
|
|
target="_blank"
|
|
|
|
>
|
|
|
|
<FormattedMessage id="general.termsOfUse" />
|
|
|
|
</a>
|
|
|
|
)
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
2019-10-03 19:25:56 -04:00
|
|
|
headerImgClass="email-step-image"
|
2019-08-16 15:05:15 -04:00
|
|
|
headerImgSrc="/images/join-flow/email-header.png"
|
2019-08-17 00:52:52 -04:00
|
|
|
innerClassName="join-flow-inner-email-step"
|
2019-08-10 23:18:33 -04:00
|
|
|
nextButton={this.props.intl.formatMessage({id: 'registration.createAccount'})}
|
2019-07-29 22:29:04 -04:00
|
|
|
title={this.props.intl.formatMessage({id: 'registration.emailStepTitle'})}
|
2019-09-15 17:11:43 -04:00
|
|
|
titleClassName="join-flow-email-title"
|
2019-08-19 20:45:16 -04:00
|
|
|
waiting={this.props.waiting || isSubmitting || this.state.captchaIsLoading}
|
2019-07-29 22:29:04 -04:00
|
|
|
onSubmit={handleSubmit}
|
2019-08-01 15:40:46 -04:00
|
|
|
>
|
|
|
|
<FormikInput
|
2019-10-01 16:43:33 -04:00
|
|
|
autoCapitalize="off"
|
|
|
|
autoComplete="off"
|
|
|
|
autoCorrect="off"
|
2019-08-01 15:40:46 -04:00
|
|
|
className={classNames(
|
|
|
|
'join-flow-input',
|
|
|
|
'join-flow-input-tall',
|
|
|
|
{fail: errors.email}
|
|
|
|
)}
|
|
|
|
error={errors.email}
|
|
|
|
id="email"
|
|
|
|
name="email"
|
2019-08-08 11:19:18 -04:00
|
|
|
placeholder={this.props.intl.formatMessage({id: 'general.emailAddress'})}
|
2019-10-01 16:43:33 -04:00
|
|
|
type="email"
|
2019-08-16 16:22:33 -04:00
|
|
|
validate={this.validateEmail}
|
2019-08-01 15:40:46 -04:00
|
|
|
validationClassName="validation-full-width-input"
|
2019-08-16 16:22:33 -04:00
|
|
|
/* eslint-disable react/jsx-no-bind */
|
|
|
|
onBlur={() => validateField('email')}
|
2019-08-25 11:31:29 -04:00
|
|
|
onChange={e => {
|
2019-10-01 18:15:38 -04:00
|
|
|
setFieldValue('email', e.target.value.substring(0, 254));
|
2019-08-25 11:31:29 -04:00
|
|
|
setFieldTouched('email');
|
|
|
|
setFieldError('email', null);
|
|
|
|
}}
|
2019-08-16 16:22:33 -04:00
|
|
|
/* eslint-enable react/jsx-no-bind */
|
2019-08-26 16:02:07 -04:00
|
|
|
onSetRef={this.handleSetEmailRef}
|
2019-08-01 15:40:46 -04:00
|
|
|
/>
|
2019-09-15 17:05:47 -04:00
|
|
|
<div className="join-flow-privacy-message join-flow-email-privacy">
|
|
|
|
<FormattedMessage id="registration.private" />
|
|
|
|
<InfoButton
|
|
|
|
message={this.props.intl.formatMessage({id: 'registration.emailStepInfo'})}
|
|
|
|
/>
|
|
|
|
</div>
|
2019-08-16 17:13:24 -04:00
|
|
|
<div className="join-flow-email-checkbox-row">
|
|
|
|
<FormikCheckbox
|
|
|
|
id="subscribeCheckbox"
|
|
|
|
label={this.props.intl.formatMessage({id: 'registration.receiveEmails'})}
|
|
|
|
name="subscribe"
|
|
|
|
/>
|
|
|
|
</div>
|
2019-08-20 17:05:43 -04:00
|
|
|
<div
|
|
|
|
className="g-recaptcha"
|
2019-08-20 17:37:52 -04:00
|
|
|
data-badge="bottomright"
|
2019-08-21 13:59:49 -04:00
|
|
|
data-sitekey={process.env.RECAPTCHA_SITE_KEY}
|
2019-08-20 17:05:43 -04:00
|
|
|
data-size="invisible"
|
|
|
|
ref={this.setCaptchaRef}
|
|
|
|
/>
|
2019-08-01 15:40:46 -04:00
|
|
|
</JoinFlowStep>
|
2019-07-29 22:29:04 -04:00
|
|
|
);
|
|
|
|
}}
|
|
|
|
</Formik>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
EmailStep.propTypes = {
|
|
|
|
intl: intlShape,
|
2019-08-19 20:45:16 -04:00
|
|
|
onNextStep: PropTypes.func,
|
2019-09-19 13:40:09 -04:00
|
|
|
onRegistrationError: PropTypes.func,
|
2019-08-19 20:45:16 -04:00
|
|
|
waiting: PropTypes.bool
|
2019-07-29 22:29:04 -04:00
|
|
|
};
|
|
|
|
|
2019-08-06 20:22:58 -04:00
|
|
|
|
2019-07-29 22:29:04 -04:00
|
|
|
module.exports = injectIntl(EmailStep);
|