/* eslint-disable react/no-multi-comp */ const bindAll = require('lodash.bindall'); const injectIntl = require('react-intl').injectIntl; const intlShape = require('react-intl').intlShape; const omit = require('lodash.omit'); const PropTypes = require('prop-types'); const React = require('react'); const api = require('../../lib/api'); const countryData = require('../../lib/country-data'); const intl = require('../../lib/intl.jsx'); const Avatar = require('../../components/avatar/avatar.jsx'); const Button = require('../../components/forms/button.jsx'); const Card = require('../../components/card/card.jsx'); const CharCount = require('../../components/forms/charcount.jsx'); const Checkbox = require('../../components/forms/checkbox.jsx'); const CheckboxGroup = require('../../components/forms/checkbox-group.jsx'); const Form = require('../../components/forms/form.jsx'); const GeneralError = require('../../components/forms/general-error.jsx'); const Input = require('../../components/forms/input.jsx'); const PhoneInput = require('../../components/forms/phone-input.jsx'); const RadioGroup = require('../../components/forms/radio-group.jsx'); const Select = require('../../components/forms/select.jsx'); const Slide = require('../../components/slide/slide.jsx'); const Spinner = require('../../components/spinner/spinner.jsx'); const StepNavigation = require('../../components/stepnavigation/stepnavigation.jsx'); const TextArea = require('../../components/forms/textarea.jsx'); const Tooltip = require('../../components/tooltip/tooltip.jsx'); require('./steps.scss'); const DEFAULT_COUNTRY = 'us'; /** * Return a list of options to give to select * @param {object} reactIntl react-intl, used to localize strings * @return {object} ordered set of county options formatted for select */ const getCountryOptions = reactIntl => ( [ { label: reactIntl.formatMessage({id: 'registration.selectCountry'}), disabled: true, value: '' }, ...countryData.registrationCountryCodeOptions ] ); const NextStepButton = props => ( ); NextStepButton.propTypes = { text: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), waiting: PropTypes.bool }; NextStepButton.defaultProps = { waiting: false, text: 'Next Step' }; /* * USERNAME STEP */ class UsernameStep extends React.Component { constructor (props) { super(props); bindAll(this, [ 'handleChangeShowPassword', 'handleUsernameBlur', 'handleValidSubmit', 'validateUsername' ]); this.state = { showPassword: props.showPassword, waiting: false, validUsername: '' }; } handleChangeShowPassword (field, value) { this.setState({showPassword: value}); } validateUsername (username, callback) { callback = callback || function () {}; if (!username) { this.form.formsy.updateInputsWithError({ 'user.username': this.props.intl.formatMessage({id: 'form.validationRequired'}) }); return callback(false); } api({ host: '', uri: `/accounts/check_username/${username}/` }, (err, body, res) => { if (err || res.statusCode !== 200) { err = err || this.props.intl.formatMessage({id: 'general.error'}); this.form.formsy.updateInputsWithError({all: err}); return callback(false); } body = body[0]; switch (body.msg) { case 'valid username': this.setState({ validUsername: 'pass' }); return callback(true); case 'username exists': this.form.formsy.updateInputsWithError({ 'user.username': this.props.intl.formatMessage({ id: 'registration.validationUsernameExists' }) }); return callback(false); case 'bad username': this.form.formsy.updateInputsWithError({ 'user.username': this.props.intl.formatMessage({ id: 'registration.validationUsernameVulgar' }) }); return callback(false); case 'invalid username': default: this.form.formsy.updateInputsWithError({ 'user.username': this.props.intl.formatMessage({ id: 'registration.validationUsernameNotAllowed' }) }); return callback(false); } }); } handleUsernameBlur (name, value) { if (this.form.formsy.inputs[0].isValidValue(value)) { this.validateUsername(value); } } handleValidSubmit (formData) { this.setState({waiting: true}); this.validateUsername(formData.user.username, isValid => { this.setState({waiting: false}); if (isValid) return this.props.onNextStep(formData); }); } render () { return (

{this.props.title ? ( this.props.title ) : ( )}

{this.props.description ? ( this.props.description ) : ( )} {this.props.tooltip ? ( ) : ( null )}

{ this.form = form; }} onValidSubmit={this.handleValidSubmit} >
} waiting={this.props.waiting || this.state.waiting} />
); } } UsernameStep.propTypes = { activeStep: PropTypes.number, description: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), intl: intl.intlShape, onNextStep: PropTypes.func, showPassword: PropTypes.bool, title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), tooltip: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), totalSteps: PropTypes.number, usernameHelp: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), waiting: PropTypes.bool }; UsernameStep.defaultProps = { showPassword: false, waiting: false }; const IntlUsernameStep = injectIntl(UsernameStep); /* * PASSWORD STEP */ class ChoosePasswordStep extends React.Component { constructor (props) { super(props); bindAll(this, [ 'handleChangeShowPassword' ]); this.state = { showPassword: props.showPassword }; } handleChangeShowPassword (field, value) { this.setState({showPassword: value}); } render () { return (

{this.props.intl.formatMessage({id: 'registration.choosePasswordStepTitle'})}

} waiting={this.props.waiting || this.state.waiting} />
); } } ChoosePasswordStep.propTypes = { activeStep: PropTypes.number, intl: intlShape, onNextStep: PropTypes.func, showPassword: PropTypes.bool, totalSteps: PropTypes.number, username: PropTypes.string, waiting: PropTypes.bool }; ChoosePasswordStep.defaultProps = { showPassword: false, username: null, waiting: false }; const IntlChoosePasswordStep = injectIntl(ChoosePasswordStep); /* * DEMOGRAPHICS STEP */ class DemographicsStep extends React.Component { constructor (props) { super(props); bindAll(this, [ 'birthDateValidator', 'countryValidator', 'getCountryName', 'getMonthOptions', 'getYearOptions', 'handleChooseGender', 'handleValidSubmit', 'isValidBirthdate' ]); this.state = { otherDisabled: true }; } getMonthOptions () { return [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ].map((label, id) => ({ value: (id + 1).toString(), label: this.props.intl.formatMessage({id: `general.month${label}`}) })); } getYearOptions () { return Array.apply(null, Array(100)).map((v, id) => { const year = (new Date().getFullYear() - (id + this.props.birthOffset)).toString(); return {value: year, label: year}; }); } handleChooseGender (name, gender) { this.setState({otherDisabled: gender !== 'other'}); } // look up country name using user's country code selection getCountryName (values) { if (values.countryCode) { const countryInfo = countryData.lookupCountryInfo(values.countryCode); if (countryInfo) { return countryInfo.name; } } return null; } handleValidSubmit (formData) { const countryName = this.getCountryName(formData); if (countryName && formData.user) { formData.user.country = countryName; return this.props.onNextStep(formData); } return false; } isValidBirthdate (year, month) { const birthdate = new Date( year, month - 1, 1 ); return (((Date.now() - birthdate) / (24 * 3600 * 1000 * 365.25)) >= this.props.birthOffset); } birthDateValidator (values) { const isValid = this.isValidBirthdate(values['user.birth.year'], values['user.birth.month']); return isValid ? true : this.props.intl.formatMessage({id: 'teacherRegistration.validationAge'}); } countryValidator (values) { const countryName = this.getCountryName(values); if (countryName) return true; return this.props.intl.formatMessage({id: 'general.invalidSelection'}); } render () { const countryOptions = getCountryOptions(this.props.intl); return (

{this.props.description ? this.props.description : }

} ]} onChange={this.handleChooseGender} /> } waiting={props.waiting} />
); NameStep.propTypes = { activeStep: PropTypes.number, intl: intlShape, onNextStep: PropTypes.func, totalSteps: PropTypes.number, waiting: PropTypes.bool }; NameStep.defaultProps = { waiting: false }; const IntlNameStep = injectIntl(NameStep); /* * PHONE NUMBER STEP */ class PhoneNumberStep extends React.Component { constructor (props) { super(props); bindAll(this, [ 'handleValidSubmit' ]); } handleValidSubmit (formData, reset, invalidate) { if (!formData.phone || formData.phone.national_number === '+') { return invalidate({ phone: this.props.intl.formatMessage({id: 'form.validationRequired'}) }); } return this.props.onNextStep(formData); } render () { return (

} waiting={this.props.waiting} />
); } } PhoneNumberStep.propTypes = { activeStep: PropTypes.number, defaultCountry: PropTypes.string, intl: intlShape, onNextStep: PropTypes.func, totalSteps: PropTypes.number, waiting: PropTypes.bool }; PhoneNumberStep.defaultProps = { defaultCountry: DEFAULT_COUNTRY, waiting: false }; const IntlPhoneNumberStep = injectIntl(PhoneNumberStep); /* * ORGANIZATION STEP */ const ORGANIZATION_L10N_STEMS = [ 'orgChoiceElementarySchool', 'orgChoiceMiddleSchool', 'orgChoiceHighSchool', 'orgChoiceUniversity', 'orgChoiceAfterschool', 'orgChoiceMuseum', 'orgChoiceLibrary', 'orgChoiceCamp' ]; class OrganizationStep extends React.Component { constructor (props) { super(props); bindAll(this, [ 'getOrganizationOptions', 'handleChooseOrganization' ]); this.state = { otherDisabled: true }; } getOrganizationOptions () { const options = ORGANIZATION_L10N_STEMS.map((choice, id) => ({ value: id.toString(), label: this.props.intl.formatMessage({ id: `teacherRegistration.${choice}` }) })); // Add "Other" option with empty string, since input field is used const otherId = options.length.toString(); options.push({value: otherId, label: ' '}); return options; } handleChooseOrganization (name, values) { this.setState({ otherDisabled: values.indexOf(ORGANIZATION_L10N_STEMS.length.toString()) === -1 }); } render () { return (

} waiting={this.props.waiting} />
); } } OrganizationStep.propTypes = { activeStep: PropTypes.number, intl: intlShape, onNextStep: PropTypes.func, totalSteps: PropTypes.number, waiting: PropTypes.bool }; OrganizationStep.defaultProps = { waiting: false }; const IntlOrganizationStep = injectIntl(OrganizationStep); /* * ADDRESS STEP */ class AddressStep extends React.Component { constructor (props) { super(props); bindAll(this, [ 'handleChangeCountry' ]); this.state = { countryChoice: props.defaultCountry, waiting: false }; } handleChangeCountry (field, choice) { this.setState({countryChoice: choice}); } render () { let stateOptions = countryData.subdivisionOptions[this.state.countryChoice]; stateOptions = [{}].concat(stateOptions); const countryOptions = getCountryOptions(this.props.intl); return (

{stateOptions.length > 2 ? } waiting={this.props.waiting || this.state.waiting} />
); } } AddressStep.propTypes = { activeStep: PropTypes.number, defaultCountry: PropTypes.string, intl: intlShape, onNextStep: PropTypes.func, totalSteps: PropTypes.number, waiting: PropTypes.bool }; AddressStep.defaultProps = { defaultCountry: DEFAULT_COUNTRY, waiting: false }; const IntlAddressStep = injectIntl(AddressStep); /* * USE SCRATCH STEP */ class UseScratchStep extends React.Component { constructor (props) { super(props); bindAll(this, [ 'handleTyping' ]); this.state = { characterCount: 0 }; } handleTyping (name, value) { this.setState({ characterCount: value.length }); } render () { const textAreaClass = (this.state.characterCount > this.props.maxCharacters) ? 'fail' : ''; return (