diff --git a/.babelrc b/.babelrc new file mode 100644 index 000000000..43777946d --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015", "react"], +} diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 9cadb556a..000000000 --- a/.eslintrc +++ /dev/null @@ -1,29 +0,0 @@ -{ - "parser": "babel-eslint", - "rules": { - "curly": [2, "multi-line"], - "eol-last": [2], - "indent": [2, 4], - "linebreak-style": [2, "unix"], - "max-len": [2, 120, 4, {"ignoreUrls": true}], - "no-trailing-spaces": [2, { "skipBlankLines": true }], - "no-unused-vars": [2, {"args": "after-used", "varsIgnorePattern": "^_"}], - "quotes": [2, "single"], - "semi": [2, "always"], - "space-before-function-paren": [2, "always"], - "strict": [2, "never"] - }, - "env": { - "browser": true, - "es6": true, - "node": true - }, - "globals": { - "formatMessage": true - }, - "plugins": [ - "react", - "json" - ], - "extends": "eslint:recommended" -} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..36ff570d9 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['scratch', 'scratch/node'] +}; diff --git a/.tx/config b/.tx/config index 2e63e88f3..d36892ba7 100644 --- a/.tx/config +++ b/.tx/config @@ -133,3 +133,9 @@ file_filter = localizations/conference-index/.json source_file = src/views/conference/2018/index/l10n.json source_lang = en type = KEYVALUEJSON + +[scratch-website.preview-faq-l10njson] +file_filter = localizations/preview-faq/.json +source_file = src/views/preview-faq/l10n.json +source_lang = en +type = KEYVALUEJSON \ No newline at end of file diff --git a/Makefile b/Makefile index 65a57a3ac..abe7f3a72 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ test: @make tap lint: - $(ESLINT) . --ext .js,.jsx,.json + $(ESLINT) . --ext .js,.jsx $(SASSLINT) ./src/*.scss $(SASSLINT) ./src/**/*.scss diff --git a/bin/configure-fastly.js b/bin/configure-fastly.js index edb7196ad..a935b906e 100644 --- a/bin/configure-fastly.js +++ b/bin/configure-fastly.js @@ -3,7 +3,7 @@ var defaults = require('lodash.defaults'); var fastlyConfig = require('./lib/fastly-config-methods'); const languages = require('../languages.json'); -var route_json = require('../src/routes.json'); +var routeJson = require('../src/routes.json'); const FASTLY_SERVICE_ID = process.env.FASTLY_SERVICE_ID || ''; const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME || ''; @@ -15,10 +15,10 @@ var extraAppRoutes = [ // TODO: Should this be added for every route? '/\\?', // View html - '/[^\/]*\.html$' + '/[^/]*.html$' ]; -var routes = route_json.map(function (route) { +var routes = routeJson.map(function (route) { return defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route); }); @@ -28,9 +28,9 @@ async.auto({ if (err) return cb(err); // Validate latest version before continuing if (response.active || response.locked) { - fastly.cloneVersion(response.number, function (err, response) { - if (err) return cb('Failed to clone latest version: ' + err); - cb(null, response.number); + fastly.cloneVersion(response.number, function (e, resp) { + if (e) return cb('Failed to clone latest version: ' + e); + cb(null, resp.number); }); } else { cb(null, response.number); @@ -46,11 +46,11 @@ async.auto({ var recvCondition = '' + 'if (' + notPassStatement + ') {\n' + ' set req.backend = F_s3;\n' + - ' set req.http.host = \"' + S3_BUCKET_NAME + '\";\n' + + ' set req.http.host = "' + S3_BUCKET_NAME + '";\n' + '} else {\n' + ' if (!req.http.Fastly-FF) {\n' + ' if (req.http.X-Forwarded-For) {\n' + - ' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For \", \" client.ip;\n' + + ' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For ", " client.ip;\n' + ' } else {\n' + ' set req.http.Fastly-Temp-XFF = client.ip;\n' + ' }\n' + @@ -171,20 +171,19 @@ async.auto({ if (err) return cb(err); cb(null, headers); }); - }]}, - function (err, results) { - if (err) throw new Error(err); - if (process.env.FASTLY_ACTIVATE_CHANGES) { - fastly.activateVersion(results.version, function (err, response) { - if (err) throw new Error(err); - process.stdout.write('Successfully configured and activated version ' + response.number + '\n'); - if (process.env.FASTLY_PURGE_ALL) { - fastly.purgeAll(FASTLY_SERVICE_ID, function (err) { - if (err) throw new Error(err); - process.stdout.write('Purged all.\n'); - }); - } - }); - } + }] +}, function (err, results) { + if (err) throw new Error(err); + if (process.env.FASTLY_ACTIVATE_CHANGES) { + fastly.activateVersion(results.version, function (e, resp) { + if (err) throw new Error(e); + process.stdout.write('Successfully configured and activated version ' + resp.number + '\n'); + if (process.env.FASTLY_PURGE_ALL) { + fastly.purgeAll(FASTLY_SERVICE_ID, function (error) { + if (error) throw new Error(error); + process.stdout.write('Purged all.\n'); + }); + } + }); } -); +}); diff --git a/bin/lib/fastly-config-methods.js b/bin/lib/fastly-config-methods.js index 750b1893b..d6a8c973d 100644 --- a/bin/lib/fastly-config-methods.js +++ b/bin/lib/fastly-config-methods.js @@ -24,9 +24,9 @@ var FastlyConfigMethods = { */ getViewPaths: function (routes) { return routes.reduce(function (paths, route) { - var path = route.routeAlias || route.pattern; - if (paths.indexOf(path) === -1) { - paths.push(path); + var p = route.routeAlias || route.pattern; + if (paths.indexOf(p) === -1) { + paths.push(p); } return paths; }, []); @@ -39,7 +39,7 @@ var FastlyConfigMethods = { * 2. /path/:arg([regex]) – :arg is removed, leaving just /path/([regex]) */ expressPatternToRegex: function (pattern) { - pattern = pattern.replace(/(:\w+)(\([^\)]+\))/gi, '$2'); + pattern = pattern.replace(/(:\w+)(\([^)]+\))/gi, '$2'); return pattern.replace(/(:\w+)/gi, '.+?'); }, @@ -84,7 +84,7 @@ var FastlyConfigMethods = { return 'redirects/' + route.pattern; }, - /** + /* * Returns custom vcl configuration as a string that sets the varnish * Time to Live (TTL) for responses that come from s3. * diff --git a/bin/lib/fastly-extended.js b/bin/lib/fastly-extended.js index 3ec9fd7e3..0e4467221 100644 --- a/bin/lib/fastly-extended.js +++ b/bin/lib/fastly-extended.js @@ -19,8 +19,8 @@ module.exports = function (apiKey, serviceId) { * * @return {string} */ - fastly.getFastlyAPIPrefix = function (serviceId, version) { - return '/service/' + encodeURIComponent(serviceId) + '/version/' + version; + fastly.getFastlyAPIPrefix = function (servId, version) { + return '/service/' + encodeURIComponent(servId) + '/version/' + version; }; /* @@ -32,15 +32,15 @@ module.exports = function (apiKey, serviceId) { if (!this.serviceId) { return cb('Failed to get latest version. No serviceId configured'); } - var url = '/service/'+ encodeURIComponent(this.serviceId) +'/version'; + var url = '/service/' + encodeURIComponent(this.serviceId) + '/version'; this.request('GET', url, function (err, versions) { if (err) { return cb('Failed to fetch versions: ' + err); } - var latestVersion = versions.reduce(function (latestVersion, version) { - if (!latestVersion) return version; - if (version.number > latestVersion.number) return version; - return latestVersion; + var latestVersion = versions.reduce(function (lateVersion, version) { + if (!lateVersion) return version; + if (version.number > lateVersion.number) return version; + return lateVersion; }); return cb(null, latestVersion); }); @@ -63,16 +63,16 @@ module.exports = function (apiKey, serviceId) { var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/condition'; return this.request('PUT', putUrl, condition, function (err, response) { if (err && err.statusCode === 404) { - this.request('POST', postUrl, condition, function (err, response) { - if (err) { - return cb('Failed while inserting condition \"' + condition.statement + '\": ' + err); + this.request('POST', postUrl, condition, function (e, resp) { + if (e) { + return cb('Failed while inserting condition "' + condition.statement + '": ' + e); } - return cb(null, response); + return cb(null, resp); }); return; } if (err) { - return cb('Failed to update condition \"' + condition.statement + '\": ' + err); + return cb('Failed to update condition "' + condition.statement + '": ' + err); } return cb(null, response); }.bind(this)); @@ -95,11 +95,11 @@ module.exports = function (apiKey, serviceId) { var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/header'; return this.request('PUT', putUrl, header, function (err, response) { if (err && err.statusCode === 404) { - this.request('POST', postUrl, header, function (err, response) { - if (err) { - return cb('Failed to insert header: ' + err); + this.request('POST', postUrl, header, function (e, resp) { + if (e) { + return cb('Failed to insert header: ' + e); } - return cb(null, response); + return cb(null, resp); }); return; } @@ -127,11 +127,11 @@ module.exports = function (apiKey, serviceId) { var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/response_object'; return this.request('PUT', putUrl, responseObj, function (err, response) { if (err && err.statusCode === 404) { - this.request('POST', postUrl, responseObj, function (err, response) { - if (err) { - return cb('Failed to insert response object: ' + err); + this.request('POST', postUrl, responseObj, function (e, resp) { + if (e) { + return cb('Failed to insert response object: ' + e); } - return cb(null, response); + return cb(null, resp); }); return; } @@ -166,7 +166,7 @@ module.exports = function (apiKey, serviceId) { this.request('PUT', url, cb); }; - /** + /* * Upsert a custom vcl file. Attempts a PUT, and falls back * to POST if not there already. * @@ -186,16 +186,16 @@ module.exports = function (apiKey, serviceId) { return this.request('PUT', url, content, function (err, response) { if (err && err.statusCode === 404) { content.name = name; - this.request('POST', postUrl, content, function (err, response) { - if (err) { - return cb('Failed while adding custom vcl \"' + name + '\": ' + err); + this.request('POST', postUrl, content, function (e, resp) { + if (e) { + return cb('Failed while adding custom vcl "' + name + '": ' + e); } - return cb(null, response); + return cb(null, resp); }); return; } if (err) { - return cb('Failed to update custom vcl \"' + name + '\": ' + err); + return cb('Failed to update custom vcl "' + name + '": ' + err); } return cb(null, response); }.bind(this)); diff --git a/dev-server/handler.js b/dev-server/handler.js index 43c12857d..339ceff5c 100644 --- a/dev-server/handler.js +++ b/dev-server/handler.js @@ -1,10 +1,12 @@ -/** +/* * Constructor */ -function Handler (route) { +const Handler = function (route) { // Handle redirects if (route.redirect) { - return (req, res) => { res.redirect(route.redirect); }; + return (req, res) => { + res.redirect(route.redirect); + }; } var url = '/' + route.name + '.html'; @@ -12,9 +14,9 @@ function Handler (route) { req.url = url; next(); }; -} +}; -/** +/* * Export a new instance */ module.exports = function (route) { diff --git a/languages.json b/languages.json index d357c7588..6591b2295 100644 --- a/languages.json +++ b/languages.json @@ -1,4 +1,5 @@ { + "ab": "Аҧсшәа", "ar": "العربية", "an": "Aragonés", "ast": "Asturianu", diff --git a/package.json b/package.json index 68fec9862..9e9ec7fa4 100644 --- a/package.json +++ b/package.json @@ -33,24 +33,27 @@ "devDependencies": { "async": "1.5.2", "autoprefixer": "6.3.6", - "babel-core": "6.10.4", - "babel-eslint": "5.0.4", - "babel-loader": "6.2.4", - "babel-preset-es2015": "6.9.0", - "babel-preset-react": "6.11.1", + "babel-cli": "6.26.0", + "babel-core": "6.23.1", + "babel-eslint": "8.0.2", + "babel-loader": "7.1.0", + "babel-preset-es2015": "6.22.0", + "babel-preset-react": "6.22.0", "cheerio": "1.0.0-rc.2", - "classnames": "2.1.3", + "classnames": "2.2.5", "cookie": "0.2.2", "copy-webpack-plugin": "0.2.0", + "create-react-class": "15.6.2", "css-loader": "0.23.1", - "eslint": "1.3.1", + "eslint": "4.7.1", + "eslint-config-scratch": "5.0.0", "eslint-plugin-json": "1.2.0", - "eslint-plugin-react": "3.3.1", + "eslint-plugin-react": "7.4.0", "exenv": "1.2.0", "fastly": "1.2.1", "file-loader": "0.8.4", - "formsy-react": "0.18.0", - "formsy-react-components": "0.7.1", + "formsy-react": "0.19.5", + "formsy-react-components": "0.11.1", "git-bundle-sha": "0.0.2", "glob": "5.0.15", "google-libphonenumber": "1.0.21", @@ -59,6 +62,7 @@ "json-loader": "0.5.2", "json2po-stream": "1.0.3", "keymirror": "0.1.1", + "lodash.bindall": "4.4.0", "lodash.clone": "3.0.3", "lodash.defaultsdeep": "3.10.0", "lodash.isarray": "3.0.4", @@ -70,21 +74,22 @@ "node-sass": "4.6.1", "pako": "0.2.8", "po2icu": "0.0.2", - "postcss-loader": "0.8.2", + "postcss-loader": "2.0.10", + "prop-types": "15.6.0", "raven-js": "3.0.4", - "react": "15.1.0", - "react-dom": "15.0.1", + "react": "15.5.4", + "react-dom": "15.5.4", "react-intl": "2.1.2", - "react-modal": "1.5.2", - "react-onclickoutside": "4.1.1", + "react-modal": "3.1.11", + "react-onclickoutside": "6.7.1", "react-redux": "4.4.5", - "react-responsive": "1.1.4", + "react-responsive": "3.0.0", "react-slick": "0.12.2", - "react-telephone-input": "3.4.5", + "react-telephone-input": "3.8.6", "redux": "3.5.2", "redux-thunk": "2.0.1", "sass-lint": "1.5.1", - "sass-loader": "2.0.1", + "sass-loader": "6.0.6", "scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master", "slick-carousel": "1.5.8", "source-map-support": "0.3.2", @@ -92,8 +97,8 @@ "tap": "7.1.2", "url-loader": "0.5.6", "watch": "0.16.0", - "webpack": "1.12.14", - "webpack-dev-middleware": "1.2.0", + "webpack": "2.7.0", + "webpack-dev-middleware": "2.0.4", "xhr": "2.2.0" }, "nyc": { diff --git a/src/.eslintrc.js b/src/.eslintrc.js new file mode 100644 index 000000000..0abd74d67 --- /dev/null +++ b/src/.eslintrc.js @@ -0,0 +1,10 @@ +module.exports = { + root: true, + extends: ['scratch', 'scratch/es6', 'scratch/react'], + env: { + browser: true + }, + globals: { + process: true + } +}; diff --git a/src/components/accordion/accordion.jsx b/src/components/accordion/accordion.jsx index e6e921a7b..431705395 100644 --- a/src/components/accordion/accordion.jsx +++ b/src/components/accordion/accordion.jsx @@ -1,33 +1,35 @@ -var classNames = require('classnames'); -var React = require('react'); +const bindAll = require('lodash.bindall'); +const classNames = require('classnames'); +const PropTypes = require('prop-types'); +const React = require('react'); require('./accordion.scss'); -var Accordion = React.createClass({ - type: 'Accordion', - getDefaultProps: function () { - return { - titleAs: 'div', - contentAs: 'div' - }; - }, - getInitialState: function () { - return { +class Accordion extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleClick' + ]); + this.state = { isOpen: false }; - }, - toggleContent: function () { + } + handleClick (e) { + e.preventDefault(); this.setState({isOpen: !this.state.isOpen}); - }, - render: function () { - var classes = classNames({ - 'content': true, - 'open': this.state.isOpen + } + render () { + const classes = classNames({ + content: true, + open: this.state.isOpen }); return (
- + {this.props.title} @@ -36,6 +38,16 @@ var Accordion = React.createClass({
); } -}); +} + +Accordion.propTypes = { + content: PropTypes.node, + title: PropTypes.string +}; + +Accordion.defaultProps = { + contentAs: 'div', + titleAs: 'div' +}; module.exports = Accordion; diff --git a/src/components/adminpanel/adminpanel.jsx b/src/components/adminpanel/adminpanel.jsx index 222258176..4d9ac9e79 100644 --- a/src/components/adminpanel/adminpanel.jsx +++ b/src/components/adminpanel/adminpanel.jsx @@ -1,37 +1,39 @@ -var React = require('react'); -var connect = require('react-redux').connect; +const bindAll = require('lodash.bindall'); +const connect = require('react-redux').connect; +const PropTypes = require('prop-types'); +const React = require('react'); -var Button = require('../forms/button.jsx'); +const Button = require('../forms/button.jsx'); require('./adminpanel.scss'); -var AdminPanel = React.createClass({ - type: 'AdminPanel', - getInitialState: function () { - return { +class AdminPanel extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleToggleVisibility' + ]); + this.state = { showPanel: false }; - }, - handleToggleVisibility: function (e) { + } + handleToggleVisibility (e) { e.preventDefault(); this.setState({showPanel: !this.state.showPanel}); - }, - render: function () { - // make sure user is present before checking if they're an admin. Don't show anything if user not an admin. - var showAdmin = false; - if (this.props.session.session.user) { - showAdmin = this.props.session.session.permissions.admin; - } - - if (!showAdmin) return false; + } + render () { + if (!this.props.isAdmin) return false; if (this.state.showPanel) { return ( -
+
- + onClick={this.handleToggleVisibility} + > x
@@ -44,8 +46,15 @@ var AdminPanel = React.createClass({
  • -
    - + +
    For anonymous users:
    -
  • +
); - } else { - return ( -
- - - > - -
- ); } + return ( +
+ + > + +
+ ); } -}); +} -var mapStateToProps = function (state) { - return { - session: state.session - }; +AdminPanel.propTypes = { + children: PropTypes.node, + isAdmin: PropTypes.bool }; -var ConnectedAdminPanel = connect(mapStateToProps)(AdminPanel); +const mapStateToProps = state => ({ + isAdmin: state.permissions.admin +}); + +const ConnectedAdminPanel = connect(mapStateToProps)(AdminPanel); module.exports = ConnectedAdminPanel; diff --git a/src/components/avatar/avatar.jsx b/src/components/avatar/avatar.jsx index 5830e5ffb..566b97199 100644 --- a/src/components/avatar/avatar.jsx +++ b/src/components/avatar/avatar.jsx @@ -1,23 +1,22 @@ -var React = require('react'); -var classNames = require('classnames'); +const classNames = require('classnames'); +const omit = require('lodash.omit'); +const PropTypes = require('prop-types'); +const React = require('react'); -var Avatar = React.createClass({ - type: 'Avatar', - propTypes: { - src: React.PropTypes.string - }, - getDefaultProps: function () { - return { - src: '//cdn2.scratch.mit.edu/get_image/user/2584924_24x24.png?v=1438702210.96' - }; - }, - render: function () { - var classes = classNames( - 'avatar', - this.props.className - ); - return ; - } -}); +const Avatar = props => ( + +); + +Avatar.propTypes = { + className: PropTypes.string, + src: PropTypes.string +}; + +Avatar.defaultProps = { + src: '//cdn2.scratch.mit.edu/get_image/user/2584924_24x24.png?v=1438702210.96' +}; module.exports = Avatar; diff --git a/src/components/box/box.jsx b/src/components/box/box.jsx index 543bc15a2..832e27ea4 100644 --- a/src/components/box/box.jsx +++ b/src/components/box/box.jsx @@ -1,40 +1,38 @@ -var classNames = require('classnames'); -var React = require('react'); +const classNames = require('classnames'); +const PropTypes = require('prop-types'); +const React = require('react'); require('./box.scss'); -var Box = React.createClass({ - type: 'Box', - propTypes: { - title: React.PropTypes.string.isRequired, - subtitle: React.PropTypes.string, - moreTitle: React.PropTypes.string, - moreHref: React.PropTypes.string, - moreProps: React.PropTypes.object - }, - render: function () { - var classes = classNames( - 'box', - this.props.className - ); - return ( -
-
-

{this.props.title}

-
{this.props.subtitle}
-

- - {this.props.moreTitle} - -

-
+const Box = props => ( +
+
+

{props.title}

+
{props.subtitle}
+

+ + {props.moreTitle} + +

+
-
- {this.props.children} -
-
- ); - } -}); +
+ {props.children} +
+
+); + +Box.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + moreHref: PropTypes.string, + moreProps: PropTypes.object, // eslint-disable-line react/forbid-prop-types + moreTitle: PropTypes.string, + subtitle: PropTypes.string, + title: PropTypes.string.isRequired +}; module.exports = Box; diff --git a/src/components/card/card.jsx b/src/components/card/card.jsx index 5ac22fae1..37f6dc13c 100644 --- a/src/components/card/card.jsx +++ b/src/components/card/card.jsx @@ -1,17 +1,18 @@ -var classNames = require('classnames'); -var React = require('react'); +const classNames = require('classnames'); +const PropTypes = require('prop-types'); +const React = require('react'); require('./card.scss'); -var Card = React.createClass({ - displayName: 'Card', - render: function () { - return ( -
- {this.props.children} -
- ); - } -}); +const Card = props => ( +
+ {props.children} +
+); + +Card.propTypes = { + children: PropTypes.node, + className: PropTypes.string +}; module.exports = Card; diff --git a/src/components/carousel/carousel.jsx b/src/components/carousel/carousel.jsx index 46e01fa1a..5438d414f 100644 --- a/src/components/carousel/carousel.jsx +++ b/src/components/carousel/carousel.jsx @@ -1,95 +1,115 @@ -var classNames = require('classnames'); -var defaults = require('lodash.defaults'); -var React = require('react'); -var Slider = require('react-slick'); +const classNames = require('classnames'); +const defaults = require('lodash.defaults'); +const PropTypes = require('prop-types'); +const React = require('react'); +const Slider = require('react-slick'); -var Thumbnail = require('../thumbnail/thumbnail.jsx'); +const Thumbnail = require('../thumbnail/thumbnail.jsx'); -var frameless = require('../../lib/frameless.js'); +const frameless = require('../../lib/frameless.js'); require('slick-carousel/slick/slick.scss'); require('slick-carousel/slick/slick-theme.scss'); require('./carousel.scss'); -/** - * Displays content in horizontal scrolling box. Example usage: splash page rows. - */ -var Carousel = React.createClass({ - type: 'Carousel', - propTypes: { - items: React.PropTypes.array - }, - getDefaultProps: function () { - return { - items: require('./carousel.json'), - showRemixes: false, - showLoves: false, - type: 'project' - }; - }, - render: function () { - var settings = this.props.settings || {}; - defaults(settings, { - centerMode: false, - dots: false, - infinite: false, - lazyLoad: true, - slidesToShow: 5, - slidesToScroll: 5, - variableWidth: true, - responsive: [ - {breakpoint: frameless.mobile, settings: { +const Carousel = props => { + defaults(props.settings, { + centerMode: false, + dots: false, + infinite: false, + lazyLoad: true, + slidesToShow: 5, + slidesToScroll: 5, + variableWidth: true, + responsive: [ + { + breakpoint: frameless.mobile, + settings: { arrows: true, slidesToScroll: 1, slidesToShow: 1, centerMode: true - }}, - {breakpoint: frameless.tablet, settings: { + } + }, + { + breakpoint: frameless.tablet, + settings: { slidesToScroll: 2, slidesToShow: 2 - }}, - {breakpoint: frameless.desktop, settings: { + } + }, + { + breakpoint: frameless.desktop, + settings: { slidesToScroll: 4, slidesToShow: 4 - }} - ] - }); - var arrows = this.props.items.length > settings.slidesToShow; - var classes = classNames( - 'carousel', - this.props.className - ); - return ( - - {this.props.items.map(function (item) { - var href = ''; - switch (this.props.type) { - case 'gallery': - href = '/studios/' + item.id + '/'; - break; - case 'project': - href = '/projects/' + item.id + '/'; - break; - default: - href = '/' + item.type + '/' + item.id + '/'; - } + } + } + ] + }); + const arrows = props.items.length > props.settings.slidesToShow; + return ( + + {props.items.map(item => { + let href = ''; + switch (props.type) { + case 'gallery': + href = `/studios/${item.id}/`; + break; + case 'project': + href = `/projects/${item.id}/`; + break; + default: + href = `/${item.type}/${item.id}/`; + } - return ( - - ); - }.bind(this))} - - ); - } -}); + return ( + + ); + })} + + ); +}; + +Carousel.propTypes = { + className: PropTypes.string, + items: PropTypes.arrayOf(PropTypes.any), + settings: PropTypes.shape({ + centerMode: PropTypes.bool, + dots: PropTypes.bool, + infinite: PropTypes.bool, + lazyLoad: PropTypes.bool, + slidesToShow: PropTypes.number, + slidesToScroll: PropTypes.number, + variableWidth: PropTypes.bool, + responsive: PropTypes.array + }), + showLoves: PropTypes.bool, + showRemixes: PropTypes.bool, + type: PropTypes.string +}; + +Carousel.defaultProps = { + items: require('./carousel.json'), + settings: {}, + showRemixes: false, + showLoves: false, + type: 'project' +}; module.exports = Carousel; diff --git a/src/components/carousel/legacy-carousel.jsx b/src/components/carousel/legacy-carousel.jsx index 36efb5cbf..d5ab1dbf8 100644 --- a/src/components/carousel/legacy-carousel.jsx +++ b/src/components/carousel/legacy-carousel.jsx @@ -1,97 +1,118 @@ // This component handles json returned via proxy from a django server, // or directly from a django server, and the model structure that system // has. -var classNames = require('classnames'); -var defaults = require('lodash.defaults'); -var React = require('react'); -var Slider = require('react-slick'); +const classNames = require('classnames'); +const defaults = require('lodash.defaults'); +const PropTypes = require('prop-types'); +const React = require('react'); +const Slider = require('react-slick'); -var Thumbnail = require('../thumbnail/thumbnail.jsx'); +const Thumbnail = require('../thumbnail/thumbnail.jsx'); -var frameless = require('../../lib/frameless.js'); +const frameless = require('../../lib/frameless.js'); require('slick-carousel/slick/slick.scss'); require('slick-carousel/slick/slick-theme.scss'); require('./carousel.scss'); -/** - * Displays content in horizontal scrolling box. Example usage: splash page rows. - */ -var LegacyCarousel = React.createClass({ - type: 'LegacyCarousel', - propTypes: { - items: React.PropTypes.array - }, - getDefaultProps: function () { - return { - items: require('./carousel.json'), - showRemixes: false, - showLoves: false - }; - }, - render: function () { - var settings = this.props.settings || {}; - defaults(settings, { - centerMode: false, - dots: false, - infinite: false, - lazyLoad: true, - slidesToShow: 5, - slidesToScroll: 5, - variableWidth: true, - responsive: [ - {breakpoint: frameless.mobile, settings: { +const Carousel = props => { + defaults(props.settings, { + centerMode: false, + dots: false, + infinite: false, + lazyLoad: true, + slidesToShow: 5, + slidesToScroll: 5, + variableWidth: true, + responsive: [ + { + breakpoint: frameless.mobile, + settings: { arrows: true, slidesToScroll: 1, slidesToShow: 1, centerMode: true - }}, - {breakpoint: frameless.tablet, settings: { + } + }, + { + breakpoint: frameless.tablet, + settings: { slidesToScroll: 2, slidesToShow: 2 - }}, - {breakpoint: frameless.desktop, settings: { + } + }, + { + breakpoint: frameless.desktop, + settings: { slidesToScroll: 4, slidesToShow: 4 - }} - ] - }); - var arrows = this.props.items.length > settings.slidesToShow; - var classes = classNames( - 'carousel', - this.props.className - ); - return ( - - {this.props.items.map(function (item) { - var href = ''; - switch (item.type) { - case 'gallery': - href = '/studios/' + item.id + '/'; - break; - case 'project': - href = '/projects/' + item.id + '/'; - break; - default: - href = '/' + item.type + '/' + item.id + '/'; - } + } + } + ] + }); + const arrows = props.items.length > props.settings.slidesToShow; + return ( + + {props.items.map(item => { + let href = ''; + switch (item.type) { + case 'gallery': + href = `/studios/${item.id}/`; + break; + case 'project': + href = `/projects/${item.id}/`; + break; + default: + href = `/${item.type}/${item.id}/`; + } - return ( - - ); - }.bind(this))} - - ); - } -}); + return ( + + ); + })} + + ); +}; -module.exports = LegacyCarousel; +Carousel.propTypes = { + className: PropTypes.string, + items: PropTypes.arrayOf(PropTypes.any), + settings: PropTypes.shape({ + centerMode: PropTypes.bool, + dots: PropTypes.bool, + infinite: PropTypes.bool, + lazyLoad: PropTypes.bool, + slidesToShow: PropTypes.number, + slidesToScroll: PropTypes.number, + variableWidth: PropTypes.bool, + responsive: PropTypes.array + }), + showLoves: PropTypes.bool, + showRemixes: PropTypes.bool, + type: PropTypes.string +}; + +Carousel.defaultProps = { + items: require('./carousel.json'), + settings: {}, + showRemixes: false, + showLoves: false, + type: 'project' +}; + +module.exports = Carousel; diff --git a/src/components/comment/comment.jsx b/src/components/comment/comment.jsx index 4a324799a..5edaeb51d 100644 --- a/src/components/comment/comment.jsx +++ b/src/components/comment/comment.jsx @@ -1,34 +1,33 @@ -var classNames = require('classnames'); -var FormattedRelative = require('react-intl').FormattedRelative; -var React = require('react'); +const classNames = require('classnames'); +const FormattedRelative = require('react-intl').FormattedRelative; +const PropTypes = require('prop-types'); +const React = require('react'); -var EmojiText = require('../emoji-text/emoji-text.jsx'); +const EmojiText = require('../emoji-text/emoji-text.jsx'); require('./comment.scss'); -var CommentText = React.createClass({ - type: 'CommentText', - propTypes: { - comment: React.PropTypes.string.isRequired, - datetimeCreated: React.PropTypes.string, - className: React.PropTypes.string - }, - render: function () { - var classes = classNames( - 'comment-text', - this.props.class - ); - return ( -
- - {typeof this.props.datetimeCreated !== 'undefined' ? [ -

- -

- ] : []} -
- ); - } -}); +const CommentText = props => ( +
+ + {typeof props.datetimeCreated === 'undefined' ? [] : [ +

+ +

+ ]} +
+); + +CommentText.propTypes = { + className: PropTypes.string, + comment: PropTypes.string.isRequired, + datetimeCreated: PropTypes.string +}; module.exports = CommentText; diff --git a/src/components/deck/deck.jsx b/src/components/deck/deck.jsx index 5d929c68a..7d150a7e8 100644 --- a/src/components/deck/deck.jsx +++ b/src/components/deck/deck.jsx @@ -1,22 +1,29 @@ -var classNames = require('classnames'); -var React = require('react'); +const classNames = require('classnames'); +const PropTypes = require('prop-types'); +const React = require('react'); require('./deck.scss'); -var Deck = React.createClass({ - displayName: 'Deck', - render: function () { - return ( -
-
- - - - {this.props.children} -
-
- ); - } -}); +const Deck = props => ( +
+
+ + + + {props.children} +
+
+); + +Deck.propTypes = { + children: PropTypes.node, + className: PropTypes.string +}; module.exports = Deck; diff --git a/src/components/dropdown-banner/banner.jsx b/src/components/dropdown-banner/banner.jsx index e9808228e..fa912f49e 100644 --- a/src/components/dropdown-banner/banner.jsx +++ b/src/components/dropdown-banner/banner.jsx @@ -1,33 +1,29 @@ -var classNames = require('classnames'); -var React = require('react'); +const classNames = require('classnames'); +const PropTypes = require('prop-types'); +const React = require('react'); require('./banner.scss'); -/** - * Container for messages displayed below the nav bar that can be dismissed - * (See: email not confirmed banner) - */ -var Banner = React.createClass({ - type: 'Banner', - propTypes: { - onRequestDismiss: React.PropTypes.func - }, - render: function () { - var classes = classNames( - 'banner', - this.props.className - ); - return ( -
-
- {this.props.children} - {this.props.onRequestDismiss ? [ - x - ] : []} -
-
- ); - } -}); +const Banner = props => ( +
+
+ {props.children} + {props.onRequestDismiss ? [ + x + ] : []} +
+
+); + +Banner.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + onRequestDismiss: PropTypes.func +}; module.exports = Banner; diff --git a/src/components/dropdown/dropdown.jsx b/src/components/dropdown/dropdown.jsx index 83990264b..095194856 100644 --- a/src/components/dropdown/dropdown.jsx +++ b/src/components/dropdown/dropdown.jsx @@ -1,40 +1,46 @@ -var React = require('react'); -var classNames = require('classnames'); +const bindAll = require('lodash.bindall'); +const classNames = require('classnames'); +const onClickOutside = require('react-onclickoutside').default; +const PropTypes = require('prop-types'); +const React = require('react'); require('./dropdown.scss'); -var Dropdown = React.createClass({ - type: 'Dropdown', - mixins: [ - require('react-onclickoutside') - ], - propTypes: { - onRequestClose: React.PropTypes.func, - isOpen: React.PropTypes.bool - }, - getDefaultProps: function () { - return { - as: 'div', - isOpen: false - }; - }, - handleClickOutside: function () { +class Dropdown extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleClickOutside' + ]); + } + handleClickOutside () { if (this.props.isOpen) { this.props.onRequestClose(); } - }, - render: function () { - var classes = classNames( - 'dropdown', - this.props.className, - {open: this.props.isOpen} - ); + } + render () { return ( - + {this.props.children} ); } -}); +} -module.exports = Dropdown; +Dropdown.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + isOpen: PropTypes.bool, + onRequestClose: PropTypes.func.isRequired +}; + +Dropdown.defaultProps = { + as: 'div', + isOpen: false +}; + +module.exports = onClickOutside(Dropdown); diff --git a/src/components/dropdown/dropdown.scss b/src/components/dropdown/dropdown.scss index 66af291ef..6aea2f067 100644 --- a/src/components/dropdown/dropdown.scss +++ b/src/components/dropdown/dropdown.scss @@ -24,8 +24,12 @@ } a { - background-color: transparent; - color: $type-white; + &:link, + &:visited, + &:active { + background-color: transparent; + color: $type-white; + } } input { diff --git a/src/components/emoji-text/emoji-text.jsx b/src/components/emoji-text/emoji-text.jsx index c6f382260..ee4c00a52 100644 --- a/src/components/emoji-text/emoji-text.jsx +++ b/src/components/emoji-text/emoji-text.jsx @@ -1,33 +1,25 @@ -var classNames = require('classnames'); -var React = require('react'); +const classNames = require('classnames'); +const PropTypes = require('prop-types'); +const React = require('react'); require('./emoji-text.scss'); -var EmojiText = React.createClass({ - type: 'EmojiText', - propTyes: { - text: React.PropTypes.string.isRequired, - className: React.PropTypes.string - }, - getDefaultProps: function () { - return { - as: 'p' - }; - }, - render: function () { - var classes = classNames( - 'emoji-text', - this.props.className - ); - return ( - - ); - } -}); +const EmojiText = props => ( + +); + +EmojiText.propTypes = { + className: PropTypes.string, + text: PropTypes.string.isRequired +}; + +EmojiText.defaultProps = { + as: 'p' +}; module.exports = EmojiText; diff --git a/src/components/flex-row/flex-row.jsx b/src/components/flex-row/flex-row.jsx index 4a2deb36e..4dc8582c6 100644 --- a/src/components/flex-row/flex-row.jsx +++ b/src/components/flex-row/flex-row.jsx @@ -1,26 +1,22 @@ -var classNames = require('classnames'); -var React = require('react'); +const classNames = require('classnames'); +const PropTypes = require('prop-types'); +const React = require('react'); require('./flex-row.scss'); -var FlexRow = React.createClass({ - type: 'FlexRow', - getDefaultProps: function () { - return { - as: 'div' - }; - }, - render: function () { - var classes = classNames( - 'flex-row', - this.props.className - ); - return ( - - {this.props.children} - - ); - } -}); +const FlexRow = props => ( + + {props.children} + +); + +FlexRow.propTypes = { + children: PropTypes.node, + className: PropTypes.string +}; + +FlexRow.defaultProps = { + as: 'div' +}; module.exports = FlexRow; diff --git a/src/components/footer/conference/2016/footer.jsx b/src/components/footer/conference/2016/footer.jsx index b342e59fd..76207f6af 100644 --- a/src/components/footer/conference/2016/footer.jsx +++ b/src/components/footer/conference/2016/footer.jsx @@ -1,125 +1,152 @@ -var React = require('react'); +const React = require('react'); -var FlexRow = require('../../../flex-row/flex-row.jsx'); -var FooterBox = require('../../container/footer.jsx'); +const FlexRow = require('../../../flex-row/flex-row.jsx'); +const FooterBox = require('../../container/footer.jsx'); require('../footer.scss'); -var ConferenceFooter = React.createClass({ - type: 'ConferenceFooter', - render: function () { - return ( - -
-

Sponsors

+const ConferenceFooter = () => ( + +
+

Sponsors

+ +
  • + + MIT Office of Digital Learning + +
  • +
  • + + Intel + +
  • +
  • + + LEGO Foundation + +
  • +
  • + + Google + +
  • +
  • + + Siegel Family Endowment + +
  • +
  • + + No Starch Press + +
  • +
  • + + Scratch Foundation + +
  • +
    +
    + +
    +

    Scratch Family

    + + +
  • + Scratch +
  • +
  • + ScratchJr +
  • +
    + +
  • + Scratch Foundation +
  • +
  • + ScratchEd +
  • +
    + +
  • + Scratch Day +
  • +
    +
    +

    + Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab. +

    +
    +
    +
    +

    Contact

    +

    + + Email Us + +

    +
    + - -
    -

    Scratch Family

    - - -
  • - Scratch -
  • -
  • - ScratchJr -
  • -
    - -
  • - Scratch Foundation -
  • -
  • - ScratchEd -
  • -
    - -
  • - Scratch Day -
  • -
    -
    -

    - Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab. -

    -
    -
    -
    -

    Contact

    -

    - - Email Us - -

    -
    -
    - -
  • - - scratch twitter - -
  • -
  • - - scratch facebook - -
  • -
  • - - scratch foundation blog - -
  • -
    -
    -
    -
    - - ); - } -}); +
    +
    +
    +); module.exports = ConferenceFooter; diff --git a/src/components/footer/conference/2017/footer.jsx b/src/components/footer/conference/2017/footer.jsx index ebd11f36d..d026ce7ee 100644 --- a/src/components/footer/conference/2017/footer.jsx +++ b/src/components/footer/conference/2017/footer.jsx @@ -1,84 +1,100 @@ -var React = require('react'); -var ReactIntl = require('react-intl'); +const injectIntl = require('react-intl').injectIntl; +const intlShape = require('react-intl').intlShape; +const FormattedMessage = require('react-intl').FormattedMessage; +const React = require('react'); -var injectIntl = ReactIntl.injectIntl; -var FormattedMessage = ReactIntl.FormattedMessage; - -var FlexRow = require('../../../flex-row/flex-row.jsx'); -var FooterBox = require('../../container/footer.jsx'); -var LanguageChooser = require('../../../languagechooser/languagechooser.jsx'); +const FlexRow = require('../../../flex-row/flex-row.jsx'); +const FooterBox = require('../../container/footer.jsx'); +const LanguageChooser = require('../../../languagechooser/languagechooser.jsx'); require('../footer.scss'); -var ConferenceFooter = React.createClass({ - type: 'ConferenceFooter', - render: function () { - return ( - - -
    -

    - - -
  • - Scratch -
  • -
  • - ScratchJr -
  • -
    - -
  • - Scratch Foundation -
  • -
  • - ScratchEd -
  • -
    - -
  • - Scratch Day -
  • -
    -
    -

    - -

    -
    -
    -
    -

    Contact

    -

    - - Email Us - -

    -
    -
    - -
  • - - scratch twitter - -
  • -
  • - - scratch facebook - -
  • -
  • - - scratch foundation blog - -
  • -
    -
    -
    +const ConferenceFooter = props => ( + + +
    +

    + + +
  • + Scratch +
  • +
  • + ScratchJr +
  • +
    + +
  • + Scratch Foundation +
  • +
  • + ScratchEd +
  • +
    + +
  • + Scratch Day +
  • +
    - - - ); - } -}); +

    + +

    +
    +
    +
    +

    Contact

    +

    + + Email Us + +

    +
    +
    + +
  • + + scratch twitter + +
  • +
  • + + scratch facebook + +
  • +
  • + + scratch foundation blog + +
  • +
    +
    +
    +
    + +
    +); + +ConferenceFooter.propTypes = { + intl: intlShape +}; module.exports = injectIntl(ConferenceFooter); diff --git a/src/components/footer/conference/2018/footer.jsx b/src/components/footer/conference/2018/footer.jsx index 06daa2e92..1a08cc823 100644 --- a/src/components/footer/conference/2018/footer.jsx +++ b/src/components/footer/conference/2018/footer.jsx @@ -1,84 +1,146 @@ -var React = require('react'); -var ReactIntl = require('react-intl'); +const FormattedMessage = require('react-intl').FormattedMessage; +const injectIntl = require('react-intl').injectIntl; +const intlShape = require('react-intl').intlShape; +const React = require('react'); -var injectIntl = ReactIntl.injectIntl; -var FormattedMessage = ReactIntl.FormattedMessage; - -var FlexRow = require('../../../flex-row/flex-row.jsx'); -var FooterBox = require('../../container/footer.jsx'); -var LanguageChooser = require('../../../languagechooser/languagechooser.jsx'); +const FlexRow = require('../../../flex-row/flex-row.jsx'); +const FooterBox = require('../../container/footer.jsx'); +const LanguageChooser = require('../../../languagechooser/languagechooser.jsx'); require('../footer.scss'); -var ConferenceFooter = React.createClass({ - type: 'ConferenceFooter', - render: function () { - return ( - - -
    -

    - - -
  • - Scratch -
  • -
  • - ScratchJr -
  • -
    - -
  • - Scratch Foundation -
  • -
  • - ScratchEd -
  • -
    - -
  • - Scratch Day -
  • -
    -
    -

    - -

    -
    -
    -
    -

    Contact

    -

    - - Email Us - -

    -
    -
    - -
  • - - scratch twitter - -
  • -
  • - - scratch facebook - -
  • -
  • - - scratch foundation blog - -
  • -
    -
    -
    +const ConferenceFooter = props => ( + + +
    +

    + + +
  • + + Scratch + +
  • +
  • + + ScratchJr + +
  • +
    + +
  • + + Scratch Foundation + +
  • +
  • + + ScratchEd + +
  • +
    + +
  • + + Scratch Day + +
  • +
    - - - ); - } -}); +

    + +

    +
    +
    +
    +

    Contact

    +

    + + Email Us + +

    +
    +
    + +
  • + + scratch twitter + +
  • +
  • + + scratch facebook + +
  • +
  • + + scratch foundation blog + +
  • +
    +
    +
    +
    + +
    +); + +ConferenceFooter.propTypes = { + intl: intlShape +}; module.exports = injectIntl(ConferenceFooter); diff --git a/src/components/footer/container/footer.jsx b/src/components/footer/container/footer.jsx index 53f7eba59..f629e9f39 100644 --- a/src/components/footer/container/footer.jsx +++ b/src/components/footer/container/footer.jsx @@ -1,16 +1,16 @@ -var React = require('react'); +const PropTypes = require('prop-types'); +const React = require('react'); require('./footer.scss'); -var FooterBox = React.createClass({ - type: 'FooterBox', - render: function () { - return ( -
    - {this.props.children} -
    - ); - } -}); +const FooterBox = props => ( +
    + {props.children} +
    +); + +FooterBox.propTypes = { + children: PropTypes.node +}; module.exports = FooterBox; diff --git a/src/components/footer/www/footer.jsx b/src/components/footer/www/footer.jsx index 0cfaa46cd..e983b4f8b 100644 --- a/src/components/footer/www/footer.jsx +++ b/src/components/footer/www/footer.jsx @@ -1,226 +1,225 @@ -var React = require('react'); -var ReactIntl = require('react-intl'); -var FormattedMessage = ReactIntl.FormattedMessage; -var injectIntl = ReactIntl.injectIntl; +const FormattedMessage = require('react-intl').FormattedMessage; +const injectIntl = require('react-intl').injectIntl; +const intlShape = require('react-intl').intlShape; +const MediaQuery = require('react-responsive').default; +const React = require('react'); -var FooterBox = require('../container/footer.jsx'); -var LanguageChooser = require('../../languagechooser/languagechooser.jsx'); +const FooterBox = require('../container/footer.jsx'); +const LanguageChooser = require('../../languagechooser/languagechooser.jsx'); -var MediaQuery = require('react-responsive'); -var frameless = require('../../../lib/frameless'); +const frameless = require('../../../lib/frameless'); require('./footer.scss'); -var Footer = React.createClass({ - type: 'Footer', - render: function () { - return ( - - -
    -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    -
    -
    - -
    -
    -
    - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    -
    -
    - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    +const Footer = props => ( + + +
    +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    +
    +
    + +
    +
    +
    + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    +
    +
    + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    -
    -
    - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    +
    +
    + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    -
    -
    - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    +
    +
    + +
    +
    + + + +
    +
    + + + +
    +
    + + + +
    +
    -
    -
    - -
    -
    - - - -
    -
    - - - -
    -
    - - Scratch Day - -
    -
    - - - -
    -
    - - - -
    -
    -
    -
    - +
    +
    + +
    +
    + + + +
    +
    + + + +
    +
    + + Scratch Day + +
    +
    + + + +
    +
    + + + +
    +
    +
    +
    + -
    -

    - -

    -
    -
    - ); - } -}); +
    +

    + +

    +
    +
    +); + +Footer.propTypes = { + intl: intlShape.isRequired +}; module.exports = injectIntl(Footer); diff --git a/src/components/forms/button.jsx b/src/components/forms/button.jsx index 2c12d7db7..b5b97e39a 100644 --- a/src/components/forms/button.jsx +++ b/src/components/forms/button.jsx @@ -1,22 +1,26 @@ -var React = require('react'); -var classNames = require('classnames'); +const classNames = require('classnames'); +const omit = require('lodash.omit'); +const PropTypes = require('prop-types'); +const React = require('react'); require('./button.scss'); -var Button = React.createClass({ - type: 'Button', - propTypes: { - - }, - render: function () { - var classes = classNames( - 'button', - this.props.className - ); - return ( - - ); - } -}); +const Button = props => { + const classes = classNames('button', props.className); + + return ( + + ); +}; + +Button.propTypes = { + children: PropTypes.node, + className: PropTypes.string +}; module.exports = Button; diff --git a/src/components/forms/charcount.jsx b/src/components/forms/charcount.jsx index af6192ebe..bf46c95f4 100644 --- a/src/components/forms/charcount.jsx +++ b/src/components/forms/charcount.jsx @@ -1,28 +1,28 @@ -var classNames = require('classnames'); -var React = require('react'); +const classNames = require('classnames'); +const PropTypes = require('prop-types'); +const React = require('react'); require('./charcount.scss'); -var CharCount = React.createClass({ - type: 'CharCount', - getDefaultProps: function () { - return { - maxCharacters: 0, - currentCharacters: 0 - }; - }, - render: function () { - var classes = classNames( - 'char-count', - this.props.className, - {overmax: (this.props.currentCharacters > this.props.maxCharacters)} - ); - return ( -

    - {this.props.currentCharacters}/{this.props.maxCharacters} -

    - ); - } -}); +const CharCount = props => ( +

    props.maxCharacters) + })} + > + {props.currentCharacters}/{props.maxCharacters} +

    +); + +CharCount.propTypes = { + className: PropTypes.string, + currentCharacters: PropTypes.number, + maxCharacters: PropTypes.number +}; + +CharCount.defaultProps = { + currentCharacters: 0, + maxCharacters: 0 +}; module.exports = CharCount; diff --git a/src/components/forms/checkbox-group.jsx b/src/components/forms/checkbox-group.jsx index 29321d268..c98a0e5dd 100644 --- a/src/components/forms/checkbox-group.jsx +++ b/src/components/forms/checkbox-group.jsx @@ -1,25 +1,25 @@ -var classNames = require('classnames'); -var FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup; -var React = require('react'); -var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; -var inputHOC = require('./input-hoc.jsx'); +const classNames = require('classnames'); +const FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup; +const PropTypes = require('prop-types'); +const React = require('react'); + +const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; +const inputHOC = require('./input-hoc.jsx'); require('./row.scss'); require('./checkbox-group.scss'); -var CheckboxGroup = React.createClass({ - type: 'CheckboxGroup', - render: function () { - var classes = classNames( - 'checkbox-group', - this.props.className - ); - return ( -
    - -
    - ); - } -}); +const CheckboxGroup = props => ( +
    + +
    +); + +CheckboxGroup.propTypes = { + className: PropTypes.string +}; module.exports = inputHOC(defaultValidationHOC(CheckboxGroup)); diff --git a/src/components/forms/checkbox.jsx b/src/components/forms/checkbox.jsx index f0aa295b8..fcaac2293 100644 --- a/src/components/forms/checkbox.jsx +++ b/src/components/forms/checkbox.jsx @@ -1,23 +1,23 @@ -var classNames = require('classnames'); -var FRCCheckbox = require('formsy-react-components').Checkbox; -var React = require('react'); -var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; -var inputHOC = require('./input-hoc.jsx'); +const classNames = require('classnames'); +const FRCCheckbox = require('formsy-react-components').Checkbox; +const PropTypes = require('prop-types'); +const React = require('react'); + +const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; +const inputHOC = require('./input-hoc.jsx'); require('./row.scss'); require('./checkbox.scss'); -var Checkbox = React.createClass({ - type: 'Checkbox', - render: function () { - var classes = classNames( - 'checkbox-row', - this.props.className - ); - return ( - - ); - } -}); +const Checkbox = props => ( + +); + +Checkbox.propTypes = { + className: PropTypes.string +}; module.exports = inputHOC(defaultValidationHOC(Checkbox)); diff --git a/src/components/forms/form.jsx b/src/components/forms/form.jsx index 17ada223a..1514787b7 100644 --- a/src/components/forms/form.jsx +++ b/src/components/forms/form.jsx @@ -1,47 +1,61 @@ -var classNames = require('classnames'); -var Formsy = require('formsy-react'); -var omit = require('lodash.omit'); -var React = require('react'); -var validations = require('./validations.jsx').validations; +const bindAll = require('lodash.bindall'); +const classNames = require('classnames'); +const Formsy = require('formsy-react'); +const omit = require('lodash.omit'); +const PropTypes = require('prop-types'); +const React = require('react'); -for (var validation in validations) { +const validations = require('./validations.jsx').validations; + +for (const validation in validations) { Formsy.addValidationRule(validation, validations[validation]); } -var Form = React.createClass({ - getDefaultProps: function () { - return { - noValidate: true, - onChange: function () {} - }; - }, - getInitialState: function () { - return { +class Form extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleChange' + ]); + this.state = { allValues: {} }; - }, - onChange: function (currentValues, isChanged) { + } + handleChange (currentValues, isChanged) { this.setState({allValues: omit(currentValues, 'all')}); this.props.onChange(currentValues, isChanged); - }, - render: function () { - var classes = classNames( - 'form', - this.props.className - ); + } + render () { return ( - - {React.Children.map(this.props.children, function (child) { + { + this.formsy = form; + }} + onChange={this.handleChange} + {...this.props} + > + {React.Children.map(this.props.children, child => { if (!child) return child; if (child.props.name === 'all') { return React.cloneElement(child, {value: this.state.allValues}); - } else { - return child; } - }.bind(this))} + return child; + })} ); } -}); +} + +Form.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + onChange: PropTypes.func +}; + +Form.defaultProps = { + noValidate: true, + onChange: function () {} +}; module.exports = Form; diff --git a/src/components/forms/general-error.jsx b/src/components/forms/general-error.jsx index d9e20463b..270e3ca39 100644 --- a/src/components/forms/general-error.jsx +++ b/src/components/forms/general-error.jsx @@ -1,5 +1,6 @@ -var Formsy = require('formsy-react'); -var React = require('react'); +const Formsy = require('formsy-react'); +const PropTypes = require('prop-types'); +const React = require('react'); require('./general-error.scss'); @@ -10,13 +11,18 @@ require('./general-error.scss'); * give it a name, and apply your validation error to * the name of the GeneralError component. */ -module.exports = Formsy.HOC(React.createClass({ - render: function () { - if (!this.props.showError()) return null; - return ( -

    - {this.props.getErrorMessage()} -

    - ); - } -})); +const GeneralError = props => { + if (!props.showError()) return null; + return ( +

    + {props.getErrorMessage()} +

    + ); +}; + +GeneralError.propTypes = { + getErrorMessage: PropTypes.func, + showError: PropTypes.func +}; + +module.exports = Formsy.HOC(GeneralError); diff --git a/src/components/forms/input-hoc.jsx b/src/components/forms/input-hoc.jsx index 1532eaefd..1ce3c1c0b 100644 --- a/src/components/forms/input-hoc.jsx +++ b/src/components/forms/input-hoc.jsx @@ -1,20 +1,32 @@ -var React = require('react'); +const omit = require('lodash.omit'); +const PropTypes = require('prop-types'); +const React = require('react'); -module.exports = function InputComponentMixin (Component) { - var InputComponent = React.createClass({ - getDefaultProps: function () { - return { - messages: { - 'general.notRequired': 'Not Required' - } - }; - }, - render: function () { - return ( - - ); +/** + * Higher-order component for building an input field + * @param {React.Component} Component an input component + * @return {React.Component} a wrapped input component + */ +module.exports = Component => { + const InputComponent = props => ( + + ); + + InputComponent.propTypes = { + messages: PropTypes.shape({ + 'general.notRequired': PropTypes.string + }), + required: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]) + }; + + InputComponent.defaultProps = { + messages: { + 'general.notRequired': 'Not Required' } - }); + }; + return InputComponent; }; diff --git a/src/components/forms/input.jsx b/src/components/forms/input.jsx index 3244ade3c..a9992a40c 100644 --- a/src/components/forms/input.jsx +++ b/src/components/forms/input.jsx @@ -1,46 +1,57 @@ -var classNames = require('classnames'); -var FRCInput = require('formsy-react-components').Input; -var React = require('react'); -var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; -var inputHOC = require('./input-hoc.jsx'); +const bindAll = require('lodash.bindall'); +const classNames = require('classnames'); +const FRCInput = require('formsy-react-components').Input; +const omit = require('lodash.omit'); +const PropTypes = require('prop-types'); +const React = require('react'); + +const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; +const inputHOC = require('./input-hoc.jsx'); require('./input.scss'); require('./row.scss'); -var Input = React.createClass({ - type: 'Input', - getDefaultProps: function () { - return {}; - }, - getInitialState: function () { - return { +class Input extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleInvalid', + 'handleValid' + ]); + this.state = { status: '' }; - }, - onValid: function () { + } + handleValid () { this.setState({ status: 'pass' }); - }, - onInvalid: function () { + } + handleInvalid () { this.setState({ status: 'fail' }); - }, - render: function () { - var classes = classNames( - this.state.status, - this.props.className, - {'no-label': (typeof this.props.label === 'undefined')} - ); + } + render () { return ( - + ); } -}); +} + +Input.propTypes = { + className: PropTypes.string, + label: PropTypes.string +}; module.exports = inputHOC(defaultValidationHOC(Input)); diff --git a/src/components/forms/phone-input.jsx b/src/components/forms/phone-input.jsx index 260c5a2ef..69d8ab23f 100644 --- a/src/components/forms/phone-input.jsx +++ b/src/components/forms/phone-input.jsx @@ -1,23 +1,33 @@ -var allCountries = require('react-telephone-input/lib/country_data').allCountries; -var classNames = require('classnames'); -var ComponentMixin = require('formsy-react-components').ComponentMixin; -var FormsyMixin = require('formsy-react').Mixin; -var React = require('react'); -var ReactPhoneInput = require('react-telephone-input/lib/withStyles'); -var Row = require('formsy-react-components').Row; +const allCountries = require('react-telephone-input/lib/country_data').allCountries; +const classNames = require('classnames'); +const ComponentMixin = require('formsy-react-components').ComponentMixin; +const createReactClass = require('create-react-class'); +const FormsyMixin = require('formsy-react').Mixin; +const omit = require('lodash.omit'); +const PropTypes = require('prop-types'); +const React = require('react'); +const ReactPhoneInput = require('react-telephone-input/lib/withStyles').default; +const Row = require('formsy-react-components').Row; -var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; -var inputHOC = require('./input-hoc.jsx'); -var intl = require('../../lib/intl.jsx'); -var validationHOCFactory = require('./validations.jsx').validationHOCFactory; +const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; +const inputHOC = require('./input-hoc.jsx'); +const intl = require('../../lib/intl.jsx'); +const validationHOCFactory = require('./validations.jsx').validationHOCFactory; -var allIso2 = allCountries.map(function (country) {return country.iso2;}); +const allIso2 = allCountries.map(country => (country.iso2)); require('./row.scss'); require('./phone-input.scss'); -var PhoneInput = React.createClass({ +const PhoneInput = createReactClass({ // eslint-disable-line react/prefer-es6-class displayName: 'PhoneInput', + propTypes: { + className: PropTypes.string, + defaultCountry: PropTypes.string, + disabled: PropTypes.bool, + name: PropTypes.string, + onChange: PropTypes.func + }, mixins: [ FormsyMixin, ComponentMixin @@ -31,29 +41,34 @@ var PhoneInput = React.createClass({ defaultCountry: 'us' }; }, - onChangeInput: function (number, country) { - var value = {national_number: number, country_code: country}; + handleChangeInput: function (number, country) { + const value = { + national_number: number, + country_code: country + }; this.setValue(value); this.props.onChange(this.props.name, value); }, render: function () { - var defaultCountry = PhoneInput.getDefaultProps().defaultCountry; + let defaultCountry = PhoneInput.getDefaultProps().defaultCountry; if (allIso2.indexOf(this.props.defaultCountry.toLowerCase()) !== -1) { - defaultCountry = this.props.defaultCountry.toLowerCase(); + defaultCountry = this.props.defaultCountry.toLowerCase(); } return ( -
    - {this.renderHelp()} {this.renderErrorMessage()} @@ -63,7 +78,7 @@ var PhoneInput = React.createClass({ } }); -var phoneValidationHOC = validationHOCFactory({ +const phoneValidationHOC = validationHOCFactory({ isPhone: }); diff --git a/src/components/forms/radio-group.jsx b/src/components/forms/radio-group.jsx index fcdf928a6..168f0a926 100644 --- a/src/components/forms/radio-group.jsx +++ b/src/components/forms/radio-group.jsx @@ -1,23 +1,23 @@ -var classNames = require('classnames'); -var FRCRadioGroup = require('formsy-react-components').RadioGroup; -var React = require('react'); -var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; -var inputHOC = require('./input-hoc.jsx'); +const classNames = require('classnames'); +const FRCRadioGroup = require('formsy-react-components').RadioGroup; +const PropTypes = require('prop-types'); +const React = require('react'); + +const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; +const inputHOC = require('./input-hoc.jsx'); require('./row.scss'); require('./radio-group.scss'); -var RadioGroup = React.createClass({ - type: 'RadioGroup', - render: function () { - var classes = classNames( - 'radio-group', - this.props.className - ); - return ( - - ); - } -}); +const RadioGroup = props => ( + +); + +RadioGroup.propTypes = { + className: PropTypes.string +}; module.exports = inputHOC(defaultValidationHOC(RadioGroup)); diff --git a/src/components/forms/select.jsx b/src/components/forms/select.jsx index a17053942..16a7e1ee2 100644 --- a/src/components/forms/select.jsx +++ b/src/components/forms/select.jsx @@ -1,33 +1,31 @@ -var classNames = require('classnames'); -var defaults = require('lodash.defaultsdeep'); -var FRCSelect = require('formsy-react-components').Select; -var React = require('react'); -var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; -var inputHOC = require('./input-hoc.jsx'); +const classNames = require('classnames'); +const defaults = require('lodash.defaultsdeep'); +const FRCSelect = require('formsy-react-components').Select; +const PropTypes = require('prop-types'); +const React = require('react'); + +const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; +const inputHOC = require('./input-hoc.jsx'); require('./row.scss'); require('./select.scss'); -var Select = React.createClass({ - type: 'Select', - propTypes: { - - }, - render: function () { - var classes = classNames( - 'select', - this.props.className - ); - var props = this.props; - if (this.props.required && !this.props.value) { - props = defaults({}, this.props, {value: this.props.options[0].value}); - } - return ( -
    - -
    - ); +const Select = props => { + if (props.required && !props.value) { + props = defaults({}, props, {value: props.options[0].value}); } -}); + return ( +
    + +
    + ); +}; + +Select.propTypes = { + className: PropTypes.string, + options: PropTypes.arrayOf(PropTypes.any), + required: PropTypes.bool, + value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]) +}; module.exports = inputHOC(defaultValidationHOC(Select)); diff --git a/src/components/forms/textarea.jsx b/src/components/forms/textarea.jsx index ec90f8b59..0c488f995 100644 --- a/src/components/forms/textarea.jsx +++ b/src/components/forms/textarea.jsx @@ -1,25 +1,25 @@ -var classNames = require('classnames'); -var FRCTextarea = require('formsy-react-components').Textarea; -var React = require('react'); -var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; -var inputHOC = require('./input-hoc.jsx'); +const classNames = require('classnames'); +const FRCTextarea = require('formsy-react-components').Textarea; +const omit = require('lodash.omit'); +const PropTypes = require('prop-types'); +const React = require('react'); + +const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; +const inputHOC = require('./input-hoc.jsx'); require('./row.scss'); require('./textarea.scss'); -var TextArea = React.createClass({ - type: 'TextArea', - render: function () { - var classes = classNames( - 'textarea-row', - this.props.className - ); - return ( - - ); - } -}); +const TextArea = props => ( + +); + +TextArea.propTypes = { + className: PropTypes.string +}; module.exports = inputHOC(defaultValidationHOC(TextArea)); diff --git a/src/components/forms/validations.jsx b/src/components/forms/validations.jsx index 113cd6fb4..c83ce63cb 100644 --- a/src/components/forms/validations.jsx +++ b/src/components/forms/validations.jsx @@ -1,48 +1,46 @@ -var defaults = require('lodash.defaultsdeep'); -var intl = require('../../lib/intl.jsx'); -var libphonenumber = require('google-libphonenumber'); -var phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance(); -var React = require('react'); - -module.exports = {}; +const defaults = require('lodash.defaultsdeep'); +const intl = require('../../lib/intl.jsx'); +const libphonenumber = require('google-libphonenumber'); +const omit = require('lodash.omit'); +const phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance(); +const PropTypes = require('prop-types'); +const React = require('react'); module.exports.validations = { - notEquals: function (values, value, neq) { - return value !== neq; - }, - notEqualsField: function (values, value, field) { - return value !== values[field]; - }, - isPhone: function (values, value) { + notEquals: (values, value, neq) => (value !== neq), + notEqualsField: (values, value, field) => (value !== values[field]), + isPhone: (values, value) => { if (typeof value === 'undefined') return true; if (value && value.national_number === '+') return true; try { - var parsed = phoneNumberUtil.parse(value.national_number, value.country_code.iso2); + const parsed = phoneNumberUtil.parse(value.national_number, value.country_code.iso2); + return phoneNumberUtil.isValidNumber(parsed); } catch (err) { return false; } - return phoneNumberUtil.isValidNumber(parsed); } }; + module.exports.validations.notEqualsUsername = module.exports.validations.notEquals; -module.exports.validationHOCFactory = function (defaultValidationErrors) { - return function (Component) { - var ValidatedComponent = React.createClass({ - render: function () { - var validationErrors = defaults( - {}, - defaultValidationErrors, - this.props.validationErrors - ); - return ( - - ); - } - }); - return ValidatedComponent; +module.exports.validationHOCFactory = defaultValidationErrors => (Component => { + const ValidatedComponent = props => ( + + ); + + ValidatedComponent.propTypes = { + validationErrors: PropTypes.object // eslint-disable-line react/forbid-prop-types }; -}; + + return ValidatedComponent; +}); module.exports.defaultValidationHOC = module.exports.validationHOCFactory({ isDefaultRequiredValue: diff --git a/src/components/grid/grid.jsx b/src/components/grid/grid.jsx index f130e5bbd..0bc8c9286 100644 --- a/src/components/grid/grid.jsx +++ b/src/components/grid/grid.jsx @@ -1,79 +1,68 @@ -var classNames = require('classnames'); -var React = require('react'); +const classNames = require('classnames'); +const PropTypes = require('prop-types'); +const React = require('react'); -var Thumbnail = require('../thumbnail/thumbnail.jsx'); -var FlexRow = require('../flex-row/flex-row.jsx'); +const Thumbnail = require('../thumbnail/thumbnail.jsx'); +const FlexRow = require('../flex-row/flex-row.jsx'); require('./grid.scss'); -var Grid = React.createClass({ - type: 'Grid', - getDefaultProps: function () { - return { - items: require('./grid.json'), - itemType: 'projects', - showLoves: false, - showFavorites: false, - showRemixes: false, - showViews: false, - showAvatar: false - }; - }, - render: function () { - var classes = classNames( - 'grid', - this.props.className - ); - return ( -
    - - {this.props.items.map(function (item, key) { - var href = '/' + this.props.itemType + '/' + item.id + '/'; +const Grid = props => ( +
    + + {props.items.map((item, key) => { + const href = `/${props.itemType}/${item.id}/`; + if (props.itemType === 'projects') { + return ( + + ); + } + return ( + + ); + })} + +
    +); - if (this.props.itemType == 'projects') { - return ( - - ); - } - else { - return ( - - ); - } - }.bind(this))} -
    -
    - ); - } -}); +Grid.propTypes = { + className: PropTypes.string, + itemType: PropTypes.string, + items: PropTypes.arrayOf(PropTypes.object) +}; + +Grid.defaultProps = { + items: require('./grid.json'), + itemType: 'projects', + showLoves: false, + showFavorites: false, + showRemixes: false, + showViews: false, + showAvatar: false +}; module.exports = Grid; diff --git a/src/components/informationpage/informationpage.jsx b/src/components/informationpage/informationpage.jsx index acc68da77..94a8bd740 100644 --- a/src/components/informationpage/informationpage.jsx +++ b/src/components/informationpage/informationpage.jsx @@ -1,39 +1,34 @@ -var classNames = require('classnames'); -var React = require('react'); -var TitleBanner = require('../../components/title-banner/title-banner.jsx'); +const classNames = require('classnames'); +const PropTypes = require('prop-types'); +const React = require('react'); + +const TitleBanner = require('../../components/title-banner/title-banner.jsx'); require('./informationpage.scss'); -/** +/* * Container for a table of contents * alongside a long body of text */ -var InformationPage = React.createClass({ - type: 'InformationPage', - propTypes: { - title: React.PropTypes.string.isRequired - }, - render: function () { - var classes = classNames( - 'info-outer', - 'inner', - this.props.className - ); - return ( -
    - -
    -

    - {this.props.title} -

    -
    -
    -
    - {this.props.children} -
    +const InformationPage = props => ( +
    + +
    +

    + {props.title} +

    - ); - } -}); +
    +
    + {props.children} +
    +
    +); + +InformationPage.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + title: PropTypes.string.isRequired +}; module.exports = InformationPage; diff --git a/src/components/intro/intro.jsx b/src/components/intro/intro.jsx index d1f7c4275..efe0473a7 100644 --- a/src/components/intro/intro.jsx +++ b/src/components/intro/intro.jsx @@ -1,111 +1,128 @@ -var connect = require('react-redux').connect; -var React = require('react'); +const bindAll = require('lodash.bindall'); +const connect = require('react-redux').connect; +const PropTypes = require('prop-types'); +const React = require('react'); -var sessionActions = require('../../redux/session.js'); +const sessionActions = require('../../redux/session.js'); -var IframeModal = require('../modal/iframe/modal.jsx'); -var Registration = require('../registration/registration.jsx'); +const IframeModal = require('../modal/iframe/modal.jsx'); +const Registration = require('../registration/registration.jsx'); require('./intro.scss'); -var Intro = React.createClass({ - type: 'Intro', - getDefaultProps: function () { - return { - messages: { - 'intro.aboutScratch': 'ABOUT SCRATCH', - 'intro.forEducators': 'FOR EDUCATORS', - 'intro.forParents': 'FOR PARENTS', - 'intro.itsFree': 'it\'s free!', - 'intro.joinScratch': 'JOIN SCRATCH', - 'intro.seeExamples': 'SEE EXAMPLES', - 'intro.tagLine': 'Create stories, games, and animations
    Share with others around the world', - 'intro.tryItOut': 'TRY IT OUT', - 'intro.description': 'A creative learning community with ' + - 'over 14 million projects shared' - }, - session: {} - }; - }, - getInitialState: function () { - return { +class Intro extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleShowVideo', + 'handleCloseVideo', + 'handleJoinClick', + 'handleCloseRegistration', + 'handleCompleteRegistration' + ]); + this.state = { videoOpen: false }; - }, - showVideo: function () { + } + handleShowVideo () { this.setState({videoOpen: true}); - }, - closeVideo: function () { + } + handleCloseVideo () { this.setState({videoOpen: false}); - }, - handleJoinClick: function (e) { + } + handleJoinClick (e) { e.preventDefault(); - this.setState({'registrationOpen': true}); - }, - closeRegistration: function () { - this.setState({'registrationOpen': false}); - }, - completeRegistration: function () { + this.setState({registrationOpen: true}); + } + handleCloseRegistration () { + this.setState({registrationOpen: false}); + } + handleCompleteRegistration () { this.props.dispatch(sessionActions.refreshSession()); this.closeRegistration(); - }, - render: function () { + } + render () { return (
    -

    -

    +

    - + Scratch Cat + /> Scratch Cat -
    + /> +
    {this.props.messages['intro.tryItOut']}
    - + Tera + /> Tera -
    + /> +
    {this.props.messages['intro.seeExamples']}
    - + Gobo + /> Gobo -
    + /> +
    -
    +
    -
    - Intro Video +
    + Intro Video
    ); } -}); +} -var mapStateToProps = function (state) { - return { - session: state.session - }; +Intro.propTypes = { + dispatch: PropTypes.func.isRequired, + messages: PropTypes.shape({ + 'intro.aboutScratch': PropTypes.string, + 'intro.forEducators': PropTypes.string, + 'intro.forParents': PropTypes.string, + 'intro.itsFree': PropTypes.string, + 'intro.joinScratch': PropTypes.string, + 'intro.seeExamples': PropTypes.string, + 'intro.tagLine': PropTypes.string, + 'intro.tryItOut': PropTypes.string, + 'intro.description': PropTypes.string + }) }; -var ConnectedIntro = connect(mapStateToProps)(Intro); +Intro.defaultProps = { + messages: { + 'intro.aboutScratch': 'ABOUT SCRATCH', + 'intro.forEducators': 'FOR EDUCATORS', + 'intro.forParents': 'FOR PARENTS', + 'intro.itsFree': 'it\'s free!', + 'intro.joinScratch': 'JOIN SCRATCH', + 'intro.seeExamples': 'SEE EXAMPLES', + 'intro.tagLine': 'Create stories, games, and animations
    Share with others around the world', + 'intro.tryItOut': 'TRY IT OUT', + 'intro.description': 'A creative learning community with ' + + 'over 14 million projects shared' + }, + session: {} +}; + +const mapStateToProps = state => ({ + session: state.session +}); + +const ConnectedIntro = connect(mapStateToProps)(Intro); module.exports = ConnectedIntro; diff --git a/src/components/languagechooser/languagechooser.jsx b/src/components/languagechooser/languagechooser.jsx index c538fb923..1e9258853 100644 --- a/src/components/languagechooser/languagechooser.jsx +++ b/src/components/languagechooser/languagechooser.jsx @@ -1,46 +1,57 @@ -var classNames = require('classnames'); -var React = require('react'); +const bindAll = require('lodash.bindall'); +const classNames = require('classnames'); +const PropTypes = require('prop-types'); +const React = require('react'); -var jar = require('../../lib/jar.js'); -var languages = require('../../../languages.json'); -var Form = require('../forms/form.jsx'); -var Select = require('../forms/select.jsx'); +const jar = require('../../lib/jar.js'); +const languages = require('../../../languages.json'); +const Form = require('../forms/form.jsx'); +const Select = require('../forms/select.jsx'); require('./languagechooser.scss'); /** * Footer dropdown menu that allows one to change their language. */ -var LanguageChooser = React.createClass({ - type: 'LanguageChooser', - getDefaultProps: function () { - return { - languages: languages, - locale: 'en' - }; - }, - onSetLanguage: function (name, value) { +class LanguageChooser extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleSetLanguage' + ]); + } + handleSetLanguage (name, value) { jar.set('scratchlanguage', value); window.location.reload(); - }, - render: function () { - var classes = classNames( - 'language-chooser', - this.props.className - ); - var languageOptions = Object.keys(this.props.languages).map(function (value) { - return {value: value, label: this.props.languages[value]}; - }.bind(this)); + } + render () { + const languageOptions = Object.keys(this.props.languages).map(value => ({ + value: value, + label: this.props.languages[value] + })); return ( -
    -
    ); } -}); +} + +LanguageChooser.propTypes = { + className: PropTypes.string, + languages: PropTypes.object, // eslint-disable-line react/forbid-prop-types + locale: PropTypes.string +}; + +LanguageChooser.defaultProps = { + languages: languages, + locale: 'en' +}; module.exports = LanguageChooser; diff --git a/src/components/login/login.jsx b/src/components/login/login.jsx index c7243d7a6..58586e5e5 100644 --- a/src/components/login/login.jsx +++ b/src/components/login/login.jsx @@ -1,66 +1,108 @@ -var React = require('react'); -var FormattedMessage = require('react-intl').FormattedMessage; +const bindAll = require('lodash.bindall'); +const FormattedMessage = require('react-intl').FormattedMessage; +const PropTypes = require('prop-types'); +const React = require('react'); -var log = require('../../lib/log.js'); +const log = require('../../lib/log.js'); -var Form = require('../forms/form.jsx'); -var Input = require('../forms/input.jsx'); -var Button = require('../forms/button.jsx'); -var Spinner = require('../spinner/spinner.jsx'); +const Form = require('../forms/form.jsx'); +const Input = require('../forms/input.jsx'); +const Button = require('../forms/button.jsx'); +const Spinner = require('../spinner/spinner.jsx'); require('./login.scss'); -var Login = React.createClass({ - type: 'Login', - propTypes: { - onLogIn: React.PropTypes.func, - error: React.PropTypes.string - }, - getInitialState: function () { - return { +class Login extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'handleSubmit' + ]); + this.state = { waiting: false }; - }, - handleSubmit: function (formData) { + } + handleSubmit (formData) { this.setState({waiting: true}); - this.props.onLogIn(formData, function (err) { + this.props.onLogIn(formData, err => { if (err) log.error(err); this.setState({waiting: false}); - }.bind(this)); - }, - render: function () { - var error; + }); + } + render () { + let error; if (this.props.error) { error =
    {this.props.error}
    ; } return (
    -
    ); } -}); +} + +Login.propTypes = { + error: PropTypes.string, + onLogIn: PropTypes.func +}; module.exports = Login; diff --git a/src/components/masonrygrid/masonrygrid.jsx b/src/components/masonrygrid/masonrygrid.jsx index ef32172e5..5f654488d 100644 --- a/src/components/masonrygrid/masonrygrid.jsx +++ b/src/components/masonrygrid/masonrygrid.jsx @@ -1,60 +1,68 @@ -var classNames = require('classnames'); -var React = require('react'); -var MediaQuery = require('react-responsive'); -var frameless = require('../../lib/frameless'); +const bindAll = require('lodash.bindall'); +const classNames = require('classnames'); +const MediaQuery = require('react-responsive').default; +const PropTypes = require('prop-types'); +const React = require('react'); + +const frameless = require('../../lib/frameless'); require('./masonrygrid.scss'); -var MasonryGrid = React.createClass({ - type: 'MasonryGrid', - getDefaultProps: function () { - return { - as: 'div' - }; - }, - reorderColumns: function (items, cols) { - var a1 = []; - var a2 = []; - var a3 = []; - var i = 0; - //only implemented for 2 and 3 columns so far - easy to extend if needed +class MasonryGrid extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'reorderColumns' + ]); + } + reorderColumns (items, cols) { + const a1 = []; + const a2 = []; + const a3 = []; + let i = 0; + // only implemented for 2 and 3 columns so far - easy to extend if needed if (cols > 1 && cols < 4) { - for (i=0;i - + + {this.props.children} - + {this.reorderColumns(this.props.children, 2)} - + {this.reorderColumns(this.props.children, 3)} ); } -}); +} + +MasonryGrid.propTypes = { + children: PropTypes.node, + className: PropTypes.string +}; + +MasonryGrid.defaultProps = { + as: 'div' +}; module.exports = MasonryGrid; diff --git a/src/components/microworld/microworld.jsx b/src/components/microworld/microworld.jsx index ec21d5a87..5923f7ac4 100644 --- a/src/components/microworld/microworld.jsx +++ b/src/components/microworld/microworld.jsx @@ -1,39 +1,53 @@ -var React = require('react'); +const bindAll = require('lodash.bindall'); +const PropTypes = require('prop-types'); +const React = require('react'); + +const Box = require('../box/box.jsx'); +const LegacyCarousel = require('../carousel/legacy-carousel.jsx'); +const IframeModal = require('../modal/iframe/modal.jsx'); +const NestedCarousel = require('../nestedcarousel/nestedcarousel.jsx'); require('./microworld.scss'); -var Box = require('../box/box.jsx'); -var LegacyCarousel = require('../carousel/legacy-carousel.jsx'); -var IframeModal = require('../modal/iframe/modal.jsx'); -var NestedCarousel = require('../nestedcarousel/nestedcarousel.jsx'); - -var Microworld = React.createClass({ - type: 'Microworld', - propTypes: { - microworldData: React.PropTypes.node.isRequired - }, - markVideoOpen: function (key) { - {/* When a video is clicked, mark it as an open video, so the video Modal will open. - Key is the number of the video, so distinguish between different videos on the page */} - - var videoOpenArr = this.state.videoOpen; - videoOpenArr[key] = true; - this.setState({videoOpen: videoOpenArr}); - }, - markVideoClosed: function (key) { - {/* When a video's x is clicked, mark it as closed, so the video Modal will disappear. - Key is the number of the video, so distinguish between different videos on the page */} - var videoOpenArr = this.state.videoOpen; - videoOpenArr[key] = false; - this.setState({videoOpen: videoOpenArr}); - }, - getInitialState: function () { - return { +class Microworld extends React.Component { + constructor (props) { + super(props); + bindAll(this, [ + 'markVideoOpen', + 'markVideoClosed', + 'renderVideos', + 'renderVideo', + 'renderEditorWindow', + 'renderTips', + 'renderStarterProject', + 'renderProjectIdeasBox', + 'renderForum', + 'renderDesignStudio' + ]); + this.state = { videoOpen: {} }; - }, - renderVideos: function () { - var videos = this.props.microworldData.videos; + } + markVideoOpen (key) { + /* + When a video is clicked, mark it as an open video, so the video Modal will open. + Key is the number of the video, so distinguish between different videos on the page + */ + const videoOpenArr = this.state.videoOpen; + videoOpenArr[key] = true; + this.setState({videoOpen: videoOpenArr}); + } + markVideoClosed (key) { + /* + When a video's x is clicked, mark it as closed, so the video Modal will disappear. + Key is the number of the video, so distinguish between different videos on the page + */ + const videoOpenArr = this.state.videoOpen; + videoOpenArr[key] = false; + this.setState({videoOpen: videoOpenArr}); + } + renderVideos () { + const videos = this.props.microworldData.videos; if (!videos || videos.length <= 0) { return null; } @@ -48,26 +62,32 @@ var Microworld = React.createClass({
    ); - }, - renderVideo: function (video, key) { + } + renderVideo (video, key) { return (
    -
    -
    +
    { // eslint-disable-line react/jsx-no-bind + this.markVideoOpen(key); + }} + />
    { // eslint-disable-line react/jsx-no-bind + this.markVideoClosed(key); + }} />
    ); - }, - renderEditorWindow: function () { - var projectId = this.props.microworldData.microworld_project_id; + } + renderEditorWindow () { + const projectId = this.props.microworldData.microworld_project_id; if (!projectId) { return null; @@ -75,30 +95,37 @@ var Microworld = React.createClass({ return (

    Start Creating!

    - + + -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - -
    - -

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - ); - } -})); +const InformationPage = require('../../../components/informationpage/informationpage.jsx'); -render(, document.getElementById('app')); +const TeacherFaq = props => ( + +
    +
    + +

    +
    +
    +
    + -
    - +const Landing = () => ( +
    + +
    +

    + +

    + +

    + +

    +
    +