mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-12-12 08:41:31 -05:00
7031bc5dfe
fixed the issue "Create a Username" has different font weight
1624 lines
58 KiB
JavaScript
1624 lines
58 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 select
|
|
* @param {object} reactIntl react-intl, used to localize strings
|
|
* @return {object} ordered set of county options formatted for select
|
|
*/
|
|
const getCountryOptions = reactIntl => (
|
|
[
|
|
{
|
|
label: reactIntl.formatMessage({id: 'registration.selectCountry'}),
|
|
disabled: true,
|
|
value: ''
|
|
},
|
|
...countryData.registrationCountryCodeOptions
|
|
]
|
|
);
|
|
|
|
const NextStepButton = props => (
|
|
<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.validationUsernameNotAllowed'
|
|
})
|
|
});
|
|
return callback(false);
|
|
}
|
|
});
|
|
}
|
|
handleUsernameBlur (name, value) {
|
|
if (this.form.formsy.inputs[0].isValidValue(value)) {
|
|
this.validateUsername(value);
|
|
}
|
|
}
|
|
handleValidSubmit (formData) {
|
|
this.setState({waiting: true});
|
|
this.validateUsername(formData.user.username, isValid => {
|
|
this.setState({waiting: false});
|
|
if (isValid) return this.props.onNextStep(formData);
|
|
});
|
|
}
|
|
render () {
|
|
return (
|
|
<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
|
|
) : (
|
|
<span>
|
|
<intl.FormattedMessage id="registration.usernameStepDescription" />
|
|
<b>
|
|
<intl.FormattedMessage id="registration.usernameStepRealName" />
|
|
</b>
|
|
</span>
|
|
)}
|
|
{this.props.tooltip ? (
|
|
<Tooltip
|
|
tipContent={this.props.tooltip}
|
|
title={'?'}
|
|
/>
|
|
) : (
|
|
null
|
|
)}
|
|
</p>
|
|
<Card>
|
|
<Form
|
|
ref={form => {
|
|
this.form = form;
|
|
}}
|
|
onValidSubmit={this.handleValidSubmit}
|
|
>
|
|
<div className="form-group row">
|
|
<label className="username-label col-sm-3">
|
|
{this.props.intl.formatMessage({id: 'registration.createUsername'})}
|
|
{this.props.usernameHelp ? (
|
|
<p className="help-text">{this.props.usernameHelp}</p>
|
|
) : (
|
|
null
|
|
)}
|
|
</label>
|
|
<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}
|
|
name="showPassword"
|
|
value={this.state.showPassword}
|
|
valueLabel={
|
|
this.props.intl.formatMessage({id: 'registration.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}
|
|
name="showPassword"
|
|
value={this.state.showPassword}
|
|
valueLabel={
|
|
this.props.intl.formatMessage({id: 'registration.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, [
|
|
'birthDateValidator',
|
|
'countryValidator',
|
|
'getCountryName',
|
|
'getMonthOptions',
|
|
'getYearOptions',
|
|
'handleChooseGender',
|
|
'handleValidSubmit',
|
|
'isValidBirthdate'
|
|
]);
|
|
this.state = {
|
|
otherDisabled: true
|
|
};
|
|
}
|
|
getMonthOptions () {
|
|
return [
|
|
'January', 'February', 'March', 'April', 'May', 'June', 'July',
|
|
'August', 'September', 'October', 'November', 'December'
|
|
].map((label, id) => ({
|
|
value: (id + 1).toString(),
|
|
label: this.props.intl.formatMessage({id: `general.month${label}`})
|
|
}));
|
|
}
|
|
getYearOptions () {
|
|
return Array.apply(null, Array(100)).map((v, id) => {
|
|
const year = (new Date().getFullYear() - (id + this.props.birthOffset)).toString();
|
|
return {value: year, label: year};
|
|
});
|
|
}
|
|
handleChooseGender (name, gender) {
|
|
this.setState({otherDisabled: gender !== 'other'});
|
|
}
|
|
// look up country name using user's country code selection
|
|
getCountryName (values) {
|
|
if (values.countryCode) {
|
|
const countryInfo = countryData.lookupCountryInfo(values.countryCode);
|
|
if (countryInfo) {
|
|
return countryInfo.name;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
handleValidSubmit (formData) {
|
|
const countryName = this.getCountryName(formData);
|
|
if (countryName && formData.user) {
|
|
formData.user.country = countryName;
|
|
return this.props.onNextStep(formData);
|
|
}
|
|
return false;
|
|
}
|
|
isValidBirthdate (year, month) {
|
|
const birthdate = new Date(
|
|
year,
|
|
month - 1,
|
|
1
|
|
);
|
|
return (((Date.now() - birthdate) / (24 * 3600 * 1000 * 365.25)) >= this.props.birthOffset);
|
|
}
|
|
birthDateValidator (values) {
|
|
const isValid = this.isValidBirthdate(values['user.birth.year'], values['user.birth.month']);
|
|
return isValid ? true : this.props.intl.formatMessage({id: 'teacherRegistration.validationAge'});
|
|
}
|
|
countryValidator (values) {
|
|
const countryName = this.getCountryName(values);
|
|
if (countryName) return true;
|
|
return this.props.intl.formatMessage({id: 'general.invalidSelection'});
|
|
}
|
|
render () {
|
|
const countryOptions = getCountryOptions(this.props.intl);
|
|
return (
|
|
<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()}
|
|
validations={{
|
|
birthDateVal: values => this.birthDateValidator(values)
|
|
}}
|
|
/>
|
|
<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="countryCode"
|
|
options={countryOptions}
|
|
validations={{
|
|
countryVal: values => this.countryValidator(values)
|
|
}}
|
|
value={countryOptions[0].value}
|
|
/>
|
|
<Checkbox
|
|
className="demographics-checkbox-is-robot"
|
|
name="user.isRobot"
|
|
valueLabel="I'm a robot!"
|
|
/>
|
|
<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
|
|
name="phoneConsent"
|
|
required="isFalse"
|
|
validationErrors={{
|
|
isFalse: this.props.intl.formatMessage({
|
|
id: 'teacherRegistration.validationPhoneConsent'
|
|
})
|
|
}}
|
|
valueLabel={
|
|
this.props.intl.formatMessage({id: 'teacherRegistration.phoneConsent'})
|
|
}
|
|
/>
|
|
<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.toString(),
|
|
label: this.props.intl.formatMessage({
|
|
id: `teacherRegistration.${choice}`
|
|
})
|
|
}));
|
|
// Add "Other" option with empty string, since input field is used
|
|
const otherId = options.length.toString();
|
|
options.push({value: otherId, label: ' '});
|
|
return options;
|
|
}
|
|
handleChooseOrganization (name, values) {
|
|
this.setState({
|
|
otherDisabled: values.indexOf(ORGANIZATION_L10N_STEMS.length.toString()) === -1
|
|
});
|
|
}
|
|
render () {
|
|
return (
|
|
<Slide className="registration-step organization-step">
|
|
<h2>
|
|
<intl.FormattedMessage id="teacherRegistration.organization" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="teacherRegistration.privacyDescription" />
|
|
<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);
|
|
const countryOptions = getCountryOptions(this.props.intl);
|
|
return (
|
|
<Slide className="registration-step address-step">
|
|
<h2>
|
|
<intl.FormattedMessage id="teacherRegistration.addressStepTitle" />
|
|
</h2>
|
|
<p className="description">
|
|
<intl.FormattedMessage id="teacherRegistration.privacyDescription" />
|
|
<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={countryOptions}
|
|
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
|
|
changeDebounceInterval={0}
|
|
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
|
|
help={null}
|
|
name="subscribe"
|
|
value={false}
|
|
valueLabel={
|
|
this.props.intl.formatMessage({id: 'registration.optIn'})
|
|
}
|
|
/>
|
|
<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;
|