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": {
"curly": [2, "multi-line"],
"eol-last": [2],
......@@ -14,16 +15,12 @@
},
"env": {
"browser": true,
"es6": true,
"node": true
},
"globals": {
"formatMessage": true
},
"ecmaFeatures": {
"arrowFunctions": true,
"blockBindings": true,
"jsx": true
},
"plugins": [
"react",
"json"
......
......@@ -18,9 +18,9 @@ var fastly = require('./lib/fastly-extended')(process.env.FASTLY_API_KEY, FASTLY
var extraAppRoutes = [
// Homepage with querystring.
// TODO: Should this be added for every route?
'^/\\?',
'/\\?',
// View html
'^/[^\/]*\.html$'
'/[^\/]*\.html$'
];
/*
......@@ -35,7 +35,7 @@ var getStaticPaths = function (pathToStatic) {
}).map(function (pathName) {
// Reduce absolute path to relative paths like '/js'
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) {
* all :arguments become .+?
*/
var expressPatternToRegex = function (pattern) {
return pattern.replace(/(:[^/]+)\//gi, '.+?/');
return pattern.replace(/(:[^/]+)/gi, '.+?');
};
/*
......@@ -66,9 +66,9 @@ var expressPatternToRegex = function (pattern) {
* string suitable for a Fastly condition
*/
var pathsToCondition = function (paths) {
return 'req.url~"' + paths.reduce(function (conditionString, pattern) {
return 'req.url~"^(' + paths.reduce(function (conditionString, pattern) {
return conditionString + (conditionString ? '|' : '') + pattern;
}, '') + '"';
}, '') + ')"';
};
/*
......
......@@ -5,4 +5,117 @@
border-radius: 8px / $em;
box-shadow: 0 0 0 .125rem $active-gray;
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({
<div className={classNames(['deck', this.props.className])}>
<div className="inner">
<a href="/" aria-label="Scratch">
<img src="/images/logo_sm.png" />
<img className="logo" src="/images/logo_sm.png" />
</a>
{this.props.children}
</div>
......
......@@ -6,146 +6,22 @@
.deck {
min-height: 100vh;
img {
.logo {
margin-left: 2px;
padding: 12px 0;
width: 76px;
}
.step-navigation {
margin-top: 2rem;
text-align: center;
}
.slide {
max-width: 28.75rem;
h2,
.description {
text-align: center;
color: $type-white;
}
.description {
margin-top: 0;
margin-bottom: 2rem;
}
}
.card {
margin: 0 auto;
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;
}
}
width: 23.75rem;
}
.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({
this.props.className
);
return (
<div className={classes}>
<FRCCheckbox {... this.props} />
</div>
<FRCCheckbox rowClassName={classes} {... this.props} />
);
}
});
......
......@@ -29,14 +29,14 @@ var Input = React.createClass({
},
render: function () {
var classes = classNames(
'input',
this.state.status,
this.props.className
this.props.className,
{'no-label': (typeof this.props.label === 'undefined')}
);
return (this.props.type === 'submit' || this.props.noformsy ?
<input {... this.props} className={classes} /> :
return (
<FRCInput {... this.props}
className={classes}
className="input"
rowClassName={classes}
onValid={this.onValid}
onInvalid={this.onInvalid} />
);
......
......@@ -12,7 +12,7 @@ $pass-bg: lighten($ui-aqua, 35%);
.input {
transition: all .5s ease;
margin: .75rem 0;
margin-bottom: .75rem;
border: 1px solid $active-gray;
border-radius: 5px;
background-color: $base-bg;
......@@ -35,4 +35,9 @@ $pass-bg: lighten($ui-aqua, 35%);
border: 1px solid $active-dark-gray;
background-color: $pass-bg;
}
/* IE10/11-specific style resets */
&::-ms-reveal, &::-ms-clear {
display: none;
}
}
......@@ -42,7 +42,7 @@ var PhoneInput = React.createClass({
return (
<Row {... this.getRowProperties()}
htmlFor={this.getId()}
className={classNames('phone-input', this.props.className)}
rowClassName={classNames('phone-input', this.props.className)}
>
<div className="input-group">
<ReactPhoneInput className="form-control"
......@@ -53,9 +53,9 @@ var PhoneInput = React.createClass({
label={null}
disabled={this.isFormDisabled() || this.props.disabled}
/>
{this.renderHelp()}
{this.renderErrorMessage()}
</div>
{this.renderHelp()}
{this.renderErrorMessage()}
</Row>
);
}
......
@import "../../colors";
.input-group {
margin: .75rem 0;
width: 100%;
}
.react-tel-input {
margin-bottom: .75rem;
width: 100%;
input {
......
......@@ -4,8 +4,19 @@
* the formsy-react-components
*/
.form-group {
.row {
.required-symbol {
display: none;
}
label {
display: inline-block;
margin-bottom: .75rem;
}
&.no-label {
label {
display: none;
}
}
}
......@@ -8,7 +8,7 @@
select {
transition: all .5s ease;
margin: .75rem 0;
margin-bottom: .75rem;
border: 1px solid $active-gray;
border-radius: 5px;
background: $ui-light-gray url("../../../static/svgs/forms/carot.svg") no-repeat right center;
......@@ -23,6 +23,12 @@
display: none;
}
&::-ms-value {
background-color: inherit;
padding-left: 1rem;
color: inherit;
}
&:focus {
transition: all .5s ease;
outline: none;
......
......@@ -11,11 +11,13 @@ var TextArea = React.createClass({
type: 'TextArea',
render: function () {
var classes = classNames(
'textarea',
'textarea-row',
this.props.className
);
return (
<FRCTextarea {... this.props} className={classes} />
<FRCTextarea {... this.props}
className="textarea"
rowClassName={classes} />
);
}
});
......
......@@ -2,7 +2,7 @@
.textarea {
transition: all 1s ease;
margin: .75rem 0;
margin-bottom: .75rem;
border: 1px solid $active-gray;
border-radius: 5px;
background-color: $ui-light-gray;
......
......@@ -11,6 +11,7 @@ var api = require('../../../lib/api');
var Avatar = require('../../avatar/avatar.jsx');
var Button = require('../../forms/button.jsx');
var Dropdown = require('../../dropdown/dropdown.jsx');
var Form = require('../../forms/form.jsx');
var Input = require('../../forms/input.jsx');
var log = require('../../../lib/log.js');
var Login = require('../../login/login.jsx');
......@@ -170,6 +171,9 @@ var Navigation = React.createClass({
this.props.dispatch(sessionActions.refreshSession());
this.closeRegistration();
},
onSearchSubmit: function (formData) {
window.location.href = '/search/projects?q=' + formData.q;
},
render: function () {
var classes = classNames({
'logged-in': this.props.session.session.user
......@@ -216,14 +220,13 @@ var Navigation = React.createClass({
</li>
<li className="search">
<form action="/search/projects" method="get">
<Form onSubmit={this.onSearchSubmit}>
<Button type="submit" className="btn-search" />
<Input type="text"
aria-label={formatMessage({id: 'general.search'})}
placeholder={formatMessage({id: 'general.search'})}
name="q"
noformsy />
</form>
name="q" />
</Form>
</li>
{this.props.session.status === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [
......
......@@ -47,12 +47,18 @@
width: 100%;
}
form {
.form {
margin: 0;
}
input,
button {
.row {
.help-block {
display: none;
}
}
.input,
.button {
display: inline-block;
margin-top: 5px;
outline: none;
......
This diff is collapsed.
@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 "../../colors";
.slide {
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) {
......
......@@ -12,7 +12,6 @@
"general.copyright": "Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab",
"general.country": "Country",
"general.create": "Create",
"general.createUsername": "Create a Username",
"general.credits": "Credits",
"general.discuss": "Discuss",
"general.dmca": "DMCA",
......@@ -23,6 +22,7 @@
"general.forParents": "For Parents",
"general.forEducators": "For Educators",
"general.forDevelopers": "For Developers",
"general.getStarted": "Get Started",
"general.gender": "Gender",
"general.guidelines": "Community Guidelines",
"general.help": "Help",
......@@ -77,9 +77,6 @@
"general.username": "Username",
"general.validationEmail": "Please enter a valid email address",
"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.website": "Website",
"general.whatsHappening": "What's Happening?",
......@@ -109,13 +106,51 @@
"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.",
"registration.lastStepTitle": "Thank you for requesting a Scratch Teacher Account",
"registration.lastStepDescription": "We are currently processing your application. ",
"registration.checkOutResources": "Get Started with Resources",
"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.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.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.checkOutResources": "Get Started with Resources",
"registration.checkOutResourcesDescription": "Explore materials for educators and facilitators written by the Scratch Team, including <a href='/educators#resources'>tips, tutorials, and guides</a>."
"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.welcomeStepDescription": "You have successfully set up a Scratch account! You are now a member of the class:",
"registration.welcomeStepPrompt": "To get started, click on the button below.",
"registration.welcomeStepTitle": "Hurray! Welcome to Scratch!"
}
......@@ -68,19 +68,31 @@ module.exports.refreshSession = function () {
uri: '/session/'
}, function (err, body) {
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') {
if (body.banned) {
return window.location = body.url;
} 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;
}
// get the permissions from the updated session
dispatch(permissionsActions.getPermissions());
return;
}
});
};
......
This diff is collapsed.
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.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.lastName": "Last Name",
"teacherRegistration.phoneStepTitle": "Phone Number",
......@@ -32,13 +19,12 @@
"teacherRegistration.orgChoiceMiddleSchool": "Middle School",
"teacherRegistration.orgChoiceHighSchool": "High School",
"teacherRegistration.orgChoiceUniversity": "College/University",
"teacherRegistration.orgChoiceAfterschool": "Afterscool Program",
"teacherRegistration.orgChoiceAfterschool": "Afterschool Program",
"teacherRegistration.orgChoiceMuseum": "Museum",
"teacherRegistration.orgChoiceLibrary": "Library",
"teacherRegistration.orgChoiceCamp": "Camp",
"teacherRegistration.orgChoiceOther": " ",
"teacherRegistration.notRequired": "Not Required",
"teacherRegistration.selectCountry": "select country",
"teacherRegistration.addressValidationError": "This doesn't look like a real address",
"teacherRegistration.addressLine1": "Address Line 1",
"teacherRegistration.addressLine2": "Address Line 2 (Optional)",
......
var connect = require('react-redux').connect;
var defaults = require('lodash.defaultsdeep');
var React = require('react');
var render = require('../../lib/render.jsx');
var api = require('../../lib/api');
var sessionActions = require('../../redux/session.js');
var Deck = require('../../components/deck/deck.jsx');
var Progression = require('../../components/progression/progression.jsx');
......@@ -67,41 +69,50 @@ var TeacherRegistration = React.createClass({
}, function (err, res) {
this.setState({waiting: false});
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});
}.bind(this));
},
render: function () {
var permissions = this.props.session.permissions || {};
return (
<Deck className="teacher-registration">
{this.state.registrationError ?
<Steps.RegistrationError {... this.state} />
<Steps.RegistrationError>
{this.state.registrationError}
</Steps.RegistrationError>
:
<Progression {... this.state}>
<Steps.UsernameStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.UsernameStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.DemographicsStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.NameStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.PhoneNumberStep onNextStep={this.advanceStep}
waiting={this.state.waiting}
defaultCountry={
this.state.formData.user && this.state.formData.user.country
} />
<Steps.NameStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.PhoneNumberStep onNextStep={this.advanceStep}
waiting={this.state.waiting}
defaultCountry={
this.state.formData.user && this.state.formData.user.country
} />
<Steps.OrganizationStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.AddressStep onNextStep={this.advanceStep}
waiting={this.state.waiting}
defaultCountry={
this.state.formData.user && this.state.formData.user.country
} />
<Steps.UseScratchStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.EmailStep onNextStep={this.register}
waiting={this.state.waiting} />
<Steps.TeacherApprovalStep email={this.state.formData.user && this.state.formData.user.email} />
<Steps.AddressStep onNextStep={this.advanceStep}
waiting={this.state.waiting}
defaultCountry={
this.state.formData.user && this.state.formData.user.country
} />
<Steps.UseScratchStep onNextStep={this.advanceStep}
waiting={this.state.waiting} />
<Steps.EmailStep onNextStep={this.register}
waiting={this.state.waiting} />
<Steps.TeacherApprovalStep email={this.state.formData.user && this.state.formData.user.email}
confirmed={permissions.social}
invited={permissions.educator_invitee}
educator={permissions.educator} />
</Progression>
}
</Deck>
......@@ -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 {
.teacher-registration {
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.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.inPracticeTitle": "Who Uses Scratch?",
"teacherlanding.inPracticeIntro": "Educators are using Scratch in a wide variety of: ",
"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.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.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.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.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.",
......
......@@ -61,38 +61,6 @@ var Landing = injectIntl(React.createClass({
<p><FormattedHTMLMessage id="teacherlanding.generalUsageGradeLevels" /></p>
<p><FormattedHTMLMessage id="teacherlanding.generalUsageSubjectAreas"/></p>
</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 id="resources">
<span className="nav-spacer"></span>
......
......@@ -10,6 +10,11 @@ require('./teacherwaitingroom.scss');
var TeacherWaitingRoom = React.createClass({
displayName: 'TeacherWaitingRoom',
componentWillReceiveProps: function (nextProps) {
if (nextProps.session.permissions.educator && nextProps.session.permissions.social) {
window.location.href = '/educators/classes/';
}
},
render: function () {
var permissions = this.props.session.permissions || {};
var user = this.props.session.user || {};
......
......@@ -68,7 +68,10 @@ module.exports = {
loaders: [
{
test: /\.jsx$/,
loader: 'jsx-loader',
loader: 'babel',
query: {
presets: ['es2015','react']
},
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