join flow register user function, registration error component

* add yesno checkbox
* allow checkbox to be hidden
* add registration error, waiting prop to join flow steps
* use redux to dispatch session refresh
* call register function in join flow
* make join flow modal stay showing even when session is refreshed
This commit is contained in:
Ben Wheeler 2019-08-20 02:45:16 +02:00
parent 20358fecb0
commit 5269cf7330
7 changed files with 195 additions and 24 deletions

View file

@ -12,9 +12,10 @@ const FormikCheckboxSubComponent = ({
id,
label,
labelClassName,
outerClassName,
...props
}) => (
<div className="checkbox">
<div className={classNames('checkbox', outerClassName)}>
<input
checked={field.value}
className="formik-checkbox"
@ -50,7 +51,8 @@ FormikCheckboxSubComponent.propTypes = {
}),
id: PropTypes.string,
label: PropTypes.string,
labelClassName: PropTypes.string
labelClassName: PropTypes.string,
outerClassName: PropTypes.string
};
@ -59,6 +61,7 @@ const FormikCheckbox = ({
label,
labelClassName,
name,
outerClassName,
...props
}) => (
<Field
@ -67,6 +70,7 @@ const FormikCheckbox = ({
label={label}
labelClassName={labelClassName}
name={name}
outerClassName={outerClassName}
{...props}
/>
);
@ -75,7 +79,8 @@ FormikCheckbox.propTypes = {
id: PropTypes.string,
label: PropTypes.string,
labelClassName: PropTypes.string,
name: PropTypes.string
name: PropTypes.string,
outerClassName: PropTypes.string
};
module.exports = FormikCheckbox;

View file

@ -8,6 +8,7 @@ const {injectIntl, intlShape} = require('react-intl');
const countryData = require('../../lib/country-data');
const FormikSelect = require('../../components/formik-forms/formik-select.jsx');
const JoinFlowStep = require('./join-flow-step.jsx');
const FormikCheckbox = require('../../components/formik-forms/formik-checkbox.jsx');
require('./join-flow-steps.scss');
@ -94,6 +95,12 @@ class CountryStep extends React.Component {
validate={this.validateSelect}
validationClassName="validation-full-width-input"
/>
<FormikCheckbox
id="yesno"
label={this.props.intl.formatMessage({id: 'registration.receiveEmails'})}
name="yesno"
outerClassName="yesNoCheckbox"
/>
</div>
</JoinFlowStep>
);

View file

@ -151,7 +151,7 @@ class EmailStep extends React.Component {
innerClassName="join-flow-inner-email-step"
nextButton={this.props.intl.formatMessage({id: 'registration.createAccount'})}
title={this.props.intl.formatMessage({id: 'registration.emailStepTitle'})}
waiting={isSubmitting || this.state.captchaIsLoading}
waiting={this.props.waiting || isSubmitting || this.state.captchaIsLoading}
onSubmit={handleSubmit}
>
<FormikInput
@ -200,7 +200,8 @@ class EmailStep extends React.Component {
EmailStep.propTypes = {
intl: intlShape,
onNextStep: PropTypes.func
onNextStep: PropTypes.func,
waiting: PropTypes.bool
};

View file

@ -132,6 +132,10 @@
margin-left: -.5rem;
}
.join-flow-registration-error {
padding-top: 5.5rem;
}
.join-flow-gender-description {
margin-top: .625rem;
margin-bottom: 1.25rem;
@ -180,3 +184,7 @@
a.join-flow-link:link, a.join-flow-link:visited, a.join-flow-link:active {
text-decoration: underline;
}
.yesNoCheckbox {
display: none;
}

View file

@ -1,10 +1,13 @@
const bindAll = require('lodash.bindall');
const connect = require('react-redux').connect;
const defaults = require('lodash.defaultsdeep');
const PropTypes = require('prop-types');
const React = require('react');
const api = require('../../lib/api');
const injectIntl = require('../../lib/intl.jsx').injectIntl;
const intlShape = require('../../lib/intl.jsx').intlShape;
const sessionActions = require('../../redux/session.js');
const Progression = require('../progression/progression.jsx');
const UsernameStep = require('./username-step.jsx');
@ -13,6 +16,7 @@ const GenderStep = require('./gender-step.jsx');
const CountryStep = require('./country-step.jsx');
const EmailStep = require('./email-step.jsx');
const WelcomeStep = require('./welcome-step.jsx');
const RegistrationError = require('./registration-error.jsx');
/*
eslint-disable react/prefer-stateless-function, react/no-unused-prop-types, no-useless-constructor
@ -21,36 +25,124 @@ class JoinFlow extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleAdvanceStep'
'handleAdvanceStep',
'handleRegister',
'handleResetForm'
]);
this.state = {
formData: {},
registrationError: null,
step: 0
step: 0,
waiting: false
};
}
handleAdvanceStep (formData) {
formData = formData || {};
handleRegister (formData) {
this.setState({waiting: true}, () => {
api({
host: '',
uri: '/accounts/register_new_user/',
method: 'post',
useCsrf: true,
formData: {
username: formData.username,
email: formData.email,
password: formData.password,
birth_month: formData.birth_month,
birth_year: formData.birth_year,
gender: (
formData.gender === 'other' ?
formData.genderOther :
formData.gender
),
country: formData.country,
subscribe: true,
is_robot: formData.yesno
// csrfmiddlewaretoken: 'abc'
}
}, (err, body, res) => {
this.setState({waiting: false}, () => {
let errStr = '';
if (!err && res.statusCode === 200) {
if (body && body[0]) {
if (body[0].success) {
this.props.refreshSession();
this.setState({
step: this.state.step + 1
});
return;
}
if (body[0].errors) {
const errorKeys = Object.keys(body[0].errors);
errorKeys.forEach(key => {
const val = body[0].errors[key];
if (val && val[0]) {
if (errStr.length) errStr += '; ';
errStr += `${key}: ${val[0]}`;
}
});
}
if (!errStr.length && body[0].msg) errStr = body[0].msg;
}
}
this.setState({
registrationError: errStr ||
`${this.props.intl.formatMessage({
id: 'registration.generalError'
})} (${res.statusCode})`
});
});
});
});
}
handleAdvanceStep (newFormData) {
newFormData = newFormData || {};
const newState = {
formData: defaults({}, newFormData, this.state.formData)
};
// for the first 4 steps, automatically advance to next step.
// but for email step, we need to submit registration and wait.
const shouldAdvance = (this.state.step < 4);
const shouldRegister = (this.state.step === 4);
if (shouldAdvance) newState.step = this.state.step + 1;
this.setState(newState, () => {
if (shouldRegister) this.handleRegister(this.state.formData);
});
}
handleResetForm () {
this.setState({
step: this.state.step + 1,
formData: defaults({}, formData, this.state.formData)
formData: {},
registrationError: null,
step: 0,
waiting: false
});
}
render () {
return (
<React.Fragment>
<Progression step={this.state.step}>
<UsernameStep onNextStep={this.handleAdvanceStep} />
<BirthDateStep onNextStep={this.handleAdvanceStep} />
<GenderStep onNextStep={this.handleAdvanceStep} />
<CountryStep onNextStep={this.handleAdvanceStep} />
<EmailStep onNextStep={this.handleAdvanceStep} />
<WelcomeStep
email={this.state.formData.email}
username={this.state.formData.username}
onNextStep={this.handleAdvanceStep}
{this.state.registrationError ? (
<RegistrationError
errorMsg={this.state.registrationError}
/* eslint-disable react/jsx-no-bind */
onTryAgain={() => this.handleRegister(this.state.formData)}
/* eslint-enable react/jsx-no-bind */
/>
</Progression>
) : (
<Progression step={this.state.step}>
<UsernameStep onNextStep={this.handleAdvanceStep} />
<BirthDateStep onNextStep={this.handleAdvanceStep} />
<GenderStep onNextStep={this.handleAdvanceStep} />
<CountryStep onNextStep={this.handleAdvanceStep} />
<EmailStep
waiting={this.state.waiting}
onNextStep={this.handleAdvanceStep}
/>
<WelcomeStep
email={this.state.formData.email}
username={this.state.formData.username}
onNextStep={this.props.onCompleteRegistration}
/>
</Progression>
)}
</React.Fragment>
);
}
@ -58,11 +150,24 @@ class JoinFlow extends React.Component {
JoinFlow.propTypes = {
intl: intlShape,
onCompleteRegistration: PropTypes.func
onCompleteRegistration: PropTypes.func,
refreshSession: PropTypes.func
};
module.exports = injectIntl(JoinFlow);
const IntlJoinFlow = injectIntl(JoinFlow);
const mapDispatchToProps = dispatch => ({
refreshSession: () => {
dispatch(sessionActions.refreshSession());
}
});
const ConnectedJoinFlow = connect(
() => ({}),
mapDispatchToProps
)(IntlJoinFlow);
module.exports = ConnectedJoinFlow;
/*
eslint-enable
*/

View file

@ -0,0 +1,44 @@
const bindAll = require('lodash.bindall');
const React = require('react');
const PropTypes = require('prop-types');
const {injectIntl, intlShape} = require('react-intl');
const JoinFlowStep = require('./join-flow-step.jsx');
require('./join-flow-steps.scss');
class RegistrationError extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleSubmit'
]);
this.state = {
};
}
handleSubmit (e) {
e.preventDefault(); // prevent page reload
this.props.onTryAgain();
}
render () {
return (
<JoinFlowStep
description={this.props.errorMsg}
innerClassName="join-flow-registration-error"
nextButton={this.props.intl.formatMessage({id: 'general.tryAgain'})}
title={this.props.intl.formatMessage({id: 'registration.generalError'})}
onSubmit={this.handleSubmit}
/>
);
}
}
RegistrationError.propTypes = {
errorMsg: PropTypes.string,
intl: intlShape,
onTryAgain: PropTypes.func
};
const IntlRegistrationError = injectIntl(RegistrationError);
module.exports = IntlRegistrationError;

View file

@ -89,6 +89,7 @@
"general.ideas": "Ideas",
"general.tipsWindow": "Tips Window",
"general.termsOfUse": "Terms of Use",
"general.tryAgain": "Try again",
"general.unhandledError": "We are so sorry, but it looks like Scratch has crashed. This bug has been automatically reported to the Scratch Team.",
"general.username": "Username",
"general.validationEmail": "Please enter a valid email address",