mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-01-07 04:52:13 -05:00
222 lines
8.1 KiB
JavaScript
222 lines
8.1 KiB
JavaScript
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');
|
|
const BirthDateStep = require('./birthdate-step.jsx');
|
|
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 RegistrationErrorStep = require('./registration-error-step.jsx');
|
|
|
|
class JoinFlow extends React.Component {
|
|
constructor (props) {
|
|
super(props);
|
|
bindAll(this, [
|
|
'handleAdvanceStep',
|
|
'handleErrorNext',
|
|
'handleRegistrationError',
|
|
'handlePrepareToRegister',
|
|
'handleRegistrationResponse',
|
|
'handleSubmitRegistration'
|
|
]);
|
|
this.initialState = {
|
|
numAttempts: 0,
|
|
formData: {},
|
|
registrationError: null,
|
|
step: 0,
|
|
waiting: false
|
|
};
|
|
// it's ok to set state by reference, because state is treated as immutable,
|
|
// so any changes to its fields will result in a new state which does not
|
|
// reference its past fields
|
|
this.state = this.initialState;
|
|
}
|
|
canTryAgain () {
|
|
return (this.state.numAttempts <= 1);
|
|
}
|
|
handleRegistrationError (message) {
|
|
if (!message) {
|
|
message = this.props.intl.formatMessage({
|
|
id: 'registration.generalError'
|
|
});
|
|
}
|
|
this.setState({registrationError: message});
|
|
}
|
|
handlePrepareToRegister (newFormData) {
|
|
newFormData = newFormData || {};
|
|
const newState = {
|
|
formData: defaults({}, newFormData, this.state.formData)
|
|
};
|
|
this.setState(newState, () => {
|
|
this.handleSubmitRegistration(this.state.formData);
|
|
});
|
|
}
|
|
handleRegistrationResponse (err, body, res) {
|
|
// example of failing response:
|
|
// [
|
|
// {
|
|
// "msg": "This field is required.",
|
|
// "errors": {
|
|
// "username": ["This field is required."],
|
|
// "recaptcha": ["Incorrect, please try again."]
|
|
// },
|
|
// "success": false
|
|
// }
|
|
// ]
|
|
// username: 'username exists'
|
|
this.setState({
|
|
numAttempts: this.state.numAttempts + 1,
|
|
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) {
|
|
// body can include zero or more error objects, each
|
|
// with its own key and description. Here we assemble
|
|
// all of them into a single string, errStr.
|
|
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})`
|
|
});
|
|
});
|
|
}
|
|
handleSubmitRegistration (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,
|
|
'g-recaptcha-response': formData['g-recaptcha-response'],
|
|
'gender': formData.gender,
|
|
'country': formData.country,
|
|
'subscribe': true,
|
|
'is_robot': formData.yesno
|
|
// no need to include csrfmiddlewaretoken; will be provided in
|
|
// X-CSRFToken header, which scratchr2 looks for in
|
|
// scratchr2/middleware/csrf.py line 237.
|
|
}
|
|
}, (err, body, res) => {
|
|
this.handleRegistrationResponse(err, body, res);
|
|
});
|
|
});
|
|
}
|
|
handleAdvanceStep (newFormData) {
|
|
newFormData = newFormData || {};
|
|
this.setState({
|
|
formData: defaults({}, newFormData, this.state.formData),
|
|
step: this.state.step + 1
|
|
});
|
|
}
|
|
handleErrorNext () {
|
|
if (this.canTryAgain()) {
|
|
this.handleSubmitRegistration(this.state.formData);
|
|
} else {
|
|
this.resetState();
|
|
}
|
|
}
|
|
resetState () {
|
|
this.setState(this.initialState);
|
|
}
|
|
render () {
|
|
return (
|
|
<React.Fragment>
|
|
{this.state.registrationError ? (
|
|
<RegistrationErrorStep
|
|
canTryAgain={this.canTryAgain()}
|
|
errorMsg={this.state.registrationError}
|
|
/* eslint-disable react/jsx-no-bind */
|
|
onSubmit={this.handleErrorNext}
|
|
/* eslint-enable react/jsx-no-bind */
|
|
/>
|
|
) : (
|
|
<Progression step={this.state.step}>
|
|
<UsernameStep onNextStep={this.handleAdvanceStep} />
|
|
<CountryStep onNextStep={this.handleAdvanceStep} />
|
|
<BirthDateStep onNextStep={this.handleAdvanceStep} />
|
|
<GenderStep onNextStep={this.handleAdvanceStep} />
|
|
<EmailStep
|
|
waiting={this.state.waiting}
|
|
onNextStep={this.handlePrepareToRegister}
|
|
onRegistrationError={this.handleRegistrationError}
|
|
/>
|
|
<WelcomeStep
|
|
createProjectOnComplete={this.props.createProjectOnComplete}
|
|
email={this.state.formData.email}
|
|
username={this.state.formData.username}
|
|
onNextStep={this.props.onCompleteRegistration}
|
|
/>
|
|
</Progression>
|
|
)}
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
}
|
|
|
|
JoinFlow.propTypes = {
|
|
createProjectOnComplete: PropTypes.bool,
|
|
intl: intlShape,
|
|
onCompleteRegistration: PropTypes.func,
|
|
refreshSession: PropTypes.func
|
|
};
|
|
|
|
const IntlJoinFlow = injectIntl(JoinFlow);
|
|
|
|
const mapDispatchToProps = dispatch => ({
|
|
refreshSession: () => {
|
|
dispatch(sessionActions.refreshSession());
|
|
}
|
|
});
|
|
|
|
// Allow incoming props to override redux-provided props. Used to mock in tests.
|
|
const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(
|
|
{}, stateProps, dispatchProps, ownProps
|
|
);
|
|
|
|
const ConnectedJoinFlow = connect(
|
|
() => ({}),
|
|
mapDispatchToProps,
|
|
mergeProps
|
|
)(IntlJoinFlow);
|
|
|
|
module.exports = ConnectedJoinFlow;
|