mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-12-12 16:51:30 -05:00
99c1f3f0b8
Setting onClick to onNextStep directly caused click event data to be passed in handleAdvanceStep, which then merged it with the formData state in student registration. This caused issues.
1596 lines
57 KiB
JavaScript
1596 lines
57 KiB
JavaScript
/* 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 frc select
|
|
* @param {object} reactIntl 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
|
|
*/
|
|
const getCountryOptions = (reactIntl, defaultCountry) => {
|
|
const options = countryData.countryOptions.concat({
|
|
label: reactIntl.formatMessage({id: 'registration.selectCountry'}),
|
|
disabled: true,
|
|
selected: true
|
|
});
|
|
|
|
if (typeof defaultCountry !== 'undefined') {
|
|
return options.sort((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;
|
|
});
|
|
}
|
|
return options;
|
|
};
|
|
|
|
const NextStepButton = props => (
|
|
<Button
|
|
className="card-button"
|
|
disabled={props.waiting}
|
|
type="submit"
|
|
{...omit(props, ['text', 'waiting'])}
|
|
>
|
|
{props.waiting ?
|
|
<Spinner /> :
|
|
props.text
|
|
}
|
|
</Button>
|
|
);
|
|
|
|
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.validationUsernameInvalid'
|
|
})
|
|
});
|
|
return callback(false);
|
|
}
|
|
});
|
|
}
|
|
handleUsernameBlur (event) {
|
|
if (this.form.formsy.inputs[0].isValidValue(event.currentTarget.value)) {
|
|
this.validateUsername(event.currentTarget.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 (
|
|
<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
|
|
tipContent={this.props.tooltip}
|
|
title={'?'}
|
|
/>
|
|
) : (
|
|
null
|
|
)}
|
|
</p>
|
|
<Card>
|
|
<Form
|
|
ref={form => {
|
|
this.form = form;
|
|
}}
|
|
onValidSubmit={this.handleValidSubmit}
|
|
>
|
|
<div>
|
|
<div className="username-label">
|
|
<b>
|
|
{this.props.intl.formatMessage({id: 'registration.createUsername'})}
|
|
</b>
|
|
{this.props.usernameHelp ? (
|
|
<p className="help-text">{this.props.usernameHelp}</p>
|
|
) : (
|
|
null
|
|
)}
|
|
</div>
|
|
<Input
|
|
required
|
|
className={this.state.validUsername}
|
|
name="user.username"
|
|
type="text"
|
|
validationErrors={{
|
|
matchRegexp: this.props.intl.formatMessage({
|
|
id: 'registration.validationUsernameRegexp'
|
|
}),
|
|
minLength: this.props.intl.formatMessage({
|
|
id: 'registration.validationUsernameMinLength'
|
|
}),
|
|
maxLength: this.props.intl.formatMessage({
|
|
id: 'registration.validationUsernameMaxLength'
|
|
})
|
|
}}
|
|
validations={{
|
|
matchRegexp: /^[\w-]*$/,
|
|
minLength: 3,
|
|
maxLength: 20
|
|
}}
|
|
onBlur={this.handleUsernameBlur}
|
|
/>
|
|
</div>
|
|
<Input
|
|
required
|
|
label={
|
|
this.props.intl.formatMessage({id: 'general.password'})
|
|
}
|
|
name="user.password"
|
|
type={this.state.showPassword ? 'text' : 'password'}
|
|
validationErrors={{
|
|
minLength: this.props.intl.formatMessage({
|
|
id: 'registration.validationPasswordLength'
|
|
}),
|
|
notEquals: this.props.intl.formatMessage({
|
|
id: 'registration.validationPasswordNotEquals'
|
|
}),
|
|
notEqualsField: this.props.intl.formatMessage({
|
|
id: 'registration.validationPasswordNotUsername'
|
|
})
|
|
}}
|
|
validations={{
|
|
minLength: 6,
|
|
notEquals: 'password',
|
|
notEqualsField: 'user.username'
|
|
}}
|
|
/>
|
|
<Checkbox
|
|
help={null}
|
|
label={
|
|
this.props.intl.formatMessage({id: 'registration.showPassword'})
|
|
}
|
|
name="showPassword"
|
|
value={this.state.showPassword}
|
|
onChange={this.handleChangeShowPassword}
|
|
/>
|
|
<GeneralError name="all" />
|
|
<NextStepButton
|
|
text={<intl.FormattedMessage id="registration.nextStep" />}
|
|
waiting={this.props.waiting || this.state.waiting}
|
|
/>
|
|
</Form>
|
|
</Card>
|
|
<StepNavigation
|
|
active={this.props.activeStep}
|
|
steps={this.props.totalSteps - 1}
|
|
/>
|
|
</Slide>
|
|
);
|
|
}
|
|
}
|
|
|
|
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 (
|
|
<Slide className="registration-step choose-password-step">
|
|
<h2>
|
|
{this.props.intl.formatMessage({id: 'registration.choosePasswordStepTitle'})}
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="registration.choosePasswordStepDescription" />
|
|
<Tooltip
|
|
tipContent={
|
|
this.props.intl.formatMessage({id: 'registration.choosePasswordStepTooltip'})
|
|
}
|
|
title={'?'}
|
|
/>
|
|
</p>
|
|
|
|
<Card>
|
|
<Form onValidSubmit={this.props.onNextStep}>
|
|
<Input
|
|
required
|
|
label={
|
|
this.props.intl.formatMessage({id: 'registration.newPassword'})
|
|
}
|
|
name="user.password"
|
|
type={this.state.showPassword ? 'text' : 'password'}
|
|
validationErrors={{
|
|
minLength: this.props.intl.formatMessage({
|
|
id: 'registration.validationPasswordLength'
|
|
}),
|
|
notEquals: this.props.intl.formatMessage({
|
|
id: 'registration.validationPasswordNotEquals'
|
|
}),
|
|
notEqualsUsername: this.props.intl.formatMessage({
|
|
id: 'registration.validationPasswordNotUsername'
|
|
})
|
|
}}
|
|
validations={{
|
|
minLength: 6,
|
|
notEquals: 'password',
|
|
notEqualsUsername: this.props.username
|
|
}}
|
|
/>
|
|
<Checkbox
|
|
help={null}
|
|
label={
|
|
this.props.intl.formatMessage({id: 'registration.showPassword'})
|
|
}
|
|
name="showPassword"
|
|
value={this.state.showPassword}
|
|
onChange={this.handleChangeShowPassword}
|
|
/>
|
|
<NextStepButton
|
|
text={<intl.FormattedMessage id="registration.nextStep" />}
|
|
waiting={this.props.waiting || this.state.waiting}
|
|
/>
|
|
</Form>
|
|
</Card>
|
|
<StepNavigation
|
|
active={this.props.activeStep}
|
|
steps={this.props.totalSteps - 1}
|
|
/>
|
|
</Slide>
|
|
);
|
|
}
|
|
}
|
|
|
|
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, [
|
|
'getMonthOptions',
|
|
'getYearOptions',
|
|
'handleChooseGender',
|
|
'handleValidSubmit'
|
|
]);
|
|
this.state = {
|
|
otherDisabled: true
|
|
};
|
|
}
|
|
getMonthOptions () {
|
|
return [
|
|
'January', 'February', 'March', 'April', 'May', 'June', 'July',
|
|
'August', 'September', 'October', 'November', 'December'
|
|
].map((label, id) => ({
|
|
value: id + 1,
|
|
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);
|
|
return {value: year, label: year};
|
|
});
|
|
}
|
|
handleChooseGender (name, gender) {
|
|
this.setState({otherDisabled: gender !== 'other'});
|
|
}
|
|
handleValidSubmit (formData, reset, invalidate) {
|
|
const birthdate = new Date(
|
|
formData.user.birth.year,
|
|
formData.user.birth.month - 1,
|
|
1
|
|
);
|
|
if (((Date.now() - birthdate) / (24 * 3600 * 1000 * 365.25)) < this.props.birthOffset) {
|
|
return invalidate({
|
|
'user.birth.year': this.props.intl.formatMessage({id: 'teacherRegistration.validationAge'})
|
|
});
|
|
}
|
|
return this.props.onNextStep(formData);
|
|
}
|
|
render () {
|
|
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
|
|
tipContent={
|
|
this.props.intl.formatMessage({id: 'registration.nameStepTooltip'})
|
|
}
|
|
title={'?'}
|
|
/>
|
|
</p>
|
|
<Card>
|
|
<Form onValidSubmit={this.handleValidSubmit}>
|
|
<Select
|
|
required
|
|
label={
|
|
this.props.intl.formatMessage({id: 'general.birthMonth'})
|
|
}
|
|
name="user.birth.month"
|
|
options={this.getMonthOptions()}
|
|
/>
|
|
<Select
|
|
required
|
|
label={
|
|
this.props.intl.formatMessage({id: 'general.birthYear'})
|
|
}
|
|
name="user.birth.year"
|
|
options={this.getYearOptions()}
|
|
/>
|
|
<RadioGroup
|
|
required
|
|
label={
|
|
this.props.intl.formatMessage({id: 'general.gender'})
|
|
}
|
|
name="user.gender"
|
|
options={[
|
|
{
|
|
value: 'female',
|
|
label: this.props.intl.formatMessage({id: 'general.female'})
|
|
},
|
|
{
|
|
value: 'male',
|
|
label: this.props.intl.formatMessage({id: 'general.male'})
|
|
},
|
|
{
|
|
value: 'other',
|
|
label: <Input
|
|
className="demographics-step-input-other"
|
|
disabled={this.state.otherDisabled}
|
|
help={null}
|
|
name="user.genderOther"
|
|
required={!this.state.otherDisabled}
|
|
type="text"
|
|
validationErrors={{
|
|
maxLength: this.props.intl.formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
validations={{
|
|
maxLength: 25
|
|
}}
|
|
/>
|
|
}
|
|
]}
|
|
onChange={this.handleChooseGender}
|
|
/>
|
|
<Select
|
|
required
|
|
label={this.props.intl.formatMessage({id: 'general.country'})}
|
|
name="user.country"
|
|
options={getCountryOptions(this.props.intl, DEFAULT_COUNTRY)}
|
|
/>
|
|
<Checkbox
|
|
className="demographics-checkbox-is-robot"
|
|
label="I'm a robot!"
|
|
name="user.isRobot"
|
|
/>
|
|
<NextStepButton
|
|
text={<intl.FormattedMessage id="registration.nextStep" />}
|
|
waiting={this.props.waiting}
|
|
/>
|
|
</Form>
|
|
</Card>
|
|
<StepNavigation
|
|
active={this.props.activeStep}
|
|
steps={this.props.totalSteps - 1}
|
|
/>
|
|
</Slide>
|
|
);
|
|
}
|
|
}
|
|
|
|
DemographicsStep.propTypes = {
|
|
activeStep: PropTypes.number,
|
|
birthOffset: PropTypes.number,
|
|
description: PropTypes.string,
|
|
intl: intlShape,
|
|
onNextStep: PropTypes.func,
|
|
totalSteps: PropTypes.number,
|
|
waiting: PropTypes.bool
|
|
};
|
|
|
|
DemographicsStep.defaultProps = {
|
|
waiting: false,
|
|
description: null,
|
|
birthOffset: 0
|
|
};
|
|
|
|
const IntlDemographicsStep = injectIntl(DemographicsStep);
|
|
|
|
|
|
/*
|
|
* NAME STEP
|
|
*/
|
|
const NameStep = props => (
|
|
<Slide className="registration-step name-step">
|
|
<h2>
|
|
<intl.FormattedHTMLMessage id="teacherRegistration.nameStepTitle" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="teacherRegistration.nameStepDescription" />
|
|
<Tooltip
|
|
tipContent={
|
|
props.intl.formatMessage({id: 'registration.nameStepTooltip'})
|
|
}
|
|
title={'?'}
|
|
/>
|
|
</p>
|
|
<Card>
|
|
<Form onValidSubmit={props.onNextStep}>
|
|
<Input
|
|
required
|
|
label={
|
|
props.intl.formatMessage({id: 'teacherRegistration.firstName'})
|
|
}
|
|
name="user.name.first"
|
|
type="text"
|
|
validationErrors={{
|
|
maxLength: props.intl.formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
validations={{
|
|
maxLength: 50
|
|
}}
|
|
/>
|
|
<Input
|
|
required
|
|
label={
|
|
props.intl.formatMessage({id: 'teacherRegistration.lastName'})
|
|
}
|
|
name="user.name.last"
|
|
type="text"
|
|
validationErrors={{
|
|
maxLength: props.intl.formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
validations={{
|
|
maxLength: 50
|
|
}}
|
|
/>
|
|
<NextStepButton
|
|
text={<intl.FormattedMessage id="registration.nextStep" />}
|
|
waiting={props.waiting}
|
|
/>
|
|
</Form>
|
|
</Card>
|
|
<StepNavigation
|
|
active={props.activeStep}
|
|
steps={props.totalSteps - 1}
|
|
/>
|
|
</Slide>
|
|
);
|
|
|
|
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 (
|
|
<Slide className="registration-step phone-step">
|
|
<h2>
|
|
<intl.FormattedMessage id="teacherRegistration.phoneNumber" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="teacherRegistration.phoneStepDescription" />
|
|
<Tooltip
|
|
tipContent={
|
|
this.props.intl.formatMessage({id: 'registration.nameStepTooltip'})
|
|
}
|
|
title={'?'}
|
|
/>
|
|
</p>
|
|
<Card>
|
|
<Form onValidSubmit={this.handleValidSubmit}>
|
|
<PhoneInput
|
|
required
|
|
defaultCountry={this.props.defaultCountry}
|
|
label={
|
|
this.props.intl.formatMessage({id: 'teacherRegistration.phoneNumber'})
|
|
}
|
|
name="phone"
|
|
/>
|
|
<Checkbox
|
|
label={
|
|
this.props.intl.formatMessage({id: 'teacherRegistration.phoneConsent'})
|
|
}
|
|
name="phoneConsent"
|
|
required="isFalse"
|
|
validationErrors={{
|
|
isFalse: this.props.intl.formatMessage({
|
|
id: 'teacherRegistration.validationPhoneConsent'
|
|
})
|
|
}}
|
|
/>
|
|
<NextStepButton
|
|
text={<intl.FormattedMessage id="registration.nextStep" />}
|
|
waiting={this.props.waiting}
|
|
/>
|
|
</Form>
|
|
</Card>
|
|
<StepNavigation
|
|
active={this.props.activeStep}
|
|
steps={this.props.totalSteps - 1}
|
|
/>
|
|
</Slide>
|
|
);
|
|
}
|
|
}
|
|
|
|
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,
|
|
label: this.props.intl.formatMessage({
|
|
id: `teacherRegistration.${choice}`
|
|
})
|
|
}));
|
|
|
|
// Add "Other" option with empty string, since input field is used
|
|
const otherId = options.length;
|
|
options.push({value: otherId, label: ' '});
|
|
|
|
return options;
|
|
}
|
|
handleChooseOrganization (name, values) {
|
|
this.setState({
|
|
otherDisabled: values.indexOf(ORGANIZATION_L10N_STEMS.length) === -1
|
|
});
|
|
}
|
|
render () {
|
|
return (
|
|
<Slide className="registration-step organization-step">
|
|
<h2>
|
|
<intl.FormattedMessage id="teacherRegistration.organization" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="teacherRegistration.orgStepDescription" />
|
|
<Tooltip
|
|
tipContent={
|
|
this.props.intl.formatMessage({id: 'registration.nameStepTooltip'})
|
|
}
|
|
title={'?'}
|
|
/>
|
|
</p>
|
|
<Card>
|
|
<Form onValidSubmit={this.props.onNextStep}>
|
|
<Input
|
|
required
|
|
label={
|
|
this.props.intl.formatMessage({id: 'teacherRegistration.organization'})
|
|
}
|
|
name="organization.name"
|
|
type="text"
|
|
validationErrors={{
|
|
maxLength: this.props.intl.formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
validations={{
|
|
maxLength: 50
|
|
}}
|
|
/>
|
|
<Input
|
|
required
|
|
label={this.props.intl.formatMessage({id: 'teacherRegistration.orgTitle'})}
|
|
name="organization.title"
|
|
type="text"
|
|
validationErrors={{
|
|
maxLength: this.props.intl.formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
validations={{
|
|
maxLength: 50
|
|
}}
|
|
/>
|
|
<div className="organization-type">
|
|
<b><intl.FormattedMessage id="teacherRegistration.orgType" /></b>
|
|
<p className="help-text">
|
|
<intl.FormattedMessage id="teacherRegistration.checkAll" />
|
|
</p>
|
|
<CheckboxGroup
|
|
required
|
|
name="organization.type"
|
|
options={this.getOrganizationOptions()}
|
|
validationErrors={{
|
|
minLength: this.props.intl.formatMessage({
|
|
id: 'form.validationRequired'
|
|
})
|
|
}}
|
|
validations={{
|
|
minLength: 1
|
|
}}
|
|
value={[]}
|
|
onChange={this.handleChooseOrganization}
|
|
/>
|
|
</div>
|
|
<div className="other-input">
|
|
<Input
|
|
disabled={this.state.otherDisabled}
|
|
help={null}
|
|
name="organization.other"
|
|
placeholder={
|
|
this.props.intl.formatMessage({id: 'general.other'})
|
|
}
|
|
required={!this.state.otherDisabled}
|
|
type="text"
|
|
validationErrors={{
|
|
maxLength: this.props.intl.formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
validations={{
|
|
maxLength: 50
|
|
}}
|
|
/>
|
|
</div>
|
|
<div className="url-input">
|
|
<b><intl.FormattedMessage id="general.website" /></b>
|
|
<p className="help-text">
|
|
<intl.FormattedMessage id="teacherRegistration.notRequired" />
|
|
</p>
|
|
<Input
|
|
name="organization.url"
|
|
placeholder={'http://'}
|
|
required="isFalse"
|
|
type="url"
|
|
validationErrors={{
|
|
maxLength: this.props.intl.formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
validations={{
|
|
maxLength: 200
|
|
}}
|
|
/>
|
|
</div>
|
|
<NextStepButton
|
|
text={<intl.FormattedMessage id="registration.nextStep" />}
|
|
waiting={this.props.waiting}
|
|
/>
|
|
</Form>
|
|
</Card>
|
|
<StepNavigation
|
|
active={this.props.activeStep}
|
|
steps={this.props.totalSteps - 1}
|
|
/>
|
|
</Slide>
|
|
);
|
|
}
|
|
}
|
|
|
|
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);
|
|
return (
|
|
<Slide className="registration-step address-step">
|
|
<h2>
|
|
<intl.FormattedMessage id="teacherRegistration.addressStepTitle" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="teacherRegistration.addressStepDescription" />
|
|
<Tooltip
|
|
tipContent={
|
|
this.props.intl.formatMessage({id: 'registration.nameStepTooltip'})
|
|
}
|
|
title={'?'}
|
|
/>
|
|
</p>
|
|
<Card>
|
|
<Form onValidSubmit={this.props.onNextStep}>
|
|
<Select
|
|
required
|
|
label={
|
|
this.props.intl.formatMessage({id: 'general.country'})
|
|
}
|
|
name="address.country"
|
|
options={getCountryOptions(this.props.intl)}
|
|
value={this.props.defaultCountry}
|
|
onChange={this.handleChangeCountry}
|
|
/>
|
|
<Input
|
|
required
|
|
label={
|
|
this.props.intl.formatMessage({id: 'teacherRegistration.addressLine1'})
|
|
}
|
|
name="address.line1"
|
|
type="text"
|
|
validationErrors={{
|
|
maxLength: this.props.intl.formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
validations={{
|
|
maxLength: 100
|
|
}}
|
|
/>
|
|
<Input
|
|
label={
|
|
this.props.intl.formatMessage({id: 'teacherRegistration.addressLine2'})
|
|
}
|
|
name="address.line2"
|
|
required="isFalse"
|
|
type="text"
|
|
validationErrors={{
|
|
maxLength: this.props.intl.formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
validations={{
|
|
maxLength: 100
|
|
}}
|
|
/>
|
|
<Input
|
|
required
|
|
label={
|
|
this.props.intl.formatMessage({id: 'teacherRegistration.city'})
|
|
}
|
|
name="address.city"
|
|
type="text"
|
|
validationErrors={{
|
|
maxLength: this.props.intl.formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
validations={{
|
|
maxLength: 50
|
|
}}
|
|
/>
|
|
{stateOptions.length > 2 ?
|
|
<Select
|
|
required
|
|
label={
|
|
this.props.intl.formatMessage({id: 'teacherRegistration.stateProvince'})
|
|
}
|
|
name="address.state"
|
|
options={stateOptions}
|
|
/> : []
|
|
}
|
|
<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
|
|
name="address.zip"
|
|
required={(this.state.countryChoice === 'us') ? true : 'isFalse'}
|
|
type="text"
|
|
validationErrors={{
|
|
maxLength: this.props.intl.formatMessage({
|
|
id: 'registration.validationMaxLength'
|
|
})
|
|
}}
|
|
validations={{
|
|
maxLength: 10
|
|
}}
|
|
/>
|
|
<GeneralError name="all" />
|
|
<NextStepButton
|
|
text={<intl.FormattedMessage id="registration.nextStep" />}
|
|
waiting={this.props.waiting || this.state.waiting}
|
|
/>
|
|
</Form>
|
|
</Card>
|
|
<StepNavigation
|
|
active={this.props.activeStep}
|
|
steps={this.props.totalSteps - 1}
|
|
/>
|
|
</Slide>
|
|
);
|
|
}
|
|
}
|
|
|
|
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 (
|
|
<Slide className="registration-step usescratch-step">
|
|
<h2>
|
|
<intl.FormattedMessage id="teacherRegistration.useScratchStepTitle" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="teacherRegistration.useScratchStepDescription" />
|
|
<Tooltip
|
|
tipContent={
|
|
this.props.intl.formatMessage({id: 'registration.nameStepTooltip'})
|
|
}
|
|
title={'?'}
|
|
/>
|
|
</p>
|
|
<Card>
|
|
<Form onValidSubmit={this.props.onNextStep}>
|
|
<TextArea
|
|
required
|
|
className={textAreaClass}
|
|
label={
|
|
this.props.intl.formatMessage({id: 'teacherRegistration.howUseScratch'})
|
|
}
|
|
name="useScratch"
|
|
validationErrors={{
|
|
maxLength: this.props.intl.formatMessage({
|
|
id: 'teacherRegistration.useScratchMaxLength'
|
|
})
|
|
}}
|
|
validations={{
|
|
maxLength: this.props.maxCharacters
|
|
}}
|
|
onChange={this.handleTyping}
|
|
/>
|
|
<CharCount
|
|
currentCharacters={this.state.characterCount}
|
|
maxCharacters={this.props.maxCharacters}
|
|
/>
|
|
<NextStepButton
|
|
text={<intl.FormattedMessage id="registration.nextStep" />}
|
|
waiting={this.props.waiting}
|
|
/>
|
|
</Form>
|
|
</Card>
|
|
<StepNavigation
|
|
active={this.props.activeStep}
|
|
steps={this.props.totalSteps - 1}
|
|
/>
|
|
</Slide>
|
|
);
|
|
}
|
|
}
|
|
|
|
UseScratchStep.propTypes = {
|
|
activeStep: PropTypes.number,
|
|
intl: intlShape,
|
|
maxCharacters: PropTypes.number,
|
|
onNextStep: PropTypes.func,
|
|
totalSteps: PropTypes.number,
|
|
waiting: PropTypes.bool
|
|
};
|
|
|
|
UseScratchStep.defaultProps = {
|
|
maxCharacters: 300,
|
|
waiting: false
|
|
};
|
|
|
|
const IntlUseScratchStep = injectIntl(UseScratchStep);
|
|
|
|
|
|
/*
|
|
* EMAIL STEP
|
|
*/
|
|
class EmailStep extends React.Component {
|
|
constructor (props) {
|
|
super(props);
|
|
bindAll(this, [
|
|
'handleValidSubmit'
|
|
]);
|
|
this.state = {
|
|
waiting: false
|
|
};
|
|
}
|
|
handleValidSubmit (formData, reset, invalidate) {
|
|
this.setState({waiting: true});
|
|
api({
|
|
host: '',
|
|
uri: '/accounts/check_email/',
|
|
params: {email: formData.user.email}
|
|
}, (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});
|
|
}
|
|
});
|
|
}
|
|
render () {
|
|
return (
|
|
<Slide className="registration-step email-step">
|
|
<h2>
|
|
<intl.FormattedMessage id="teacherRegistration.emailStepTitle" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="teacherRegistration.emailStepDescription" />
|
|
<Tooltip
|
|
tipContent={
|
|
this.props.intl.formatMessage({id: 'registration.nameStepTooltip'})
|
|
}
|
|
title={'?'}
|
|
/>
|
|
</p>
|
|
<Card>
|
|
<Form onValidSubmit={this.handleValidSubmit}>
|
|
<Input
|
|
required
|
|
label={
|
|
this.props.intl.formatMessage({id: 'general.emailAddress'})
|
|
}
|
|
name="user.email"
|
|
type="text"
|
|
validationError={
|
|
this.props.intl.formatMessage({id: 'general.validationEmail'})
|
|
}
|
|
validations="isEmail"
|
|
/>
|
|
<Input
|
|
required
|
|
label={this.props.intl.formatMessage({id: 'general.confirmEmail'})}
|
|
name="confirmEmail"
|
|
type="text"
|
|
validationErrors={{
|
|
equalsField: this.props.intl.formatMessage({id: 'general.validationEmailMatch'})
|
|
}}
|
|
validations="equalsField:user.email"
|
|
/>
|
|
<Checkbox
|
|
value
|
|
help={null}
|
|
label={
|
|
this.props.intl.formatMessage({id: 'registration.optIn'})
|
|
}
|
|
name="subscribe"
|
|
/>
|
|
<GeneralError name="all" />
|
|
<NextStepButton
|
|
text={<intl.FormattedMessage id="registration.nextStep" />}
|
|
waiting={this.props.waiting}
|
|
/>
|
|
</Form>
|
|
</Card>
|
|
<StepNavigation
|
|
active={this.props.activeStep}
|
|
steps={this.props.totalSteps - 1}
|
|
/>
|
|
</Slide>
|
|
);
|
|
}
|
|
}
|
|
|
|
EmailStep.propTypes = {
|
|
activeStep: PropTypes.number,
|
|
intl: intlShape,
|
|
onNextStep: PropTypes.func,
|
|
totalSteps: PropTypes.number,
|
|
waiting: PropTypes.bool
|
|
};
|
|
|
|
EmailStep.defaultProps = {
|
|
waiting: false
|
|
};
|
|
|
|
const IntlEmailStep = injectIntl(EmailStep);
|
|
|
|
|
|
/*
|
|
* TEACHER APPROVAL STEP
|
|
*/
|
|
const TeacherApprovalStep = props => (
|
|
<Slide className="registration-step last-step">
|
|
<h2>
|
|
<intl.FormattedMessage id="registration.lastStepTitle" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="registration.lastStepDescription" />
|
|
</p>
|
|
{props.confirmed || !props.email ?
|
|
[] : (
|
|
<Card className="confirm">
|
|
<h4><intl.FormattedMessage id="registration.confirmYourEmail" /></h4>
|
|
<p>
|
|
<intl.FormattedMessage id="registration.confirmYourEmailDescription" /><br />
|
|
<strong>{props.email}</strong>
|
|
</p>
|
|
</Card>
|
|
)
|
|
}
|
|
{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>
|
|
);
|
|
|
|
TeacherApprovalStep.propTypes = {
|
|
confirmed: PropTypes.bool,
|
|
email: PropTypes.string,
|
|
invited: PropTypes.bool
|
|
};
|
|
|
|
TeacherApprovalStep.defaultProps = {
|
|
confirmed: false,
|
|
email: null,
|
|
invited: false
|
|
};
|
|
|
|
const IntlTeacherApprovalStep = injectIntl(TeacherApprovalStep);
|
|
|
|
|
|
/*
|
|
* CLASS INVITE NEW STUDENT STEP
|
|
*/
|
|
class ClassInviteNewStudentStep extends React.Component {
|
|
constructor (props) {
|
|
super(props);
|
|
bindAll(this, [
|
|
'handleNextStep'
|
|
]);
|
|
}
|
|
handleNextStep () {
|
|
return this.props.onNextStep();
|
|
}
|
|
render () {
|
|
return (
|
|
<Slide className="registration-step class-invite-step">
|
|
{this.props.waiting ? [
|
|
<Spinner key="spinner" />
|
|
] : [
|
|
<Avatar
|
|
className="invite-avatar"
|
|
key="avatar"
|
|
src={this.props.classroom.educator.profile.images['50x50']}
|
|
/>,
|
|
<h2 key="username">{this.props.classroom.educator.username}</h2>,
|
|
<p
|
|
className="description"
|
|
key="description"
|
|
>
|
|
{this.props.intl.formatMessage({id: 'registration.classroomInviteNewStudentStepDescription'})}
|
|
</p>,
|
|
<Card key="card">
|
|
<div className="contents">
|
|
<h3>{this.props.classroom.title}</h3>
|
|
<img
|
|
className="class-image"
|
|
src={this.props.classroom.images['250x150']}
|
|
/>
|
|
</div>
|
|
<NextStepButton
|
|
text={this.props.intl.formatMessage({id: 'general.getStarted'})}
|
|
waiting={this.props.waiting}
|
|
onClick={this.handleNextStep}
|
|
/>
|
|
</Card>,
|
|
<StepNavigation
|
|
active={this.props.activeStep}
|
|
key="step"
|
|
steps={this.props.totalSteps - 1}
|
|
/>
|
|
]}
|
|
</Slide>
|
|
);
|
|
}
|
|
}
|
|
|
|
ClassInviteNewStudentStep.propTypes = {
|
|
activeStep: PropTypes.number,
|
|
classroom: PropTypes.shape({
|
|
educator: PropTypes.shape({
|
|
profile: PropTypes.object,
|
|
username: PropTypes.string
|
|
}),
|
|
images: PropTypes.object,
|
|
title: PropTypes.string
|
|
}),
|
|
intl: intlShape,
|
|
onNextStep: PropTypes.func,
|
|
totalSteps: PropTypes.number,
|
|
waiting: PropTypes.bool
|
|
};
|
|
|
|
ClassInviteNewStudentStep.defaultProps = {
|
|
waiting: false
|
|
};
|
|
|
|
const IntlClassInviteNewStudentStep = injectIntl(ClassInviteNewStudentStep);
|
|
|
|
|
|
/*
|
|
* CLASS INVITE EXISTING STUDENT STEP
|
|
*/
|
|
class ClassInviteExistingStudentStep extends React.Component {
|
|
constructor (props) {
|
|
super(props);
|
|
bindAll(this, [
|
|
'handleNextStep'
|
|
]);
|
|
}
|
|
handleNextStep () {
|
|
return this.props.onNextStep();
|
|
}
|
|
render () {
|
|
return (
|
|
<Slide className="registration-step class-invite-step">
|
|
{this.props.waiting ? [
|
|
<Spinner key="spinner" />
|
|
] : [
|
|
<h2 key="username">{this.props.studentUsername}</h2>,
|
|
<p
|
|
className="description"
|
|
key="description"
|
|
>
|
|
{this.props.intl.formatMessage({
|
|
id: 'registration.classroomInviteExistingStudentStepDescription'
|
|
})}
|
|
</p>,
|
|
<Card key="card">
|
|
<div className="contents">
|
|
<h3>{this.props.classroom.title}</h3>
|
|
<img
|
|
className="class-image"
|
|
src={this.props.classroom.images['250x150']}
|
|
/>
|
|
<p>{this.props.intl.formatMessage({id: 'registration.invitedBy'})}</p>
|
|
<p><strong>{this.props.classroom.educator.username}</strong></p>
|
|
</div>
|
|
<NextStepButton
|
|
text={this.props.intl.formatMessage({id: 'general.getStarted'})}
|
|
waiting={this.props.waiting}
|
|
onClick={this.handleNextStep}
|
|
/>
|
|
</Card>,
|
|
<p key="logout">
|
|
<a onClick={this.props.onHandleLogOut}>
|
|
{this.props.intl.formatMessage({id: 'registration.notYou'})}
|
|
</a>
|
|
</p>,
|
|
<StepNavigation
|
|
active={this.props.activeStep}
|
|
key="step"
|
|
steps={this.props.totalSteps - 1}
|
|
/>
|
|
]}
|
|
</Slide>
|
|
);
|
|
}
|
|
}
|
|
|
|
ClassInviteExistingStudentStep.propTypes = {
|
|
activeStep: PropTypes.number,
|
|
classroom: PropTypes.shape({
|
|
educator: PropTypes.shape({
|
|
profile: PropTypes.object,
|
|
username: PropTypes.string
|
|
}),
|
|
images: PropTypes.object,
|
|
title: PropTypes.string
|
|
}),
|
|
intl: intlShape,
|
|
onHandleLogOut: PropTypes.func,
|
|
onNextStep: PropTypes.func,
|
|
studentUsername: PropTypes.string,
|
|
totalSteps: PropTypes.number,
|
|
waiting: PropTypes.bool
|
|
};
|
|
|
|
ClassInviteExistingStudentStep.defaultProps = {
|
|
classroom: null,
|
|
onHandleLogOut: () => {},
|
|
studentUsername: null,
|
|
waiting: false
|
|
};
|
|
|
|
const IntlClassInviteExistingStudentStep = injectIntl(ClassInviteExistingStudentStep);
|
|
|
|
|
|
/*
|
|
* CLASS WELCOME STEP
|
|
*/
|
|
const ClassWelcomeStep = props => (
|
|
<Slide className="registration-step class-welcome-step">
|
|
{props.waiting ? [
|
|
<Spinner key="spinner" />
|
|
] : [
|
|
<h2 key="title">
|
|
{props.intl.formatMessage({id: 'registration.welcomeStepTitle'})}
|
|
</h2>,
|
|
<p
|
|
className="description"
|
|
key="description"
|
|
>
|
|
{props.intl.formatMessage({id: 'registration.welcomeStepDescription'})}
|
|
</p>,
|
|
<Card key="card">
|
|
{props.classroom ? (
|
|
<div className="contents">
|
|
<h3>{props.classroom.title}</h3>
|
|
<img
|
|
className="class-image"
|
|
src={props.classroom.images['250x150']}
|
|
/>
|
|
<p>{props.intl.formatMessage({id: 'registration.welcomeStepPrompt'})}</p>
|
|
</div>
|
|
) : (
|
|
null
|
|
)}
|
|
<NextStepButton
|
|
text={
|
|
props.intl.formatMessage({id: 'registration.goToClass'})
|
|
}
|
|
waiting={props.waiting}
|
|
onClick={props.onNextStep}
|
|
/>
|
|
</Card>
|
|
]}
|
|
</Slide>
|
|
);
|
|
|
|
ClassWelcomeStep.propTypes = {
|
|
classroom: PropTypes.shape({
|
|
educator: PropTypes.shape({
|
|
profile: PropTypes.object,
|
|
username: PropTypes.string
|
|
}),
|
|
images: PropTypes.object,
|
|
title: PropTypes.string
|
|
}),
|
|
intl: intlShape,
|
|
onNextStep: PropTypes.func,
|
|
waiting: PropTypes.bool
|
|
};
|
|
|
|
ClassWelcomeStep.defaultProps = {
|
|
waiting: false
|
|
};
|
|
|
|
const IntlClassWelcomeStep = injectIntl(ClassWelcomeStep);
|
|
|
|
|
|
/*
|
|
* REGISTRATION ERROR STEP
|
|
*/
|
|
const RegistrationError = props => (
|
|
<Slide className="registration-step error-step">
|
|
<h2>Something went wrong</h2>
|
|
<Card>
|
|
<h4>There was an error while processing your registration</h4>
|
|
<p>
|
|
{props.children}
|
|
</p>
|
|
</Card>
|
|
</Slide>
|
|
);
|
|
|
|
RegistrationError.propTypes = {
|
|
children: PropTypes.node
|
|
};
|
|
|
|
const IntlRegistrationError = injectIntl(RegistrationError);
|
|
|
|
module.exports.UsernameStep = IntlUsernameStep;
|
|
module.exports.ChoosePasswordStep = IntlChoosePasswordStep;
|
|
module.exports.DemographicsStep = IntlDemographicsStep;
|
|
module.exports.NameStep = IntlNameStep;
|
|
module.exports.PhoneNumberStep = IntlPhoneNumberStep;
|
|
module.exports.OrganizationStep = IntlOrganizationStep;
|
|
module.exports.AddressStep = IntlAddressStep;
|
|
module.exports.UseScratchStep = IntlUseScratchStep;
|
|
module.exports.EmailStep = IntlEmailStep;
|
|
module.exports.TeacherApprovalStep = IntlTeacherApprovalStep;
|
|
module.exports.ClassInviteNewStudentStep = IntlClassInviteNewStudentStep;
|
|
module.exports.ClassInviteExistingStudentStep = IntlClassInviteExistingStudentStep;
|
|
module.exports.ClassWelcomeStep = IntlClassWelcomeStep;
|
|
module.exports.RegistrationError = IntlRegistrationError;
|