scratch-www/src/components/registration/steps.jsx

1076 lines
51 KiB
React
Raw Normal View History

2016-06-02 16:21:14 -04:00
var React = require('react');
var api = require('../../lib/api');
var countryData = require('../../lib/country-data');
2016-06-07 18:04:18 -04:00
var intl = require('../../lib/intl.jsx');
2016-06-06 10:10:27 -04:00
var log = require('../../lib/log');
var smartyStreets = require('../../lib/smarty-streets');
var Avatar = require('../../components/avatar/avatar.jsx');
2016-06-02 16:21:14 -04:00
var Button = require('../../components/forms/button.jsx');
2016-06-15 15:08:56 -04:00
var Card = require('../../components/card/card.jsx');
2016-06-23 07:27:43 -04:00
var CharCount = require('../../components/forms/charcount.jsx');
2016-06-02 16:21:14 -04:00
var Checkbox = require('../../components/forms/checkbox.jsx');
var CheckboxGroup = require('../../components/forms/checkbox-group.jsx');
var Form = require('../../components/forms/form.jsx');
var GeneralError = require('../../components/forms/general-error.jsx');
2016-06-02 16:21:14 -04:00
var Input = require('../../components/forms/input.jsx');
var PhoneInput = require('../../components/forms/phone-input.jsx');
var RadioGroup = require('../../components/forms/radio-group.jsx');
var Select = require('../../components/forms/select.jsx');
2016-06-15 15:08:56 -04:00
var Slide = require('../../components/slide/slide.jsx');
2016-06-06 10:10:27 -04:00
var Spinner = require('../../components/spinner/spinner.jsx');
2016-06-16 10:54:36 -04:00
var StepNavigation = require('../../components/stepnavigation/stepnavigation.jsx');
2016-06-02 16:21:14 -04:00
var TextArea = require('../../components/forms/textarea.jsx');
2016-06-23 07:27:43 -04:00
var Tooltip = require('../../components/tooltip/tooltip.jsx');
2016-06-02 16:21:14 -04:00
2016-07-19 16:49:12 -04:00
require('./steps.scss');
var DEFAULT_COUNTRY = 'us';
2016-08-12 16:43:56 -04:00
/**
* Return a list of options to give to frc select
* @param {Object} intl react-intl, used to localize strings
* @param {String} defaultCountry optional string of default country to put at top of list
* @return {Object} ordered set of county options formatted for frc select
*/
var getCountryOptions = function (intl, defaultCountry) {
var options = countryData.countryOptions.concat({
label: intl.formatMessage({id: 'registration.selectCountry'}),
disabled: true,
selected: true
});
if (typeof defaultCountry !== 'undefined') {
return options.sort(function (a, b) {
if (a.disabled) return -1;
if (b.disabled) return 1;
if (a.value === defaultCountry) return -1;
if (b.value === defaultCountry) return 1;
return 0;
}.bind(this));
}
return options;
};
2016-06-02 16:21:14 -04:00
2016-06-16 17:25:14 -04:00
var NextStepButton = React.createClass({
getDefaultProps: function () {
return {
waiting: false,
text: 'Next Step'
};
},
render: function () {
return (
2016-07-19 16:51:28 -04:00
<Button type="submit" disabled={this.props.waiting} className="card-button" {... this.props}>
2016-06-16 17:25:14 -04:00
{this.props.waiting ?
<Spinner /> :
this.props.text
}
</Button>
);
}
});
2016-06-02 16:21:14 -04:00
module.exports = {
2016-06-07 18:04:18 -04:00
UsernameStep: intl.injectIntl(React.createClass({
getDefaultProps: function () {
return {
2016-07-21 21:07:02 -04:00
showPassword: false,
waiting: false
};
},
getInitialState: function () {
return {
2016-07-21 21:07:02 -04:00
showPassword: this.props.showPassword,
2016-06-23 07:27:43 -04:00
waiting: false,
validUsername: ''
};
},
onChangeShowPassword: function (field, value) {
this.setState({showPassword: value});
},
validateUsername: function (username, callback) {
2016-08-03 16:24:51 -04:00
callback = callback || function () {};
if (!username) {
this.refs.form.refs.formsy.updateInputsWithError({
'user.username': formatMessage({id: 'teacherRegistration.validationRequired'})
});
return callback(false);
}
api({
host: '',
uri: '/accounts/check_username/' + username + '/'
}, function (err, body, res) {
var formatMessage = this.props.intl.formatMessage;
if (err || res.statusCode !== 200) {
err = err || formatMessage({id: 'general.error'});
this.refs.form.refs.formsy.updateInputsWithError({all: err});
2016-08-03 16:24:51 -04:00
return callback(false);
}
body = body[0];
switch (body.msg) {
case 'valid username':
2016-06-23 07:27:43 -04:00
this.setState({
validUsername: 'pass'
});
2016-08-03 16:24:51 -04:00
return callback(true);
case 'username exists':
this.refs.form.refs.formsy.updateInputsWithError({
2016-07-19 16:51:28 -04:00
'user.username': formatMessage({id: 'registration.validationUsernameExists'})
});
2016-08-03 16:24:51 -04:00
return callback(false);
case 'bad username':
this.refs.form.refs.formsy.updateInputsWithError({
2016-07-19 16:51:28 -04:00
'user.username': formatMessage({id: 'registration.validationUsernameVulgar'})
});
2016-08-03 16:24:51 -04:00
return callback(false);
case 'invalid username':
default:
this.refs.form.refs.formsy.updateInputsWithError({
2016-07-19 16:51:28 -04:00
'user.username': formatMessage({id: 'registration.validationUsernameInvalid'})
});
2016-08-03 16:24:51 -04:00
return callback(false);
}
}.bind(this));
},
onUsernameBlur: function (event) {
this.validateUsername(event.currentTarget.value);
},
onValidSubmit: function (formData) {
this.setState({waiting: true});
this.validateUsername(formData.user.username, function (isValid) {
this.setState({waiting: false});
if (isValid) return this.props.onNextStep(formData);
}.bind(this));
},
2016-06-02 16:21:14 -04:00
render: function () {
2016-06-07 18:04:18 -04:00
var formatMessage = this.props.intl.formatMessage;
2016-06-02 16:21:14 -04:00
return (
2016-07-19 16:49:12 -04:00
<Slide className="registration-step username-step">
2016-07-21 19:38:06 -04:00
<h2>
{this.props.title ? (
this.props.title
) : (
<intl.FormattedMessage id="registration.usernameStepTitle" />
)}
</h2>
2016-06-15 15:08:56 -04:00
<p className="description">
2016-07-21 19:38:06 -04:00
{this.props.description ? (
this.props.description
) : (
<intl.FormattedMessage id="registration.usernameStepDescription" />
)}
{this.props.tooltip ? (
<Tooltip title={'?'}
tipContent={this.props.tooltip} />
) : (
null
)}
2016-06-15 15:08:56 -04:00
</p>
<Card>
<Form onValidSubmit={this.onValidSubmit} ref="form">
2016-07-21 20:43:58 -04:00
<div>
<div className="username-label">
<b>{formatMessage({id: 'registration.createUsername'})}</b>
{this.props.usernameHelp ? (
<p className="help-text">{this.props.usernameHelp}</p>
):(
null
)}
</div>
2016-07-21 20:43:58 -04:00
<Input className={this.state.validUsername}
type="text"
name="user.username"
onBlur={this.onUsernameBlur}
2016-07-21 20:43:58 -04:00
validations={{
matchRegexp: /^[\w-]*$/,
minLength: 3,
maxLength: 20
}}
validationErrors={{
matchRegexp: formatMessage({
id: 'registration.validationUsernameRegexp'
}),
minLength: formatMessage({
id: 'registration.validationUsernameMinLength'
}),
maxLength: formatMessage({
id: 'registration.validationUsernameMaxLength'
})
}}
required />
</div>
2016-06-15 15:08:56 -04:00
<Input label={formatMessage({id: 'general.password'})}
type={this.state.showPassword ? 'text' : 'password'}
name="user.password"
validations={{
minLength: 6,
notEquals: 'password',
notEqualsField: 'user.username'
}}
validationErrors={{
minLength: formatMessage({
2016-07-19 16:51:28 -04:00
id: 'registration.validationPasswordLength'
2016-06-15 15:08:56 -04:00
}),
notEquals: formatMessage({
2016-07-19 16:51:28 -04:00
id: 'registration.validationPasswordNotEquals'
2016-06-15 15:08:56 -04:00
}),
notEqualsField: formatMessage({
2016-07-19 16:51:28 -04:00
id: 'registration.validationPasswordNotUsername'
2016-06-15 15:08:56 -04:00
})
}}
required />
2016-07-19 16:51:28 -04:00
<Checkbox label={formatMessage({id: 'registration.showPassword'})}
2016-06-15 15:08:56 -04:00
value={this.state.showPassword}
onChange={this.onChangeShowPassword}
help={null}
name="showPassword" />
<GeneralError name="all" />
<NextStepButton waiting={this.props.waiting || this.state.waiting}
2016-07-19 16:51:28 -04:00
text={<intl.FormattedMessage id="registration.nextStep" />} />
2016-06-15 15:08:56 -04:00
</Form>
</Card>
2016-06-16 10:54:36 -04:00
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
2016-06-15 15:08:56 -04:00
</Slide>
2016-06-02 16:21:14 -04:00
);
}
2016-06-07 18:04:18 -04:00
})),
2016-07-21 16:56:01 -04:00
ChoosePasswordStep: intl.injectIntl(React.createClass({
getDefaultProps: function () {
return {
username: null,
2016-07-21 21:07:02 -04:00
showPassword: false,
2016-07-21 16:56:01 -04:00
waiting: false
};
},
getInitialState: function () {
return {
2016-07-21 21:07:02 -04:00
showPassword: this.props.showPassword
2016-07-21 16:56:01 -04:00
};
},
onChangeShowPassword: function (field, value) {
this.setState({showPassword: value});
},
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
<Slide className="registration-step choose-password-step">
<h2>{formatMessage({id: 'registration.choosePasswordStepTitle'})}</h2>
2016-07-21 17:27:07 -04:00
<p className="description">
<intl.FormattedMessage id="registration.choosePasswordStepDescription" />
<Tooltip title={'?'}
tipContent={formatMessage({id: 'registration.choosePasswordStepTooltip'})} />
</p>
2016-07-21 16:56:01 -04:00
<Card>
<Form onValidSubmit={this.props.onNextStep}>
2016-07-21 17:27:07 -04:00
<Input label={formatMessage({id: 'registration.newPassword'})}
2016-07-21 16:56:01 -04:00
type={this.state.showPassword ? 'text' : 'password'}
name="user.password"
validations={{
minLength: 6,
notEquals: 'password',
notEqualsUsername: this.props.username
2016-07-21 16:56:01 -04:00
}}
validationErrors={{
minLength: formatMessage({
id: 'registration.validationPasswordLength'
}),
notEquals: formatMessage({
id: 'registration.validationPasswordNotEquals'
}),
notEqualsUsername: formatMessage({
2016-07-21 16:56:01 -04:00
id: 'registration.validationPasswordNotUsername'
})
}}
required />
<Checkbox label={formatMessage({id: 'registration.showPassword'})}
value={this.state.showPassword}
onChange={this.onChangeShowPassword}
help={null}
name="showPassword" />
<NextStepButton waiting={this.props.waiting || this.state.waiting}
text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form>
</Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
</Slide>
);
}
})),
2016-06-07 18:04:18 -04:00
DemographicsStep: intl.injectIntl(React.createClass({
getDefaultProps: function () {
return {
waiting: false,
description: null
};
},
2016-06-02 16:21:14 -04:00
getInitialState: function () {
return {otherDisabled: true};
},
2016-06-07 18:04:18 -04:00
getMonthOptions: function () {
return [
2016-06-02 16:21:14 -04:00
'January', 'February', 'March', 'April', 'May', 'June', 'July',
'August', 'September', 'October', 'November', 'December'
].map(function (label, id) {
2016-06-07 18:04:18 -04:00
return {
value: id + 1,
2016-06-07 18:04:18 -04:00
label: this.props.intl.formatMessage({id: 'general.month' + label})};
}.bind(this));
},
getYearOptions: function () {
return Array.apply(null, Array(100)).map(function (v, id) {
2016-06-02 16:21:14 -04:00
var year = 2016 - id;
return {value: year, label: year};
});
2016-06-07 18:04:18 -04:00
},
onChooseGender: function (name, gender) {
this.setState({otherDisabled: gender !== 'other'});
},
render: function () {
var formatMessage = this.props.intl.formatMessage;
2016-06-02 16:21:14 -04:00
return (
2016-07-19 16:49:12 -04:00
<Slide className="registration-step demographics-step">
2016-06-23 07:27:43 -04:00
<h2>
2016-07-19 16:51:28 -04:00
<intl.FormattedMessage id="registration.personalStepTitle" />
2016-06-23 07:27:43 -04:00
</h2>
2016-06-15 15:08:56 -04:00
<p className="description">
{this.props.description ?
this.props.description
:
<intl.FormattedMessage id="registration.personalStepDescription" />
}
2016-06-23 07:27:43 -04:00
<Tooltip title={'?'}
2016-07-19 16:51:28 -04:00
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
2016-06-15 15:08:56 -04:00
</p>
<Card>
<Form onValidSubmit={this.props.onNextStep}>
<Select label={formatMessage({id: 'general.birthMonth'})}
name="user.birth.month"
options={this.getMonthOptions()}
required />
<Select label={formatMessage({id: 'general.birthYear'})}
name="user.birth.year"
options={this.getYearOptions()} required />
<RadioGroup label={formatMessage({id: 'general.gender'})}
name="user.gender"
onChange={this.onChooseGender}
options={[
{value: 'female', label: formatMessage({id: 'general.female'})},
{value: 'male', label: formatMessage({id: 'general.male'})},
2016-06-23 07:27:43 -04:00
{value: 'other', label: ''}
2016-06-15 15:08:56 -04:00
]}
required />
2016-06-23 07:27:43 -04:00
<div className="gender-input">
<Input name="user.genderOther"
type="text"
validations={{
maxLength: 25
}}
validationErrors={{
maxLength: formatMessage({
id: 'registration.validationMaxLength'
})
}}
2016-06-23 07:27:43 -04:00
disabled={this.state.otherDisabled}
required={!this.state.otherDisabled}
help={null} />
</div>
2016-06-15 15:08:56 -04:00
<Select label={formatMessage({id: 'general.country'})}
name="user.country"
2016-08-12 16:43:56 -04:00
options={getCountryOptions(this.props.intl, DEFAULT_COUNTRY)}
2016-06-02 16:21:14 -04:00
required />
2016-06-23 07:27:43 -04:00
<Checkbox className="demographics-checkbox-is-robot"
label="I'm a robot!"
name="user.isRobot" />
<NextStepButton waiting={this.props.waiting}
2016-07-19 16:51:28 -04:00
text={<intl.FormattedMessage id="registration.nextStep" />} />
2016-06-15 15:08:56 -04:00
</Form>
</Card>
2016-06-16 10:54:36 -04:00
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
2016-06-15 15:08:56 -04:00
</Slide>
2016-06-02 16:21:14 -04:00
);
}
2016-06-07 18:04:18 -04:00
})),
NameStep: intl.injectIntl(React.createClass({
getDefaultProps: function () {
return {
waiting: false
};
},
2016-06-02 16:21:14 -04:00
render: function () {
2016-06-07 18:04:18 -04:00
var formatMessage = this.props.intl.formatMessage;
2016-06-02 16:21:14 -04:00
return (
2016-07-19 16:49:12 -04:00
<Slide className="registration-step name-step">
2016-06-23 07:27:43 -04:00
<h2>
2016-06-17 12:22:29 -04:00
<intl.FormattedHTMLMessage id="teacherRegistration.nameStepTitle" />
2016-06-23 07:27:43 -04:00
</h2>
2016-06-15 15:08:56 -04:00
<p className="description">
<intl.FormattedMessage id="teacherRegistration.nameStepDescription" />
2016-06-23 07:27:43 -04:00
<Tooltip title={'?'}
2016-07-19 16:51:28 -04:00
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
2016-06-15 15:08:56 -04:00
</p>
<Card>
<Form onValidSubmit={this.props.onNextStep}>
<Input label={formatMessage({id: 'teacherRegistration.firstName'})}
type="text"
name="user.name.first"
validations={{
maxLength: 50
}}
validationErrors={{
maxLength: formatMessage({
id: 'registration.validationMaxLength'
})
}}
2016-06-15 15:08:56 -04:00
required />
<Input label={formatMessage({id: 'teacherRegistration.lastName'})}
type="text"
name="user.name.last"
validations={{
maxLength: 50
}}
validationErrors={{
maxLength: formatMessage({
id: 'registration.validationMaxLength'
})
}}
2016-06-15 15:08:56 -04:00
required />
<NextStepButton waiting={this.props.waiting}
2016-07-19 16:51:28 -04:00
text={<intl.FormattedMessage id="registration.nextStep" />} />
2016-06-15 15:08:56 -04:00
</Form>
</Card>
2016-06-16 10:54:36 -04:00
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
2016-06-15 15:08:56 -04:00
</Slide>
2016-06-02 16:21:14 -04:00
);
}
2016-06-07 18:04:18 -04:00
})),
PhoneNumberStep: intl.injectIntl(React.createClass({
getDefaultProps: function () {
return {
defaultCountry: DEFAULT_COUNTRY,
waiting: false
};
},
onValidSubmit: function (formData, reset, invalidate) {
2016-11-04 11:51:48 -04:00
if (!formData.phone || formData.phone.national_number === '+') {
return invalidate({
'phone': this.props.intl.formatMessage({id: 'teacherRegistration.validationRequired'})
});
}
return this.props.onNextStep(formData);
},
2016-06-02 16:21:14 -04:00
render: function () {
2016-06-07 18:04:18 -04:00
var formatMessage = this.props.intl.formatMessage;
2016-06-02 16:21:14 -04:00
return (
2016-07-19 16:49:12 -04:00
<Slide className="registration-step phone-step">
2016-06-23 07:27:43 -04:00
<h2>
2016-06-15 15:08:56 -04:00
<intl.FormattedMessage id="teacherRegistration.phoneStepTitle" />
2016-06-23 07:27:43 -04:00
</h2>
<p className="description">
2016-06-15 15:08:56 -04:00
<intl.FormattedMessage id="teacherRegistration.phoneStepDescription" />
2016-06-23 07:27:43 -04:00
<Tooltip title={'?'}
2016-07-19 16:51:28 -04:00
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
2016-06-15 15:08:56 -04:00
</p>
<Card>
<Form onValidSubmit={this.onValidSubmit}>
2016-06-15 15:08:56 -04:00
<PhoneInput label={formatMessage({id: 'teacherRegistration.phoneNumber'})}
name="phone"
defaultCountry={this.props.defaultCountry}
2016-06-15 15:08:56 -04:00
required />
<Checkbox label={formatMessage({id: 'teacherRegistration.phoneConsent'})}
name="phoneConsent"
required="isFalse"
validationErrors={{
isFalse: formatMessage({id: 'teacherRegistration.validationPhoneConsent'})
}} />
<NextStepButton waiting={this.props.waiting}
2016-07-19 16:51:28 -04:00
text={<intl.FormattedMessage id="registration.nextStep" />} />
2016-06-15 15:08:56 -04:00
</Form>
</Card>
2016-06-16 10:54:36 -04:00
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
2016-06-15 15:08:56 -04:00
</Slide>
2016-06-02 16:21:14 -04:00
);
}
2016-06-07 18:04:18 -04:00
})),
OrganizationStep: intl.injectIntl(React.createClass({
2016-06-02 16:21:14 -04:00
getInitialState: function () {
return {
otherDisabled: true
};
},
getDefaultProps: function () {
return {
waiting: false
};
},
organizationL10nStems: [
'orgChoiceElementarySchool',
'orgChoiceMiddleSchool',
'orgChoiceHighSchool',
'orgChoiceUniversity',
'orgChoiceAfterschool',
'orgChoiceMuseum',
'orgChoiceLibrary',
'orgChoiceCamp',
'orgChoiceOther'
],
2016-06-07 18:04:18 -04:00
getOrganizationOptions: function () {
return this.organizationL10nStems.map(function (choice, id) {
2016-06-07 18:04:18 -04:00
return {
2016-06-13 13:29:37 -04:00
value: id,
2016-06-07 18:04:18 -04:00
label: this.props.intl.formatMessage({
2016-06-13 13:29:37 -04:00
id: 'teacherRegistration.' + choice
2016-06-07 18:04:18 -04:00
})
};
}.bind(this));
},
2016-06-02 16:21:14 -04:00
onChooseOrganization: function (name, values) {
this.setState({otherDisabled: values.indexOf(this.organizationL10nStems.indexOf('orgChoiceOther')) === -1});
2016-06-02 16:21:14 -04:00
},
render: function () {
2016-06-13 13:29:37 -04:00
var formatMessage = this.props.intl.formatMessage;
2016-06-02 16:21:14 -04:00
return (
2016-07-19 16:49:12 -04:00
<Slide className="registration-step organization-step">
2016-06-23 07:27:43 -04:00
<h2>
2016-06-15 15:08:56 -04:00
<intl.FormattedMessage id="teacherRegistration.orgStepTitle" />
2016-06-23 07:27:43 -04:00
</h2>
<p className="description">
2016-06-15 15:08:56 -04:00
<intl.FormattedMessage id="teacherRegistration.orgStepDescription" />
2016-06-23 07:27:43 -04:00
<Tooltip title={'?'}
2016-07-19 16:51:28 -04:00
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
2016-06-15 15:08:56 -04:00
</p>
<Card>
2016-08-03 15:20:58 -04:00
<Form onValidSubmit={this.props.onNextStep}>
2016-06-15 15:08:56 -04:00
<Input label={formatMessage({id: 'teacherRegistration.organization'})}
type="text"
name="organization.name"
validations={{
maxLength: 50
}}
validationErrors={{
maxLength: formatMessage({
id: 'registration.validationMaxLength'
})
}}
2016-06-15 15:08:56 -04:00
required />
<Input label={formatMessage({id: 'teacherRegistration.orgTitle'})}
type="text"
name="organization.title"
validations={{
maxLength: 50
}}
validationErrors={{
maxLength: formatMessage({
id: 'registration.validationMaxLength'
})
}}
2016-06-15 15:08:56 -04:00
required />
2016-06-23 07:27:43 -04:00
<div className="organization-type">
<b><intl.FormattedMessage id="teacherRegistration.orgType" /></b>
2016-07-21 20:43:58 -04:00
<p className="help-text">
<intl.FormattedMessage id="teacherRegistration.checkAll" />
</p>
2016-06-23 07:27:43 -04:00
<CheckboxGroup name="organization.type"
value={[]}
options={this.getOrganizationOptions()}
onChange={this.onChooseOrganization}
validations={{
minLength: 1
}}
validationErrors={{
minLength: formatMessage({
id: 'teacherRegistration.validationRequired'
})
}}
2016-06-23 07:27:43 -04:00
required />
</div>
<div className="other-input">
<Input name="organization.other"
type="text"
validations={{
maxLength: 50
}}
validationErrors={{
maxLength: formatMessage({
id: 'registration.validationMaxLength'
})
}}
2016-06-23 07:27:43 -04:00
disabled={this.state.otherDisabled}
required={!this.state.otherDisabled}
help={null}
2016-06-23 07:27:43 -04:00
placeholder={formatMessage({id: 'general.other'})} />
</div>
<div className="url-input">
<b><intl.FormattedMessage id="general.website" /></b>
2016-07-21 20:43:58 -04:00
<p className="help-text">
<intl.FormattedMessage id="teacherRegistration.notRequired" />
</p>
2016-06-23 07:27:43 -04:00
<Input type="url"
name="organization.url"
validations={{
maxLength: 200
}}
validationErrors={{
maxLength: formatMessage({
id: 'registration.validationMaxLength'
})
}}
2016-06-23 07:27:43 -04:00
required="isFalse"
placeholder={'http://'} />
</div>
<NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="registration.nextStep" />} />
2016-06-15 15:08:56 -04:00
</Form>
</Card>
2016-06-16 10:54:36 -04:00
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
2016-06-15 15:08:56 -04:00
</Slide>
2016-06-02 16:21:14 -04:00
);
}
2016-06-07 18:04:18 -04:00
})),
2016-06-13 13:29:37 -04:00
AddressStep: intl.injectIntl(React.createClass({
getDefaultProps: function () {
return {
defaultCountry: DEFAULT_COUNTRY,
waiting: false
};
},
getInitialState: function () {
return {
countryChoice: this.props.defaultCountry,
2016-06-06 10:10:27 -04:00
waiting: false
};
},
onChangeCountry: function (field, choice) {
this.setState({countryChoice: choice});
},
2016-06-06 10:10:27 -04:00
onValidSubmit: function (formData, reset, invalidate) {
if (formData.address.country !== 'us') {
return this.props.onNextStep(formData);
}
this.setState({waiting: true});
var address = {
street: formData.address.line1,
secondary: formData.address.line2 || '',
city: formData.address.city,
state: formData.address.state,
zipcode: formData.address.zip
};
smartyStreets(address, function (err, res) {
this.setState({waiting: false});
if (err) {
// We don't want to prevent registration because
// address validation isn't working. Log it and
// move on.
log.error(err);
return this.props.onNextStep(formData);
}
if (res && res.length > 0) {
return this.props.onNextStep(formData);
} else {
return invalidate({
'all': this.props.intl.formatMessage({id: 'teacherRegistration.addressValidationError'})
2016-06-06 10:10:27 -04:00
});
}
}.bind(this));
},
2016-06-02 16:21:14 -04:00
render: function () {
2016-06-13 13:29:37 -04:00
var formatMessage = this.props.intl.formatMessage;
var stateOptions = countryData.subdivisionOptions[this.state.countryChoice];
2016-06-13 13:29:37 -04:00
stateOptions = [{}].concat(stateOptions);
2016-06-02 16:21:14 -04:00
return (
2016-07-19 16:49:12 -04:00
<Slide className="registration-step address-step">
2016-06-23 07:27:43 -04:00
<h2>
2016-06-15 15:08:56 -04:00
<intl.FormattedMessage id="teacherRegistration.addressStepTitle" />
2016-06-23 07:27:43 -04:00
</h2>
2016-06-15 15:08:56 -04:00
<p className="description">
<intl.FormattedMessage id="teacherRegistration.addressStepDescription" />
2016-06-23 07:27:43 -04:00
<Tooltip title={'?'}
2016-07-19 16:51:28 -04:00
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
2016-06-15 15:08:56 -04:00
</p>
<Card>
<Form onValidSubmit={this.onValidSubmit}>
<Select label={formatMessage({id: 'general.country'})}
name="address.country"
options={getCountryOptions(this.props.intl)}
value={this.props.defaultCountry}
2016-06-15 15:08:56 -04:00
onChange={this.onChangeCountry}
required />
<Input label={formatMessage({id: 'teacherRegistration.addressLine1'})}
type="text"
name="address.line1"
validations={{
maxLength: 100
}}
validationErrors={{
maxLength: formatMessage({
id: 'registration.validationMaxLength'
})
}}
2016-06-15 15:08:56 -04:00
required />
<Input label={formatMessage({id: 'teacherRegistration.addressLine2'})}
type="text"
2016-06-23 07:27:43 -04:00
name="address.line2"
validations={{
maxLength: 100
}}
validationErrors={{
maxLength: formatMessage({
id: 'registration.validationMaxLength'
})
}}
2016-06-23 07:27:43 -04:00
required="isFalse" />
2016-06-15 15:08:56 -04:00
<Input label={formatMessage({id: 'teacherRegistration.city'})}
type="text"
name="address.city"
validations={{
maxLength: 50
}}
validationErrors={{
maxLength: formatMessage({
id: 'registration.validationMaxLength'
})
}}
2016-06-15 15:08:56 -04:00
required />
{stateOptions.length > 2 ?
<Select label={formatMessage({id: 'teacherRegistration.stateProvince'})}
name="address.state"
options={stateOptions}
required /> :
[]
2016-06-06 10:10:27 -04:00
}
<b className="row-label">
<intl.FormattedMessage id="teacherRegistration.zipCode" />
</b>
{this.state.countryChoice !== 'us' ?
<p className="help-text">
<intl.FormattedMessage id="teacherRegistration.notRequired" />
</p> : []
}
<Input type="text"
2016-06-15 15:08:56 -04:00
name="address.zip"
validations={{
maxLength: 10
}}
validationErrors={{
maxLength: formatMessage({
id: 'registration.validationMaxLength'
})
}}
required={(this.state.countryChoice === 'us') ? true : 'isFalse'} />
<GeneralError name="all" />
<NextStepButton waiting={this.props.waiting || this.state.waiting}
2016-07-19 16:51:28 -04:00
text={<intl.FormattedMessage id="registration.nextStep" />} />
2016-06-15 15:08:56 -04:00
</Form>
</Card>
2016-06-16 10:54:36 -04:00
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
2016-06-15 15:08:56 -04:00
</Slide>
2016-06-02 16:21:14 -04:00
);
}
2016-06-13 13:29:37 -04:00
})),
UseScratchStep: intl.injectIntl(React.createClass({
getDefaultProps: function () {
return {
2016-06-23 07:27:43 -04:00
waiting: false,
maxCharacters: 300
};
},
2016-06-23 07:27:43 -04:00
getInitialState: function () {
return {
characterCount: 0
};
},
handleTyping: function (name, value) {
this.setState({
characterCount: value.length
});
},
2016-06-02 16:21:14 -04:00
render: function () {
2016-06-13 13:29:37 -04:00
var formatMessage = this.props.intl.formatMessage;
var textAreaClass = (this.state.characterCount > this.props.maxCharacters) ? 'fail' : '';
2016-06-02 16:21:14 -04:00
return (
2016-07-19 16:49:12 -04:00
<Slide className="registration-step usescratch-step">
2016-06-23 07:27:43 -04:00
<h2>
2016-06-15 15:08:56 -04:00
<intl.FormattedMessage id="teacherRegistration.useScratchStepTitle" />
2016-06-23 07:27:43 -04:00
</h2>
2016-06-15 15:08:56 -04:00
<p className="description">
<intl.FormattedMessage id="teacherRegistration.useScratchStepDescription" />
2016-06-23 07:27:43 -04:00
<Tooltip title={'?'}
2016-07-19 16:51:28 -04:00
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
2016-06-15 15:08:56 -04:00
</p>
<Card>
<Form onValidSubmit={this.props.onNextStep}>
<TextArea label={formatMessage({id: 'teacherRegistration.howUseScratch'})}
name="useScratch"
className={textAreaClass}
2016-06-23 07:27:43 -04:00
onChange={this.handleTyping}
validations={{
maxLength: this.props.maxCharacters
}}
validationErrors={{
maxLength: formatMessage({
id: 'teacherRegistration.useScratchMaxLength'
})
}}
2016-06-15 15:08:56 -04:00
required />
2016-06-23 07:27:43 -04:00
<CharCount maxCharacters={this.props.maxCharacters}
currentCharacters={this.state.characterCount} />
<NextStepButton waiting={this.props.waiting}
2016-07-19 16:51:28 -04:00
text={<intl.FormattedMessage id="registration.nextStep" />} />
2016-06-15 15:08:56 -04:00
</Form>
</Card>
2016-06-16 10:54:36 -04:00
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
2016-06-15 15:08:56 -04:00
</Slide>
2016-06-02 16:21:14 -04:00
);
}
2016-06-13 13:29:37 -04:00
})),
EmailStep: intl.injectIntl(React.createClass({
getDefaultProps: function () {
return {
waiting: false
};
2016-06-16 17:25:14 -04:00
},
2016-06-17 13:01:53 -04:00
getInitialState: function () {
return {
waiting: false
};
},
onValidSubmit: function (formData, reset, invalidate) {
this.setState({waiting: true});
api({
host: '',
uri: '/accounts/check_email/',
params: {email: formData.user.email}
}, function (err, res) {
this.setState({waiting: false});
if (err) return invalidate({all: err});
res = res[0];
switch (res.msg) {
case 'valid email':
return this.props.onNextStep(formData);
default:
return invalidate({'user.email': res.msg});
}
}.bind(this));
},
2016-06-02 16:21:14 -04:00
render: function () {
2016-06-13 13:29:37 -04:00
var formatMessage = this.props.intl.formatMessage;
2016-06-02 16:21:14 -04:00
return (
2016-07-19 16:49:12 -04:00
<Slide className="registration-step email-step">
2016-06-23 07:27:43 -04:00
<h2>
2016-06-15 15:08:56 -04:00
<intl.FormattedMessage id="teacherRegistration.emailStepTitle" />
2016-06-23 07:27:43 -04:00
</h2>
2016-06-17 12:22:29 -04:00
<p className="description">
2016-06-15 15:08:56 -04:00
<intl.FormattedMessage id="teacherRegistration.emailStepDescription" />
2016-06-23 07:27:43 -04:00
<Tooltip title={'?'}
2016-07-19 16:51:28 -04:00
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
2016-06-15 15:08:56 -04:00
</p>
<Card>
2016-06-17 13:01:53 -04:00
<Form onValidSubmit={this.onValidSubmit}>
2016-06-15 15:08:56 -04:00
<Input label={formatMessage({id: 'general.emailAddress'})}
type="text"
name="user.email"
validations="isEmail"
validationError={formatMessage({id: 'general.validationEmail'})}
required />
<Input label={formatMessage({id: 'general.confirmEmail'})}
type="text"
name="confirmEmail"
validations="equalsField:user.email"
validationErrors={{
equalsField: formatMessage({id: 'general.validationEmailMatch'})
}}
required />
<GeneralError name="all" />
<NextStepButton waiting={this.props.waiting}
2016-07-19 16:51:28 -04:00
text={<intl.FormattedMessage id="registration.nextStep" />} />
2016-06-15 15:08:56 -04:00
</Form>
</Card>
2016-06-16 10:54:36 -04:00
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
2016-06-15 15:08:56 -04:00
</Slide>
2016-06-02 16:21:14 -04:00
);
}
2016-06-13 13:29:37 -04:00
})),
2016-06-22 13:35:38 -04:00
TeacherApprovalStep: intl.injectIntl(React.createClass({
getDefaultProps: function () {
return {
2016-06-22 13:35:38 -04:00
email: null,
invited: false,
2016-07-26 17:03:41 -04:00
confirmed: false
};
},
2016-06-02 16:21:14 -04:00
render: function () {
return (
2016-07-19 16:49:12 -04:00
<Slide className="registration-step last-step">
2016-06-23 07:27:43 -04:00
<h2>
2016-06-22 13:35:38 -04:00
<intl.FormattedMessage id="registration.lastStepTitle" />
2016-06-23 07:27:43 -04:00
</h2>
2016-06-17 12:22:29 -04:00
<p className="description">
2016-06-22 13:35:38 -04:00
<intl.FormattedMessage id="registration.lastStepDescription" />
2016-06-15 15:08:56 -04:00
</p>
2016-06-22 13:35:38 -04:00
{this.props.confirmed || !this.props.email ?
[]
:
(<Card className="confirm">
<h4><intl.FormattedMessage id="registration.confirmYourEmail" /></h4>
2016-06-22 13:35:38 -04:00
<p>
<intl.FormattedMessage id="registration.confirmYourEmailDescription" /><br />
<strong>{this.props.email}</strong>
</p>
</Card>)
}
{this.props.invited ?
<Card className="wait">
<h4><intl.FormattedMessage id="registration.waitForApproval" /></h4>
2016-06-22 13:35:38 -04:00
<p>
<intl.FormattedMessage id="registration.waitForApprovalDescription" />
</p>
</Card>
:
[]
}
2016-06-15 15:08:56 -04:00
<Card className="resources">
<h4><intl.FormattedMessage id="registration.checkOutResources" /></h4>
2016-06-02 16:21:14 -04:00
<p>
2016-06-22 13:35:38 -04:00
<intl.FormattedHTMLMessage id="registration.checkOutResourcesDescription" />
2016-06-02 16:21:14 -04:00
</p>
2016-06-15 15:08:56 -04:00
</Card>
</Slide>
2016-06-02 16:21:14 -04:00
);
}
})),
ClassInviteNewStudentStep: intl.injectIntl(React.createClass({
getDefaultProps: function () {
return {
waiting: false
};
},
onNextStep: function () {
this.props.onNextStep();
},
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
<Slide className="registration-step class-invite-step">
{this.props.waiting ? [
<Spinner />
] : [
<Avatar className="invite-avatar"
src={this.props.classroom.educator.profile.images['50x50']} />,
<h2>{this.props.classroom.educator.username}</h2>,
<p className="description">
{formatMessage({id: 'registration.classroomInviteNewStudentStepDescription'})}
</p>,
<Card>
<div className="contents">
<h3>{this.props.classroom.title}</h3>
<img className="class-image" src={this.props.classroom.images['250x150']} />
</div>
<NextStepButton onClick={this.onNextStep}
waiting={this.props.waiting}
text={formatMessage({id: 'general.getStarted'})} />
</Card>,
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
]}
</Slide>
);
}
})),
ClassInviteExistingStudentStep: intl.injectIntl(React.createClass({
2016-07-19 16:51:28 -04:00
getDefaultProps: function () {
return {
classroom: null,
onHandleLogOut: function () {},
studentUsername: null,
2016-07-19 16:51:28 -04:00
waiting: false
};
},
onNextStep: function () {
this.props.onNextStep();
},
render: function () {
var formatMessage = this.props.intl.formatMessage;
2016-07-19 16:51:28 -04:00
return (
<Slide className="registration-step class-invite-step">
{this.props.waiting ? [
<Spinner />
] : [
<h2>{this.props.studentUsername}</h2>,
<p className="description">
{formatMessage({id: 'registration.classroomInviteExistingStudentStepDescription'})}
</p>,
<Card>
<div className="contents">
<h3>{this.props.classroom.title}</h3>
<img className="class-image" src={this.props.classroom.images['250x150']} />
<p>{formatMessage({id: 'registration.invitedBy'})}</p>
<p><strong>{this.props.classroom.educator.username}</strong></p>
</div>
<NextStepButton onClick={this.onNextStep}
waiting={this.props.waiting}
text={formatMessage({id: 'general.getStarted'})} />
</Card>,
<p><a onClick={this.props.onHandleLogOut}>{formatMessage({id: 'registration.notYou'})}</a></p>,
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
]}
2016-07-19 16:51:28 -04:00
</Slide>
);
}
})),
ClassWelcomeStep: intl.injectIntl(React.createClass({
2016-07-19 16:51:28 -04:00
getDefaultProps: function () {
return {
waiting: false
2016-07-19 16:51:28 -04:00
};
},
onNextStep: function () {
this.props.onNextStep();
},
render: function () {
var formatMessage = this.props.intl.formatMessage;
2016-07-19 16:51:28 -04:00
return (
<Slide className="registration-step class-welcome-step">
{this.props.waiting ? [
<Spinner />
] : [
<h2>{formatMessage({id: 'registration.welcomeStepTitle'})}</h2>,
<p className="description">{formatMessage({id: 'registration.welcomeStepDescription'})}</p>,
<Card>
{this.props.classroom ? (
<div className="contents">
<h3>{this.props.classroom.title}</h3>
<img className="class-image" src={this.props.classroom.images['250x150']} />
<p>{formatMessage({id: 'registration.welcomeStepPrompt'})}</p>
</div>
) : (
null
)}
<NextStepButton onClick={this.onNextStep}
waiting={this.props.waiting}
text={formatMessage({id: 'registration.goToClass'})} />
</Card>
]}
2016-07-19 16:51:28 -04:00
</Slide>
);
}
})),
RegistrationError: intl.injectIntl(React.createClass({
render: function () {
return (
2016-07-19 16:49:12 -04:00
<Slide className="registration-step error-step">
2016-06-23 07:27:43 -04:00
<h2>Something went wrong</h2>
<Card>
<h4>There was an error while processing your registration</h4>
<p>
{this.props.children}
</p>
</Card>
</Slide>
);
}
2016-06-13 13:29:37 -04:00
}))
2016-06-02 16:21:14 -04:00
};