Commit 00c7dc92 authored by Ray Schamp's avatar Ray Schamp Committed by GitHub

Merge pull request #750 from LLK/release/2.2.11

[Master] Release 2.2.11
parents 15dd4db7 7e89ccef
{ {
"parser": "babel-eslint",
"rules": { "rules": {
"curly": [2, "multi-line"], "curly": [2, "multi-line"],
"eol-last": [2], "eol-last": [2],
...@@ -14,16 +15,12 @@ ...@@ -14,16 +15,12 @@
}, },
"env": { "env": {
"browser": true, "browser": true,
"es6": true,
"node": true "node": true
}, },
"globals": { "globals": {
"formatMessage": true "formatMessage": true
}, },
"ecmaFeatures": {
"arrowFunctions": true,
"blockBindings": true,
"jsx": true
},
"plugins": [ "plugins": [
"react", "react",
"json" "json"
......
...@@ -18,9 +18,9 @@ var fastly = require('./lib/fastly-extended')(process.env.FASTLY_API_KEY, FASTLY ...@@ -18,9 +18,9 @@ var fastly = require('./lib/fastly-extended')(process.env.FASTLY_API_KEY, FASTLY
var extraAppRoutes = [ var extraAppRoutes = [
// Homepage with querystring. // Homepage with querystring.
// TODO: Should this be added for every route? // TODO: Should this be added for every route?
'^/\\?', '/\\?',
// View html // View html
'^/[^\/]*\.html$' '/[^\/]*\.html$'
]; ];
/* /*
...@@ -35,7 +35,7 @@ var getStaticPaths = function (pathToStatic) { ...@@ -35,7 +35,7 @@ var getStaticPaths = function (pathToStatic) {
}).map(function (pathName) { }).map(function (pathName) {
// Reduce absolute path to relative paths like '/js' // Reduce absolute path to relative paths like '/js'
var base = path.dirname(path.resolve(__dirname, pathToStatic)); var base = path.dirname(path.resolve(__dirname, pathToStatic));
return '^' + pathName.replace(base, '') + (path.extname(pathName) ? '' : '/'); return pathName.replace(base, '') + (path.extname(pathName) ? '' : '/');
}); });
}; };
...@@ -58,7 +58,7 @@ var getViewPaths = function (routes) { ...@@ -58,7 +58,7 @@ var getViewPaths = function (routes) {
* all :arguments become .+? * all :arguments become .+?
*/ */
var expressPatternToRegex = function (pattern) { var expressPatternToRegex = function (pattern) {
return pattern.replace(/(:[^/]+)\//gi, '.+?/'); return pattern.replace(/(:[^/]+)/gi, '.+?');
}; };
/* /*
...@@ -66,9 +66,9 @@ var expressPatternToRegex = function (pattern) { ...@@ -66,9 +66,9 @@ var expressPatternToRegex = function (pattern) {
* string suitable for a Fastly condition * string suitable for a Fastly condition
*/ */
var pathsToCondition = function (paths) { var pathsToCondition = function (paths) {
return 'req.url~"' + paths.reduce(function (conditionString, pattern) { return 'req.url~"^(' + paths.reduce(function (conditionString, pattern) {
return conditionString + (conditionString ? '|' : '') + pattern; return conditionString + (conditionString ? '|' : '') + pattern;
}, '') + '"'; }, '') + ')"';
}; };
/* /*
......
...@@ -33,6 +33,11 @@ ...@@ -33,6 +33,11 @@
"devDependencies": { "devDependencies": {
"async": "1.5.2", "async": "1.5.2",
"autoprefixer": "6.3.6", "autoprefixer": "6.3.6",
"babel-core": "6.10.4",
"babel-eslint": "5.0.4",
"babel-loader": "6.2.4",
"babel-preset-es2015": "6.9.0",
"babel-preset-react": "6.11.1",
"classnames": "2.1.3", "classnames": "2.1.3",
"cookie": "0.2.2", "cookie": "0.2.2",
"copy-webpack-plugin": "0.2.0", "copy-webpack-plugin": "0.2.0",
...@@ -51,7 +56,6 @@ ...@@ -51,7 +56,6 @@
"iso-3166-2": "0.4.0", "iso-3166-2": "0.4.0",
"json-loader": "0.5.2", "json-loader": "0.5.2",
"json2po-stream": "1.0.3", "json2po-stream": "1.0.3",
"jsx-loader": "0.13.2",
"keymirror": "0.1.1", "keymirror": "0.1.1",
"lodash.clone": "3.0.3", "lodash.clone": "3.0.3",
"lodash.defaultsdeep": "3.10.0", "lodash.defaultsdeep": "3.10.0",
......
...@@ -5,4 +5,117 @@ ...@@ -5,4 +5,117 @@
border-radius: 8px / $em; border-radius: 8px / $em;
box-shadow: 0 0 0 .125rem $active-gray; box-shadow: 0 0 0 .125rem $active-gray;
background-color: $ui-white; background-color: $ui-white;
.card-button {
display: block;
border-radius: .5rem;
border-top-left-radius: 0;
border-top-right-radius: 0;
box-shadow: none;
background-color: $ui-aqua;
width: 23.75rem;
height: 4rem;
&:hover {
box-shadow: none;
}
}
.validation-message {
$arrow-border-width: 1rem;
display: block;
position: absolute;
top: 0;
left: 0;
transform: translate(16rem, 0);
margin-left: $arrow-border-width;
border: 1px solid $active-gray;
border-radius: 5px;
background-color: $ui-orange;
padding: 1rem;
max-width: 18.75rem;
min-height: 1rem;
max-height: 3rem;
overflow: visible;
color: $type-white;
&:before {
display: block;
position: absolute;
top: 1rem;
left: -$arrow-border-width / 2;
transform: rotate(45deg);
border-bottom: 1px solid $active-gray;
border-left: 1px solid $active-gray;
border-radius: 5px;
background-color: $ui-orange;
width: $arrow-border-width;
height: $arrow-border-width;
content: "";
}
}
.form {
padding: 3rem 4rem;
.card-button {
margin: 0 0 -3rem -4rem;
}
.row {
margin-bottom: 1.2rem;
&.has-error {
.input {
border: 1px solid $ui-orange;
}
}
.col-sm-9 {
position: relative;
}
}
}
}
@media only screen and (max-width: $mobile - 1) {
.card {
width: 22.5rem;
.form {
text-align: left;
.button {
width: 22.5rem;
}
}
}
}
@media only screen and (max-width: $tablet - 1) {
.card {
.input {
width: 90%;
}
}
}
@media only screen and (max-width: $desktop - 1) {
.card {
.validation-message {
position: relative;
transform: none;
margin: inherit;
width: 100%;
height: inherit;
&:before {
display: none;
}
}
}
} }
...@@ -10,7 +10,7 @@ var Deck = React.createClass({ ...@@ -10,7 +10,7 @@ var Deck = React.createClass({
<div className={classNames(['deck', this.props.className])}> <div className={classNames(['deck', this.props.className])}>
<div className="inner"> <div className="inner">
<a href="/" aria-label="Scratch"> <a href="/" aria-label="Scratch">
<img src="/images/logo_sm.png" /> <img className="logo" src="/images/logo_sm.png" />
</a> </a>
{this.props.children} {this.props.children}
</div> </div>
......
...@@ -6,146 +6,22 @@ ...@@ -6,146 +6,22 @@
.deck { .deck {
min-height: 100vh; min-height: 100vh;
img { .logo {
margin-left: 2px; margin-left: 2px;
padding: 12px 0; padding: 12px 0;
width: 76px; width: 76px;
} }
.step-navigation {
margin-top: 2rem;
text-align: center;
}
.slide { .slide {
max-width: 28.75rem; max-width: 28.75rem;
h2,
.description {
text-align: center;
color: $type-white;
}
.description {
margin-top: 0;
margin-bottom: 2rem;
}
} }
.card { .card {
margin: 0 auto; width: 23.75rem;
width: 23.75rem;
}
.form {
padding: 3rem 4rem;
.form-group {
margin-bottom: 1.2rem;
&.has-error {
.input {
border: 1px solid $ui-orange;
}
}
}
.button {
margin: 0 0 -3rem -4rem;
border-radius: .5rem;
box-shadow: none;
width: 23.75rem;
height: 4rem;
&.card-button {
display: block;
border-top-left-radius: 0;
border-top-right-radius: 0;
background-color: $ui-aqua;
}
&:hover {
box-shadow: none;
}
}
} }
.input { .input {
width: $cols5; width: 100%;
} }
.help-block {
$arrow-border-width: 1rem;
display: block;
position: absolute;
margin-left: $arrow-border-width;
border: 1px solid $active-gray;
border-radius: 5px;
background-color: $ui-orange;
padding: 1rem;
max-width: 18.75rem;
min-height: 1rem;
max-height: 3rem;
overflow: visible;
color: $type-white;
&:before {
display: block;
position: absolute;
top: 1rem;
left: -$arrow-border-width / 2;
transform: rotate(45deg);
border-bottom: 1px solid $active-gray;
border-left: 1px solid $active-gray;
border-radius: 5px;
background-color: $ui-orange;
width: $arrow-border-width;
height: $arrow-border-width;
content: "";
}
}
}
@media only screen and (max-width: $mobile - 1) {
.deck {
.card {
width: 22.5rem;
}
.form {
text-align: left;
.button {
width: 22.5rem;
}
}
}
}
@media only screen and (max-width: $tablet - 1) {
.deck {
.input {
width: 90%;
}
}
}
@media only screen and (max-width: $desktop - 1) {
.deck {
.help-block {
position: relative;
transform: none;
margin: inherit;
width: 100%;
height: inherit;
&:before {
display: none;
}
}
}
} }
...@@ -15,9 +15,7 @@ var Checkbox = React.createClass({ ...@@ -15,9 +15,7 @@ var Checkbox = React.createClass({
this.props.className this.props.className
); );
return ( return (
<div className={classes}> <FRCCheckbox rowClassName={classes} {... this.props} />
<FRCCheckbox {... this.props} />
</div>
); );
} }
}); });
......
...@@ -29,14 +29,14 @@ var Input = React.createClass({ ...@@ -29,14 +29,14 @@ var Input = React.createClass({
}, },
render: function () { render: function () {
var classes = classNames( var classes = classNames(
'input',
this.state.status, this.state.status,
this.props.className this.props.className,
{'no-label': (typeof this.props.label === 'undefined')}
); );
return (this.props.type === 'submit' || this.props.noformsy ? return (
<input {... this.props} className={classes} /> :
<FRCInput {... this.props} <FRCInput {... this.props}
className={classes} className="input"
rowClassName={classes}
onValid={this.onValid} onValid={this.onValid}
onInvalid={this.onInvalid} /> onInvalid={this.onInvalid} />
); );
......
...@@ -12,7 +12,7 @@ $pass-bg: lighten($ui-aqua, 35%); ...@@ -12,7 +12,7 @@ $pass-bg: lighten($ui-aqua, 35%);
.input { .input {
transition: all .5s ease; transition: all .5s ease;
margin: .75rem 0; margin-bottom: .75rem;
border: 1px solid $active-gray; border: 1px solid $active-gray;
border-radius: 5px; border-radius: 5px;
background-color: $base-bg; background-color: $base-bg;
...@@ -35,4 +35,9 @@ $pass-bg: lighten($ui-aqua, 35%); ...@@ -35,4 +35,9 @@ $pass-bg: lighten($ui-aqua, 35%);
border: 1px solid $active-dark-gray; border: 1px solid $active-dark-gray;
background-color: $pass-bg; background-color: $pass-bg;
} }
/* IE10/11-specific style resets */
&::-ms-reveal, &::-ms-clear {
display: none;
}
} }
...@@ -42,7 +42,7 @@ var PhoneInput = React.createClass({ ...@@ -42,7 +42,7 @@ var PhoneInput = React.createClass({
return ( return (
<Row {... this.getRowProperties()} <Row {... this.getRowProperties()}
htmlFor={this.getId()} htmlFor={this.getId()}
className={classNames('phone-input', this.props.className)} rowClassName={classNames('phone-input', this.props.className)}
> >
<div className="input-group"> <div className="input-group">
<ReactPhoneInput className="form-control" <ReactPhoneInput className="form-control"
...@@ -53,9 +53,9 @@ var PhoneInput = React.createClass({ ...@@ -53,9 +53,9 @@ var PhoneInput = React.createClass({
label={null} label={null}
disabled={this.isFormDisabled() || this.props.disabled} disabled={this.isFormDisabled() || this.props.disabled}
/> />
{this.renderHelp()}
{this.renderErrorMessage()}
</div> </div>
{this.renderHelp()}
{this.renderErrorMessage()}
</Row> </Row>
); );
} }
......
@import "../../colors"; @import "../../colors";
.input-group { .input-group {
margin: .75rem 0;
width: 100%; width: 100%;
} }
.react-tel-input { .react-tel-input {
margin-bottom: .75rem;
width: 100%; width: 100%;
input { input {
......
...@@ -4,8 +4,19 @@ ...@@ -4,8 +4,19 @@
* the formsy-react-components * the formsy-react-components
*/ */
.form-group { .row {
.required-symbol { .required-symbol {
display: none; display: none;
} }
label {
display: inline-block;
margin-bottom: .75rem;
}
&.no-label {
label {
display: none;
}
}
} }
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
select { select {
transition: all .5s ease; transition: all .5s ease;
margin: .75rem 0; margin-bottom: .75rem;
border: 1px solid $active-gray; border: 1px solid $active-gray;
border-radius: 5px; border-radius: 5px;
background: $ui-light-gray url("../../../static/svgs/forms/carot.svg") no-repeat right center; background: $ui-light-gray url("../../../static/svgs/forms/carot.svg") no-repeat right center;
...@@ -23,6 +23,12 @@ ...@@ -23,6 +23,12 @@
display: none; display: none;
} }
&::-ms-value {
background-color: inherit;
padding-left: 1rem;
color: inherit;
}
&:focus { &:focus {
transition: all .5s ease; transition: all .5s ease;
outline: none; outline: none;
......
...@@ -11,11 +11,13 @@ var TextArea = React.createClass({ ...@@ -11,11 +11,13 @@ var TextArea = React.createClass({
type: 'TextArea', type: 'TextArea',
render: function () { render: function () {
var classes = classNames( var classes = classNames(
'textarea', 'textarea-row',
this.props.className this.props.className
); );
return ( return (
<FRCTextarea {... this.props} className={classes} /> <FRCTextarea {... this.props}
className="textarea"
rowClassName={classes} />
); );
} }
}); });
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
.textarea { .textarea {
transition: all 1s ease; transition: all 1s ease;
margin: .75rem 0; margin-bottom: .75rem;
border: 1px solid $active-gray; border: 1px solid $active-gray;
border-radius: 5px; border-radius: 5px;
background-color: $ui-light-gray; background-color: $ui-light-gray;
......
...@@ -11,6 +11,7 @@ var api = require('../../../lib/api'); ...@@ -11,6 +11,7 @@ var api = require('../../../lib/api');
var Avatar = require('../../avatar/avatar.jsx'); var Avatar = require('../../avatar/avatar.jsx');
var Button = require('../../forms/button.jsx'); var Button = require('../../forms/button.jsx');
var Dropdown = require('../../dropdown/dropdown.jsx'); var Dropdown = require('../../dropdown/dropdown.jsx');
var Form = require('../../forms/form.jsx');
var Input = require('../../forms/input.jsx'); var Input = require('../../forms/input.jsx');
var log = require('../../../lib/log.js'); var log = require('../../../lib/log.js');
var Login = require('../../login/login.jsx'); var Login = require('../../login/login.jsx');
...@@ -170,6 +171,9 @@ var Navigation = React.createClass({ ...@@ -170,6 +171,9 @@ var Navigation = React.createClass({
this.props.dispatch(sessionActions.refreshSession()); this.props.dispatch(sessionActions.refreshSession());
this.closeRegistration(); this.closeRegistration();
}, },
onSearchSubmit: function (formData) {
window.location.href = '/search/projects?q=' + formData.q;
},
render: function () { render: function () {
var classes = classNames({ var classes = classNames({
'logged-in': this.props.session.session.user 'logged-in': this.props.session.session.user
...@@ -216,14 +220,13 @@ var Navigation = React.createClass({ ...@@ -216,14 +220,13 @@ var Navigation = React.createClass({
</li> </li>
<li className="search"> <li className="search">
<form action="/search/projects" method="get"> <Form onSubmit={this.onSearchSubmit}>
<Button type="submit" className="btn-search" /> <Button type="submit" className="btn-search" />
<Input type="text" <Input type="text"
aria-label={formatMessage({id: 'general.search'})} aria-label={formatMessage({id: 'general.search'})}
placeholder={formatMessage({id: 'general.search'})} placeholder={formatMessage({id: 'general.search'})}
name="q" name="q" />
noformsy /> </Form>
</form>
</li> </li>
{this.props.session.status === sessionActions.Status.FETCHED ? ( {this.props.session.status === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [ this.props.session.session.user ? [
......
...@@ -47,12 +47,18 @@ ...@@ -47,12 +47,18 @@
width: 100%; width: 100%;
} }
form { .form {
margin: 0; margin: 0;
} }
input, .row {
button { .help-block {
display: none;
}
}
.input,
.button {
display: inline-block; display: inline-block;
margin-top: 5px; margin-top: 5px;
outline: none; outline: none;
......
...@@ -6,6 +6,7 @@ var intl = require('../../lib/intl.jsx'); ...@@ -6,6 +6,7 @@ var intl = require('../../lib/intl.jsx');
var log = require('../../lib/log'); var log = require('../../lib/log');
var smartyStreets = require('../../lib/smarty-streets'); var smartyStreets = require('../../lib/smarty-streets');
var Avatar = require('../../components/avatar/avatar.jsx');
var Button = require('../../components/forms/button.jsx'); var Button = require('../../components/forms/button.jsx');
var Card = require('../../components/card/card.jsx'); var Card = require('../../components/card/card.jsx');
var CharCount = require('../../components/forms/charcount.jsx'); var CharCount = require('../../components/forms/charcount.jsx');
...@@ -23,7 +24,26 @@ var StepNavigation = require('../../components/stepnavigation/stepnavigation.jsx ...@@ -23,7 +24,26 @@ var StepNavigation = require('../../components/stepnavigation/stepnavigation.jsx
var TextArea = require('../../components/forms/textarea.jsx'); var TextArea = require('../../components/forms/textarea.jsx');
var Tooltip = require('../../components/tooltip/tooltip.jsx'); var Tooltip = require('../../components/tooltip/tooltip.jsx');
require('./steps.scss');
var DEFAULT_COUNTRY = 'us'; var DEFAULT_COUNTRY = 'us';
var getCountryOptions = function (defaultCountry) {
var options = countryData.countryOptions.concat({
label: <intl.FormattedMessage 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({ var NextStepButton = React.createClass({
getDefaultProps: function () { getDefaultProps: function () {
...@@ -34,7 +54,7 @@ var NextStepButton = React.createClass({ ...@@ -34,7 +54,7 @@ var NextStepButton = React.createClass({
}, },
render: function () { render: function () {
return ( return (
<Button type="submit" disabled={this.props.waiting} className="card-button"> <Button type="submit" disabled={this.props.waiting} className="card-button" {... this.props}>
{this.props.waiting ? {this.props.waiting ?
<Spinner /> : <Spinner /> :
this.props.text this.props.text
...@@ -48,12 +68,13 @@ module.exports = { ...@@ -48,12 +68,13 @@ module.exports = {
UsernameStep: intl.injectIntl(React.createClass({ UsernameStep: intl.injectIntl(React.createClass({
getDefaultProps: function () { getDefaultProps: function () {
return { return {
showPassword: false,
waiting: false waiting: false
}; };
}, },
getInitialState: function () { getInitialState: function () {
return { return {
showPassword: false, showPassword: this.props.showPassword,
waiting: false, waiting: false,
validUsername: '' validUsername: ''
}; };
...@@ -79,16 +100,16 @@ module.exports = { ...@@ -79,16 +100,16 @@ module.exports = {
return this.props.onNextStep(formData); return this.props.onNextStep(formData);
case 'username exists': case 'username exists':
return invalidate({ return invalidate({
'user.username': formatMessage({id: 'general.validationUsernameExists'}) 'user.username': formatMessage({id: 'registration.validationUsernameExists'})
}); });
case 'bad username': case 'bad username':
return invalidate({ return invalidate({
'user.username': formatMessage({id: 'general.validationUsernameVulgar'}) 'user.username': formatMessage({id: 'registration.validationUsernameVulgar'})
}); });
case 'invalid username': case 'invalid username':
default: default:
return invalidate({ return invalidate({
'user.username': formatMessage({id: 'general.validationUsernameInvalid'}) 'user.username': formatMessage({id: 'registration.validationUsernameInvalid'})
}); });
} }
}.bind(this)); }.bind(this));
...@@ -96,35 +117,123 @@ module.exports = { ...@@ -96,35 +117,123 @@ module.exports = {
render: function () { render: function () {
var formatMessage = this.props.intl.formatMessage; var formatMessage = this.props.intl.formatMessage;
return ( return (
<Slide className="username-step"> <Slide className="registration-step username-step">
<h2><intl.FormattedMessage id="teacherRegistration.usernameStepTitle" /></h2> <h2>
{this.props.title ? (
this.props.title
) : (
<intl.FormattedMessage id="registration.usernameStepTitle" />
)}
</h2>
<p className="description"> <p className="description">
<intl.FormattedMessage id="teacherRegistration.usernameStepDescription" /> {this.props.description ? (
this.props.description
) : (
<intl.FormattedMessage id="registration.usernameStepDescription" />
)}
{this.props.tooltip ? (
<Tooltip title={'?'}
tipContent={this.props.tooltip} />
) : (
null
)}
</p> </p>
<Card> <Card>
<Form onValidSubmit={this.onValidSubmit}> <Form onValidSubmit={this.onValidSubmit}>
<Input label={formatMessage({id: 'general.createUsername'})} <div>
className={this.state.validUsername} <div className="username-label">
type="text" <b>{formatMessage({id: 'registration.createUsername'})}</b>
name="user.username" {this.props.usernameHelp ? (
<p className="help-text">{this.props.usernameHelp}</p>
):(
null
)}
</div>
<Input className={this.state.validUsername}
type="text"
name="user.username"
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={{ validations={{
matchRegexp: /^[\w-]*$/, minLength: 6,
minLength: 3, notEquals: 'password',
maxLength: 20 notEqualsField: 'user.username'
}} }}
validationErrors={{ validationErrors={{
matchRegexp: formatMessage({
id: 'teacherRegistration.validationUsernameRegexp'
}),
minLength: formatMessage({ minLength: formatMessage({
id: 'teacherRegistration.validationUsernameMinLength' id: 'registration.validationPasswordLength'
}), }),
maxLength: formatMessage({ notEquals: formatMessage({
id: 'teacherRegistration.validationUsernameMaxLength' id: 'registration.validationPasswordNotEquals'
}),
notEqualsField: formatMessage({
id: 'registration.validationPasswordNotUsername'
}) })
}} }}
required /> required />
<Input label={formatMessage({id: 'general.password'})} <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 {
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'} type={this.state.showPassword ? 'text' : 'password'}
name="user.password" name="user.password"
validations={{ validations={{
...@@ -134,24 +243,23 @@ module.exports = { ...@@ -134,24 +243,23 @@ module.exports = {
}} }}
validationErrors={{ validationErrors={{
minLength: formatMessage({ minLength: formatMessage({
id: 'teacherRegistration.validationPasswordLength' id: 'registration.validationPasswordLength'
}), }),
notEquals: formatMessage({ notEquals: formatMessage({
id: 'teacherRegistration.validationPasswordNotEquals' id: 'registration.validationPasswordNotEquals'
}), }),
notEqualsField: formatMessage({ notEqualsField: formatMessage({
id: 'teacherRegistration.validationPasswordNotUsername' id: 'registration.validationPasswordNotUsername'
}) })
}} }}
required /> required />
<Checkbox label={formatMessage({id: 'teacherRegistration.showPassword'})} <Checkbox label={formatMessage({id: 'registration.showPassword'})}
value={this.state.showPassword} value={this.state.showPassword}
onChange={this.onChangeShowPassword} onChange={this.onChangeShowPassword}
help={null} help={null}
name="showPassword" /> name="showPassword" />
<GeneralError name="all" />
<NextStepButton waiting={this.props.waiting || this.state.waiting} <NextStepButton waiting={this.props.waiting || this.state.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} /> text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form> </Form>
</Card> </Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} /> <StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
...@@ -162,8 +270,8 @@ module.exports = { ...@@ -162,8 +270,8 @@ module.exports = {
DemographicsStep: intl.injectIntl(React.createClass({ DemographicsStep: intl.injectIntl(React.createClass({
getDefaultProps: function () { getDefaultProps: function () {
return { return {
defaultCountry: DEFAULT_COUNTRY, waiting: false,
waiting: false description: null
}; };
}, },
getInitialState: function () { getInitialState: function () {
...@@ -175,7 +283,7 @@ module.exports = { ...@@ -175,7 +283,7 @@ module.exports = {
'August', 'September', 'October', 'November', 'December' 'August', 'September', 'October', 'November', 'December'
].map(function (label, id) { ].map(function (label, id) {
return { return {
value: id+1, value: id + 1,
label: this.props.intl.formatMessage({id: 'general.month' + label})}; label: this.props.intl.formatMessage({id: 'general.month' + label})};
}.bind(this)); }.bind(this));
}, },
...@@ -191,14 +299,18 @@ module.exports = { ...@@ -191,14 +299,18 @@ module.exports = {
render: function () { render: function () {
var formatMessage = this.props.intl.formatMessage; var formatMessage = this.props.intl.formatMessage;
return ( return (
<Slide className="demographics-step"> <Slide className="registration-step demographics-step">
<h2> <h2>
<intl.FormattedMessage id="teacherRegistration.personalStepTitle" /> <intl.FormattedMessage id="registration.personalStepTitle" />
</h2> </h2>
<p className="description"> <p className="description">
<intl.FormattedMessage id="teacherRegistration.personalStepDescription" /> {this.props.description ?
this.props.description
:
<intl.FormattedMessage id="registration.personalStepDescription" />
}
<Tooltip title={'?'} <Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} /> tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p> </p>
<Card> <Card>
<Form onValidSubmit={this.props.onNextStep}> <Form onValidSubmit={this.props.onNextStep}>
...@@ -227,14 +339,13 @@ module.exports = { ...@@ -227,14 +339,13 @@ module.exports = {
</div> </div>
<Select label={formatMessage({id: 'general.country'})} <Select label={formatMessage({id: 'general.country'})}
name="user.country" name="user.country"
options={countryData.countryOptions} options={getCountryOptions(DEFAULT_COUNTRY)}
value={this.props.defaultCountry}
required /> required />
<Checkbox className="demographics-checkbox-is-robot" <Checkbox className="demographics-checkbox-is-robot"
label="I'm a robot!" label="I'm a robot!"
name="user.isRobot" /> name="user.isRobot" />
<NextStepButton waiting={this.props.waiting} <NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} /> text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form> </Form>
</Card> </Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} /> <StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
...@@ -251,14 +362,14 @@ module.exports = { ...@@ -251,14 +362,14 @@ module.exports = {
render: function () { render: function () {
var formatMessage = this.props.intl.formatMessage; var formatMessage = this.props.intl.formatMessage;
return ( return (
<Slide className="name-step"> <Slide className="registration-step name-step">
<h2> <h2>
<intl.FormattedHTMLMessage id="teacherRegistration.nameStepTitle" /> <intl.FormattedHTMLMessage id="teacherRegistration.nameStepTitle" />
</h2> </h2>
<p className="description"> <p className="description">
<intl.FormattedMessage id="teacherRegistration.nameStepDescription" /> <intl.FormattedMessage id="teacherRegistration.nameStepDescription" />
<Tooltip title={'?'} <Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} /> tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p> </p>
<Card> <Card>
<Form onValidSubmit={this.props.onNextStep}> <Form onValidSubmit={this.props.onNextStep}>
...@@ -271,7 +382,7 @@ module.exports = { ...@@ -271,7 +382,7 @@ module.exports = {
name="user.name.last" name="user.name.last"
required /> required />
<NextStepButton waiting={this.props.waiting} <NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} /> text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form> </Form>
</Card> </Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} /> <StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
...@@ -297,14 +408,14 @@ module.exports = { ...@@ -297,14 +408,14 @@ module.exports = {
render: function () { render: function () {
var formatMessage = this.props.intl.formatMessage; var formatMessage = this.props.intl.formatMessage;
return ( return (
<Slide className="phone-step"> <Slide className="registration-step phone-step">
<h2> <h2>
<intl.FormattedMessage id="teacherRegistration.phoneStepTitle" /> <intl.FormattedMessage id="teacherRegistration.phoneStepTitle" />
</h2> </h2>
<p className="description"> <p className="description">
<intl.FormattedMessage id="teacherRegistration.phoneStepDescription" /> <intl.FormattedMessage id="teacherRegistration.phoneStepDescription" />
<Tooltip title={'?'} <Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} /> tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p> </p>
<Card> <Card>
<Form onValidSubmit={this.onValidSubmit}> <Form onValidSubmit={this.onValidSubmit}>
...@@ -319,7 +430,7 @@ module.exports = { ...@@ -319,7 +430,7 @@ module.exports = {
isFalse: formatMessage({id: 'teacherRegistration.validationPhoneConsent'}) isFalse: formatMessage({id: 'teacherRegistration.validationPhoneConsent'})
}} /> }} />
<NextStepButton waiting={this.props.waiting} <NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} /> text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form> </Form>
</Card> </Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} /> <StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
...@@ -373,14 +484,14 @@ module.exports = { ...@@ -373,14 +484,14 @@ module.exports = {
render: function () { render: function () {
var formatMessage = this.props.intl.formatMessage; var formatMessage = this.props.intl.formatMessage;
return ( return (
<Slide className="organization-step"> <Slide className="registration-step organization-step">
<h2> <h2>
<intl.FormattedMessage id="teacherRegistration.orgStepTitle" /> <intl.FormattedMessage id="teacherRegistration.orgStepTitle" />
</h2> </h2>
<p className="description"> <p className="description">
<intl.FormattedMessage id="teacherRegistration.orgStepDescription" /> <intl.FormattedMessage id="teacherRegistration.orgStepDescription" />
<Tooltip title={'?'} <Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} /> tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p> </p>
<Card> <Card>
<Form onValidSubmit={this.onValidSubmit}> <Form onValidSubmit={this.onValidSubmit}>
...@@ -394,7 +505,9 @@ module.exports = { ...@@ -394,7 +505,9 @@ module.exports = {
required /> required />
<div className="organization-type"> <div className="organization-type">
<b><intl.FormattedMessage id="teacherRegistration.orgType" /></b> <b><intl.FormattedMessage id="teacherRegistration.orgType" /></b>
<p><intl.FormattedMessage id="teacherRegistration.checkAll" /></p> <p className="help-text">
<intl.FormattedMessage id="teacherRegistration.checkAll" />
</p>
<CheckboxGroup name="organization.type" <CheckboxGroup name="organization.type"
value={[]} value={[]}
options={this.getOrganizationOptions()} options={this.getOrganizationOptions()}
...@@ -410,14 +523,16 @@ module.exports = { ...@@ -410,14 +523,16 @@ module.exports = {
</div> </div>
<div className="url-input"> <div className="url-input">
<b><intl.FormattedMessage id="general.website" /></b> <b><intl.FormattedMessage id="general.website" /></b>
<p><intl.FormattedMessage id="teacherRegistration.notRequired" /></p> <p className="help-text">
<intl.FormattedMessage id="teacherRegistration.notRequired" />
</p>
<Input type="url" <Input type="url"
name="organization.url" name="organization.url"
required="isFalse" required="isFalse"
placeholder={'http://'} /> placeholder={'http://'} />
</div> </div>
<NextStepButton waiting={this.props.waiting} <NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} /> text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form> </Form>
</Card> </Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} /> <StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
...@@ -475,32 +590,22 @@ module.exports = { ...@@ -475,32 +590,22 @@ module.exports = {
var formatMessage = this.props.intl.formatMessage; var formatMessage = this.props.intl.formatMessage;
var stateOptions = countryData.subdivisionOptions[this.state.countryChoice]; var stateOptions = countryData.subdivisionOptions[this.state.countryChoice];
stateOptions = [{}].concat(stateOptions); stateOptions = [{}].concat(stateOptions);
var countryOptions = countryData.countryOptions.concat({
label: formatMessage({id: 'teacherRegistration.selectCountry'}),
disabled: true,
selected: true
}).sort(function (a, b) {
if (a.disabled) return -1;
if (b.disabled) return 1;
if (a.value === this.props.defaultCountry) return -1;
if (b.value === this.props.defaultCountry) return 1;
return 0;
}.bind(this));
return ( return (
<Slide className="address-step"> <Slide className="registration-step address-step">
<h2> <h2>
<intl.FormattedMessage id="teacherRegistration.addressStepTitle" /> <intl.FormattedMessage id="teacherRegistration.addressStepTitle" />
</h2> </h2>
<p className="description"> <p className="description">
<intl.FormattedMessage id="teacherRegistration.addressStepDescription" /> <intl.FormattedMessage id="teacherRegistration.addressStepDescription" />
<Tooltip title={'?'} <Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} /> tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p> </p>
<Card> <Card>
<Form onValidSubmit={this.onValidSubmit}> <Form onValidSubmit={this.onValidSubmit}>
<Select label={formatMessage({id: 'general.country'})} <Select label={formatMessage({id: 'general.country'})}
name="address.country" name="address.country"
options={countryOptions} options={getCountryOptions()}
value={this.props.defaultCountry}
onChange={this.onChangeCountry} onChange={this.onChangeCountry}
required /> required />
<Input label={formatMessage({id: 'teacherRegistration.addressLine1'})} <Input label={formatMessage({id: 'teacherRegistration.addressLine1'})}
...@@ -528,7 +633,7 @@ module.exports = { ...@@ -528,7 +633,7 @@ module.exports = {
required /> required />
<GeneralError name="all" /> <GeneralError name="all" />
<NextStepButton waiting={this.props.waiting || this.state.waiting} <NextStepButton waiting={this.props.waiting || this.state.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} /> text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form> </Form>
</Card> </Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} /> <StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
...@@ -558,14 +663,14 @@ module.exports = { ...@@ -558,14 +663,14 @@ module.exports = {
var textAreaClass = (this.state.characterCount > this.props.maxCharacters) ? 'fail' : ''; var textAreaClass = (this.state.characterCount > this.props.maxCharacters) ? 'fail' : '';
return ( return (
<Slide className="usescratch-step"> <Slide className="registration-step usescratch-step">
<h2> <h2>
<intl.FormattedMessage id="teacherRegistration.useScratchStepTitle" /> <intl.FormattedMessage id="teacherRegistration.useScratchStepTitle" />
</h2> </h2>
<p className="description"> <p className="description">
<intl.FormattedMessage id="teacherRegistration.useScratchStepDescription" /> <intl.FormattedMessage id="teacherRegistration.useScratchStepDescription" />
<Tooltip title={'?'} <Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} /> tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p> </p>
<Card> <Card>
<Form onValidSubmit={this.props.onNextStep}> <Form onValidSubmit={this.props.onNextStep}>
...@@ -585,7 +690,7 @@ module.exports = { ...@@ -585,7 +690,7 @@ module.exports = {
<CharCount maxCharacters={this.props.maxCharacters} <CharCount maxCharacters={this.props.maxCharacters}
currentCharacters={this.state.characterCount} /> currentCharacters={this.state.characterCount} />
<NextStepButton waiting={this.props.waiting} <NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} /> text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form> </Form>
</Card> </Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} /> <StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
...@@ -625,14 +730,14 @@ module.exports = { ...@@ -625,14 +730,14 @@ module.exports = {
render: function () { render: function () {
var formatMessage = this.props.intl.formatMessage; var formatMessage = this.props.intl.formatMessage;
return ( return (
<Slide className="email-step"> <Slide className="registration-step email-step">
<h2> <h2>
<intl.FormattedMessage id="teacherRegistration.emailStepTitle" /> <intl.FormattedMessage id="teacherRegistration.emailStepTitle" />
</h2> </h2>
<p className="description"> <p className="description">
<intl.FormattedMessage id="teacherRegistration.emailStepDescription" /> <intl.FormattedMessage id="teacherRegistration.emailStepDescription" />
<Tooltip title={'?'} <Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} /> tipContent={formatMessage({id: 'registration.nameStepTooltip'})} />
</p> </p>
<Card> <Card>
<Form onValidSubmit={this.onValidSubmit}> <Form onValidSubmit={this.onValidSubmit}>
...@@ -652,7 +757,7 @@ module.exports = { ...@@ -652,7 +757,7 @@ module.exports = {
required /> required />
<GeneralError name="all" /> <GeneralError name="all" />
<NextStepButton waiting={this.props.waiting} <NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} /> text={<intl.FormattedMessage id="registration.nextStep" />} />
</Form> </Form>
</Card> </Card>
<StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} /> <StepNavigation steps={this.props.totalSteps - 1} active={this.props.activeStep} />
...@@ -664,12 +769,13 @@ module.exports = { ...@@ -664,12 +769,13 @@ module.exports = {
getDefaultProps: function () { getDefaultProps: function () {
return { return {
email: null, email: null,
invited: false invited: false,
confirmed: false
}; };
}, },
render: function () { render: function () {
return ( return (
<Slide className="last-step"> <Slide className="registration-step last-step">
<h2> <h2>
<intl.FormattedMessage id="registration.lastStepTitle" /> <intl.FormattedMessage id="registration.lastStepTitle" />
</h2> </h2>
...@@ -707,15 +813,130 @@ module.exports = { ...@@ -707,15 +813,130 @@ module.exports = {
); );
} }
})), })),
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({ RegistrationError: intl.injectIntl(React.createClass({
render: function () { render: function () {
return ( return (
<Slide className="error-step"> <Slide className="registration-step error-step">
<h2>Something went wrong</h2> <h2>Something went wrong</h2>
<Card> <Card>
<h4>There was an error while processing your registration</h4> <h4>There was an error while processing your registration</h4>
<p> <p>
{this.props.registrationError} {this.props.children}
</p> </p>
</Card> </Card>
</Slide> </Slide>
......
@import "../../colors";
@import "../../frameless";
.registration-step {
.demographics-checkbox-is-robot {
display: none;
}
.invite-avatar {
display: block;
margin: 0 auto 1rem auto;
border: 2px solid $ui-white;
border-radius: 8px;
}
.gender-input,
.other-input {
float: right;
width: 90%;
.row {
margin-left: .5rem;
}
}
.help-text {
margin: .25rem 0;
text-align: left;
color: $ui-dark-gray;
}
&.class-invite-step {
text-align: center;
> p a {
text-decoration: underline;
color: $ui-white;
font-weight: inherit;
}
}
&.class-invite-step,
&.class-welcome-step {
.card {
text-align: center;
.contents {
padding: 2rem 0;
}
}
}
&.username-step {
.username-label {
margin-bottom: .75rem;
}
}
&.demographics-step {
.gender-input {
margin-top: -5.5rem;
}
.radio {
margin-right: 2.5rem;
line-height: 3rem;
input {
margin-right: 1rem;
}
}
}
&.phone-step {
.form-group {
margin-bottom: 2rem;
}
input {
&[type=checkbox] {
margin-bottom: 1.25rem;
}
}
}
&.organization-step {
.checkbox-group {
.validation-message {
transform: translate(16rem, 8rem);
}
}
input {
&[value="8"] {
margin: 1rem 0;
}
}
.other-input {
margin-top: -5.75rem;
}
}
&.usescratch-step {
.form {
.form-group {
margin-bottom: 0;
&.has-error {
.textarea {
border: 1px solid $ui-orange;
}
}
}
}
p {
&.char-count {
margin-top: 0;
margin-bottom: 1rem;
text-align: right;
}
}
}
&.last-step,
&.error-step {
&.slide {
max-width: 38.75rem;
}
.card {
margin: 1rem auto;
padding: 1.5rem;
width: initial;
h4,
p {
text-align: left;
}
p {
margin: 0;
}
}
}
}
@media only screen and (max-width: $mobile - 1) {
.registration-step {
&.demographics-step {
.radio {
width: 100%;
text-align: left;
}
}
&.last-step,
&.error-step {
.card {
margin: 0 auto;
width: 18.75rem;
}
}
}
}
@media only screen and (max-width: $desktop - 1) {
.registration-step {
.form {
text-align: left;
}
&.phone-step {
.checkbox,
.validation-message {
text-align: left;
}
.checkbox {
margin-bottom: 1rem;
}
}
&.organization-step {
.checkbox-group {
text-align: left;
}
}
}
}
/* IE10 and IE11 fallback */
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
.registration-step {
&.username-step,
&.demographics-step,
&.name-step,
&.phone-step,
&.organization-step,
&.address-step,
&.email-step {
.validation-message {
position: relative;
transform: none;
margin: inherit;
width: 100%;
height: inherit;
&:before {
display: none;
}
}
}
}
}
@import "../../frameless"; @import "../../frameless";
@import "../../colors";
.slide { .slide {
padding: 10px; padding: 10px;
> h2,
> .description {
text-align: center;
color: $type-white;
}
> .description {
margin-top: 0;
margin-bottom: 2rem;
}
.card {
margin: 0 auto;
}
.step-navigation {
margin-top: 2rem;
text-align: center;
}
} }
@media only screen and (max-width: $tablet - 1) { @media only screen and (max-width: $tablet - 1) {
......
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
"general.copyright": "Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab", "general.copyright": "Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab",
"general.country": "Country", "general.country": "Country",
"general.create": "Create", "general.create": "Create",
"general.createUsername": "Create a Username",
"general.credits": "Credits", "general.credits": "Credits",
"general.discuss": "Discuss", "general.discuss": "Discuss",
"general.dmca": "DMCA", "general.dmca": "DMCA",
...@@ -23,6 +22,7 @@ ...@@ -23,6 +22,7 @@
"general.forParents": "For Parents", "general.forParents": "For Parents",
"general.forEducators": "For Educators", "general.forEducators": "For Educators",
"general.forDevelopers": "For Developers", "general.forDevelopers": "For Developers",
"general.getStarted": "Get Started",
"general.gender": "Gender", "general.gender": "Gender",
"general.guidelines": "Community Guidelines", "general.guidelines": "Community Guidelines",
"general.help": "Help", "general.help": "Help",
...@@ -77,9 +77,6 @@ ...@@ -77,9 +77,6 @@
"general.username": "Username", "general.username": "Username",
"general.validationEmail": "Please enter a valid email address", "general.validationEmail": "Please enter a valid email address",
"general.validationEmailMatch": "The emails do not match", "general.validationEmailMatch": "The emails do not match",
"general.validationUsernameExists": "Sorry, that username already exists",
"general.validationUsernameVulgar": "Hmm, that looks inappropriate",
"general.validationUsernameInvalid": "Invalid username",
"general.viewAll": "View All", "general.viewAll": "View All",
"general.website": "Website", "general.website": "Website",
"general.whatsHappening": "What's Happening?", "general.whatsHappening": "What's Happening?",
...@@ -109,13 +106,51 @@ ...@@ -109,13 +106,51 @@
"parents.FaqResourcesQ": "What resources are available for learning Scratch?", "parents.FaqResourcesQ": "What resources are available for learning Scratch?",
"parents.introDescription": "Scratch is a programming language and an online community where children can program and share interactive media such as stories, games, and animation with people from all over the world. As children create with Scratch, they learn to think creatively, work collaboratively, and reason systematically. Scratch is designed and maintained by the Lifelong Kindergarten group at the MIT Media Lab.", "parents.introDescription": "Scratch is a programming language and an online community where children can program and share interactive media such as stories, games, and animation with people from all over the world. As children create with Scratch, they learn to think creatively, work collaboratively, and reason systematically. Scratch is designed and maintained by the Lifelong Kindergarten group at the MIT Media Lab.",
"registration.lastStepTitle": "Thank you for requesting a Scratch Teacher Account", "registration.checkOutResources": "Get Started with Resources",
"registration.lastStepDescription": "We are currently processing your application. ", "registration.checkOutResourcesDescription": "Explore materials for educators and facilitators written by the Scratch Team, including <a href='/educators#resources'>tips, tutorials, and guides</a>.",
"registration.choosePasswordStepDescription": "Type in a new password for your account. You will use this password the next time you log into Scratch.",
"registration.choosePasswordStepTitle": "Create a password",
"registration.choosePasswordStepTooltip": "Don't use your name or anything that's easy for someone else to guess.",
"registration.classroomApiGeneralError": "Sorry, we could not find the registration information for this class",
"registration.classroomInviteExistingStudentStepDescription": "you have been invited to join the class:",
"registration.classroomInviteNewStudentStepDescription": "has invited you to join the class:",
"registration.confirmYourEmail": "Confirm Your Email", "registration.confirmYourEmail": "Confirm Your Email",
"registration.confirmYourEmailDescription": "If you haven't already, please click the link in the confirmation email sent to:", "registration.confirmYourEmailDescription": "If you haven't already, please click the link in the confirmation email sent to:",
"registration.createUsername": "Create a Username",
"registration.goToClass": "Go to Class",
"registration.invitedBy": "invited by",
"registration.lastStepTitle": "Thank you for requesting a Scratch Teacher Account",
"registration.lastStepDescription": "We are currently processing your application. ",
"registration.mustBeNewStudent": "You must be a new student to complete your registration",
"registration.nameStepTooltip": "This information is used for verification and to aggregate usage statistics.",
"registration.newPassword": "New Password",
"registration.nextStep": "Next Step",
"registration.notYou": "Not you? Log in as another user",
"registration.personalStepTitle": "Personal Information",
"registration.personalStepDescription": "Your individual responses will not be displayed publicly, and will be kept confidential and secure",
"registration.selectCountry": "select country",
"registration.studentPersonalStepDescription": "This information will not appear on the Scratch website.",
"registration.showPassword": "Show password",
"registration.usernameStepDescription": "Fill in the following forms to request an account. The approval process may take up to 24 hours.",
"registration.studentUsernameStepDescription": "You can make games, animations, and stories using Scratch. Setting up an account is easy and it's free. Fill in the form below to get started.",
"registration.studentUsernameStepHelpText": "Already have a Scratch account?",
"registration.studentUsernameStepTitle": "Create a Scratch Account",
"registration.studentUsernameStepTooltip": "You'll need to create a new Scratch account to join this class.",
"registration.studentUsernameFieldHelpText": "For safety, don't use your real name!",
"registration.usernameStepTitle": "Request a Teacher Account",
"registration.usernameStepTitleScratcher": "Create a Scratch Account",
"registration.validationPasswordLength": "Passwords must be at least six characters",
"registration.validationPasswordNotEquals": "Your password may not be \"password\"",
"registration.validationPasswordNotUsername": "Your password may not be your username",
"registration.validationUsernameRegexp": "Your username may only contain letters, numbers, \"-\", and \"_\"",
"registration.validationUsernameMinLength": "Usernames must be at least 3 characters",
"registration.validationUsernameMaxLength": "Usernames must be at most 20 characters",
"registration.validationUsernameExists": "Sorry, that username already exists",
"registration.validationUsernameVulgar": "Hmm, that looks inappropriate",
"registration.validationUsernameInvalid": "Invalid username",
"registration.waitForApproval": "Wait for Approval", "registration.waitForApproval": "Wait for Approval",
"registration.waitForApprovalDescription": "Your information is being reviewed. Please be patient, the approval process can take up to 24 hours. You will receive an email with your login information once your account has been created.", "registration.waitForApprovalDescription": "You can log into your Scratch Account now, but the features specific to Teachers are not yet available. Your information is being reviewed. Please be patient, the approval process can take up to 24 hours. You will receive an email indicating your account has been upgraded once your account has been approved.",
"registration.checkOutResources": "Get Started with Resources", "registration.welcomeStepDescription": "You have successfully set up a Scratch account! You are now a member of the class:",
"registration.checkOutResourcesDescription": "Explore materials for educators and facilitators written by the Scratch Team, including <a href='/educators#resources'>tips, tutorials, and guides</a>." "registration.welcomeStepPrompt": "To get started, click on the button below.",
"registration.welcomeStepTitle": "Hurray! Welcome to Scratch!"
} }
...@@ -68,19 +68,31 @@ module.exports.refreshSession = function () { ...@@ -68,19 +68,31 @@ module.exports.refreshSession = function () {
uri: '/session/' uri: '/session/'
}, function (err, body) { }, function (err, body) {
if (err) return dispatch(module.exports.setSessionError(err)); if (err) return dispatch(module.exports.setSessionError(err));
if (typeof body === 'undefined') return dispatch(module.exports.setSessionError('No session content'));
if (
body.user &&
body.user.banned &&
window.location.pathname !== '/accounts/banned-response/') {
return window.location = '/accounts/banned-response/';
} else if (
body.flags &&
body.flags.must_complete_registration &&
window.location.pathname !== '/classes/complete_registration') {
return window.location = '/classes/complete_registration';
} else if (
body.flags &&
body.flags.must_reset_password &&
!body.flags.must_complete_registration &&
window.location.pathname !== '/classes/student_password_reset/') {
return window.location = '/classes/student_password_reset/';
} else {
dispatch(tokenActions.getToken());
dispatch(module.exports.setSession(body));
dispatch(module.exports.setStatus(module.exports.Status.FETCHED));
if (typeof body !== 'undefined') { // get the permissions from the updated session
if (body.banned) { dispatch(permissionsActions.getPermissions());
return window.location = body.url; return;
} else {
dispatch(tokenActions.getToken());
dispatch(module.exports.setSession(body));
dispatch(module.exports.setStatus(module.exports.Status.FETCHED));
// get the permissions from the updated session
dispatch(permissionsActions.getPermissions());
return;
}
} }
}); });
}; };
......
...@@ -2,104 +2,110 @@ ...@@ -2,104 +2,110 @@
{ {
"name": "splash", "name": "splash",
"pattern": "^/?$", "pattern": "^/?$",
"routeAlias": "/?$",
"view": "splash/splash", "view": "splash/splash",
"title": "Imagine, Program, Share" "title": "Imagine, Program, Share"
}, },
{ {
"name": "about", "name": "about",
"pattern": "^/about/?$", "pattern": "^/about/?$",
"routeAlias": "/about/?$",
"view": "about/about", "view": "about/about",
"title": "About" "title": "About"
}, },
{ {
"name": "developers", "name": "guidelines",
"pattern": "^/developers/?$", "pattern": "^/community_guidelines/?$",
"view": "developers/developers", "routeAlias": "/community_guidelines/?$",
"title": "Developers" "view": "guidelines/guidelines",
"title": "Scratch Community Guidelines"
}, },
{ {
"name": "hoc", "name": "student-complete-registration",
"pattern": "^/hoc/?$", "pattern": "^/classes/complete_registration",
"view": "hoc/hoc", "routeAlias": "/classes/(complete_registration|.+/register/.+)",
"title": "Hour of Code" "view": "studentcompleteregistration/studentcompleteregistration",
"title": "Complete your Registration"
}, },
{ {
"name": "explore", "name": "student-registration",
"pattern": "^/explore/:projects/:all/?$", "pattern": "^/classes/:id/register/:token",
"routeAlias": "^/explore(?!/ajax)", "routeAlias": "/classes/(complete_registration|.+/register/.+)",
"view": "explore/explore", "view": "studentregistration/studentregistration",
"title": "Explore" "title": "Class Registration"
}, },
{ {
"name": "explore-redirect", "name": "conference-index",
"pattern": "^/explore/?$", "pattern": "^/conference/?$",
"routeAlias": "^/explore(?!/ajax)", "routeAlias": "/conference(?!/201[4-5])",
"redirect": "/explore/projects/all" "view": "conference/index/index",
"title": "Scratch Conference",
"viewportWidth": "device-width"
}, },
{ {
"name": "explore-projects-redirect", "name": "conference-plan",
"pattern": "^/explore/projects/?$", "pattern": "^/conference/plan/?$",
"routeAlias": "^/explore(?!/ajax)", "routeAlias": "/conference(?!/201[4-5])",
"redirect": "/explore/projects/all" "view": "conference/plan/plan",
"title": "Plan Your Visit",
"viewportWidth": "device-width"
}, },
{ {
"name": "explore-studios-redirect", "name": "conference-expectations",
"pattern": "^/explore/studios/?$", "pattern": "^/conference/expect/?$",
"routeAlias": "^/explore(?!/ajax)", "routeAlias": "/conference(?!/201[4-5])",
"redirect": "/explore/studios/all" "view": "conference/expect/expect",
"title": "What to Expect",
"viewportWidth": "device-width"
}, },
{ {
"name": "search", "name": "conference-schedule",
"pattern": "^/search/:projects?$/?$", "pattern": "^/conference/schedule/?$",
"routeAlias": "^/search", "routeAlias": "/conference(?!/201[4-5])",
"view": "search/search", "view": "conference/schedule/schedule",
"title": "Search" "title": "Conference Schedule",
"viewportWidth": "device-width"
}, },
{ {
"name": "credits", "name": "conference-details",
"pattern": "^/info/credits/?$", "pattern": "^/conference/:id/details/?$",
"view": "credits/credits", "routeAlias": "/conference(?!/201[4-5])",
"title": "Credits" "view": "conference/details/details",
"title": "Event Details",
"viewportWidth": "device-width"
}, },
{ {
"name": "faq", "name": "developers",
"pattern": "^/info/faq/?$", "pattern": "^/developers/?$",
"view": "faq/faq", "routeAlias": "/developers/?$",
"title": "FAQ" "view": "developers/developers",
"title": "Developers"
},
{
"name": "dmca",
"pattern": "^/DMCA/?$",
"routeAlias": "/DMCA/?$",
"view": "dmca/dmca",
"title": "DMCA"
}, },
{ {
"name": "educator-landing", "name": "educator-landing",
"pattern": "^/educators/?$", "pattern": "^/educators/?$",
"routeAlias": "/educators(?:/(faq|register|waiting))?/?$",
"view": "teachers/landing/landing", "view": "teachers/landing/landing",
"title": "Educators" "title": "Educators"
}, },
{ {
"name": "teacher-faq", "name": "teacher-faq",
"pattern": "^/educators/faq/?$", "pattern": "^/educators/faq/?$",
"routeAlias": "/educators(?:/(faq|register|waiting))?/?$",
"view": "teachers/faq/faq", "view": "teachers/faq/faq",
"title": "Teacher Accounts FAQ" "title": "Teacher Accounts FAQ"
}, },
{
"name": "cards",
"pattern": "^/info/cards/?$",
"view": "cards/cards",
"title": "Cards"
},
{
"name": "communityblocks-interviews",
"pattern": "^/info/communityblocks-interviews/?$",
"view": "communityblocks-interviews/communityblocks-interviews",
"title": "Community Blocks Beta Tester Interviews"
},
{
"name": "jobs",
"pattern": "^/jobs/?$",
"view": "jobs/jobs",
"title": "Jobs"
},
{ {
"name": "teacherregistration", "name": "teacherregistration",
"pattern": "^/educators/register$", "pattern": "^/educators/register$",
"routeAlias": "/educators(?:/(faq|register|waiting))?/?$",
"view": "teacherregistration/teacherregistration", "view": "teacherregistration/teacherregistration",
"title": "Teacher Registration", "title": "Teacher Registration",
"viewportWidth": "device-width" "viewportWidth": "device-width"
...@@ -107,82 +113,109 @@ ...@@ -107,82 +113,109 @@
{ {
"name": "teacherwaitingroom", "name": "teacherwaitingroom",
"pattern": "^/educators/waiting", "pattern": "^/educators/waiting",
"routeAlias": "/educators(?:/(faq|register|waiting))?/?$",
"view": "teacherwaitingroom/teacherwaitingroom", "view": "teacherwaitingroom/teacherwaitingroom",
"title": "Thank you for requesting a Scratch Teacher Account" "title": "Thank you for requesting a Scratch Teacher Account"
}, },
{ {
"name": "wedo2", "name": "explore",
"pattern": "^/wedo/?$", "pattern": "^/explore/:projects/:all/?$",
"view": "wedo2/wedo2", "routeAlias": "/explore(?!/ajax)",
"title": "LEGO WeDo 2.0" "view": "explore/explore",
}, "title": "Explore"
{
"name": "conference-index",
"pattern": "^/conference/?$",
"routeAlias": "^/conference(?!/201[4-5])",
"view": "conference/index/index",
"title": "Scratch Conference",
"viewportWidth": "device-width"
},
{
"name": "conference-plan",
"pattern": "^/conference/plan/?$",
"routeAlias": "^/conference(?!/201[4-5])",
"view": "conference/plan/plan",
"title": "Plan Your Visit",
"viewportWidth": "device-width"
}, },
{ {
"name": "conference-expectations", "name": "hoc",
"pattern": "^/conference/expect/?$", "pattern": "^/hoc/?$",
"routeAlias": "^/conference(?!/201[4-5])", "routeAlias": "/hoc/?$",
"view": "conference/expect/expect", "view": "hoc/hoc",
"title": "What to Expect", "title": "Hour of Code"
"viewportWidth": "device-width"
}, },
{ {
"name": "conference-schedule", "name": "cards",
"pattern": "^/conference/schedule/?$", "pattern": "^/info/cards/?$",
"routeAlias": "^/conference(?!/201[4-5])", "routeAlias": "/info/(cards|communityblocks-interviews|credits|faq)/?$",
"view": "conference/schedule/schedule", "view": "cards/cards",
"title": "Conference Schedule", "title": "Cards"
"viewportWidth": "device-width"
}, },
{ {
"name": "conference-details", "name": "communityblocks-interviews",
"pattern": "^/conference/:id/details/?$", "pattern": "^/info/communityblocks-interviews/?$",
"routeAlias": "^/conference(?!/201[4-5])", "routeAlias": "/info/(cards|communityblocks-interviews|credits|faq|donate)/?$",
"view": "conference/details/details", "view": "communityblocks-interviews/communityblocks-interviews",
"title": "Event Details", "title": "Community Blocks Beta Tester Interviews"
"viewportWidth": "device-width"
}, },
{ {
"name": "donate", "name": "credits",
"pattern": "^/info/donate/?", "pattern": "^/info/credits/?$",
"redirect": "https://secure.donationpay.org/scratchfoundation/" "routeAlias": "/info/(cards|communityblocks-interviews|credits|faq)/?$",
"view": "credits/credits",
"title": "Credits"
}, },
{ {
"name": "dmca", "name": "faq",
"pattern": "^/DMCA/?$", "pattern": "^/info/faq/?$",
"view": "dmca/dmca", "routeAlias": "/info/(cards|communityblocks-interviews|credits|faq)/?$",
"title": "DMCA" "view": "faq/faq",
"title": "FAQ"
}, },
{ {
"name": "guidelines", "name": "jobs",
"pattern": "^/community_guidelines/?$", "pattern": "^/jobs/?$",
"view": "guidelines/guidelines", "routeAlias": "/jobs/?$",
"title": "Scratch Community Guidelines" "view": "jobs/jobs",
"title": "Jobs"
}, },
{ {
"name": "privacypolicy", "name": "privacypolicy",
"pattern": "^/privacy_policy/?$", "pattern": "^/privacy_policy/?$",
"routeAlias": "/privacy_policy/?$",
"view": "privacypolicy/privacypolicy", "view": "privacypolicy/privacypolicy",
"title": "Privacy Policy" "title": "Privacy Policy"
}, },
{
"name": "search",
"pattern": "^/search/:projects?$/?$",
"routeAlias": "/search",
"view": "search/search",
"title": "Search"
},
{ {
"name": "terms", "name": "terms",
"pattern": "^/terms_of_use/?$", "pattern": "^/terms_of_use/?$",
"routeAlias": "/terms_of_use/?$",
"view": "terms/terms", "view": "terms/terms",
"title": "Scratch Terms of Use" "title": "Scratch Terms of Use"
},
{
"name": "wedo2",
"pattern": "^/wedo/?$",
"routeAlias": "/wedo/?$",
"view": "wedo2/wedo2",
"title": "LEGO WeDo 2.0"
},
{
"name": "donate",
"pattern": "^/info/donate/?",
"routeAlias": "/info/(cards|communityblocks-interviews|credits|faq|donate)/?$",
"redirect": "https://secure.donationpay.org/scratchfoundation/"
},
{
"name": "explore-redirect",
"pattern": "^/explore/?$",
"routeAlias": "/explore(?!/ajax)",
"redirect": "/explore/projects/all"
},
{
"name": "explore-projects-redirect",
"pattern": "^/explore/projects/?$",
"routeAlias": "/explore(?!/ajax)",
"redirect": "/explore/projects/all"
},
{
"name": "explore-studios-redirect",
"pattern": "^/explore/studios/?$",
"routeAlias": "/explore(?!/ajax)",
"redirect": "/explore/studios/all"
} }
] ]
var connect = require('react-redux').connect;
var defaults = require('lodash.defaultsdeep');
var React = require('react');
var render = require('../../lib/render.jsx');
var sessionStatus = require('../../redux/session').Status;
var api = require('../../lib/api');
var intl = require('../../lib/intl.jsx');
var log = require('../../lib/log.js');
var Deck = require('../../components/deck/deck.jsx');
var Progression = require('../../components/progression/progression.jsx');
var Spinner = require('../../components/spinner/spinner.jsx');
var Steps = require('../../components/registration/steps.jsx');
require('./studentcompleteregistration.scss');
var StudentCompleteRegistration = intl.injectIntl(React.createClass({
type: 'StudentCompleteRegistration',
getInitialState: function () {
return {
classroom: null,
formData: {},
registrationErrors: null,
step: 0,
waiting: false
};
},
advanceStep: function (formData) {
formData = formData || {};
this.setState({
step: this.state.step + 1,
formData: defaults({}, formData, this.state.formData)
});
},
componentDidUpdate: function (prevProps) {
if (prevProps.studentUsername !== this.props.studentUsername && this.props.newStudent) {
this.setState({waiting: true});
api({
uri: '/classrooms/' + this.props.classroomId
}, function (err, body, res) {
this.setState({waiting: false});
if (err || res.statusCode !== 200) {
return this.setState({
registrationErrors: {
__all__: this.props.intl.formatMessage({id: 'registration.classroomApiGeneralError'})
}
});
}
this.setState({classroom: body});
}.bind(this));
}
},
handleLogOut: function (e) {
e.preventDefault();
api({
host: '',
method: 'post',
uri: '/accounts/logout/',
useCsrf: true
}, function (err) {
if (err) return log.error(err);
window.location = '/';
}.bind(this));
},
register: function (formData) {
this.setState({waiting: true});
formData = defaults({}, formData || {}, this.state.formData);
var submittedData = {
birth_month: formData.user.birth.month,
birth_year: formData.user.birth.year,
gender: (
formData.user.gender === 'other' ?
formData.user.genderOther :
formData.user.gender
),
country: formData.user.country,
is_robot: formData.user.isRobot
};
if (this.props.must_reset_password) {
submittedData.password = formData.user.password;
}
api({
host: '',
uri: '/classes/student_update_registration/',
method: 'post',
useCsrf: true,
formData: submittedData
}, function (err, body) {
this.setState({waiting: false});
if (err) return this.setState({registrationError: err});
if (body.success) return this.advanceStep(formData);
this.setState({registrationErrors: body.errors});
}.bind(this));
},
goToClass: function () {
window.location = '/classes/' + this.state.classroom.id + '/';
},
render: function () {
var demographicsDescription = this.props.intl.formatMessage({
id: 'registration.studentPersonalStepDescription'});
var registrationErrors = this.state.registrationErrors;
if (!this.props.newStudent) {
registrationErrors = {
__all__: this.props.intl.formatMessage({id: 'registration.mustBeNewStudent'})
};
}
return (
<Deck className="student-registration">
{registrationErrors ? (
<Steps.RegistrationError>
<ul>
{Object.keys(registrationErrors).map(function (field) {
var label = field + ': ';
if (field === '__all__') {
label = '';
}
return (<li>{label}{registrationErrors[field]}</li>);
})}
</ul>
</Steps.RegistrationError>
) : (
this.state.waiting || !this.state.classroom ? (
<Spinner />
) : (
<Progression {... this.state}>
<Steps.ClassInviteExistingStudentStep classroom={this.state.classroom}
onHandleLogOut={this.handleLogOut}
onNextStep={this.advanceStep}
studentUsername={this.props.studentUsername}
waiting={this.state.waiting} />
{this.props.must_reset_password ?
<Steps.ChoosePasswordStep onNextStep={this.advanceStep}
showPassword={true}
waiting={this.state.waiting} />
:
[]
}
<Steps.DemographicsStep description={demographicsDescription}
onNextStep={this.register}
waiting={this.state.waiting} />
<Steps.ClassWelcomeStep classroom={this.state.classroom}
onNextStep={this.goToClass}
waiting={this.state.waiting} />
</Progression>
)
)}
</Deck>
);
}
}));
var mapStateToProps = function (state) {
return {
classroomId: state.session.session.user && state.session.session.user.classroomId,
must_reset_password: state.session.session.flags && state.session.session.flags.must_reset_password,
newStudent: (
state.session.session.permissions &&
state.session.session.permissions.student &&
state.session.session.flags.must_complete_registration),
sessionFetched: state.session.status === sessionStatus.FETCHED,
studentUsername: state.session.session.user && state.session.session.user.username
};
};
var ConnectedStudentCompleteRegistration = connect(mapStateToProps)(StudentCompleteRegistration);
render(<ConnectedStudentCompleteRegistration />, document.getElementById('app'));
@import "../../colors";
@import "../../frameless";
@include responsive-layout (".student-registration", ".slide");
html,
body {
background-color: darken($ui-purple, 8%);
}
.student-complete-registration {
background-color: $ui-purple;
}
var defaults = require('lodash.defaultsdeep');
var React = require('react');
var render = require('../../lib/render.jsx');
var api = require('../../lib/api');
var intl = require('../../lib/intl.jsx');
var Deck = require('../../components/deck/deck.jsx');
var Progression = require('../../components/progression/progression.jsx');
var Steps = require('../../components/registration/steps.jsx');
require('./studentregistration.scss');
var StudentRegistration = intl.injectIntl(React.createClass({
type: 'StudentRegistration',
getDefaultProps: function () {
return {
classroomId: null,
classroomToken: null
};
},
getInitialState: function () {
return {
formData: {},
registrationError: null,
step: 0,
waiting: false
};
},
advanceStep: function (formData) {
formData = formData || {};
this.setState({
step: this.state.step + 1,
formData: defaults({}, formData, this.state.formData)
});
},
componentDidMount: function () {
this.setState({waiting: true});
api({
uri: '/classrooms/' + this.props.classroomId,
params: {token: this.props.classroomToken}
}, function (err, body, res) {
this.setState({waiting: false});
if (err) {
return this.setState({
registrationError: this.props.intl.formatMessage({
id: 'registration.classroomApiGeneralError'
})
});
}
if (res.statusCode === 404) {
// TODO: Use react-router for this
return window.location = '/404';
}
this.setState({classroom: body});
}.bind(this));
},
register: function (formData) {
this.setState({waiting: true});
formData = defaults({}, formData || {}, this.state.formData);
api({
host: '',
uri: '/classes/register_new_student/',
method: 'post',
useCsrf: true,
formData: {
username: formData.user.username,
password: formData.user.password,
birth_month: formData.user.birth.month,
birth_year: formData.user.birth.year,
gender: (
formData.user.gender === 'other' ?
formData.user.genderOther :
formData.user.gender
),
country: formData.user.country,
is_robot: formData.user.isRobot,
classroom_id: this.props.classroomId,
classroom_token: this.props.classroomToken
}
}, function (err, res) {
this.setState({waiting: false});
if (err) return this.setState({registrationError: err});
if (res[0].success) return this.advanceStep(formData);
this.setState({registrationError: res[0].msg});
}.bind(this));
},
goToClass: function () {
window.location = '/classes/' + this.props.classroomId + '/';
},
render: function () {
var demographicsDescription = this.props.intl.formatMessage({
id: 'registration.studentPersonalStepDescription'});
var usernameTitle = this.props.intl.formatMessage({id: 'registration.studentUsernameStepTitle'});
var usernameHelp = this.props.intl.formatMessage({id: 'registration.studentUsernameFieldHelpText'});
var usernameDescription = (
this.props.intl.formatMessage({id: 'registration.studentUsernameStepDescription'}) + ' ' +
this.props.intl.formatMessage({id: 'registration.studentUsernameStepHelpText'})
);
var usernameTooltip = this.props.intl.formatMessage({id: 'registration.studentUsernameStepTooltip'});
return (
<Deck className="student-registration">
{this.state.registrationError ?
<Steps.RegistrationError>
{this.state.registrationError}
</Steps.RegistrationError>
:
<Progression {... this.state}>
<Steps.ClassInviteNewStudentStep classroom={this.state.classroom}
onNextStep={this.advanceStep}
waiting={this.state.waiting || !this.state.classroom} />
<Steps.UsernameStep onNextStep={this.advanceStep}
title={usernameTitle}
description={usernameDescription}
tooltip={usernameTooltip}
usernameHelp={usernameHelp}
waiting={this.state.waiting} />
<Steps.DemographicsStep description={demographicsDescription}
onNextStep={this.register}
waiting={this.state.waiting} />
<Steps.ClassWelcomeStep classroom={this.state.classroom}
onNextStep={this.goToClass}
waiting={this.state.waiting || !this.state.classroom} />
</Progression>
}
</Deck>
);
}
}));
var [classroomId, _, classroomToken] = document.location.pathname.split('/')
.filter(function (p) {
if (p) return p;
})
.slice(-3);
var props = {classroomId, classroomToken};
render(<StudentRegistration {... props} />, document.getElementById('app'));
@import "../../colors";
@import "../../frameless";
@include responsive-layout (".student-registration", ".slide");
html,
body {
background-color: darken($ui-purple, 8%);
}
.student-registration {
background-color: $ui-purple;
}
{ {
"teacherRegistration.usernameStepDescription": "Fill in the following forms to request an account. The approval process may take up to 24 hours.",
"teacherRegistration.usernameStepTitle": "Request a Teacher Account",
"teacherRegistration.validationUsernameRegexp": "Your username may only contain characters and \"-\"",
"teacherRegistration.validationUsernameMinLength": "Usernames must be at least 3 characters",
"teacherRegistration.validationUsernameMaxLength": "Usernames must be at most 20 characters",
"teacherRegistration.validationPasswordLength": "Passwords must be at least six characters",
"teacherRegistration.validationPasswordNotEquals": "Your password may not be \"password\"",
"teacherRegistration.validationPasswordNotUsername": "Your password may not be your username",
"teacherRegistration.showPassword": "Show password",
"teacherRegistration.nextStep": "Next Step",
"teacherRegistration.personalStepTitle": "Personal Information",
"teacherRegistration.personalStepDescription": "Your individual responses will not be displayed publicly, and will be kept confidential and secure",
"teacherRegistration.nameStepTitle": "First &amp; Last Name", "teacherRegistration.nameStepTitle": "First &amp; Last Name",
"teacherRegistration.nameStepDescription": "Your name will not be displayed publicly, and will be kept confidential and secure.", "teacherRegistration.nameStepDescription": "Your name will not be displayed publicly, and will be kept confidential and secure.",
"teacherRegistration.nameStepTooltip": "This information is used for verification and to aggregate usage statistics.",
"teacherRegistration.firstName": "First Name", "teacherRegistration.firstName": "First Name",
"teacherRegistration.lastName": "Last Name", "teacherRegistration.lastName": "Last Name",
"teacherRegistration.phoneStepTitle": "Phone Number", "teacherRegistration.phoneStepTitle": "Phone Number",
...@@ -32,13 +19,12 @@ ...@@ -32,13 +19,12 @@
"teacherRegistration.orgChoiceMiddleSchool": "Middle School", "teacherRegistration.orgChoiceMiddleSchool": "Middle School",
"teacherRegistration.orgChoiceHighSchool": "High School", "teacherRegistration.orgChoiceHighSchool": "High School",
"teacherRegistration.orgChoiceUniversity": "College/University", "teacherRegistration.orgChoiceUniversity": "College/University",
"teacherRegistration.orgChoiceAfterschool": "Afterscool Program", "teacherRegistration.orgChoiceAfterschool": "Afterschool Program",
"teacherRegistration.orgChoiceMuseum": "Museum", "teacherRegistration.orgChoiceMuseum": "Museum",
"teacherRegistration.orgChoiceLibrary": "Library", "teacherRegistration.orgChoiceLibrary": "Library",
"teacherRegistration.orgChoiceCamp": "Camp", "teacherRegistration.orgChoiceCamp": "Camp",
"teacherRegistration.orgChoiceOther": " ", "teacherRegistration.orgChoiceOther": " ",
"teacherRegistration.notRequired": "Not Required", "teacherRegistration.notRequired": "Not Required",
"teacherRegistration.selectCountry": "select country",
"teacherRegistration.addressValidationError": "This doesn't look like a real address", "teacherRegistration.addressValidationError": "This doesn't look like a real address",
"teacherRegistration.addressLine1": "Address Line 1", "teacherRegistration.addressLine1": "Address Line 1",
"teacherRegistration.addressLine2": "Address Line 2 (Optional)", "teacherRegistration.addressLine2": "Address Line 2 (Optional)",
......
var connect = require('react-redux').connect;
var defaults = require('lodash.defaultsdeep'); var defaults = require('lodash.defaultsdeep');
var React = require('react'); var React = require('react');
var render = require('../../lib/render.jsx'); var render = require('../../lib/render.jsx');
var api = require('../../lib/api'); var api = require('../../lib/api');
var sessionActions = require('../../redux/session.js');
var Deck = require('../../components/deck/deck.jsx'); var Deck = require('../../components/deck/deck.jsx');
var Progression = require('../../components/progression/progression.jsx'); var Progression = require('../../components/progression/progression.jsx');
...@@ -67,41 +69,50 @@ var TeacherRegistration = React.createClass({ ...@@ -67,41 +69,50 @@ var TeacherRegistration = React.createClass({
}, function (err, res) { }, function (err, res) {
this.setState({waiting: false}); this.setState({waiting: false});
if (err) return this.setState({registrationError: err}); if (err) return this.setState({registrationError: err});
if (res[0].success) return this.advanceStep(formData); if (res[0].success) {
this.props.dispatch(sessionActions.refreshSession());
return this.advanceStep(formData);
}
this.setState({registrationError: res[0].msg}); this.setState({registrationError: res[0].msg});
}.bind(this)); }.bind(this));
}, },
render: function () { render: function () {
var permissions = this.props.session.permissions || {};
return ( return (
<Deck className="teacher-registration"> <Deck className="teacher-registration">
{this.state.registrationError ? {this.state.registrationError ?
<Steps.RegistrationError {... this.state} /> <Steps.RegistrationError>
{this.state.registrationError}
</Steps.RegistrationError>
: :
<Progression {... this.state}> <Progression {... this.state}>
<Steps.UsernameStep onNextStep={this.advanceStep} <Steps.UsernameStep onNextStep={this.advanceStep}
waiting={this.state.waiting} /> waiting={this.state.waiting} />
<Steps.DemographicsStep onNextStep={this.advanceStep} <Steps.DemographicsStep onNextStep={this.advanceStep}
waiting={this.state.waiting} /> waiting={this.state.waiting} />
<Steps.NameStep onNextStep={this.advanceStep} <Steps.NameStep onNextStep={this.advanceStep}
waiting={this.state.waiting} /> waiting={this.state.waiting} />
<Steps.PhoneNumberStep onNextStep={this.advanceStep} <Steps.PhoneNumberStep onNextStep={this.advanceStep}
waiting={this.state.waiting} waiting={this.state.waiting}
defaultCountry={ defaultCountry={
this.state.formData.user && this.state.formData.user.country this.state.formData.user && this.state.formData.user.country
} /> } />
<Steps.OrganizationStep onNextStep={this.advanceStep} <Steps.OrganizationStep onNextStep={this.advanceStep}
waiting={this.state.waiting} /> waiting={this.state.waiting} />
<Steps.AddressStep onNextStep={this.advanceStep} <Steps.AddressStep onNextStep={this.advanceStep}
waiting={this.state.waiting} waiting={this.state.waiting}
defaultCountry={ defaultCountry={
this.state.formData.user && this.state.formData.user.country this.state.formData.user && this.state.formData.user.country
} /> } />
<Steps.UseScratchStep onNextStep={this.advanceStep} <Steps.UseScratchStep onNextStep={this.advanceStep}
waiting={this.state.waiting} /> waiting={this.state.waiting} />
<Steps.EmailStep onNextStep={this.register} <Steps.EmailStep onNextStep={this.register}
waiting={this.state.waiting} /> waiting={this.state.waiting} />
<Steps.TeacherApprovalStep email={this.state.formData.user && this.state.formData.user.email} /> <Steps.TeacherApprovalStep email={this.state.formData.user && this.state.formData.user.email}
confirmed={permissions.social}
invited={permissions.educator_invitee}
educator={permissions.educator} />
</Progression> </Progression>
} }
</Deck> </Deck>
...@@ -109,4 +120,12 @@ var TeacherRegistration = React.createClass({ ...@@ -109,4 +120,12 @@ var TeacherRegistration = React.createClass({
} }
}); });
render(<TeacherRegistration />, document.getElementById('app')); var mapStateToProps = function (state) {
return {
session: state.session.session
};
};
var ConnectedTeacherRegistration = connect(mapStateToProps)(TeacherRegistration);
render(<ConnectedTeacherRegistration />, document.getElementById('app'));
...@@ -10,209 +10,4 @@ body { ...@@ -10,209 +10,4 @@ body {
.teacher-registration { .teacher-registration {
background-color: $ui-purple; background-color: $ui-purple;
.demographics-checkbox-is-robot {
display: none;
}
.gender-input,
.other-input {
float: right;
width: 90%;
.row {
margin-left: .5rem;
}
}
.username-step,
.name-step,
.address-step,
.email-step {
.help-block {
transform: translate(15.75rem, -4rem);
}
}
.demographics-step {
.gender-input {
margin-top: -5.5rem;
}
.help-block {
transform: translate(13rem, -2rem);
}
.radio {
margin-right: 2.5rem;
line-height: 3rem;
input {
margin-right: 1rem;
}
}
}
.phone-step {
.form-group {
margin-bottom: 2rem;
}
input {
&[type=checkbox] {
margin-bottom: 1.25rem;
}
}
.help-block {
margin-top: .5rem;
}
.checkbox-row {
.help-block {
margin-top: 0;
}
}
}
.organization-step {
.help-block {
transform: translate(16rem, -4rem);
}
.checkbox-group {
.help-block {
transform: translate(16rem, -16rem);
}
}
.organization-type,
.url-input {
p {
margin: .25rem 0;
text-align: left;
color: $ui-dark-gray;
}
}
input {
&[value="8"] {
margin: 1rem 0;
}
}
.other-input {
margin-top: -5.75rem;
}
}
.address-step {
.select {
.help-block {
transform: translate(0, .5rem);
}
}
}
.usescratch-step {
.form {
.form-group {
margin-bottom: 0;
&.has-error {
.textarea {
border: 1px solid $ui-orange;
}
}
}
}
.help-block {
margin-top: .75rem;
}
p {
&.char-count {
margin-top: 0;
margin-bottom: 1rem;
text-align: right;
}
}
}
.last-step,
.error-step {
&.slide {
max-width: 38.75rem;
}
.card {
margin: 1rem 0;
padding: 1.5rem;
width: initial;
h4,
p {
text-align: left;
}
p {
margin: 0;
}
}
}
}
@media only screen and (max-width: $mobile - 1) {
.teacher-registration {
.demographics-step {
.radio {
width: 100%;
text-align: left;
}
}
.last-step,
.error-step {
.card {
margin: 0 auto;
width: 18.75rem;
}
}
}
}
@media only screen and (max-width: $desktop - 1) {
.teacher-registration {
.form {
text-align: left;
}
.username-step,
.demographics-step,
.name-step {
.help-block {
transform: none;
}
}
.phone-step {
.checkbox,
.help-block {
text-align: left;
}
.checkbox {
margin-bottom: 1rem;
}
}
.organization-step {
.checkbox-group {
text-align: left;
}
}
}
} }
{ {
"teacherlanding.title": "Scratch for Educators", "teacherlanding.title": "Scratch for Educators",
"teacherlanding.intro": "Your students can use Scratch to code their own interactive stories, animations, and games. In the process, they learn to think creatively, reason systematically, and work collaboratively — essential skills for everyone in today’s society.", "teacherlanding.intro": "Your students can use Scratch to code their own interactive stories, animations, and games. In the process, they learn to think creatively, reason systematically, and work collaboratively — essential skills for everyone in today’s society.",
"teacherlanding.inPracticeAnchor": "In Practice", "teacherlanding.inPracticeAnchor": "Who Uses Scratch?",
"teacherlanding.resourcesAnchor": "Resources", "teacherlanding.resourcesAnchor": "Resources",
"teacherlanding.inPracticeTitle": "Who Uses Scratch?", "teacherlanding.inPracticeTitle": "Who Uses Scratch?",
"teacherlanding.inPracticeIntro": "Educators are using Scratch in a wide variety of: ", "teacherlanding.inPracticeIntro": "Educators are using Scratch in a wide variety of: ",
"teacherlanding.generalUsageSettings": "<b>Settings:</b> schools, museums, libraries, community centers", "teacherlanding.generalUsageSettings": "<b>Settings:</b> schools, museums, libraries, community centers",
"teacherlanding.generalUsageGradeLevels": "<b>Grade Levels:</b> elementary, middle, and high school (and some colleges too!)", "teacherlanding.generalUsageGradeLevels": "<b>Grade Levels:</b> elementary, middle, and high school (and some colleges too!)",
"teacherlanding.generalUsageSubjectAreas": "<b>Subject Areas:</b> language arts, science, social studies, math, computer science, foreign languages, and the arts", "teacherlanding.generalUsageSubjectAreas": "<b>Subject Areas:</b> language arts, science, social studies, math, computer science, foreign languages, and the arts",
"teacherlanding.ingridTitle": "Instructional Technology Specialist",
"teacherlanding.dylanTitle": "Educational Technologist",
"teacherlanding.afterSchoolTitle": "After-School Program",
"teacherlanding.resourcesTitle": "Educator Resources", "teacherlanding.resourcesTitle": "Educator Resources",
"teacherlanding.scratchEdTitle": "A Community for Educators", "teacherlanding.scratchEdTitle": "A Community for Educators",
"teacherlanding.scratchEdDescription": "<a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a> is an online community where Scratch educators <a href=\"http://scratched.gse.harvard.edu/stories\">share stories</a>, exchange resources, ask questions, and find people. ScratchEd is developed and supported by the Harvard Graduate School of Education.", "teacherlanding.scratchEdDescription": "<a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a> is an online community where Scratch educators <a href=\"http://scratched.gse.harvard.edu/stories\">share stories</a>, exchange resources, ask questions, and find people. ScratchEd is developed and supported by the Harvard Graduate School of Education.",
"teacherlanding.meetupTitle": "In-Person Gatherings", "teacherlanding.meetupTitle": "In-Person Gatherings",
"teacherlanding.meetupDescription": "<a href=\"http://www.meetup.com/pro/scratched/\">Scratch Educator Meetups</a> from each other, sharing their ideas and strategies for supporting computational creativity in all its forms.", "teacherlanding.meetupDescription": "<a href=\"http://www.meetup.com/pro/scratched/\">Scratch Educator Meetups</a> are gatherings of Scratch Educators who want to learn with and from each other, sharing their ideas and strategies for supporting computational creativity in all its forms.",
"teacherlanding.guidesTitle": "Guides & Tutorials", "teacherlanding.guidesTitle": "Guides & Tutorials",
"teacherlanding.helpPage": "On the <a href=\"/help\">Help Page</a>, you can find workshop guides, Scratch Cards, videos, and other resources.", "teacherlanding.helpPage": "On the <a href=\"/help\">Help Page</a>, you can find workshop guides, Scratch Cards, videos, and other resources.",
"teacherlanding.tipsWindow" : "The <a href=\"/projects/editor/?tip_bar=home\">Tips Window</a> features step-by-step tutorials for getting started in Scratch.", "teacherlanding.tipsWindow" : "The <a href=\"/projects/editor/?tip_bar=home\">Tips Window</a> features step-by-step tutorials for getting started in Scratch.",
......
...@@ -61,38 +61,6 @@ var Landing = injectIntl(React.createClass({ ...@@ -61,38 +61,6 @@ var Landing = injectIntl(React.createClass({
<p><FormattedHTMLMessage id="teacherlanding.generalUsageGradeLevels" /></p> <p><FormattedHTMLMessage id="teacherlanding.generalUsageGradeLevels" /></p>
<p><FormattedHTMLMessage id="teacherlanding.generalUsageSubjectAreas"/></p> <p><FormattedHTMLMessage id="teacherlanding.generalUsageSubjectAreas"/></p>
</FlexRow> </FlexRow>
<FlexRow className="stories">
<a href="//bit.ly/28SBsa9" className="story">
<img src="/images/teachers/stories/ingrid.jpg" alt="ingrid's story" />
<div className="story-info">
<p className="name">Ingrid Gustafson</p>
<p><FormattedMessage id="teacherlanding.ingridTitle" /></p>
</div>
</a>
<a href="//bit.ly/28Q5l6P" className="story">
<img src="/images/teachers/stories/dylan.jpg" alt="dylan's story" />
<div className="story-info">
<p className="name">Dylan Ryder</p>
<p><FormattedMessage id="teacherlanding.dylanTitle" /></p>
</div>
</a>
<a href="//bit.ly/28SC1AY" className="story">
<img src="/images/teachers/stories/plug-in-studio.jpg"
alt="plug in studio's story" />
<div className="story-info">
<p className="name">Plug-In Studios</p>
<p><FormattedMessage id="teacherlanding.afterSchoolTitle" /></p>
</div>
</a>
<a href="//bit.ly/28UzapJ" className="story">
<img src="/images/teachers/stories/ghana-code-club.jpg"
alt="ghana code club's story" />
<div className="story-info">
<p className="name">Ghana Code Club</p>
<p><FormattedMessage id="teacherlanding.afterSchoolTitle" /></p>
</div>
</a>
</FlexRow>
</section> </section>
<section id="resources"> <section id="resources">
<span className="nav-spacer"></span> <span className="nav-spacer"></span>
......
...@@ -10,6 +10,11 @@ require('./teacherwaitingroom.scss'); ...@@ -10,6 +10,11 @@ require('./teacherwaitingroom.scss');
var TeacherWaitingRoom = React.createClass({ var TeacherWaitingRoom = React.createClass({
displayName: 'TeacherWaitingRoom', displayName: 'TeacherWaitingRoom',
componentWillReceiveProps: function (nextProps) {
if (nextProps.session.permissions.educator && nextProps.session.permissions.social) {
window.location.href = '/educators/classes/';
}
},
render: function () { render: function () {
var permissions = this.props.session.permissions || {}; var permissions = this.props.session.permissions || {};
var user = this.props.session.user || {}; var user = this.props.session.user || {};
......
...@@ -68,7 +68,10 @@ module.exports = { ...@@ -68,7 +68,10 @@ module.exports = {
loaders: [ loaders: [
{ {
test: /\.jsx$/, test: /\.jsx$/,
loader: 'jsx-loader', loader: 'babel',
query: {
presets: ['es2015','react']
},
include: path.resolve(__dirname, 'src') include: path.resolve(__dirname, 'src')
}, },
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment