Merge pull request #703 from LLK/release/2.2.10

[Develop] Release 2.2.10
This commit is contained in:
Matthew Taylor 2016-07-08 09:03:11 -04:00 committed by GitHub
commit 99c76ee3bf
41 changed files with 579 additions and 371 deletions

View file

@ -76,12 +76,14 @@ Helpers.getMD5Map = function (ICUIdMap) {
* key: '<react-intl string id>' * key: '<react-intl string id>'
* value: translated version of string * value: translated version of string
*/ */
Helpers.getTranslationsForLanguage = function (lang, idsWithICU, md5WithIds) { Helpers.getTranslationsForLanguage = function (lang, idsWithICU, md5WithIds, separator) {
var poUiDir = path.resolve(__dirname, '../../node_modules/scratchr2_translations/ui'); var poUiDir = path.resolve(__dirname, '../../node_modules/scratchr2_translations/ui');
var jsFile = path.resolve(poUiDir, lang + '/LC_MESSAGES/djangojs.po'); var jsFile = path.resolve(poUiDir, lang + '/LC_MESSAGES/djangojs.po');
var pyFile = path.resolve(poUiDir, lang + '/LC_MESSAGES/django.po'); var pyFile = path.resolve(poUiDir, lang + '/LC_MESSAGES/django.po');
var translations = {}; var translations = {};
separator = separator || ':';
try { try {
fs.accessSync(jsFile, fs.R_OK); fs.accessSync(jsFile, fs.R_OK);
var jsTranslations = po2icu.poFileToICUSync(lang, jsFile); var jsTranslations = po2icu.poFileToICUSync(lang, jsFile);
@ -104,7 +106,7 @@ Helpers.getTranslationsForLanguage = function (lang, idsWithICU, md5WithIds) {
var translationsByView = {}; var translationsByView = {};
for (var id in translations) { for (var id in translations) {
var ids = id.split('-'); // [viewName, stringId] var ids = id.split(separator); // [viewName, stringId]
var viewName = ids[0]; var viewName = ids[0];
var stringId = ids[1]; var stringId = ids[1];
@ -127,20 +129,24 @@ Helpers.writeTranslationsToJS = function (outputDir, viewName, translationObject
// Returns a FormattedMessage id with english string as value. Use for default values in translations // Returns a FormattedMessage id with english string as value. Use for default values in translations
// Sample structure: { 'general-general.blah': 'blah', 'about-about.blah': 'blahblah' } // Sample structure: { 'general-general.blah': 'blah', 'about-about.blah': 'blahblah' }
Helpers.idToICUMap = function (viewName, ids) { Helpers.idToICUMap = function (viewName, ids, separator) {
var idsToICU = {}; var idsToICU = {};
separator = separator || ':';
for (var id in ids) { for (var id in ids) {
idsToICU[viewName + '-' + id] = ids[id]; idsToICU[viewName + separator + id] = ids[id];
} }
return idsToICU; return idsToICU;
}; };
// Reuturns reverse (i.e. english string with message key as the value) object for searching po files. // Reuturns reverse (i.e. english string with message key as the value) object for searching po files.
// Sample structure: { 'blah': 'general-general.blah', 'blahblah': 'about-about.blah' } // Sample structure: { 'blah': 'general-general.blah', 'blahblah': 'about-about.blah' }
Helpers.icuToIdMap = function (viewName, ids) { Helpers.icuToIdMap = function (viewName, ids, separator) {
var icuToIds = {}; var icuToIds = {};
separator = separator || ':';
for (var id in ids) { for (var id in ids) {
icuToIds[ids[id]] = viewName + '-' + id; icuToIds[ids[id]] = viewName + separator + id;
} }
return icuToIds; return icuToIds;
}; };

View file

@ -55,7 +55,7 @@ var Carousel = React.createClass({
} }
return ( return (
<Thumbnail key={item.id} <Thumbnail key={[this.key, item.id].join('.')}
showLoves={this.props.showLoves} showLoves={this.props.showLoves}
showRemixes={this.props.showRemixes} showRemixes={this.props.showRemixes}
type={item.type} type={item.type}

View file

@ -9,7 +9,9 @@ var Deck = React.createClass({
return ( return (
<div className={classNames(['deck', this.props.className])}> <div className={classNames(['deck', this.props.className])}>
<div className="inner"> <div className="inner">
<img src="/images/logo_sm.png" /> <a href="/" aria-label="Scratch">
<img src="/images/logo_sm.png" />
</a>
{this.props.children} {this.props.children}
</div> </div>
</div> </div>

View file

@ -14,141 +14,138 @@
.step-navigation { .step-navigation {
margin-top: 2rem; margin-top: 2rem;
} text-align: center;
}
.slide {
max-width: 28.75rem;
h2,
p {
text-align: center;
color: $type-white;
} }
.description { .slide {
margin-top: 0; max-width: 28.75rem;
margin-bottom: 2rem;
h2,
.description {
text-align: center;
color: $type-white;
}
.description {
margin-top: 0;
margin-bottom: 2rem;
}
} }
}
.card { .card {
margin: 0 auto; margin: 0 auto;
width: 23.75rem; width: 23.75rem;
&,
h2,
p {
color: $type-gray;
} }
}
.step-navigation { .form {
text-align: center; padding: 3rem 4rem;
}
.form { .form-group {
padding: 3rem 4rem; margin-bottom: 1.2rem;
.form-group { &.has-error {
margin-bottom: 1.2rem; .input {
border: 1px solid $ui-orange;
}
}
}
&.has-error { .button {
.input { margin: 0 0 -3rem -4rem;
border: 1px solid $ui-orange; 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;
} }
} }
} }
.button { .input {
margin: 0 0 -3rem -4rem; width: $cols5;
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 { .help-block {
width: $cols5; $arrow-border-width: 1rem;
}
.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; display: block;
position: absolute; position: absolute;
top: 1rem; margin-left: $arrow-border-width;
left: -$arrow-border-width / 2; border: 1px solid $active-gray;
transform: rotate(45deg);
border-bottom: 1px solid $active-gray;
border-left: 1px solid $active-gray;
border-radius: 5px; border-radius: 5px;
background-color: $ui-orange; background-color: $ui-orange;
width: $arrow-border-width; padding: 1rem;
height: $arrow-border-width; max-width: 18.75rem;
min-height: 1rem;
max-height: 3rem;
overflow: visible;
color: $type-white;
content: ""; &: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) { @media only screen and (max-width: $mobile - 1) {
.card { .deck {
width: 22.5rem; .card {
}
.form {
text-align: left;
.button {
width: 22.5rem; width: 22.5rem;
} }
.form {
text-align: left;
.button {
width: 22.5rem;
}
}
} }
} }
@media only screen and (max-width: $tablet - 1) { @media only screen and (max-width: $tablet - 1) {
.input { .deck {
width: 90%; .input {
width: 90%;
}
} }
} }
@media only screen and (max-width: $desktop - 1) { @media only screen and (max-width: $desktop - 1) {
.help-block { .deck {
position: relative; .help-block {
transform: none; position: relative;
margin: inherit; transform: none;
width: 100%; margin: inherit;
height: inherit; width: 100%;
height: inherit;
&:before { &:before {
display: none; display: none;
}
} }
} }
} }

View file

@ -15,6 +15,10 @@
font-size: .8125rem; font-size: .8125rem;
font-weight: normal; font-weight: normal;
&.staging {
background-color: $ui-orange;
}
&.open { &.open {
display: block; display: block;
} }
@ -53,7 +57,7 @@
text-decoration: none; text-decoration: none;
} }
} }
} }
&.with-arrow { &.with-arrow {
$arrow-border-width: 14px; $arrow-border-width: 14px;
@ -61,12 +65,12 @@
border-radius: 5px; border-radius: 5px;
overflow: visible; overflow: visible;
&:before { &:before {
display: block; display: block;
position: absolute; position: absolute;
top: -$arrow-border-width / 2; top: -$arrow-border-width / 2;
right: 10%; right: 10%;
transform: rotate(45deg); transform: rotate(45deg);
border-top: 1px solid $active-gray; border-top: 1px solid $active-gray;

View file

@ -1,13 +1,11 @@
@import "../../colors"; @import "../../colors";
p { .char-count {
&.char-count { letter-spacing: 1px;
letter-spacing: 1px; color: lighten($type-gray, 30%);
color: lighten($type-gray, 30%); font-weight: 500;
font-weight: 500;
&.overmax { &.overmax {
color: $ui-orange; color: $ui-orange;
}
} }
} }

View file

@ -1,9 +1,11 @@
.checkbox-group { .checkbox-group {
.col-sm-9 { .row {
flex-flow: column wrap; .col-sm-9 {
flex-flow: column wrap;
.checkbox { .checkbox {
margin: .5rem 0; margin: .5rem 0;
}
} }
} }
} }

View file

@ -6,37 +6,37 @@
label { label {
font-weight: 300; font-weight: 300;
} }
}
}
input { input {
&[type=checkbox] { &[type=checkbox] {
display: block; display: block;
float: left; float: left;
margin-right: 1rem; margin-right: 1rem;
border: 1px solid $active-gray; border: 1px solid $active-gray;
border-radius: 3px; border-radius: 3px;
width: 1.25rem; width: 1.25rem;
height: 1.25rem; height: 1.25rem;
appearance: none; appearance: none;
&:checked, &:checked,
&:focus { &:focus {
outline: none; transition: all .5s ease;
} outline: none;
box-shadow: 0 0 0 .25rem $active-gray;
}
&:checked { &:checked {
transition: all .5s ease; background-color: $ui-blue;
box-shadow: 0 0 0 .25rem $active-gray; text-align: center;
background-color: $ui-blue; text-indent: .125rem;
text-align: center; line-height: 1.25rem;
text-indent: .125rem; font-size: .75rem;
line-height: 1.25rem;
font-size: .75rem;
&:after { &:after {
color: $type-white; color: $type-white;
content: "\2714"; content: "\2714";
}
}
} }
} }
} }

View file

@ -1,7 +1,7 @@
var classNames = require('classnames'); var classNames = require('classnames');
var Formsy = require('formsy-react'); var Formsy = require('formsy-react');
var omit = require('lodash.omit');
var React = require('react'); var React = require('react');
var GeneralError = require('./general-error.jsx');
var validations = require('./validations.jsx').validations; var validations = require('./validations.jsx').validations;
for (var validation in validations) { for (var validation in validations) {
@ -11,18 +11,34 @@ for (var validation in validations) {
var Form = React.createClass({ var Form = React.createClass({
getDefaultProps: function () { getDefaultProps: function () {
return { return {
noValidate: true noValidate: true,
onChange: function () {}
}; };
}, },
getInitialState: function () {
return {
allValues: {}
};
},
onChange: function (currentValues, isChanged) {
this.setState({allValues: omit(currentValues, 'all')});
this.props.onChange(currentValues, isChanged);
},
render: function () { render: function () {
var classes = classNames( var classes = classNames(
'form', 'form',
this.props.className this.props.className
); );
return ( return (
<Formsy.Form {... this.props} className={classes}> <Formsy.Form {... this.props} className={classes} ref="formsy" onChange={this.onChange}>
<GeneralError name="all" /> {React.Children.map(this.props.children, function (child) {
{this.props.children} if (!child) return child;
if (child.props.name === 'all') {
return React.cloneElement(child, {value: this.state.allValues});
} else {
return child;
}
}.bind(this))}
</Formsy.Form> </Formsy.Form>
); );
} }

View file

@ -1,6 +1,8 @@
var Formsy = require('formsy-react'); var Formsy = require('formsy-react');
var React = require('react'); var React = require('react');
require('./general-error.scss');
/* /*
* A special formsy-react component that only outputs * A special formsy-react component that only outputs
* error messages. If you want to display errors that * error messages. If you want to display errors that
@ -12,7 +14,7 @@ module.exports = Formsy.HOC(React.createClass({
render: function () { render: function () {
if (!this.props.showError()) return null; if (!this.props.showError()) return null;
return ( return (
<p className="error"> <p className="general-error">
{this.props.getErrorMessage()} {this.props.getErrorMessage()}
</p> </p>
); );

View file

@ -0,0 +1,9 @@
@import "../../colors";
.general-error {
border: 1px solid $active-gray;
border-radius: 4px;
background-color: $ui-orange;
padding: 1rem;
color: $type-white;
}

View file

@ -6,39 +6,39 @@
font-weight: 300; font-weight: 300;
} }
} }
}
.col-sm-9 { .col-sm-9 {
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
input { input {
&[type="radio"] { &[type="radio"] {
margin-top: 1px; margin-top: 1px;
border: 1px solid $active-gray; border: 1px solid $active-gray;
border-radius: 50%; border-radius: 50%;
width: .875rem; width: .875rem;
height: .875rem; height: .875rem;
appearance: none; appearance: none;
&:checked, &:checked,
&:focus { &:focus {
outline: none; outline: none;
} }
&:checked { &:checked {
transition: all .25s ease; transition: all .25s ease;
box-shadow: 0 0 0 .25rem $active-gray; box-shadow: 0 0 0 .25rem $active-gray;
background-color: $ui-blue; background-color: $ui-blue;
&:after { &:after {
display: block; display: block;
transform: translate(.25rem, .25rem); transform: translate(.25rem, .25rem);
border-radius: 50%; border-radius: 50%;
background-color: $ui-white; background-color: $ui-white;
width: .25rem; width: .25rem;
height: .25rem; height: .25rem;
content: ""; content: "";
}
} }
} }
} }

View file

@ -5,34 +5,38 @@
label { label {
font-weight: 500; font-weight: 500;
} }
}
select { select {
transition: all .5s ease;
margin: .75rem 0;
border: 1px solid $active-gray;
border-radius: 5px;
background: $ui-light-gray url("../../../static/svgs/forms/carot.svg") no-repeat right center;
padding-right: 4rem;
width: 100%;
height: 3rem;
text-indent: 1rem;
font-size: .875rem;
appearance: none;
&:focus {
transition: all .5s ease; transition: all .5s ease;
outline: none; margin: .75rem 0;
border: 1px solid $ui-blue; border: 1px solid $active-gray;
} border-radius: 5px;
background: $ui-light-gray url("../../../static/svgs/forms/carot.svg") no-repeat right center;
&:focus, padding-right: 4rem;
&:hover {
background: $ui-light-gray url("../../../static/svgs/forms/carot-hover.svg") no-repeat right center;
}
> option {
background-color: $ui-white;
width: 100%; width: 100%;
height: 3rem;
text-indent: 1rem;
font-size: .875rem;
appearance: none;
&::-ms-expand {
display: none;
}
&:focus {
transition: all .5s ease;
outline: none;
border: 1px solid $ui-blue;
}
&:focus,
&:hover {
background: $ui-light-gray url("../../../static/svgs/forms/carot-hover.svg") no-repeat right center;
}
> option {
background-color: $ui-white;
width: 100%;
}
} }
} }

View file

@ -29,6 +29,7 @@ module.exports.validationHOCFactory = function (defaultValidationErrors) {
var ValidatedComponent = React.createClass({ var ValidatedComponent = React.createClass({
render: function () { render: function () {
var validationErrors = defaults( var validationErrors = defaults(
{},
defaultValidationErrors, defaultValidationErrors,
this.props.validationErrors this.props.validationErrors
); );

View file

@ -6,6 +6,8 @@ var languages = require('../../../languages.json');
var Form = require('../forms/form.jsx'); var Form = require('../forms/form.jsx');
var Select = require('../forms/select.jsx'); var Select = require('../forms/select.jsx');
require('./languagechooser.scss');
/** /**
* Footer dropdown menu that allows one to change their language. * Footer dropdown menu that allows one to change their language.
*/ */
@ -33,7 +35,7 @@ var LanguageChooser = React.createClass({
<Form className={classes}> <Form className={classes}>
<Select name="language" <Select name="language"
options={languageOptions} options={languageOptions}
defaultValue={this.props.locale} value={this.props.locale}
onChange={this.onSetLanguage} onChange={this.onSetLanguage}
required /> required />
</Form> </Form>

View file

@ -0,0 +1,10 @@
@import "../../frameless";
.language-chooser {
.select {
select {
width: 13.75rem;
/* 3 columns */
}
}
}

View file

@ -11,8 +11,12 @@
box-shadow: 0 0 3px $box-shadow-gray; box-shadow: 0 0 3px $box-shadow-gray;
background-color: $ui-blue; background-color: $ui-blue;
&.staging {
background-color: $ui-orange;
}
width: 100%;
width: 100%;
/* NOTE: Height should match offset settings in main.scss file */ /* NOTE: Height should match offset settings in main.scss file */
height: 50px; height: 50px;
@ -35,7 +39,7 @@
.ie9 & { .ie9 & {
display: table-row; display: table-row;
} }
> li { > li {
display: inline-block; display: inline-block;
@ -75,7 +79,7 @@
display: block; display: block;
padding: 13px 15px 4px 15px; padding: 13px 15px 4px 15px;
height: 33px; height: 33px;
text-decoration: none; text-decoration: none;
white-space: nowrap; white-space: nowrap;
color: $type-white; color: $type-white;

View file

@ -178,6 +178,10 @@ var Navigation = React.createClass({
'message-count': true, 'message-count': true,
'show': this.state.unreadMessageCount > 0 'show': this.state.unreadMessageCount > 0
}); });
var dropdownClasses = classNames({
'user-info': true,
'open': this.state.accountNavOpen
});
var formatMessage = this.props.intl.formatMessage; var formatMessage = this.props.intl.formatMessage;
var createLink = this.props.session.session.user ? '/projects/editor/' : '/projects/editor/?tip_bar=home'; var createLink = this.props.session.session.user ? '/projects/editor/' : '/projects/editor/?tip_bar=home';
return ( return (
@ -241,14 +245,16 @@ var Navigation = React.createClass({
</a> </a>
</li>, </li>,
<li className="link right account-nav" key="account-nav"> <li className="link right account-nav" key="account-nav">
<a className="user-info" href="#" onClick={this.handleAccountNavClick}> <a className={dropdownClasses}
href="#" onClick={this.handleAccountNavClick}>
<Avatar src={this.props.session.session.user.thumbnailUrl} alt="" /> <Avatar src={this.props.session.session.user.thumbnailUrl} alt="" />
{this.props.session.session.user.username} {this.props.session.session.user.username}
</a> </a>
<Dropdown <Dropdown
as="ul" as="ul"
isOpen={this.state.accountNavOpen} isOpen={this.state.accountNavOpen}
onRequestClose={this.closeAccountNav}> onRequestClose={this.closeAccountNav}
className={process.env.SCRATCH_ENV}>
<li> <li>
<a href={this.getProfileUrl()}> <a href={this.getProfileUrl()}>
<FormattedMessage id="general.profile" /> <FormattedMessage id="general.profile" />
@ -259,14 +265,14 @@ var Navigation = React.createClass({
<FormattedMessage id="general.myStuff" /> <FormattedMessage id="general.myStuff" />
</a> </a>
</li> </li>
{this.props.session.session.permissions.educator ? [ {this.props.permissions.educator ? [
<li> <li key="my-classes-li">
<a href="/educators/classes/"> <a href="/educators/classes/">
<FormattedMessage id="general.myClasses" /> <FormattedMessage id="general.myClasses" />
</a> </a>
</li> </li>
] : []} ] : []}
{this.props.session.session.permissions.student ? [ {this.props.permissions.student ? [
<li> <li>
<a href={'/classes/' + this.props.session.session.user.classroomId + '/'}> <a href={'/classes/' + this.props.session.session.user.classroomId + '/'}>
<FormattedMessage id="general.myClass" /> <FormattedMessage id="general.myClass" />
@ -335,7 +341,8 @@ var Navigation = React.createClass({
var mapStateToProps = function (state) { var mapStateToProps = function (state) {
return { return {
session: state.session session: state.session,
permissions: state.permissions
}; };
}; };

View file

@ -1,6 +1,18 @@
@import "../../../colors"; @import "../../../colors";
#navigation { #navigation {
&.staging {
.messages {
.message-count {
display: none;
&.show {
background-color: $ui-blue;
}
}
}
}
.logo { .logo {
margin-right: 10px; margin-right: 10px;
@ -18,7 +30,7 @@
height: 50px; height: 50px;
&:hover { &:hover {
transition: .15s ease all; transition: .15s ease all;
background-size: 100%; background-size: 100%;
} }
} }
@ -49,7 +61,7 @@
height: 14px; height: 14px;
&[type=text] { &[type=text] {
transition: .15s ease background-color; transition: .15s ease background-color;
padding: 0; padding: 0;
padding-right: 10px; padding-right: 10px;
padding-left: 40px; padding-left: 40px;
@ -76,14 +88,14 @@
.btn-search { .btn-search {
position: absolute; position: absolute;
box-shadow: none; box-shadow: none;
background-color: transparent; background-color: transparent;
background-image: url("/images/nav-search-glass.png"); background-image: url("/images/nav-search-glass.png");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center center; background-position: center center;
background-size: 14px 14px; background-size: 14px 14px;
width: 40px; width: 40px;
height: 40px; height: 40px;
@ -147,6 +159,21 @@
.login-dropdown { .login-dropdown {
width: 200px; width: 200px;
.button {
padding: .75em;
}
}
.dropdown {
.row {
margin-bottom: 1.25rem;
input {
margin: 0;
height: 2.25rem;
}
}
} }
.account-nav { .account-nav {
@ -170,6 +197,10 @@
vertical-align: middle; vertical-align: middle;
} }
&.open {
background-color: $active-gray;
}
&:after { &:after {
display: inline-block; display: inline-block;
margin-left: 8px; margin-left: 8px;
@ -186,9 +217,11 @@
} }
.dropdown { .dropdown {
top: 50px;
padding: 0; padding: 0;
padding-top: 5px; padding-top: 5px;
width: 100%; width: 100%;
box-sizing: border-box;
} }
} }
} }

View file

@ -1,4 +1,5 @@
var React = require('react'); var React = require('react');
var classNames = require('classnames');
var Navigation = require('../../navigation/www/navigation.jsx'); var Navigation = require('../../navigation/www/navigation.jsx');
var Footer = require('../../footer/www/footer.jsx'); var Footer = require('../../footer/www/footer.jsx');
@ -6,9 +7,12 @@ var Footer = require('../../footer/www/footer.jsx');
var Page = React.createClass({ var Page = React.createClass({
type: 'Page', type: 'Page',
render: function () { render: function () {
var classes = classNames({
'staging': process.env.SCRATCH_ENV == 'staging'
});
return ( return (
<div className="page"> <div className="page">
<div id="navigation"> <div id="navigation" className={classes}>
<Navigation /> <Navigation />
</div> </div>
<div id="view"> <div id="view">

View file

@ -12,6 +12,7 @@ var CharCount = require('../../components/forms/charcount.jsx');
var Checkbox = require('../../components/forms/checkbox.jsx'); var Checkbox = require('../../components/forms/checkbox.jsx');
var CheckboxGroup = require('../../components/forms/checkbox-group.jsx'); var CheckboxGroup = require('../../components/forms/checkbox-group.jsx');
var Form = require('../../components/forms/form.jsx'); var Form = require('../../components/forms/form.jsx');
var GeneralError = require('../../components/forms/general-error.jsx');
var Input = require('../../components/forms/input.jsx'); var Input = require('../../components/forms/input.jsx');
var PhoneInput = require('../../components/forms/phone-input.jsx'); var PhoneInput = require('../../components/forms/phone-input.jsx');
var RadioGroup = require('../../components/forms/radio-group.jsx'); var RadioGroup = require('../../components/forms/radio-group.jsx');
@ -102,7 +103,7 @@ module.exports = {
</p> </p>
<Card> <Card>
<Form onValidSubmit={this.onValidSubmit}> <Form onValidSubmit={this.onValidSubmit}>
<Input label={formatMessage({id: 'general.username'})} <Input label={formatMessage({id: 'general.createUsername'})}
className={this.state.validUsername} className={this.state.validUsername}
type="text" type="text"
name="user.username" name="user.username"
@ -148,6 +149,7 @@ module.exports = {
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="teacherRegistration.nextStep" />} />
</Form> </Form>
@ -284,6 +286,14 @@ module.exports = {
waiting: false waiting: false
}; };
}, },
onValidSubmit: function (formData, reset, invalidate) {
if (formData.phone.national_number.length !== formData.phone.country_code.format.length) {
return invalidate({
'phone': this.props.intl.formatMessage({id: 'teacherRegistration.validationPhoneNumber'})
});
}
return this.props.onNextStep(formData);
},
render: function () { render: function () {
var formatMessage = this.props.intl.formatMessage; var formatMessage = this.props.intl.formatMessage;
return ( return (
@ -291,13 +301,13 @@ module.exports = {
<h2> <h2>
<intl.FormattedMessage id="teacherRegistration.phoneStepTitle" /> <intl.FormattedMessage id="teacherRegistration.phoneStepTitle" />
</h2> </h2>
<p> <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: 'teacherRegistration.nameStepTooltip'})} />
</p> </p>
<Card> <Card>
<Form onValidSubmit={this.props.onNextStep}> <Form onValidSubmit={this.onValidSubmit}>
<PhoneInput label={formatMessage({id: 'teacherRegistration.phoneNumber'})} <PhoneInput label={formatMessage({id: 'teacherRegistration.phoneNumber'})}
name="phone" name="phone"
defaultCountry={this.props.defaultCountry} defaultCountry={this.props.defaultCountry}
@ -352,6 +362,14 @@ module.exports = {
onChooseOrganization: function (name, values) { onChooseOrganization: function (name, values) {
this.setState({otherDisabled: values.indexOf(this.organizationL10nStems.indexOf('orgChoiceOther')) === -1}); this.setState({otherDisabled: values.indexOf(this.organizationL10nStems.indexOf('orgChoiceOther')) === -1});
}, },
onValidSubmit: function (formData, reset, invalidate) {
if (formData.organization.type.length < 1) {
return invalidate({
'organization.type': this.props.intl.formatMessage({id: 'teacherRegistration.validationRequired'})
});
}
return this.props.onNextStep(formData);
},
render: function () { render: function () {
var formatMessage = this.props.intl.formatMessage; var formatMessage = this.props.intl.formatMessage;
return ( return (
@ -359,13 +377,13 @@ module.exports = {
<h2> <h2>
<intl.FormattedMessage id="teacherRegistration.orgStepTitle" /> <intl.FormattedMessage id="teacherRegistration.orgStepTitle" />
</h2> </h2>
<p> <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: 'teacherRegistration.nameStepTooltip'})} />
</p> </p>
<Card> <Card>
<Form onValidSubmit={this.props.onNextStep}> <Form onValidSubmit={this.onValidSubmit}>
<Input label={formatMessage({id: 'teacherRegistration.organization'})} <Input label={formatMessage({id: 'teacherRegistration.organization'})}
type="text" type="text"
name="organization.name" name="organization.name"
@ -448,7 +466,7 @@ module.exports = {
return this.props.onNextStep(formData); return this.props.onNextStep(formData);
} else { } else {
return invalidate({ return invalidate({
'all': <FormattedMessage id="teacherRegistration.addressValidationError" /> 'all': this.props.intl.formatMessage({id: 'teacherRegistration.addressValidationError'})
}); });
} }
}.bind(this)); }.bind(this));
@ -508,6 +526,7 @@ module.exports = {
type="text" type="text"
name="address.zip" name="address.zip"
required /> required />
<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="teacherRegistration.nextStep" />} />
</Form> </Form>
@ -631,6 +650,7 @@ module.exports = {
equalsField: formatMessage({id: 'general.validationEmailMatch'}) equalsField: formatMessage({id: 'general.validationEmailMatch'})
}} }}
required /> required />
<GeneralError name="all" />
<NextStepButton waiting={this.props.waiting} <NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} /> text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} />
</Form> </Form>
@ -693,7 +713,7 @@ module.exports = {
<Slide className="error-step"> <Slide className="error-step">
<h2>Something went wrong</h2> <h2>Something went wrong</h2>
<Card> <Card>
<h2>There was an error while processing your registration</h2> <h4>There was an error while processing your registration</h4>
<p> <p>
{this.props.registrationError} {this.props.registrationError}
</p> </p>

View file

@ -1,4 +1,5 @@
var classNames = require('classnames'); var classNames = require('classnames');
var connect = require('react-redux').connect;
var React = require('react'); var React = require('react');
var sessionActions = require('../../redux/session.js'); var sessionActions = require('../../redux/session.js');
@ -34,11 +35,11 @@ var TeacherBanner = React.createClass({
<div className="welcome"> <div className="welcome">
{this.props.session.status === sessionActions.Status.FETCHED ? ( {this.props.session.status === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [ this.props.session.session.user ? [
<h3> <h3 key="greeting">
{this.props.messages['teacherbanner.greeting']},{' '} {this.props.messages['teacherbanner.greeting']},{' '}
{this.props.session.session.user.username} {this.props.session.session.user.username}
</h3>, </h3>,
<p> <p key="subgreeting">
{this.props.messages['teacherbanner.subgreeting']} {this.props.messages['teacherbanner.subgreeting']}
</p> </p>
] : [] ] : []
@ -47,17 +48,17 @@ var TeacherBanner = React.createClass({
<FlexRow className="quick-links"> <FlexRow className="quick-links">
{this.props.session.status === sessionActions.Status.FETCHED ? ( {this.props.session.status === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [ this.props.session.session.user ? [
<a href="/educators/classes"> <a href="/educators/classes" key="classes-button">
<Button> <Button>
{this.props.messages['teacherbanner.classesButton']} {this.props.messages['teacherbanner.classesButton']}
</Button> </Button>
</a>, </a>,
<a href="/info/educators"> <a href="/info/educators" key="resources-button">
<Button> <Button>
{this.props.messages['teacherbanner.resourcesButton']} {this.props.messages['teacherbanner.resourcesButton']}
</Button> </Button>
</a>, </a>,
<a href="/info/educators/faq"> <a href="/educators/faq" key="faq-button">
<Button> <Button>
{this.props.messages['teacherbanner.faqButton']} {this.props.messages['teacherbanner.faqButton']}
</Button> </Button>
@ -71,4 +72,12 @@ var TeacherBanner = React.createClass({
} }
}); });
module.exports = TeacherBanner; var mapStateToProps = function (state) {
return {
session: state.session
};
};
var ConnectedTeacherBanner = connect(mapStateToProps)(TeacherBanner);
module.exports = ConnectedTeacherBanner;

View file

@ -12,6 +12,7 @@
"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",
@ -92,14 +93,17 @@
"general.stories": "Stories", "general.stories": "Stories",
"general.results": "Results", "general.results": "Results",
"general.teacherAccounts": "Teacher Accounts",
"footer.discuss": "Discussion Forums", "footer.discuss": "Discussion Forums",
"footer.help": "Help Page", "footer.help": "Help Page",
"footer.scratchFamily": "Scratch Family", "footer.scratchFamily": "Scratch Family",
"login.forgotPassword": "Forgot Password?", "login.forgotPassword": "Forgot Password?",
"navigation.signOut": "Sign out", "navigation.signOut": "Sign out",
"parents.FaqAgeRangeA": "While Scratch is primarily designed for 8 to 16 year olds, it is also used by people of all ages, including younger children with their parents.", "parents.FaqAgeRangeA": "While Scratch is primarily designed for 8 to 16 year olds, it is also used by people of all ages, including younger children with their parents.",
"parents.FaqAgeRangeQ": "What is the age range for Scratch?", "parents.FaqAgeRangeQ": "What is the age range for Scratch?",
"parents.FaqResourcesQ": "What resources are available for learning Scratch?", "parents.FaqResourcesQ": "What resources are available for learning Scratch?",

View file

@ -54,6 +54,16 @@ module.exports = function (opts, callback) {
} }
xhr(opts, function (err, res, body) { xhr(opts, function (err, res, body) {
if (err) log.error(err); if (err) log.error(err);
if (opts.responseType === 'json' && typeof body === 'string') {
// IE doesn't parse responses as JSON without the json attribute,
// even with responseType: 'json'.
// See https://github.com/Raynos/xhr/issues/123
try {
body = JSON.parse(body);
} catch (e) {
// Not parseable anyway, don't worry about it
}
}
// Legacy API responses come as lists, and indicate to redirect the client like // Legacy API responses come as lists, and indicate to redirect the client like
// [{success: true, redirect: "/location/to/redirect"}] // [{success: true, redirect: "/location/to/redirect"}]
try { try {

View file

@ -15,7 +15,8 @@ var Jar = {
// Return the usable content portion of a signed, compressed cookie generated by // Return the usable content portion of a signed, compressed cookie generated by
// Django's signing module // Django's signing module
// https://github.com/django/django/blob/stable/1.8.x/django/core/signing.py // https://github.com/django/django/blob/stable/1.8.x/django/core/signing.py
if (!value) return callback('No value to unsign'); if (typeof value === 'undefined') return callback(null, value);
try { try {
var b64Data = value.split(':')[0]; var b64Data = value.split(':')[0];
var decompress = false; var decompress = false;
@ -78,8 +79,11 @@ var Jar = {
// Get a value from a signed object // Get a value from a signed object
Jar.get(cookieName, function (err, value) { Jar.get(cookieName, function (err, value) {
if (err) return callback(err); if (err) return callback(err);
if (typeof value === 'undefined') return callback(null, value);
Jar.unsign(value, function (err, contents) { Jar.unsign(value, function (err, contents) {
if (err) return callback(err); if (err) return callback(err);
try { try {
var data = JSON.parse(contents); var data = JSON.parse(contents);
} catch (err) { } catch (err) {

View file

@ -6,6 +6,7 @@ var ReactDOM = require('react-dom');
var StoreProvider = require('react-redux').Provider; var StoreProvider = require('react-redux').Provider;
var IntlProvider = require('./intl.jsx').IntlProvider; var IntlProvider = require('./intl.jsx').IntlProvider;
var permissionsActions = require('../redux/permissions.js');
var sessionActions = require('../redux/session.js'); var sessionActions = require('../redux/session.js');
var reducer = require('../redux/reducer.js'); var reducer = require('../redux/reducer.js');
@ -42,7 +43,8 @@ var render = function (jsx, element) {
element element
); );
// Get initial session // Get initial session & permissions
store.dispatch(permissionsActions.getPermissions());
store.dispatch(sessionActions.refreshSession()); store.dispatch(sessionActions.refreshSession());
}; };

View file

@ -24,6 +24,8 @@ module.exports.getPermissions = function () {
return function (dispatch) { return function (dispatch) {
jar.getUnsignedValue('scratchsessionsid', 'permissions', function (err, value) { jar.getUnsignedValue('scratchsessionsid', 'permissions', function (err, value) {
if (err) return dispatch(module.exports.setPermissionsError(err)); if (err) return dispatch(module.exports.setPermissionsError(err));
value = value || {};
return dispatch(module.exports.setPermissions(value)); return dispatch(module.exports.setPermissions(value));
}); });
}; };

View file

@ -2,6 +2,7 @@ var keyMirror = require('keymirror');
var defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
var api = require('../lib/api'); var api = require('../lib/api');
var permissionsActions = require('./permissions.js');
var tokenActions = require('./token.js'); var tokenActions = require('./token.js');
var Types = keyMirror({ var Types = keyMirror({
@ -75,6 +76,9 @@ module.exports.refreshSession = function () {
dispatch(tokenActions.getToken()); dispatch(tokenActions.getToken());
dispatch(module.exports.setSession(body)); dispatch(module.exports.setSession(body));
dispatch(module.exports.setStatus(module.exports.Status.FETCHED)); dispatch(module.exports.setStatus(module.exports.Status.FETCHED));
// get the permissions from the updated session
dispatch(permissionsActions.getPermissions());
return; return;
} }
} }

View file

@ -27,6 +27,8 @@ module.exports.getToken = function () {
return function (dispatch) { return function (dispatch) {
jar.getUnsignedValue('scratchsessionsid', 'token', function (err, value) { jar.getUnsignedValue('scratchsessionsid', 'token', function (err, value) {
if (err) return dispatch(module.exports.setTokenError(err)); if (err) return dispatch(module.exports.setTokenError(err));
value = value || '';
return dispatch(module.exports.setToken(value)); return dispatch(module.exports.setToken(value));
}); });
}; };

View file

@ -33,7 +33,7 @@ var ConferenceExpectations = React.createClass({
<p className="intro"> <p className="intro">
The Scratch community keeps growing and growing.{' '} The Scratch community keeps growing and growing.{' '}
Young people around the world have shared more than{' '} Young people around the world have shared more than{' '}
14 million projects in the Scratch online community{' '} 15 million projects in the Scratch online community{' '}
with 20,000 new projects every day. with 20,000 new projects every day.
</p> </p>
<p className="intro"> <p className="intro">
@ -58,7 +58,7 @@ var ConferenceExpectations = React.createClass({
<p className="intro"> <p className="intro">
We are planning a very participatory conference, with lots of{' '} We are planning a very participatory conference, with lots of{' '}
hands-on workshops and opportunities for collaboration and sharing.{' '} hands-on workshops and opportunities for collaboration and sharing.{' '}
We hope youll join us. Lets learn together! Lets learn together!
</p> </p>
</div> </div>
</FlexRow> </FlexRow>

View file

@ -1,3 +1,4 @@
var classNames = require('classnames');
var injectIntl = require('react-intl').injectIntl; var injectIntl = require('react-intl').injectIntl;
var FormattedMessage = require('react-intl').FormattedMessage; var FormattedMessage = require('react-intl').FormattedMessage;
var React = require('react'); var React = require('react');
@ -17,7 +18,14 @@ require('./explore.scss');
var Explore = injectIntl(React.createClass({ var Explore = injectIntl(React.createClass({
type: 'Explore', type: 'Explore',
getDefaultProps: function () { getDefaultProps: function () {
var categoryOptions = ['all','animations','art','games','music','stories']; var categoryOptions = {
all: '*',
animations: 'animations',
art: 'art',
games: 'games',
music: 'music',
stories: 'stories'
};
var typeOptions = ['projects','studios']; var typeOptions = ['projects','studios'];
var pathname = window.location.pathname.toLowerCase(); var pathname = window.location.pathname.toLowerCase();
@ -28,7 +36,7 @@ var Explore = injectIntl(React.createClass({
var currentCategory = pathname.substring(slash + 1,pathname.length); var currentCategory = pathname.substring(slash + 1,pathname.length);
var typeStart = pathname.indexOf('explore/'); var typeStart = pathname.indexOf('explore/');
var type = pathname.substring(typeStart + 8,slash); var type = pathname.substring(typeStart + 8,slash);
if (categoryOptions.indexOf(currentCategory) === -1 || typeOptions.indexOf(type) === -1) { if (Object.keys(categoryOptions).indexOf(currentCategory) === -1 || typeOptions.indexOf(type) === -1) {
window.location = window.location.origin + '/explore/projects/all/'; window.location = window.location.origin + '/explore/projects/all/';
} }
@ -50,14 +58,13 @@ var Explore = injectIntl(React.createClass({
this.getExploreMore(); this.getExploreMore();
}, },
getExploreMore: function () { getExploreMore: function () {
var qText = ''; var qText = '&q=' + this.props.acceptableTabs[this.props.category] || '*';
if (this.props.tab != 'all') {
qText = '&q=' + this.props.category;
}
api({ api({
uri: '/search/' + this.props.itemType + uri: '/search/' + this.props.itemType +
'?limit=' + this.props.loadNumber + '?limit=' + this.props.loadNumber +
'&offset=' + this.state.offset + '&offset=' + this.state.offset +
'&language=' + this.props.intl.locale +
qText qText
}, function (err, body) { }, function (err, body) {
if (!err) { if (!err) {
@ -80,34 +87,28 @@ var Explore = injectIntl(React.createClass({
window.location = window.location.origin + '/explore/' + newType + '/' + this.props.tab; window.location = window.location.origin + '/explore/' + newType + '/' + this.props.tab;
}, },
getBubble: function (type) { getBubble: function (type) {
var allBubble = <a href={'/explore/' + this.props.itemType + '/' + type + '/'}> var classes = classNames({
<li> active: (this.props.category === type)
<FormattedMessage id={'general.' + type} /> });
</li> return (
</a>; <a href={'/explore/' + this.props.itemType + '/' + type + '/'}>
if (this.props.category === type) { <li className={classes}>
allBubble = <a href={'/explore/' + this.props.itemType+'/' + type + '/'}> <FormattedMessage id={'general.' + type} />
<li className='active'> </li>
<FormattedMessage id={'general.' + type} /> </a>
</li> );
</a>;
}
return allBubble;
}, },
getTab: function (type) { getTab: function (type) {
var allTab = <a href={'/explore/' + type + '/' + this.props.category + '/'}> var classes = classNames({
<li> active: (this.props.itemType === type)
<FormattedMessage id={'general.' + type} /> });
</li> return (
</a>; <a href={'/explore/' + type + '/' + this.props.category + '/'}>
if (this.props.itemType === type) { <li className={classes}>
allTab = <a href={'/explore/' + type +' /' + this.props.category + '/'}> <FormattedMessage id={'general.' + type} />
<li className='active'> </li>
<FormattedMessage id={'general.' + type} /> </a>
</li> );
</a>;
}
return allTab;
}, },
render: function () { render: function () {
return ( return (

View file

@ -121,7 +121,7 @@
"faq.cloudLagBody":"It depends. If both Scratchers have a reasonably fast Internet connection (DSL/Cable), and there are no restrictive firewalls on the computers/network, updates should be transmitted in milliseconds. However, a lot of computers have firewall software running in them, and if the firewall software blocks outgoing connections to TCP port 531 and TCP port 843, the time-lag becomes one-second. We are currently trying to figure out ways in which we can work around this limitation.", "faq.cloudLagBody":"It depends. If both Scratchers have a reasonably fast Internet connection (DSL/Cable), and there are no restrictive firewalls on the computers/network, updates should be transmitted in milliseconds. However, a lot of computers have firewall software running in them, and if the firewall software blocks outgoing connections to TCP port 531 and TCP port 843, the time-lag becomes one-second. We are currently trying to figure out ways in which we can work around this limitation.",
"faq.schoolsTitle":"Scratch in Schools", "faq.schoolsTitle":"Scratch in Schools",
"faq.howTitle":"How is Scratch used in schools?", "faq.howTitle":"How is Scratch used in schools?",
"faq.howBody":"Scratch is used in thousands of schools around the world, in many different subject areas (including language arts, science, history, math, and computer science). You can learn more about strategies and resources for using Scratch in schools and other learning environments (such as museums, libraries, and community centers) on our <a href=\"/educators\">Educators Page</a>. You can also join the <a href=\"//scratched.gse.harvard.edu/\">ScratchEd</a> online community for educators, which is managed by our friends at the Harvard Graduate School of Education.", "faq.howBody":"Scratch is used in thousands of schools around the world, in many different subject areas (including language arts, science, history, math, and computer science). You can learn more about strategies and resources for using Scratch in schools and other learning environments (such as museums, libraries, and community centers) on our <a href=\"/educators\">Educators Page</a>. You can also join the <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a> online community for educators, which is managed by our friends at the Harvard Graduate School of Education.",
"faq.ageTitle":"What is the age range for Scratch?", "faq.ageTitle":"What is the age range for Scratch?",
"faq.ageBody":"Scratch was developed especially for young people 8 to 16 years old, so it is used most often in elementary schools and middle schools, but people of all ages create and share Scratch projects. Scratch is even used in some introductory computer-science courses in colleges. Younger children may want to try <a href=\"//www.scratchjr.org/\">ScratchJr</a>, a simplified version of Scratch designed for ages 5 to 7.", "faq.ageBody":"Scratch was developed especially for young people 8 to 16 years old, so it is used most often in elementary schools and middle schools, but people of all ages create and share Scratch projects. Scratch is even used in some introductory computer-science courses in colleges. Younger children may want to try <a href=\"//www.scratchjr.org/\">ScratchJr</a>, a simplified version of Scratch designed for ages 5 to 7.",
"faq.noInternetTitle":"Is there a way for students to use Scratch without an internet connection?", "faq.noInternetTitle":"Is there a way for students to use Scratch without an internet connection?",
@ -133,7 +133,7 @@
"faq.requestTitle":"How do I request a Scratch Teacher Account?", "faq.requestTitle":"How do I request a Scratch Teacher Account?",
"faq.requestBody":"You may request a Scratch Teacher Account from the <a href=\"/educators\">Educators Page</a> on Scratch. We ask for additional information during the registration process in order to verify your role as an educator.", "faq.requestBody":"You may request a Scratch Teacher Account from the <a href=\"/educators\">Educators Page</a> on Scratch. We ask for additional information during the registration process in order to verify your role as an educator.",
"faq.edTitle":"What is the difference between a Scratch Teacher Account and a ScratchEd Account?", "faq.edTitle":"What is the difference between a Scratch Teacher Account and a ScratchEd Account?",
"faq.edBody":"Scratch Teacher Accounts are special user accounts on Scratch that have access to additional features to facilitate the creation and management of student accounts. ScratchEd Accounts are accounts on the <a href=\"//scratched.gse.harvard.edu/\">ScratchEd community</a>, a separate website (managed by the Harvard Graduate School of Education) where educators share stories, exchange resources, ask questions, and meet other Scratch educators.", "faq.edBody":"Scratch Teacher Accounts are special user accounts on Scratch that have access to additional features to facilitate the creation and management of student accounts. ScratchEd Accounts are accounts on the <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd community</a>, a separate website (managed by the Harvard Graduate School of Education) where educators share stories, exchange resources, ask questions, and meet other Scratch educators.",
"faq.dataTitle":"What data does Scratch collect about students?", "faq.dataTitle":"What data does Scratch collect about students?",
"faq.dataBody":"When a student first signs up on Scratch, we ask for basic demographic data including gender, age (birth month and year), country, and an email address for verification. This data is used (in aggregated form) in research studies intended to improve our understanding of how people learn with Scratch. When an educator uses a Scratch Teacher Account to create student accounts in bulk, no student demographic data is required for account setup.", "faq.dataBody":"When a student first signs up on Scratch, we ask for basic demographic data including gender, age (birth month and year), country, and an email address for verification. This data is used (in aggregated form) in research studies intended to improve our understanding of how people learn with Scratch. When an educator uses a Scratch Teacher Account to create student accounts in bulk, no student demographic data is required for account setup.",
"faq.schoolsMoreInfo":"For more more questions about Teacher Accounts, see the <a href=\"/educators/faq\">Teacher Account FAQ</a>" "faq.schoolsMoreInfo":"For more more questions about Teacher Accounts, see the <a href=\"/educators/faq\">Teacher Account FAQ</a>"

View file

@ -61,6 +61,7 @@ var Search = injectIntl(React.createClass({
uri: '/search/' + this.props.tab + uri: '/search/' + this.props.tab +
'?limit=' + this.props.loadNumber + '?limit=' + this.props.loadNumber +
'&offset=' + this.state.offset + '&offset=' + this.state.offset +
'&language=' + this.props.intl.locale +
termText termText
}, function (err, body) { }, function (err, body) {
var loadedSoFar = this.state.loaded; var loadedSoFar = this.state.loaded;

View file

@ -4,7 +4,6 @@ var omit = require('lodash.omit');
var React = require('react'); var React = require('react');
var api = require('../../lib/api'); var api = require('../../lib/api');
var permissionsActions = require('../../redux/permissions.js');
var render = require('../../lib/render.jsx'); var render = require('../../lib/render.jsx');
var sessionActions = require('../../redux/session.js'); var sessionActions = require('../../redux/session.js');
var shuffle = require('../../lib/shuffle.js').shuffle; var shuffle = require('../../lib/shuffle.js').shuffle;
@ -62,10 +61,6 @@ var Splash = injectIntl(React.createClass({
} }
} }
}, },
componentWillMount: function () {
// Determine whether to show the teacher banner or not
this.props.dispatch(permissionsActions.getPermissions());
},
componentDidMount: function () { componentDidMount: function () {
this.getFeaturedGlobal(); this.getFeaturedGlobal();
if (this.props.session.session.user) { if (this.props.session.session.user) {
@ -382,7 +377,7 @@ var Splash = injectIntl(React.createClass({
</Modal> </Modal>
] : []} ] : []}
{this.props.permissions.educator ? [ {this.props.permissions.educator ? [
<TeacherBanner messages={messages} /> <TeacherBanner key="teacherbanner" messages={messages} />
] : []} ] : []}
<div key="inner" className="inner"> <div key="inner" className="inner">
{this.props.session.status === sessionActions.Status.FETCHED ? ( {this.props.session.status === sessionActions.Status.FETCHED ? (

View file

@ -20,6 +20,7 @@
"teacherRegistration.phoneNumber": "Phone Number", "teacherRegistration.phoneNumber": "Phone Number",
"teacherRegistration.phoneStepDescription": "Your phone number will not be displayed publicly, and will be kept confidential and secure", "teacherRegistration.phoneStepDescription": "Your phone number will not be displayed publicly, and will be kept confidential and secure",
"teacherRegistration.phoneConsent": "Yes, the Scratch Team may call me to verify my Teacher Account if needed", "teacherRegistration.phoneConsent": "Yes, the Scratch Team may call me to verify my Teacher Account if needed",
"teacherRegistration.validationPhoneNumber": "Please enter a valid phone number",
"teacherRegistration.validationPhoneConsent": "You must consent to verification of your Teacher Account", "teacherRegistration.validationPhoneConsent": "You must consent to verification of your Teacher Account",
"teacherRegistration.orgStepTitle": "Organization", "teacherRegistration.orgStepTitle": "Organization",
"teacherRegistration.orgStepDescription": "Your information will not be displayed publicly, and will be kept confidential and secure", "teacherRegistration.orgStepDescription": "Your information will not be displayed publicly, and will be kept confidential and secure",
@ -38,7 +39,7 @@
"teacherRegistration.orgChoiceOther": " ", "teacherRegistration.orgChoiceOther": " ",
"teacherRegistration.notRequired": "Not Required", "teacherRegistration.notRequired": "Not Required",
"teacherRegistration.selectCountry": "select country", "teacherRegistration.selectCountry": "select country",
"teacherRegistration.validationAddress": "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)",
"teacherRegistration.zipCode": "ZIP", "teacherRegistration.zipCode": "ZIP",
@ -52,5 +53,6 @@
"teacherRegistration.howUseScratch": "How do you plan to use Scratch at your organization?", "teacherRegistration.howUseScratch": "How do you plan to use Scratch at your organization?",
"teacherRegistration.emailStepTitle": "Email Address", "teacherRegistration.emailStepTitle": "Email Address",
"teacherRegistration.emailStepDescription": "We will send you a confirmation email that will allow you to access your Scratch Teacher Account.", "teacherRegistration.emailStepDescription": "We will send you a confirmation email that will allow you to access your Scratch Teacher Account.",
"teacherRegistration.validationEmailMatch": "The emails do not match" "teacherRegistration.validationEmailMatch": "The emails do not match",
"teacherRegistration.validationRequired": "This field is required"
} }

View file

@ -30,7 +30,7 @@ body {
.address-step, .address-step,
.email-step { .email-step {
.help-block { .help-block {
transform: translate(15.5rem, -4rem); transform: translate(15.75rem, -4rem);
} }
} }
@ -40,7 +40,7 @@ body {
} }
.help-block { .help-block {
transform: translate(14rem, -4rem); transform: translate(13rem, -2rem);
} }
.radio { .radio {
@ -65,7 +65,7 @@ body {
} }
.help-block { .help-block {
margin-top: .7rem; margin-top: .5rem;
} }
.checkbox-row { .checkbox-row {
@ -80,6 +80,12 @@ body {
transform: translate(16rem, -4rem); transform: translate(16rem, -4rem);
} }
.checkbox-group {
.help-block {
transform: translate(16rem, -16rem);
}
}
.organization-type, .organization-type,
.url-input { .url-input {
p { p {
@ -100,6 +106,14 @@ body {
} }
} }
.address-step {
.select {
.help-block {
transform: translate(0, .5rem);
}
}
}
.usescratch-step { .usescratch-step {
.form { .form {
.form-group { .form-group {
@ -128,7 +142,8 @@ body {
} }
} }
.last-step { .last-step,
.error-step {
&.slide { &.slide {
max-width: 38.75rem; max-width: 38.75rem;
} }
@ -159,7 +174,8 @@ body {
} }
} }
.last-step { .last-step,
.error-step {
.card { .card {
margin: 0 auto; margin: 0 auto;
width: 18.75rem; width: 18.75rem;

View file

@ -9,7 +9,7 @@
"teacherfaq.teacherGoogleTitle": "Are teacher accounts integrated with Google Classroom or any other classroom managment service?", "teacherfaq.teacherGoogleTitle": "Are teacher accounts integrated with Google Classroom or any other classroom managment service?",
"teacherfaq.teacherGoogleBody": "Scratch Teacher accounts are not integrated with any classroom management services.", "teacherfaq.teacherGoogleBody": "Scratch Teacher accounts are not integrated with any classroom management services.",
"teacherfaq.teacherEdTitle": "Are ScratchEd & Scratch Teacher accounts the same thing?", "teacherfaq.teacherEdTitle": "Are ScratchEd & Scratch Teacher accounts the same thing?",
"teacherfaq.teacherEdBody": "<a href=\"//scratched.gse.harvard.edu/\">ScratchEd</a> accounts are not linked to Scratch Teacher accounts.", "teacherfaq.teacherEdBody": "<a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a> accounts are not linked to Scratch Teacher accounts.",
"teacherfaq.teacherWaitTitle": "Why do I have to wait 24 hours for my account?", "teacherfaq.teacherWaitTitle": "Why do I have to wait 24 hours for my account?",
"teacherfaq.teacherWaitBody": "The Scratch Team uses this time to manually review account creation submissions to verify the account creator is an educator.", "teacherfaq.teacherWaitBody": "The Scratch Team uses this time to manually review account creation submissions to verify the account creator is an educator.",
"teacherfaq.teacherPersonalTitle": "Why do you need to know my personal information during registration?", "teacherfaq.teacherPersonalTitle": "Why do you need to know my personal information during registration?",
@ -33,7 +33,7 @@
"teacherfaq.studentMultipleTitle": "Can a student be in multiple classes? ", "teacherfaq.studentMultipleTitle": "Can a student be in multiple classes? ",
"teacherfaq.studentMultipleBody": "A student can only be a part of one class. However, we are looking into adding this functionality in later versions.", "teacherfaq.studentMultipleBody": "A student can only be a part of one class. However, we are looking into adding this functionality in later versions.",
"teacherfaq.studentDiscussTitle": "Is there a space to discuss Teacher Accounts with other teachers?", "teacherfaq.studentDiscussTitle": "Is there a space to discuss Teacher Accounts with other teachers?",
"teacherfaq.studentDiscussBody": "Yes, you can engage in discussions with other teachers at <a href=\"//scratched.gse.harvard.edu/\">ScratchEd</a>, an online community for Scratch educators. Check out their forums to join conversations about a <a href=\"//scratched.gse.harvard.edu/discussions\">number of topics</a>, including but not limited to Teacher Accounts. ScratchEd is developed and supported by the Harvard Graduate School of Education.", "teacherfaq.studentDiscussBody": "Yes, you can engage in discussions with other teachers at <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a>, an online community for Scratch educators. Check out their forums to join conversations about a <a href=\"http://scratched.gse.harvard.edu/discussions\">number of topics</a>, including but not limited to Teacher Accounts. ScratchEd is developed and supported by the Harvard Graduate School of Education.",
"teacherfaq.commTitle": "Community", "teacherfaq.commTitle": "Community",
"teacherfaq.commHiddenTitle": "Can I create a hidden class?", "teacherfaq.commHiddenTitle": "Can I create a hidden class?",

View file

@ -0,0 +1,26 @@
{
"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 todays society.",
"teacherlanding.inPracticeAnchor": "In Practice",
"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.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.",
"teacherlanding.creativeComputing": "The <a href=\"http://scratched.gse.harvard.edu/guide/\">Creative Computing Curriculum Guide</a> provides plans, activities, and strategies for introducing creative computing.",
"teacherlanding.accountsTitle": "Teacher Accounts in Scratch",
"teacherlanding.accountsDescription": "As an educator, you can request a Scratch Teacher Account, which makes it easier to create accounts for groups of students and to manage your students projects and comments. To learn more, see the <a href=\"/educators/faq\">Teacher Account FAQ page</a>.",
"teacherlanding.accountsButton": "Coming Soon"
}

View file

@ -1,49 +1,50 @@
var React = require('react'); var React = require('react');
var render = require('../../../lib/render.jsx'); var render = require('../../../lib/render.jsx');
var FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage;
var FormattedMessage = require('react-intl').FormattedMessage;
var injectIntl = require('react-intl').injectIntl;
var Page = require('../../../components/page/www/page.jsx'); var Page = require('../../../components/page/www/page.jsx');
var FlexRow = require('../../../components/flex-row/flex-row.jsx'); var FlexRow = require('../../../components/flex-row/flex-row.jsx');
var SubNavigation = require('../../../components/subnavigation/subnavigation.jsx'); var SubNavigation = require('../../../components/subnavigation/subnavigation.jsx');
var TitleBanner = require('../../../components/title-banner/title-banner.jsx'); var TitleBanner = require('../../../components/title-banner/title-banner.jsx');
var Button = require('../../../components/forms/button.jsx');
require('./landing.scss'); require('./landing.scss');
var Landing = React.createClass({ var Landing = injectIntl(React.createClass({
type: 'Landing', type: 'Landing',
render: function () { render: function () {
return ( return (
<div className="educators"> <div className="educators">
<TitleBanner className="masthead"> <TitleBanner className="masthead">
<div className="inner"> <div className="inner">
<h1>Scratch for Educators</h1> <h1><FormattedMessage id="teacherlanding.title" /></h1>
<FlexRow className="masthead-info"> <FlexRow className="masthead-info">
<p className="intro"> <p className="intro">
Your students can use Scratch to code their own{' '} <FormattedMessage id="teacherlanding.intro" />
interactive stories, animations, and games.{' '}
In the process, they learn to think creatively,{' '}
reason systematically, and work{' '}
collaboratively essential skills for everyone in todays society.
</p> </p>
<iframe src="https://www.youtube.com/embed/uPSuG063jhA" <div className="ted-talk">
frameborder="0" allowfullscreen></iframe> <iframe src="https://www.youtube.com/embed/uPSuG063jhA?border=0&wmode=transparent"
frameBorder="0" allowFullScreen></iframe>
</div>
</FlexRow> </FlexRow>
</div> </div>
<div className="band"> <div className="band">
<SubNavigation className="inner"> <SubNavigation className="inner">
<a href="#in-practice"> <a href="#in-practice">
<li> <li>
In Practice <FormattedMessage id="teacherlanding.inPracticeAnchor" />
</li> </li>
</a> </a>
<a href="#resources"> <a href="#resources">
<li> <li>
Resources <FormattedMessage id="teacherlanding.resourcesAnchor" />
</li> </li>
</a> </a>
<a href="#teacher-accounts"> <a href="#teacher-accounts">
<li> <li>
Teacher Accounts <FormattedMessage id="general.teacherAccounts" />
</li> </li>
</a> </a>
</SubNavigation> </SubNavigation>
@ -53,96 +54,81 @@ var Landing = React.createClass({
<div className="inner"> <div className="inner">
<section id="in-practice"> <section id="in-practice">
<span className="nav-spacer"></span> <span className="nav-spacer"></span>
<h2>Who Uses Scratch?</h2> <h2><FormattedMessage id="teacherlanding.inPracticeTitle" /></h2>
<p className="intro">Educators are using Scratch in a wide variety of: </p> <p className="intro"><FormattedMessage id="teacherlanding.inPracticeIntro" /></p>
<FlexRow className="general-usage"> <FlexRow className="general-usage">
<p><b>Settings:</b> schools, museums, libraries, community centers</p> <p><FormattedHTMLMessage id="teacherlanding.generalUsageSettings" /></p>
<p><b>Grade Levels:</b> elementary, middle, and high school (and some colleges too!)</p> <p><FormattedHTMLMessage id="teacherlanding.generalUsageGradeLevels" /></p>
<p><b>Subject Areas:</b> language arts, science, social studies,{' '} <p><FormattedHTMLMessage id="teacherlanding.generalUsageSubjectAreas"/></p>
math, computer science, foreign languages, and the arts</p>
</FlexRow> </FlexRow>
<FlexRow className="stories"> <FlexRow className="stories">
<div className="story"> <a href="//bit.ly/28SBsa9" className="story">
<img src="/images/teachers/stories/ingrid.jpg" alt="ingrid's story" /> <img src="/images/teachers/stories/ingrid.jpg" alt="ingrid's story" />
<div className="story-info"> <div className="story-info">
<a href="//bit.ly/28SBsa9">Ingrid Gustafson</a> <p className="name">Ingrid Gustafson</p>
<p>Instructional Technology Specialist</p> <p><FormattedMessage id="teacherlanding.ingridTitle" /></p>
</div> </div>
</div> </a>
<div className="story"> <a href="//bit.ly/28Q5l6P" className="story">
<img src="/images/teachers/stories/dylan.jpg" alt="dylan's story" /> <img src="/images/teachers/stories/dylan.jpg" alt="dylan's story" />
<div className="story-info"> <div className="story-info">
<a href="//bit.ly/28Q5l6P">Dylan Ryder</a> <p className="name">Dylan Ryder</p>
<p>Educational Technologist</p> <p><FormattedMessage id="teacherlanding.dylanTitle" /></p>
</div> </div>
</div> </a>
<div className="story"> <a href="//bit.ly/28SC1AY" className="story">
<img src="/images/teachers/stories/plug-in-studio.jpg" <img src="/images/teachers/stories/plug-in-studio.jpg"
alt="plug in studio's story" /> alt="plug in studio's story" />
<div className="story-info"> <div className="story-info">
<a href="//bit.ly/28SC1AY">Plug-In Studios</a> <p className="name">Plug-In Studios</p>
<p>After-School Program</p> <p><FormattedMessage id="teacherlanding.afterSchoolTitle" /></p>
</div> </div>
</div> </a>
<div className="story"> <a href="//bit.ly/28UzapJ" className="story">
<img src="/images/teachers/stories/ghana-code-club.jpg" <img src="/images/teachers/stories/ghana-code-club.jpg"
alt="ghana code club's story" /> alt="ghana code club's story" />
<div className="story-info"> <div className="story-info">
<a href="//bit.ly/28UzapJ">Ghana Code Club</a> <p className="name">Ghana Code Club</p>
<p>After-School Program</p> <p><FormattedMessage id="teacherlanding.afterSchoolTitle" /></p>
</div> </div>
</div> </a>
</FlexRow> </FlexRow>
</section> </section>
<section id="resources"> <section id="resources">
<span className="nav-spacer"></span> <span className="nav-spacer"></span>
<h2>Educator Resources</h2> <h2><FormattedMessage id="teacherlanding.resourcesTitle" /></h2>
<FlexRow className="educator-community"> <FlexRow className="educator-community">
<div> <div>
<h3>A Community for Educators</h3> <h3><FormattedMessage id="teacherlanding.scratchEdTitle" /></h3>
<p> <p>
<a href="http://scratched.gse.harvard.edu/">ScratchEd</a> is an{' '} <FormattedHTMLMessage id="teacherlanding.scratchEdDescription" />
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.
</p> </p>
</div> </div>
<div> <div>
<h3>In-Person Gatherings</h3> <h3><FormattedMessage id="teacherlanding.meetupTitle" /></h3>
<p> <p>
<a href="http://www.meetup.com/pro/scratched/">Scratch Educator Meetups</a>{' '} <FormattedHTMLMessage id="teacherlanding.meetupDescription" />
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.
</p> </p>
</div> </div>
</FlexRow> </FlexRow>
<h3 id="guides-header">Guides & Tutorials</h3> <h3 id="guides-header"><FormattedMessage id="teacherlanding.guidesTitle" /></h3>
<FlexRow className="guides-and-tutorials"> <FlexRow className="guides-and-tutorials">
<div> <div>
<img src="/svgs/teachers/resources.svg" alt="resources icon" /> <img src="/svgs/teachers/resources.svg" alt="resources icon" />
<p> <p>
On the <a href="https://scratch.mit.edu/help/">Help Page</a>,{' '} <FormattedHTMLMessage id="teacherlanding.helpPage" />
you can find workshop guides, Scratch Cards, videos, and other resources.
</p> </p>
</div> </div>
<div> <div>
<img src="/svgs/teachers/tips-window.svg" alt="tips window icon" /> <img src="/svgs/teachers/tips-window.svg" alt="tips window icon" />
<p> <p>
The{' '} <FormattedHTMLMessage id="teacherlanding.tipsWindow" />
<a href="https://scratch.mit.edu/projects/editor/?tip_bar=home">Tips Window</a>{' '}
features step-by-step tutorials for getting started in Scratch.
</p> </p>
</div> </div>
<div> <div>
<img src="/svgs/teachers/creative-computing.svg" alt="creative computing icon" /> <img src="/svgs/teachers/creative-computing.svg" alt="creative computing icon" />
<p> <p>
The <a href="http://scratched.gse.harvard.edu/guide/">Creative Computing{' '} <FormattedHTMLMessage id="teacherlanding.creativeComputing" />
Curriculum Guide</a>{' '}
provides plans, activities, and{' '}
strategies for introducing creative computing.
</p> </p>
</div> </div>
</FlexRow> </FlexRow>
@ -151,15 +137,11 @@ var Landing = React.createClass({
<div id="teacher-accounts"> <div id="teacher-accounts">
<div className="inner account-flex"> <div className="inner account-flex">
<div id="left"> <div id="left">
<h2>Teacher Accounts in Scratch</h2> <h2><FormattedMessage id="teacherlanding.accountsTitle" /></h2>
<p> <p>
As an educator, you can request a Scratch Teacher Account,{' '} <FormattedHTMLMessage id="teacherlanding.accountsDescription" />
which makes it easier to create accounts for{' '}
groups of students and to manage your students{' '}
projects and comments. To learn more, see the{' '}
<a href="faq">Teacher Account FAQ page</a>.
</p> </p>
<a href="register"><Button>Request Account</Button></a> <div className="coming-soon"><FormattedMessage id="teacherlanding.accountsButton" /></div>
</div> </div>
<img src="/images/teachers/teacher-account.png" alt="teacher account" id="teacher-icon"/> <img src="/images/teachers/teacher-account.png" alt="teacher account" id="teacher-icon"/>
</div> </div>
@ -167,6 +149,6 @@ var Landing = React.createClass({
</div> </div>
); );
} }
}); }));
render(<Page><Landing /></Page>, document.getElementById('app')); render(<Page><Landing /></Page>, document.getElementById('app'));

View file

@ -48,12 +48,21 @@ $story-width: $cols3;
color: $ui-white; color: $ui-white;
} }
} }
}
.ted-talk {
position: relative;
margin-bottom: $gutter;
border: 2px solid $ui-border;
border-radius: 10px;
width: $cols4;
height: $cols4 * .5625;
overflow: hidden;
iframe { iframe {
margin-bottom: $gutter; border: 0;
border: 2px solid $ui-border; width: inherit;
border-radius: 10px; height: inherit;
width: $cols4;
} }
} }
@ -150,6 +159,14 @@ $story-width: $cols3;
padding-top: 10px; padding-top: 10px;
padding-left: 10px; padding-left: 10px;
.name {
margin: 0;
line-height: initial;
color: $ui-blue;
font-size: initial;
font-weight: 500;
}
p { p {
margin: 10px 0; margin: 10px 0;
font-size: .75rem; font-size: .75rem;
@ -213,13 +230,22 @@ $story-width: $cols3;
margin-bottom: 3.5rem; margin-bottom: 3.5rem;
} }
button { .coming-soon {
background-color: $ui-white; border: 2px solid $ui-white;
border-radius: 50px;
box-shadow: none;
background-color: transparent;
padding: 16px 16px; padding: 16px 16px;
width: $cols5 / 2; width: $cols5 / 2;
color: $ui-blue; text-align: center;
color: $ui-white;
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
box-sizing: border-box;
&:hover {
box-shadow: none;
}
} }
} }

View file

@ -116,7 +116,8 @@ module.exports = {
'process.env.NODE_ENV': '"' + (process.env.NODE_ENV || 'development') + '"', 'process.env.NODE_ENV': '"' + (process.env.NODE_ENV || 'development') + '"',
'process.env.SENTRY_DSN': '"' + (process.env.SENTRY_DSN || '') + '"', 'process.env.SENTRY_DSN': '"' + (process.env.SENTRY_DSN || '') + '"',
'process.env.API_HOST': '"' + (process.env.API_HOST || 'https://api.scratch.mit.edu') + '"', 'process.env.API_HOST': '"' + (process.env.API_HOST || 'https://api.scratch.mit.edu') + '"',
'process.env.SMARTY_STREETS_API_KEY': '"' + (process.env.SMARTY_STREETS_API_KEY || '') + '"' 'process.env.SMARTY_STREETS_API_KEY': '"' + (process.env.SMARTY_STREETS_API_KEY || '') + '"',
'process.env.SCRATCH_ENV': '"'+ (process.env.SCRATCH_ENV || 'development') + '"'
}), }),
new webpack.optimize.CommonsChunkPlugin('common', 'js/common.bundle.js'), new webpack.optimize.CommonsChunkPlugin('common', 'js/common.bundle.js'),
new webpack.optimize.OccurenceOrderPlugin() new webpack.optimize.OccurenceOrderPlugin()