diff --git a/bin/lib/locale-compare.js b/bin/lib/locale-compare.js index 0a064b8aa..5a2e3bc81 100644 --- a/bin/lib/locale-compare.js +++ b/bin/lib/locale-compare.js @@ -76,12 +76,14 @@ Helpers.getMD5Map = function (ICUIdMap) { * key: '' * 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 jsFile = path.resolve(poUiDir, lang + '/LC_MESSAGES/djangojs.po'); var pyFile = path.resolve(poUiDir, lang + '/LC_MESSAGES/django.po'); var translations = {}; + separator = separator || ':'; + try { fs.accessSync(jsFile, fs.R_OK); var jsTranslations = po2icu.poFileToICUSync(lang, jsFile); @@ -104,7 +106,7 @@ Helpers.getTranslationsForLanguage = function (lang, idsWithICU, md5WithIds) { var translationsByView = {}; for (var id in translations) { - var ids = id.split('-'); // [viewName, stringId] + var ids = id.split(separator); // [viewName, stringId] var viewName = ids[0]; 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 // Sample structure: { 'general-general.blah': 'blah', 'about-about.blah': 'blahblah' } -Helpers.idToICUMap = function (viewName, ids) { +Helpers.idToICUMap = function (viewName, ids, separator) { var idsToICU = {}; + separator = separator || ':'; + for (var id in ids) { - idsToICU[viewName + '-' + id] = ids[id]; + idsToICU[viewName + separator + id] = ids[id]; } return idsToICU; }; // 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' } -Helpers.icuToIdMap = function (viewName, ids) { +Helpers.icuToIdMap = function (viewName, ids, separator) { var icuToIds = {}; + separator = separator || ':'; + for (var id in ids) { - icuToIds[ids[id]] = viewName + '-' + id; + icuToIds[ids[id]] = viewName + separator + id; } return icuToIds; }; diff --git a/src/components/carousel/carousel.jsx b/src/components/carousel/carousel.jsx index 4120b835d..fbebed63d 100644 --- a/src/components/carousel/carousel.jsx +++ b/src/components/carousel/carousel.jsx @@ -55,7 +55,7 @@ var Carousel = React.createClass({ } return ( -
- + + + {this.props.children}
diff --git a/src/components/deck/deck.scss b/src/components/deck/deck.scss index ac527f7b7..c8ff26caf 100644 --- a/src/components/deck/deck.scss +++ b/src/components/deck/deck.scss @@ -14,141 +14,138 @@ .step-navigation { margin-top: 2rem; - } -} - -.slide { - max-width: 28.75rem; - - h2, - p { - text-align: center; - color: $type-white; + text-align: center; } - .description { - margin-top: 0; - margin-bottom: 2rem; + .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; - - &, - h2, - p { - color: $type-gray; + .card { + margin: 0 auto; + width: 23.75rem; } -} -.step-navigation { - text-align: center; -} + .form { + padding: 3rem 4rem; -.form { - padding: 3rem 4rem; + .form-group { + margin-bottom: 1.2rem; - .form-group { - margin-bottom: 1.2rem; + &.has-error { + .input { + border: 1px solid $ui-orange; + } + } + } - &.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; } } } - .button { - margin: 0 0 -3rem -4rem; - border-radius: .5rem; - box-shadow: none; - width: 23.75rem; - height: 4rem; - - &.card-button { - display: block; - border-top-left-radius: 0; - border-top-right-radius: 0; - background-color: $ui-aqua; - } - - &:hover { - box-shadow: none; - } + .input { + width: $cols5; } -} -.input { - width: $cols5; -} - -.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 { + .help-block { + $arrow-border-width: 1rem; 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; + margin-left: $arrow-border-width; + border: 1px solid $active-gray; border-radius: 5px; - background-color: $ui-orange; - width: $arrow-border-width; - height: $arrow-border-width; + padding: 1rem; + 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) { - .card { - width: 22.5rem; - } - - .form { - text-align: left; - - .button { + .deck { + .card { width: 22.5rem; } + + .form { + text-align: left; + + .button { + width: 22.5rem; + } + } } } @media only screen and (max-width: $tablet - 1) { - .input { - width: 90%; + .deck { + .input { + width: 90%; + } } } @media only screen and (max-width: $desktop - 1) { - .help-block { - position: relative; - transform: none; - margin: inherit; - width: 100%; - height: inherit; + .deck { + .help-block { + position: relative; + transform: none; + margin: inherit; + width: 100%; + height: inherit; - &:before { - display: none; + &:before { + display: none; + } } } } diff --git a/src/components/dropdown/dropdown.scss b/src/components/dropdown/dropdown.scss index 78ef27a81..4b0c56cb5 100644 --- a/src/components/dropdown/dropdown.scss +++ b/src/components/dropdown/dropdown.scss @@ -15,6 +15,10 @@ font-size: .8125rem; font-weight: normal; + &.staging { + background-color: $ui-orange; + } + &.open { display: block; } @@ -53,7 +57,7 @@ text-decoration: none; } } - } + } &.with-arrow { $arrow-border-width: 14px; @@ -61,12 +65,12 @@ border-radius: 5px; overflow: visible; - &:before { + &:before { display: block; position: absolute; top: -$arrow-border-width / 2; right: 10%; - + transform: rotate(45deg); border-top: 1px solid $active-gray; diff --git a/src/components/forms/charcount.scss b/src/components/forms/charcount.scss index 57f968a8d..ff5d85e94 100644 --- a/src/components/forms/charcount.scss +++ b/src/components/forms/charcount.scss @@ -1,13 +1,11 @@ @import "../../colors"; -p { - &.char-count { - letter-spacing: 1px; - color: lighten($type-gray, 30%); - font-weight: 500; +.char-count { + letter-spacing: 1px; + color: lighten($type-gray, 30%); + font-weight: 500; - &.overmax { - color: $ui-orange; - } + &.overmax { + color: $ui-orange; } } diff --git a/src/components/forms/checkbox-group.scss b/src/components/forms/checkbox-group.scss index 9ce20f29f..29613c177 100644 --- a/src/components/forms/checkbox-group.scss +++ b/src/components/forms/checkbox-group.scss @@ -1,9 +1,11 @@ .checkbox-group { - .col-sm-9 { - flex-flow: column wrap; + .row { + .col-sm-9 { + flex-flow: column wrap; - .checkbox { - margin: .5rem 0; + .checkbox { + margin: .5rem 0; + } } } } diff --git a/src/components/forms/checkbox.scss b/src/components/forms/checkbox.scss index be3e0a97b..df8a17237 100644 --- a/src/components/forms/checkbox.scss +++ b/src/components/forms/checkbox.scss @@ -6,37 +6,37 @@ label { font-weight: 300; } - } -} -input { - &[type=checkbox] { - display: block; - float: left; - margin-right: 1rem; - border: 1px solid $active-gray; - border-radius: 3px; - width: 1.25rem; - height: 1.25rem; - appearance: none; + input { + &[type=checkbox] { + display: block; + float: left; + margin-right: 1rem; + border: 1px solid $active-gray; + border-radius: 3px; + width: 1.25rem; + height: 1.25rem; + appearance: none; - &:checked, - &:focus { - outline: none; - } + &:checked, + &:focus { + transition: all .5s ease; + outline: none; + box-shadow: 0 0 0 .25rem $active-gray; + } - &:checked { - transition: all .5s ease; - box-shadow: 0 0 0 .25rem $active-gray; - background-color: $ui-blue; - text-align: center; - text-indent: .125rem; - line-height: 1.25rem; - font-size: .75rem; + &:checked { + background-color: $ui-blue; + text-align: center; + text-indent: .125rem; + line-height: 1.25rem; + font-size: .75rem; - &:after { - color: $type-white; - content: "\2714"; + &:after { + color: $type-white; + content: "\2714"; + } + } } } } diff --git a/src/components/forms/form.jsx b/src/components/forms/form.jsx index 00c5487c4..17ada223a 100644 --- a/src/components/forms/form.jsx +++ b/src/components/forms/form.jsx @@ -1,7 +1,7 @@ var classNames = require('classnames'); var Formsy = require('formsy-react'); +var omit = require('lodash.omit'); var React = require('react'); -var GeneralError = require('./general-error.jsx'); var validations = require('./validations.jsx').validations; for (var validation in validations) { @@ -11,18 +11,34 @@ for (var validation in validations) { var Form = React.createClass({ getDefaultProps: function () { 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 () { var classes = classNames( 'form', this.props.className ); return ( - - - {this.props.children} + + {React.Children.map(this.props.children, function (child) { + if (!child) return child; + if (child.props.name === 'all') { + return React.cloneElement(child, {value: this.state.allValues}); + } else { + return child; + } + }.bind(this))} ); } diff --git a/src/components/forms/general-error.jsx b/src/components/forms/general-error.jsx index bca50e20d..d9e20463b 100644 --- a/src/components/forms/general-error.jsx +++ b/src/components/forms/general-error.jsx @@ -1,6 +1,8 @@ var Formsy = require('formsy-react'); var React = require('react'); +require('./general-error.scss'); + /* * A special formsy-react component that only outputs * error messages. If you want to display errors that @@ -12,7 +14,7 @@ module.exports = Formsy.HOC(React.createClass({ render: function () { if (!this.props.showError()) return null; return ( -

+

{this.props.getErrorMessage()}

); diff --git a/src/components/forms/general-error.scss b/src/components/forms/general-error.scss new file mode 100644 index 000000000..c5da01c49 --- /dev/null +++ b/src/components/forms/general-error.scss @@ -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; +} diff --git a/src/components/forms/radio-group.scss b/src/components/forms/radio-group.scss index 7594003ae..dbb4626e2 100644 --- a/src/components/forms/radio-group.scss +++ b/src/components/forms/radio-group.scss @@ -6,39 +6,39 @@ font-weight: 300; } } -} -.col-sm-9 { - display: flex; - flex-flow: row wrap; + .col-sm-9 { + display: flex; + flex-flow: row wrap; - input { - &[type="radio"] { - margin-top: 1px; - border: 1px solid $active-gray; - border-radius: 50%; - width: .875rem; - height: .875rem; - appearance: none; + input { + &[type="radio"] { + margin-top: 1px; + border: 1px solid $active-gray; + border-radius: 50%; + width: .875rem; + height: .875rem; + appearance: none; - &:checked, - &:focus { - outline: none; - } + &:checked, + &:focus { + outline: none; + } - &:checked { - transition: all .25s ease; - box-shadow: 0 0 0 .25rem $active-gray; - background-color: $ui-blue; + &:checked { + transition: all .25s ease; + box-shadow: 0 0 0 .25rem $active-gray; + background-color: $ui-blue; - &:after { - display: block; - transform: translate(.25rem, .25rem); - border-radius: 50%; - background-color: $ui-white; - width: .25rem; - height: .25rem; - content: ""; + &:after { + display: block; + transform: translate(.25rem, .25rem); + border-radius: 50%; + background-color: $ui-white; + width: .25rem; + height: .25rem; + content: ""; + } } } } diff --git a/src/components/forms/select.scss b/src/components/forms/select.scss index a070404fa..e85704973 100644 --- a/src/components/forms/select.scss +++ b/src/components/forms/select.scss @@ -5,34 +5,38 @@ label { font-weight: 500; } -} -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 { + select { 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; + 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; + + &::-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%; + } } } diff --git a/src/components/forms/validations.jsx b/src/components/forms/validations.jsx index db0b71632..6410322b9 100644 --- a/src/components/forms/validations.jsx +++ b/src/components/forms/validations.jsx @@ -29,6 +29,7 @@ module.exports.validationHOCFactory = function (defaultValidationErrors) { var ValidatedComponent = React.createClass({ render: function () { var validationErrors = defaults( + {}, defaultValidationErrors, this.props.validationErrors ); diff --git a/src/components/languagechooser/languagechooser.jsx b/src/components/languagechooser/languagechooser.jsx index c9e0af69d..bb6dc5910 100644 --- a/src/components/languagechooser/languagechooser.jsx +++ b/src/components/languagechooser/languagechooser.jsx @@ -6,6 +6,8 @@ var languages = require('../../../languages.json'); var Form = require('../forms/form.jsx'); var Select = require('../forms/select.jsx'); +require('./languagechooser.scss'); + /** * Footer dropdown menu that allows one to change their language. */ @@ -33,7 +35,7 @@ var LanguageChooser = React.createClass({
+ } /> @@ -284,6 +286,14 @@ module.exports = { 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 () { var formatMessage = this.props.intl.formatMessage; return ( @@ -291,13 +301,13 @@ module.exports = {

-

+

-
+ -

+

- + + 'all': this.props.intl.formatMessage({id: 'teacherRegistration.addressValidationError'}) }); } }.bind(this)); @@ -508,6 +526,7 @@ module.exports = { type="text" name="address.zip" required /> + } /> @@ -631,6 +650,7 @@ module.exports = { equalsField: formatMessage({id: 'general.validationEmailMatch'}) }} required /> + } /> @@ -693,7 +713,7 @@ module.exports = {

Something went wrong

-

There was an error while processing your registration

+

There was an error while processing your registration

{this.props.registrationError}

diff --git a/src/components/teacher-banner/teacher-banner.jsx b/src/components/teacher-banner/teacher-banner.jsx index c4d2d59fa..91b76e23d 100644 --- a/src/components/teacher-banner/teacher-banner.jsx +++ b/src/components/teacher-banner/teacher-banner.jsx @@ -1,4 +1,5 @@ var classNames = require('classnames'); +var connect = require('react-redux').connect; var React = require('react'); var sessionActions = require('../../redux/session.js'); @@ -34,11 +35,11 @@ var TeacherBanner = React.createClass({
{this.props.session.status === sessionActions.Status.FETCHED ? ( this.props.session.session.user ? [ -

+

{this.props.messages['teacherbanner.greeting']},{' '} {this.props.session.session.user.username}

, -

+

{this.props.messages['teacherbanner.subgreeting']}

] : [] @@ -47,17 +48,17 @@ var TeacherBanner = React.createClass({ {this.props.session.status === sessionActions.Status.FETCHED ? ( this.props.session.session.user ? [ - + , - + , - + @@ -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; diff --git a/src/l10n.json b/src/l10n.json index 1e1625093..3685f0c1d 100644 --- a/src/l10n.json +++ b/src/l10n.json @@ -12,6 +12,7 @@ "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", @@ -92,14 +93,17 @@ "general.stories": "Stories", "general.results": "Results", + "general.teacherAccounts": "Teacher Accounts", + + "footer.discuss": "Discussion Forums", "footer.help": "Help Page", "footer.scratchFamily": "Scratch Family", "login.forgotPassword": "Forgot Password?", - + "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.FaqAgeRangeQ": "What is the age range for Scratch?", "parents.FaqResourcesQ": "What resources are available for learning Scratch?", diff --git a/src/lib/api.js b/src/lib/api.js index 05cc2bf1d..2efd806b7 100644 --- a/src/lib/api.js +++ b/src/lib/api.js @@ -54,6 +54,16 @@ module.exports = function (opts, callback) { } xhr(opts, function (err, res, body) { 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 // [{success: true, redirect: "/location/to/redirect"}] try { diff --git a/src/lib/jar.js b/src/lib/jar.js index 02cf98400..b5a16846c 100644 --- a/src/lib/jar.js +++ b/src/lib/jar.js @@ -15,7 +15,8 @@ var Jar = { // Return the usable content portion of a signed, compressed cookie generated by // Django's signing module // 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 { var b64Data = value.split(':')[0]; var decompress = false; @@ -78,8 +79,11 @@ var Jar = { // Get a value from a signed object Jar.get(cookieName, function (err, value) { if (err) return callback(err); + if (typeof value === 'undefined') return callback(null, value); + Jar.unsign(value, function (err, contents) { if (err) return callback(err); + try { var data = JSON.parse(contents); } catch (err) { diff --git a/src/lib/render.jsx b/src/lib/render.jsx index 9c7ed465c..833694552 100644 --- a/src/lib/render.jsx +++ b/src/lib/render.jsx @@ -6,6 +6,7 @@ var ReactDOM = require('react-dom'); var StoreProvider = require('react-redux').Provider; var IntlProvider = require('./intl.jsx').IntlProvider; +var permissionsActions = require('../redux/permissions.js'); var sessionActions = require('../redux/session.js'); var reducer = require('../redux/reducer.js'); @@ -42,7 +43,8 @@ var render = function (jsx, element) { element ); - // Get initial session + // Get initial session & permissions + store.dispatch(permissionsActions.getPermissions()); store.dispatch(sessionActions.refreshSession()); }; diff --git a/src/redux/permissions.js b/src/redux/permissions.js index 4d040d72b..c741ee865 100644 --- a/src/redux/permissions.js +++ b/src/redux/permissions.js @@ -24,6 +24,8 @@ module.exports.getPermissions = function () { return function (dispatch) { jar.getUnsignedValue('scratchsessionsid', 'permissions', function (err, value) { if (err) return dispatch(module.exports.setPermissionsError(err)); + + value = value || {}; return dispatch(module.exports.setPermissions(value)); }); }; diff --git a/src/redux/session.js b/src/redux/session.js index 16be21028..c6fa5ed25 100644 --- a/src/redux/session.js +++ b/src/redux/session.js @@ -2,6 +2,7 @@ var keyMirror = require('keymirror'); var defaults = require('lodash.defaults'); var api = require('../lib/api'); +var permissionsActions = require('./permissions.js'); var tokenActions = require('./token.js'); var Types = keyMirror({ @@ -75,6 +76,9 @@ module.exports.refreshSession = function () { 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; } } diff --git a/src/redux/token.js b/src/redux/token.js index 8bf8cd2ce..cb4baaadb 100644 --- a/src/redux/token.js +++ b/src/redux/token.js @@ -27,6 +27,8 @@ module.exports.getToken = function () { return function (dispatch) { jar.getUnsignedValue('scratchsessionsid', 'token', function (err, value) { if (err) return dispatch(module.exports.setTokenError(err)); + + value = value || ''; return dispatch(module.exports.setToken(value)); }); }; diff --git a/src/views/conference/expect/expect.jsx b/src/views/conference/expect/expect.jsx index 35a0d1613..cc403b72e 100644 --- a/src/views/conference/expect/expect.jsx +++ b/src/views/conference/expect/expect.jsx @@ -33,7 +33,7 @@ var ConferenceExpectations = React.createClass({

The Scratch community keeps growing and growing.{' '} 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.

@@ -58,7 +58,7 @@ var ConferenceExpectations = React.createClass({

We are planning a very participatory conference, with lots of{' '} hands-on workshops and opportunities for collaboration and sharing.{' '} - We hope you’ll join us. Let’s learn together! + Let’s learn together!

diff --git a/src/views/explore/explore.jsx b/src/views/explore/explore.jsx index 0b1cd7d69..6fb119741 100644 --- a/src/views/explore/explore.jsx +++ b/src/views/explore/explore.jsx @@ -1,3 +1,4 @@ +var classNames = require('classnames'); var injectIntl = require('react-intl').injectIntl; var FormattedMessage = require('react-intl').FormattedMessage; var React = require('react'); @@ -17,7 +18,14 @@ require('./explore.scss'); var Explore = injectIntl(React.createClass({ type: 'Explore', 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 pathname = window.location.pathname.toLowerCase(); @@ -28,7 +36,7 @@ var Explore = injectIntl(React.createClass({ var currentCategory = pathname.substring(slash + 1,pathname.length); var typeStart = pathname.indexOf('explore/'); 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/'; } @@ -50,14 +58,13 @@ var Explore = injectIntl(React.createClass({ this.getExploreMore(); }, getExploreMore: function () { - var qText = ''; - if (this.props.tab != 'all') { - qText = '&q=' + this.props.category; - } + var qText = '&q=' + this.props.acceptableTabs[this.props.category] || '*'; + api({ uri: '/search/' + this.props.itemType + '?limit=' + this.props.loadNumber + '&offset=' + this.state.offset + + '&language=' + this.props.intl.locale + qText }, function (err, body) { if (!err) { @@ -80,34 +87,28 @@ var Explore = injectIntl(React.createClass({ window.location = window.location.origin + '/explore/' + newType + '/' + this.props.tab; }, getBubble: function (type) { - var allBubble = -
  • - -
  • -
    ; - if (this.props.category === type) { - allBubble = -
  • - -
  • -
    ; - } - return allBubble; + var classes = classNames({ + active: (this.props.category === type) + }); + return ( + +
  • + +
  • +
    + ); }, getTab: function (type) { - var allTab = -
  • - -
  • -
    ; - if (this.props.itemType === type) { - allTab = -
  • - -
  • -
    ; - } - return allTab; + var classes = classNames({ + active: (this.props.itemType === type) + }); + return ( + +
  • + +
  • +
    + ); }, render: function () { return ( diff --git a/src/views/faq/l10n.json b/src/views/faq/l10n.json index 9f615d28b..ab60cb8a3 100644 --- a/src/views/faq/l10n.json +++ b/src/views/faq/l10n.json @@ -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.schoolsTitle":"Scratch 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 Educators Page. You can also join the ScratchEd 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 Educators Page. You can also join the ScratchEd 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.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 ScratchJr, 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?", @@ -133,7 +133,7 @@ "faq.requestTitle":"How do I request a Scratch Teacher Account?", "faq.requestBody":"You may request a Scratch Teacher Account from the Educators Page 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.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 ScratchEd community, 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 ScratchEd community, 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.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 Teacher Account FAQ" diff --git a/src/views/search/search.jsx b/src/views/search/search.jsx index ecd6cff29..58883db54 100644 --- a/src/views/search/search.jsx +++ b/src/views/search/search.jsx @@ -61,6 +61,7 @@ var Search = injectIntl(React.createClass({ uri: '/search/' + this.props.tab + '?limit=' + this.props.loadNumber + '&offset=' + this.state.offset + + '&language=' + this.props.intl.locale + termText }, function (err, body) { var loadedSoFar = this.state.loaded; diff --git a/src/views/splash/splash.jsx b/src/views/splash/splash.jsx index c4372f7e1..50f574a4b 100644 --- a/src/views/splash/splash.jsx +++ b/src/views/splash/splash.jsx @@ -4,7 +4,6 @@ var omit = require('lodash.omit'); var React = require('react'); var api = require('../../lib/api'); -var permissionsActions = require('../../redux/permissions.js'); var render = require('../../lib/render.jsx'); var sessionActions = require('../../redux/session.js'); 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 () { this.getFeaturedGlobal(); if (this.props.session.session.user) { @@ -382,7 +377,7 @@ var Splash = injectIntl(React.createClass({ ] : []} {this.props.permissions.educator ? [ - + ] : []}
    {this.props.session.status === sessionActions.Status.FETCHED ? ( diff --git a/src/views/teacherregistration/l10n.json b/src/views/teacherregistration/l10n.json index 558064a13..889e3301c 100644 --- a/src/views/teacherregistration/l10n.json +++ b/src/views/teacherregistration/l10n.json @@ -20,6 +20,7 @@ "teacherRegistration.phoneNumber": "Phone Number", "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.validationPhoneNumber": "Please enter a valid phone number", "teacherRegistration.validationPhoneConsent": "You must consent to verification of your Teacher Account", "teacherRegistration.orgStepTitle": "Organization", "teacherRegistration.orgStepDescription": "Your information will not be displayed publicly, and will be kept confidential and secure", @@ -38,7 +39,7 @@ "teacherRegistration.orgChoiceOther": " ", "teacherRegistration.notRequired": "Not Required", "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.addressLine2": "Address Line 2 (Optional)", "teacherRegistration.zipCode": "ZIP", @@ -52,5 +53,6 @@ "teacherRegistration.howUseScratch": "How do you plan to use Scratch at your organization?", "teacherRegistration.emailStepTitle": "Email Address", "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" } diff --git a/src/views/teacherregistration/teacherregistration.scss b/src/views/teacherregistration/teacherregistration.scss index 4527cb0d1..3a1d0455b 100644 --- a/src/views/teacherregistration/teacherregistration.scss +++ b/src/views/teacherregistration/teacherregistration.scss @@ -30,7 +30,7 @@ body { .address-step, .email-step { .help-block { - transform: translate(15.5rem, -4rem); + transform: translate(15.75rem, -4rem); } } @@ -40,7 +40,7 @@ body { } .help-block { - transform: translate(14rem, -4rem); + transform: translate(13rem, -2rem); } .radio { @@ -65,7 +65,7 @@ body { } .help-block { - margin-top: .7rem; + margin-top: .5rem; } .checkbox-row { @@ -80,6 +80,12 @@ body { transform: translate(16rem, -4rem); } + .checkbox-group { + .help-block { + transform: translate(16rem, -16rem); + } + } + .organization-type, .url-input { p { @@ -100,6 +106,14 @@ body { } } + .address-step { + .select { + .help-block { + transform: translate(0, .5rem); + } + } + } + .usescratch-step { .form { .form-group { @@ -128,7 +142,8 @@ body { } } - .last-step { + .last-step, + .error-step { &.slide { max-width: 38.75rem; } @@ -159,7 +174,8 @@ body { } } - .last-step { + .last-step, + .error-step { .card { margin: 0 auto; width: 18.75rem; diff --git a/src/views/teachers/faq/l10n.json b/src/views/teachers/faq/l10n.json index 0aeb0e329..a86fef323 100644 --- a/src/views/teachers/faq/l10n.json +++ b/src/views/teachers/faq/l10n.json @@ -9,7 +9,7 @@ "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.teacherEdTitle": "Are ScratchEd & Scratch Teacher accounts the same thing?", - "teacherfaq.teacherEdBody": "ScratchEd accounts are not linked to Scratch Teacher accounts.", + "teacherfaq.teacherEdBody": "ScratchEd accounts are not linked to Scratch Teacher accounts.", "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.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.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.studentDiscussBody": "Yes, you can engage in discussions with other teachers at ScratchEd, an online community for Scratch educators. Check out their forums to join conversations about a number of topics, 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 ScratchEd, an online community for Scratch educators. Check out their forums to join conversations about a number of topics, including but not limited to Teacher Accounts. ScratchEd is developed and supported by the Harvard Graduate School of Education.", "teacherfaq.commTitle": "Community", "teacherfaq.commHiddenTitle": "Can I create a hidden class?", diff --git a/src/views/teachers/landing/l10n.json b/src/views/teachers/landing/l10n.json new file mode 100644 index 000000000..8e8ac23b1 --- /dev/null +++ b/src/views/teachers/landing/l10n.json @@ -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 today’s 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": "Settings: schools, museums, libraries, community centers", + "teacherlanding.generalUsageGradeLevels": "Grade Levels: elementary, middle, and high school (and some colleges too!)", + "teacherlanding.generalUsageSubjectAreas": "Subject Areas: 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": "ScratchEd is an online community where Scratch educators share stories, 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": "Scratch Educator Meetups from each other, sharing their ideas and strategies for supporting computational creativity in all its forms.", + "teacherlanding.guidesTitle": "Guides & Tutorials", + "teacherlanding.helpPage": "On the Help Page, you can find workshop guides, Scratch Cards, videos, and other resources.", + "teacherlanding.tipsWindow" : "The Tips Window features step-by-step tutorials for getting started in Scratch.", + "teacherlanding.creativeComputing": "The Creative Computing Curriculum Guide 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 Teacher Account FAQ page.", + "teacherlanding.accountsButton": "Coming Soon" +} diff --git a/src/views/teachers/landing/landing.jsx b/src/views/teachers/landing/landing.jsx index de746480d..e61973412 100644 --- a/src/views/teachers/landing/landing.jsx +++ b/src/views/teachers/landing/landing.jsx @@ -1,49 +1,50 @@ var React = require('react'); 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 FlexRow = require('../../../components/flex-row/flex-row.jsx'); var SubNavigation = require('../../../components/subnavigation/subnavigation.jsx'); var TitleBanner = require('../../../components/title-banner/title-banner.jsx'); -var Button = require('../../../components/forms/button.jsx'); require('./landing.scss'); -var Landing = React.createClass({ +var Landing = injectIntl(React.createClass({ type: 'Landing', render: function () { return (
    -

    Scratch for Educators

    +

    - 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. +

    - +
    + +
  • - In Practice +
  • - Resources +
  • - Teacher Accounts +
  • @@ -53,96 +54,81 @@ var Landing = React.createClass({
    -

    Who Uses Scratch?

    -

    Educators are using Scratch in a wide variety of:

    +

    +

    -

    Settings: schools, museums, libraries, community centers

    -

    Grade Levels: elementary, middle, and high school (and some colleges too!)

    -

    Subject Areas: language arts, science, social studies,{' '} - math, computer science, foreign languages, and the arts

    +

    +

    +

    -
    - ingrid's story -
    - Ingrid Gustafson -

    Instructional Technology Specialist

    -
    -
    -
    + + ingrid's story +
    +

    Ingrid Gustafson

    +

    +
    +
    + dylan's story
    - Dylan Ryder -

    Educational Technologist

    +

    Dylan Ryder

    +

    -
    -
    + + plug in studio's story
    - Plug-In Studios -

    After-School Program

    +

    Plug-In Studios

    +

    -
    -
    + + ghana code club's story
    - Ghana Code Club -

    After-School Program

    +

    Ghana Code Club

    +

    -
    +
    -

    Educator Resources

    +

    -

    A Community for Educators

    +

    - ScratchEd is an{' '} - online community where Scratch educators{' '} - share stories, - exchange resources, ask questions, and{' '} - find people. ScratchEd is developed and supported by{' '} - the Harvard Graduate School of Education. +

    -

    In-Person Gatherings

    +

    - Scratch Educator Meetups{' '} - 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. +

    -

    Guides & Tutorials

    +

    resources icon

    - On the Help Page,{' '} - you can find workshop guides, Scratch Cards, videos, and other resources. +

    tips window icon

    - The{' '} - Tips Window{' '} - features step-by-step tutorials for getting started in Scratch. +

    creative computing icon

    - The Creative Computing{' '} - Curriculum Guide{' '} - provides plans, activities, and{' '} - strategies for introducing creative computing. +

    @@ -151,15 +137,11 @@ var Landing = React.createClass({
    -

    Teacher Accounts in Scratch

    +

    - 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{' '} - Teacher Account FAQ page. +

    - +
    teacher account
    @@ -167,6 +149,6 @@ var Landing = React.createClass({
    ); } -}); +})); render(, document.getElementById('app')); diff --git a/src/views/teachers/landing/landing.scss b/src/views/teachers/landing/landing.scss index 43610d88b..5f43f11e2 100644 --- a/src/views/teachers/landing/landing.scss +++ b/src/views/teachers/landing/landing.scss @@ -48,12 +48,21 @@ $story-width: $cols3; 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 { - margin-bottom: $gutter; - border: 2px solid $ui-border; - border-radius: 10px; - width: $cols4; + border: 0; + width: inherit; + height: inherit; } } @@ -150,6 +159,14 @@ $story-width: $cols3; padding-top: 10px; padding-left: 10px; + .name { + margin: 0; + line-height: initial; + color: $ui-blue; + font-size: initial; + font-weight: 500; + } + p { margin: 10px 0; font-size: .75rem; @@ -213,13 +230,22 @@ $story-width: $cols3; margin-bottom: 3.5rem; } - button { - background-color: $ui-white; + .coming-soon { + border: 2px solid $ui-white; + border-radius: 50px; + box-shadow: none; + background-color: transparent; padding: 16px 16px; width: $cols5 / 2; - color: $ui-blue; + text-align: center; + color: $ui-white; font-size: 16px; font-weight: 500; + box-sizing: border-box; + + &:hover { + box-shadow: none; + } } } diff --git a/webpack.config.js b/webpack.config.js index aa1ab7b6f..fbe1b679c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -116,7 +116,8 @@ module.exports = { 'process.env.NODE_ENV': '"' + (process.env.NODE_ENV || 'development') + '"', 'process.env.SENTRY_DSN': '"' + (process.env.SENTRY_DSN || '') + '"', '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.OccurenceOrderPlugin()