mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-12-12 16:51:30 -05:00
e7b0415a6b
Since we only check for the US, only require for that country. Other countries have zip codes, but it seems better to do it this way than codify which countries do/do not have zip codes (in case local practices are not the same as international information). Fixes #825
1061 lines
50 KiB
JavaScript
1061 lines
50 KiB
JavaScript
var React = require('react');
|
|
|
|
var api = require('../../lib/api');
|
|
var countryData = require('../../lib/country-data');
|
|
var intl = require('../../lib/intl.jsx');
|
|
var log = require('../../lib/log');
|
|
var smartyStreets = require('../../lib/smarty-streets');
|
|
|
|
var Avatar = require('../../components/avatar/avatar.jsx');
|
|
var Button = require('../../components/forms/button.jsx');
|
|
var Card = require('../../components/card/card.jsx');
|
|
var CharCount = require('../../components/forms/charcount.jsx');
|
|
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');
|
|
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');
|
|
var Slide = require('../../components/slide/slide.jsx');
|
|
var Spinner = require('../../components/spinner/spinner.jsx');
|
|
var StepNavigation = require('../../components/stepnavigation/stepnavigation.jsx');
|
|
var TextArea = require('../../components/forms/textarea.jsx');
|
|
var Tooltip = require('../../components/tooltip/tooltip.jsx');
|
|
|
|
require('./steps.scss');
|
|
|
|
var DEFAULT_COUNTRY = 'us';
|
|
|
|
/**
|
|
* 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;
|
|
};
|
|
|
|
var NextStepButton = React.createClass({
|
|
getDefaultProps: function () {
|
|
return {
|
|
waiting: false,
|
|
text: 'Next Step'
|
|
};
|
|
},
|
|
render: function () {
|
|
return (
|
|
<Button type="submit" disabled={this.props.waiting} className="card-button" {... this.props}>
|
|
{this.props.waiting ?
|
|
<Spinner /> :
|
|
this.props.text
|
|
}
|
|
</Button>
|
|
);
|
|
}
|
|
});
|
|
|
|
module.exports = {
|
|
UsernameStep: intl.injectIntl(React.createClass({
|
|
getDefaultProps: function () {
|
|
return {
|
|
showPassword: false,
|
|
waiting: false
|
|
};
|
|
},
|
|
getInitialState: function () {
|
|
return {
|
|
showPassword: this.props.showPassword,
|
|
waiting: false,
|
|
validUsername: ''
|
|
};
|
|
},
|
|
onChangeShowPassword: function (field, value) {
|
|
this.setState({showPassword: value});
|
|
},
|
|
validateUsername: function (username, callback) {
|
|
callback = callback || function () {};
|
|
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});
|
|
return callback(false);
|
|
}
|
|
body = body[0];
|
|
|
|
switch (body.msg) {
|
|
case 'valid username':
|
|
this.setState({
|
|
validUsername: 'pass'
|
|
});
|
|
return callback(true);
|
|
case 'username exists':
|
|
this.refs.form.refs.formsy.updateInputsWithError({
|
|
'user.username': formatMessage({id: 'registration.validationUsernameExists'})
|
|
});
|
|
return callback(false);
|
|
case 'bad username':
|
|
this.refs.form.refs.formsy.updateInputsWithError({
|
|
'user.username': formatMessage({id: 'registration.validationUsernameVulgar'})
|
|
});
|
|
return callback(false);
|
|
case 'invalid username':
|
|
default:
|
|
this.refs.form.refs.formsy.updateInputsWithError({
|
|
'user.username': formatMessage({id: 'registration.validationUsernameInvalid'})
|
|
});
|
|
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));
|
|
},
|
|
render: function () {
|
|
var formatMessage = this.props.intl.formatMessage;
|
|
return (
|
|
<Slide className="registration-step username-step">
|
|
<h2>
|
|
{this.props.title ? (
|
|
this.props.title
|
|
) : (
|
|
<intl.FormattedMessage id="registration.usernameStepTitle" />
|
|
)}
|
|
</h2>
|
|
<p className="description">
|
|
{this.props.description ? (
|
|
this.props.description
|
|
) : (
|
|
<intl.FormattedMessage id="registration.usernameStepDescription" />
|
|
)}
|
|
{this.props.tooltip ? (
|
|
<Tooltip title={'?'}
|
|
tipContent={this.props.tooltip} />
|
|
) : (
|
|
null
|
|
)}
|
|
</p>
|
|
<Card>
|
|
<Form onValidSubmit={this.onValidSubmit} ref="form">
|
|
<div>
|
|
<div className="username-label">
|
|
<b>{formatMessage({id: 'registration.createUsername'})}</b>
|
|
{this.props.usernameHelp ? (
|
|
<p className="help-text">{this.props.usernameHelp}</p>
|
|
):(
|
|
null
|
|
)}
|
|
</div>
|
|
<Input className={this.state.validUsername}
|
|
type="text"
|
|
name="user.username"
|
|
onBlur={this.onUsernameBlur}
|
|
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>
|
|
<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({
|
|
id: 'registration.validationPasswordLength'
|
|
}),
|
|
notEquals: formatMessage({
|
|
id: 'registration.validationPasswordNotEquals'
|
|
}),
|
|
notEqualsField: formatMessage({
|
|
id: 'registration.validationPasswordNotUsername'
|
|
})
|
|
}}
|
|
required />
|
|
<Checkbox label={formatMessage({id: 'registration.showPassword'})}
|
|
value={this.state.showPassword}
|
|
onChange={this.onChangeShowPassword}
|
|
help={null}
|
|
name="showPassword" />
|
|
<GeneralError name="all" />
|
|
<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>
|
|
);
|
|
}
|
|
})),
|
|
ChoosePasswordStep: intl.injectIntl(React.createClass({
|
|
getDefaultProps: function () {
|
|
return {
|
|
username: null,
|
|
showPassword: false,
|
|
waiting: false
|
|
};
|
|
},
|
|
getInitialState: function () {
|
|
return {
|
|
showPassword: this.props.showPassword
|
|
};
|
|
},
|
|
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>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="registration.choosePasswordStepDescription" />
|
|
<Tooltip title={'?'}
|
|
tipContent={formatMessage({id: 'registration.choosePasswordStepTooltip'})} />
|
|
</p>
|
|
|
|
<Card>
|
|
<Form onValidSubmit={this.props.onNextStep}>
|
|
<Input label={formatMessage({id: 'registration.newPassword'})}
|
|
type={this.state.showPassword ? 'text' : 'password'}
|
|
name="user.password"
|
|
validations={{
|
|
minLength: 6,
|
|
notEquals: 'password',
|
|
notEqualsUsername: this.props.username
|
|
}}
|
|
validationErrors={{
|
|
minLength: formatMessage({
|
|
id: 'registration.validationPasswordLength'
|
|
}),
|
|
notEquals: formatMessage({
|
|
id: 'registration.validationPasswordNotEquals'
|
|
}),
|
|
notEqualsUsername: formatMessage({
|
|
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>
|
|
);
|
|
}
|
|
})),
|
|
DemographicsStep: intl.injectIntl(React.createClass({
|
|
getDefaultProps: function () {
|
|
return {
|
|
waiting: false,
|
|
description: null
|
|
};
|
|
},
|
|
getInitialState: function () {
|
|
return {otherDisabled: true};
|
|
},
|
|
getMonthOptions: function () {
|
|
return [
|
|
'January', 'February', 'March', 'April', 'May', 'June', 'July',
|
|
'August', 'September', 'October', 'November', 'December'
|
|
].map(function (label, id) {
|
|
return {
|
|
value: id + 1,
|
|
label: this.props.intl.formatMessage({id: 'general.month' + label})};
|
|
}.bind(this));
|
|
},
|
|
getYearOptions: function () {
|
|
return Array.apply(null, Array(100)).map(function (v, id) {
|
|
var year = 2016 - id;
|
|
return {value: year, label: year};
|
|
});
|
|
},
|
|
onChooseGender: function (name, gender) {
|
|
this.setState({otherDisabled: gender !== 'other'});
|
|
},
|
|
render: function () {
|
|
var formatMessage = this.props.intl.formatMessage;
|
|
return (
|
|
<Slide className="registration-step demographics-step">
|
|
<h2>
|
|
<intl.FormattedMessage id="registration.personalStepTitle" />
|
|
</h2>
|
|
<p className="description">
|
|
{this.props.description ?
|
|
this.props.description
|
|
:
|
|
<intl.FormattedMessage id="registration.personalStepDescription" />
|
|
}
|
|
<Tooltip title={'?'}
|
|
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
|
|
</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'})},
|
|
{value: 'other', label: ''}
|
|
]}
|
|
required />
|
|
<div className="gender-input">
|
|
<Input name="user.genderOther"
|
|
type="text"
|
|
validations={{
|
|
maxLength: 25
|
|
}}
|
|
validationErrors={{
|
|
maxLength: formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
disabled={this.state.otherDisabled}
|
|
required={!this.state.otherDisabled}
|
|
help={null} />
|
|
</div>
|
|
<Select label={formatMessage({id: 'general.country'})}
|
|
name="user.country"
|
|
options={getCountryOptions(this.props.intl, DEFAULT_COUNTRY)}
|
|
required />
|
|
<Checkbox className="demographics-checkbox-is-robot"
|
|
label="I'm a robot!"
|
|
name="user.isRobot" />
|
|
<NextStepButton waiting={this.props.waiting}
|
|
text={<intl.FormattedMessage id="registration.nextStep" />} />
|
|
</Form>
|
|
</Card>
|
|
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
|
|
</Slide>
|
|
);
|
|
}
|
|
})),
|
|
NameStep: intl.injectIntl(React.createClass({
|
|
getDefaultProps: function () {
|
|
return {
|
|
waiting: false
|
|
};
|
|
},
|
|
render: function () {
|
|
var formatMessage = this.props.intl.formatMessage;
|
|
return (
|
|
<Slide className="registration-step name-step">
|
|
<h2>
|
|
<intl.FormattedHTMLMessage id="teacherRegistration.nameStepTitle" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="teacherRegistration.nameStepDescription" />
|
|
<Tooltip title={'?'}
|
|
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
|
|
</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'
|
|
})
|
|
}}
|
|
required />
|
|
<Input label={formatMessage({id: 'teacherRegistration.lastName'})}
|
|
type="text"
|
|
name="user.name.last"
|
|
validations={{
|
|
maxLength: 50
|
|
}}
|
|
validationErrors={{
|
|
maxLength: formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
required />
|
|
<NextStepButton waiting={this.props.waiting}
|
|
text={<intl.FormattedMessage id="registration.nextStep" />} />
|
|
</Form>
|
|
</Card>
|
|
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
|
|
</Slide>
|
|
);
|
|
}
|
|
})),
|
|
PhoneNumberStep: intl.injectIntl(React.createClass({
|
|
getDefaultProps: function () {
|
|
return {
|
|
defaultCountry: DEFAULT_COUNTRY,
|
|
waiting: false
|
|
};
|
|
},
|
|
onValidSubmit: function (formData, reset, invalidate) {
|
|
if (!formData.phone || formData.phone.national_number === '+') {
|
|
return invalidate({
|
|
'phone': this.props.intl.formatMessage({id: 'teacherRegistration.validationPhoneNumber'})
|
|
});
|
|
}
|
|
return this.props.onNextStep(formData);
|
|
},
|
|
render: function () {
|
|
var formatMessage = this.props.intl.formatMessage;
|
|
return (
|
|
<Slide className="registration-step phone-step">
|
|
<h2>
|
|
<intl.FormattedMessage id="teacherRegistration.phoneStepTitle" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="teacherRegistration.phoneStepDescription" />
|
|
<Tooltip title={'?'}
|
|
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
|
|
</p>
|
|
<Card>
|
|
<Form onValidSubmit={this.onValidSubmit}>
|
|
<PhoneInput label={formatMessage({id: 'teacherRegistration.phoneNumber'})}
|
|
name="phone"
|
|
defaultCountry={this.props.defaultCountry}
|
|
required />
|
|
<Checkbox label={formatMessage({id: 'teacherRegistration.phoneConsent'})}
|
|
name="phoneConsent"
|
|
required="isFalse"
|
|
validationErrors={{
|
|
isFalse: formatMessage({id: 'teacherRegistration.validationPhoneConsent'})
|
|
}} />
|
|
<NextStepButton waiting={this.props.waiting}
|
|
text={<intl.FormattedMessage id="registration.nextStep" />} />
|
|
</Form>
|
|
</Card>
|
|
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
|
|
</Slide>
|
|
);
|
|
}
|
|
})),
|
|
OrganizationStep: intl.injectIntl(React.createClass({
|
|
getInitialState: function () {
|
|
return {
|
|
otherDisabled: true
|
|
};
|
|
},
|
|
getDefaultProps: function () {
|
|
return {
|
|
waiting: false
|
|
};
|
|
},
|
|
organizationL10nStems: [
|
|
'orgChoiceElementarySchool',
|
|
'orgChoiceMiddleSchool',
|
|
'orgChoiceHighSchool',
|
|
'orgChoiceUniversity',
|
|
'orgChoiceAfterschool',
|
|
'orgChoiceMuseum',
|
|
'orgChoiceLibrary',
|
|
'orgChoiceCamp',
|
|
'orgChoiceOther'
|
|
],
|
|
getOrganizationOptions: function () {
|
|
return this.organizationL10nStems.map(function (choice, id) {
|
|
return {
|
|
value: id,
|
|
label: this.props.intl.formatMessage({
|
|
id: 'teacherRegistration.' + choice
|
|
})
|
|
};
|
|
}.bind(this));
|
|
},
|
|
onChooseOrganization: function (name, values) {
|
|
this.setState({otherDisabled: values.indexOf(this.organizationL10nStems.indexOf('orgChoiceOther')) === -1});
|
|
},
|
|
render: function () {
|
|
var formatMessage = this.props.intl.formatMessage;
|
|
return (
|
|
<Slide className="registration-step organization-step">
|
|
<h2>
|
|
<intl.FormattedMessage id="teacherRegistration.orgStepTitle" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="teacherRegistration.orgStepDescription" />
|
|
<Tooltip title={'?'}
|
|
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
|
|
</p>
|
|
<Card>
|
|
<Form onValidSubmit={this.props.onNextStep}>
|
|
<Input label={formatMessage({id: 'teacherRegistration.organization'})}
|
|
type="text"
|
|
name="organization.name"
|
|
validations={{
|
|
maxLength: 50
|
|
}}
|
|
validationErrors={{
|
|
maxLength: formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
required />
|
|
<Input label={formatMessage({id: 'teacherRegistration.orgTitle'})}
|
|
type="text"
|
|
name="organization.title"
|
|
validations={{
|
|
maxLength: 50
|
|
}}
|
|
validationErrors={{
|
|
maxLength: formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
required />
|
|
<div className="organization-type">
|
|
<b><intl.FormattedMessage id="teacherRegistration.orgType" /></b>
|
|
<p className="help-text">
|
|
<intl.FormattedMessage id="teacherRegistration.checkAll" />
|
|
</p>
|
|
<CheckboxGroup name="organization.type"
|
|
value={[]}
|
|
options={this.getOrganizationOptions()}
|
|
onChange={this.onChooseOrganization}
|
|
validations={{
|
|
minLength: 1
|
|
}}
|
|
validationErrors={{
|
|
minLength: formatMessage({
|
|
id: 'teacherRegistration.validationRequired'
|
|
})
|
|
}}
|
|
required />
|
|
</div>
|
|
<div className="other-input">
|
|
<Input name="organization.other"
|
|
type="text"
|
|
validations={{
|
|
maxLength: 50
|
|
}}
|
|
validationErrors={{
|
|
maxLength: formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
disabled={this.state.otherDisabled}
|
|
required={!this.state.otherDisabled}
|
|
help={null}
|
|
placeholder={formatMessage({id: 'general.other'})} />
|
|
</div>
|
|
<div className="url-input">
|
|
<b><intl.FormattedMessage id="general.website" /></b>
|
|
<p className="help-text">
|
|
<intl.FormattedMessage id="teacherRegistration.notRequired" />
|
|
</p>
|
|
<Input type="url"
|
|
name="organization.url"
|
|
validations={{
|
|
maxLength: 200
|
|
}}
|
|
validationErrors={{
|
|
maxLength: formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
required="isFalse"
|
|
placeholder={'http://'} />
|
|
</div>
|
|
<NextStepButton waiting={this.props.waiting}
|
|
text={<intl.FormattedMessage id="registration.nextStep" />} />
|
|
</Form>
|
|
</Card>
|
|
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
|
|
</Slide>
|
|
);
|
|
}
|
|
})),
|
|
AddressStep: intl.injectIntl(React.createClass({
|
|
getDefaultProps: function () {
|
|
return {
|
|
defaultCountry: DEFAULT_COUNTRY,
|
|
waiting: false
|
|
};
|
|
},
|
|
getInitialState: function () {
|
|
return {
|
|
countryChoice: this.props.defaultCountry,
|
|
waiting: false
|
|
};
|
|
},
|
|
onChangeCountry: function (field, choice) {
|
|
this.setState({countryChoice: choice});
|
|
},
|
|
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'})
|
|
});
|
|
}
|
|
}.bind(this));
|
|
},
|
|
render: function () {
|
|
var formatMessage = this.props.intl.formatMessage;
|
|
var stateOptions = countryData.subdivisionOptions[this.state.countryChoice];
|
|
stateOptions = [{}].concat(stateOptions);
|
|
return (
|
|
<Slide className="registration-step address-step">
|
|
<h2>
|
|
<intl.FormattedMessage id="teacherRegistration.addressStepTitle" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="teacherRegistration.addressStepDescription" />
|
|
<Tooltip title={'?'}
|
|
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
|
|
</p>
|
|
<Card>
|
|
<Form onValidSubmit={this.onValidSubmit}>
|
|
<Select label={formatMessage({id: 'general.country'})}
|
|
name="address.country"
|
|
options={getCountryOptions(this.props.intl)}
|
|
value={this.props.defaultCountry}
|
|
onChange={this.onChangeCountry}
|
|
required />
|
|
<Input label={formatMessage({id: 'teacherRegistration.addressLine1'})}
|
|
type="text"
|
|
name="address.line1"
|
|
validations={{
|
|
maxLength: 100
|
|
}}
|
|
validationErrors={{
|
|
maxLength: formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
required />
|
|
<Input label={formatMessage({id: 'teacherRegistration.addressLine2'})}
|
|
type="text"
|
|
name="address.line2"
|
|
validations={{
|
|
maxLength: 100
|
|
}}
|
|
validationErrors={{
|
|
maxLength: formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
required="isFalse" />
|
|
<Input label={formatMessage({id: 'teacherRegistration.city'})}
|
|
type="text"
|
|
name="address.city"
|
|
validations={{
|
|
maxLength: 50
|
|
}}
|
|
validationErrors={{
|
|
maxLength: formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
required />
|
|
{stateOptions.length > 2 ?
|
|
<Select label={formatMessage({id: 'teacherRegistration.stateProvince'})}
|
|
name="address.state"
|
|
options={stateOptions}
|
|
required /> :
|
|
[]
|
|
}
|
|
<Input label={formatMessage({id: 'teacherRegistration.zipCode'})}
|
|
type="text"
|
|
name="address.zip"
|
|
validations={{
|
|
maxLength: 10
|
|
}}
|
|
validationErrors={{
|
|
maxLength: formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
required={(this.state.countryChoice === 'us')} />
|
|
<GeneralError name="all" />
|
|
<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>
|
|
);
|
|
}
|
|
})),
|
|
UseScratchStep: intl.injectIntl(React.createClass({
|
|
getDefaultProps: function () {
|
|
return {
|
|
waiting: false,
|
|
maxCharacters: 300
|
|
};
|
|
},
|
|
getInitialState: function () {
|
|
return {
|
|
characterCount: 0
|
|
};
|
|
},
|
|
handleTyping: function (name, value) {
|
|
this.setState({
|
|
characterCount: value.length
|
|
});
|
|
},
|
|
render: function () {
|
|
var formatMessage = this.props.intl.formatMessage;
|
|
var textAreaClass = (this.state.characterCount > this.props.maxCharacters) ? 'fail' : '';
|
|
|
|
return (
|
|
<Slide className="registration-step usescratch-step">
|
|
<h2>
|
|
<intl.FormattedMessage id="teacherRegistration.useScratchStepTitle" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="teacherRegistration.useScratchStepDescription" />
|
|
<Tooltip title={'?'}
|
|
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
|
|
</p>
|
|
<Card>
|
|
<Form onValidSubmit={this.props.onNextStep}>
|
|
<TextArea label={formatMessage({id: 'teacherRegistration.howUseScratch'})}
|
|
name="useScratch"
|
|
className={textAreaClass}
|
|
onChange={this.handleTyping}
|
|
validations={{
|
|
maxLength: this.props.maxCharacters
|
|
}}
|
|
validationErrors={{
|
|
maxLength: formatMessage({
|
|
id: 'teacherRegistration.useScratchMaxLength'
|
|
})
|
|
}}
|
|
required />
|
|
<CharCount maxCharacters={this.props.maxCharacters}
|
|
currentCharacters={this.state.characterCount} />
|
|
<NextStepButton waiting={this.props.waiting}
|
|
text={<intl.FormattedMessage id="registration.nextStep" />} />
|
|
</Form>
|
|
</Card>
|
|
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
|
|
</Slide>
|
|
);
|
|
}
|
|
})),
|
|
EmailStep: intl.injectIntl(React.createClass({
|
|
getDefaultProps: function () {
|
|
return {
|
|
waiting: false
|
|
};
|
|
},
|
|
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));
|
|
},
|
|
render: function () {
|
|
var formatMessage = this.props.intl.formatMessage;
|
|
return (
|
|
<Slide className="registration-step email-step">
|
|
<h2>
|
|
<intl.FormattedMessage id="teacherRegistration.emailStepTitle" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="teacherRegistration.emailStepDescription" />
|
|
<Tooltip title={'?'}
|
|
tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
|
|
</p>
|
|
<Card>
|
|
<Form onValidSubmit={this.onValidSubmit}>
|
|
<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}
|
|
text={<intl.FormattedMessage id="registration.nextStep" />} />
|
|
</Form>
|
|
</Card>
|
|
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
|
|
</Slide>
|
|
);
|
|
}
|
|
})),
|
|
TeacherApprovalStep: intl.injectIntl(React.createClass({
|
|
getDefaultProps: function () {
|
|
return {
|
|
email: null,
|
|
invited: false,
|
|
confirmed: false
|
|
};
|
|
},
|
|
render: function () {
|
|
return (
|
|
<Slide className="registration-step last-step">
|
|
<h2>
|
|
<intl.FormattedMessage id="registration.lastStepTitle" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="registration.lastStepDescription" />
|
|
</p>
|
|
{this.props.confirmed || !this.props.email ?
|
|
[]
|
|
:
|
|
(<Card className="confirm">
|
|
<h4><intl.FormattedMessage id="registration.confirmYourEmail" /></h4>
|
|
<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>
|
|
<p>
|
|
<intl.FormattedMessage id="registration.waitForApprovalDescription" />
|
|
</p>
|
|
</Card>
|
|
:
|
|
[]
|
|
}
|
|
<Card className="resources">
|
|
<h4><intl.FormattedMessage id="registration.checkOutResources" /></h4>
|
|
<p>
|
|
<intl.FormattedHTMLMessage id="registration.checkOutResourcesDescription" />
|
|
</p>
|
|
</Card>
|
|
</Slide>
|
|
);
|
|
}
|
|
})),
|
|
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({
|
|
getDefaultProps: function () {
|
|
return {
|
|
classroom: null,
|
|
onHandleLogOut: function () {},
|
|
studentUsername: null,
|
|
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 />
|
|
] : [
|
|
<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} />
|
|
]}
|
|
</Slide>
|
|
);
|
|
}
|
|
})),
|
|
ClassWelcomeStep: 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-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>
|
|
]}
|
|
</Slide>
|
|
);
|
|
}
|
|
})),
|
|
RegistrationError: intl.injectIntl(React.createClass({
|
|
render: function () {
|
|
return (
|
|
<Slide className="registration-step error-step">
|
|
<h2>Something went wrong</h2>
|
|
<Card>
|
|
<h4>There was an error while processing your registration</h4>
|
|
<p>
|
|
{this.props.children}
|
|
</p>
|
|
</Card>
|
|
</Slide>
|
|
);
|
|
}
|
|
}))
|
|
};
|