Merge pull request #1789 from LLK/hotfix/es6-upgrade

[Master] Upgrade to ES6, Take II
This commit is contained in:
Ray Schamp 2018-01-30 16:29:51 -05:00 committed by GitHub
commit 51533bbf39
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
188 changed files with 13697 additions and 11511 deletions

3
.babelrc Normal file
View file

@ -0,0 +1,3 @@
{
"presets": ["es2015", "react"],
}

View file

@ -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"
}

4
.eslintrc.js Normal file
View file

@ -0,0 +1,4 @@
module.exports = {
extends: ['scratch', 'scratch/node'],
plugins: ['json']
};

View file

@ -133,3 +133,9 @@ file_filter = localizations/conference-index/<lang>.json
source_file = src/views/conference/2018/index/l10n.json source_file = src/views/conference/2018/index/l10n.json
source_lang = en source_lang = en
type = KEYVALUEJSON type = KEYVALUEJSON
[scratch-website.preview-faq-l10njson]
file_filter = localizations/preview-faq/<lang>.json
source_file = src/views/preview-faq/l10n.json
source_lang = en
type = KEYVALUEJSON

View file

@ -3,7 +3,7 @@ var defaults = require('lodash.defaults');
var fastlyConfig = require('./lib/fastly-config-methods'); var fastlyConfig = require('./lib/fastly-config-methods');
const languages = require('../languages.json'); 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 FASTLY_SERVICE_ID = process.env.FASTLY_SERVICE_ID || '';
const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME || ''; const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME || '';
@ -15,10 +15,10 @@ var extraAppRoutes = [
// TODO: Should this be added for every route? // TODO: Should this be added for every route?
'/\\?', '/\\?',
// View html // 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); return defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route);
}); });
@ -28,9 +28,9 @@ async.auto({
if (err) return cb(err); if (err) return cb(err);
// Validate latest version before continuing // Validate latest version before continuing
if (response.active || response.locked) { if (response.active || response.locked) {
fastly.cloneVersion(response.number, function (err, response) { fastly.cloneVersion(response.number, function (e, resp) {
if (err) return cb('Failed to clone latest version: ' + err); if (e) return cb('Failed to clone latest version: ' + e);
cb(null, response.number); cb(null, resp.number);
}); });
} else { } else {
cb(null, response.number); cb(null, response.number);
@ -46,11 +46,11 @@ async.auto({
var recvCondition = '' + var recvCondition = '' +
'if (' + notPassStatement + ') {\n' + 'if (' + notPassStatement + ') {\n' +
' set req.backend = F_s3;\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' + '} else {\n' +
' if (!req.http.Fastly-FF) {\n' + ' if (!req.http.Fastly-FF) {\n' +
' if (req.http.X-Forwarded-For) {\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' + ' } else {\n' +
' set req.http.Fastly-Temp-XFF = client.ip;\n' + ' set req.http.Fastly-Temp-XFF = client.ip;\n' +
' }\n' + ' }\n' +
@ -171,20 +171,19 @@ async.auto({
if (err) return cb(err); if (err) return cb(err);
cb(null, headers); cb(null, headers);
}); });
}]}, }]
function (err, results) { }, function (err, results) {
if (err) throw new Error(err); if (err) throw new Error(err);
if (process.env.FASTLY_ACTIVATE_CHANGES) { if (process.env.FASTLY_ACTIVATE_CHANGES) {
fastly.activateVersion(results.version, function (err, response) { fastly.activateVersion(results.version, function (e, resp) {
if (err) throw new Error(err); if (err) throw new Error(e);
process.stdout.write('Successfully configured and activated version ' + response.number + '\n'); process.stdout.write('Successfully configured and activated version ' + resp.number + '\n');
if (process.env.FASTLY_PURGE_ALL) { if (process.env.FASTLY_PURGE_ALL) {
fastly.purgeAll(FASTLY_SERVICE_ID, function (err) { fastly.purgeAll(FASTLY_SERVICE_ID, function (error) {
if (err) throw new Error(err); if (error) throw new Error(error);
process.stdout.write('Purged all.\n'); process.stdout.write('Purged all.\n');
}); });
} }
}); });
} }
} });
);

View file

@ -24,9 +24,9 @@ var FastlyConfigMethods = {
*/ */
getViewPaths: function (routes) { getViewPaths: function (routes) {
return routes.reduce(function (paths, route) { return routes.reduce(function (paths, route) {
var path = route.routeAlias || route.pattern; var p = route.routeAlias || route.pattern;
if (paths.indexOf(path) === -1) { if (paths.indexOf(p) === -1) {
paths.push(path); paths.push(p);
} }
return paths; return paths;
}, []); }, []);
@ -39,7 +39,7 @@ var FastlyConfigMethods = {
* 2. /path/:arg([regex]) :arg is removed, leaving just /path/([regex]) * 2. /path/:arg([regex]) :arg is removed, leaving just /path/([regex])
*/ */
expressPatternToRegex: function (pattern) { expressPatternToRegex: function (pattern) {
pattern = pattern.replace(/(:\w+)(\([^\)]+\))/gi, '$2'); pattern = pattern.replace(/(:\w+)(\([^)]+\))/gi, '$2');
return pattern.replace(/(:\w+)/gi, '.+?'); return pattern.replace(/(:\w+)/gi, '.+?');
}, },
@ -84,7 +84,7 @@ var FastlyConfigMethods = {
return 'redirects/' + route.pattern; return 'redirects/' + route.pattern;
}, },
/** /*
* Returns custom vcl configuration as a string that sets the varnish * Returns custom vcl configuration as a string that sets the varnish
* Time to Live (TTL) for responses that come from s3. * Time to Live (TTL) for responses that come from s3.
* *

View file

@ -19,8 +19,8 @@ module.exports = function (apiKey, serviceId) {
* *
* @return {string} * @return {string}
*/ */
fastly.getFastlyAPIPrefix = function (serviceId, version) { fastly.getFastlyAPIPrefix = function (servId, version) {
return '/service/' + encodeURIComponent(serviceId) + '/version/' + version; return '/service/' + encodeURIComponent(servId) + '/version/' + version;
}; };
/* /*
@ -37,10 +37,10 @@ module.exports = function (apiKey, serviceId) {
if (err) { if (err) {
return cb('Failed to fetch versions: ' + err); return cb('Failed to fetch versions: ' + err);
} }
var latestVersion = versions.reduce(function (latestVersion, version) { var latestVersion = versions.reduce(function (lateVersion, version) {
if (!latestVersion) return version; if (!lateVersion) return version;
if (version.number > latestVersion.number) return version; if (version.number > lateVersion.number) return version;
return latestVersion; return lateVersion;
}); });
return cb(null, latestVersion); return cb(null, latestVersion);
}); });
@ -63,16 +63,16 @@ module.exports = function (apiKey, serviceId) {
var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/condition'; var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/condition';
return this.request('PUT', putUrl, condition, function (err, response) { return this.request('PUT', putUrl, condition, function (err, response) {
if (err && err.statusCode === 404) { if (err && err.statusCode === 404) {
this.request('POST', postUrl, condition, function (err, response) { this.request('POST', postUrl, condition, function (e, resp) {
if (err) { if (e) {
return cb('Failed while inserting condition \"' + condition.statement + '\": ' + err); return cb('Failed while inserting condition "' + condition.statement + '": ' + e);
} }
return cb(null, response); return cb(null, resp);
}); });
return; return;
} }
if (err) { if (err) {
return cb('Failed to update condition \"' + condition.statement + '\": ' + err); return cb('Failed to update condition "' + condition.statement + '": ' + err);
} }
return cb(null, response); return cb(null, response);
}.bind(this)); }.bind(this));
@ -95,11 +95,11 @@ module.exports = function (apiKey, serviceId) {
var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/header'; var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/header';
return this.request('PUT', putUrl, header, function (err, response) { return this.request('PUT', putUrl, header, function (err, response) {
if (err && err.statusCode === 404) { if (err && err.statusCode === 404) {
this.request('POST', postUrl, header, function (err, response) { this.request('POST', postUrl, header, function (e, resp) {
if (err) { if (e) {
return cb('Failed to insert header: ' + err); return cb('Failed to insert header: ' + e);
} }
return cb(null, response); return cb(null, resp);
}); });
return; return;
} }
@ -127,11 +127,11 @@ module.exports = function (apiKey, serviceId) {
var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/response_object'; var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/response_object';
return this.request('PUT', putUrl, responseObj, function (err, response) { return this.request('PUT', putUrl, responseObj, function (err, response) {
if (err && err.statusCode === 404) { if (err && err.statusCode === 404) {
this.request('POST', postUrl, responseObj, function (err, response) { this.request('POST', postUrl, responseObj, function (e, resp) {
if (err) { if (e) {
return cb('Failed to insert response object: ' + err); return cb('Failed to insert response object: ' + e);
} }
return cb(null, response); return cb(null, resp);
}); });
return; return;
} }
@ -166,7 +166,7 @@ module.exports = function (apiKey, serviceId) {
this.request('PUT', url, cb); this.request('PUT', url, cb);
}; };
/** /*
* Upsert a custom vcl file. Attempts a PUT, and falls back * Upsert a custom vcl file. Attempts a PUT, and falls back
* to POST if not there already. * to POST if not there already.
* *
@ -186,16 +186,16 @@ module.exports = function (apiKey, serviceId) {
return this.request('PUT', url, content, function (err, response) { return this.request('PUT', url, content, function (err, response) {
if (err && err.statusCode === 404) { if (err && err.statusCode === 404) {
content.name = name; content.name = name;
this.request('POST', postUrl, content, function (err, response) { this.request('POST', postUrl, content, function (e, resp) {
if (err) { if (e) {
return cb('Failed while adding custom vcl \"' + name + '\": ' + err); return cb('Failed while adding custom vcl "' + name + '": ' + e);
} }
return cb(null, response); return cb(null, resp);
}); });
return; return;
} }
if (err) { if (err) {
return cb('Failed to update custom vcl \"' + name + '\": ' + err); return cb('Failed to update custom vcl "' + name + '": ' + err);
} }
return cb(null, response); return cb(null, response);
}.bind(this)); }.bind(this));

View file

@ -1,10 +1,12 @@
/** /*
* Constructor * Constructor
*/ */
function Handler (route) { const Handler = function (route) {
// Handle redirects // Handle redirects
if (route.redirect) { if (route.redirect) {
return (req, res) => { res.redirect(route.redirect); }; return (req, res) => {
res.redirect(route.redirect);
};
} }
var url = '/' + route.name + '.html'; var url = '/' + route.name + '.html';
@ -12,9 +14,9 @@ function Handler (route) {
req.url = url; req.url = url;
next(); next();
}; };
} };
/** /*
* Export a new instance * Export a new instance
*/ */
module.exports = function (route) { module.exports = function (route) {

View file

@ -1,4 +1,5 @@
{ {
"ab": "Аҧсшәа",
"ar": "العربية", "ar": "العربية",
"an": "Aragonés", "an": "Aragonés",
"ast": "Asturianu", "ast": "Asturianu",

View file

@ -33,24 +33,27 @@
"devDependencies": { "devDependencies": {
"async": "1.5.2", "async": "1.5.2",
"autoprefixer": "6.3.6", "autoprefixer": "6.3.6",
"babel-core": "6.10.4", "babel-cli": "6.26.0",
"babel-eslint": "5.0.4", "babel-core": "6.23.1",
"babel-loader": "6.2.4", "babel-eslint": "8.0.2",
"babel-preset-es2015": "6.9.0", "babel-loader": "7.1.0",
"babel-preset-react": "6.11.1", "babel-preset-es2015": "6.22.0",
"babel-preset-react": "6.22.0",
"cheerio": "1.0.0-rc.2", "cheerio": "1.0.0-rc.2",
"classnames": "2.1.3", "classnames": "2.2.5",
"cookie": "0.2.2", "cookie": "0.2.2",
"copy-webpack-plugin": "0.2.0", "copy-webpack-plugin": "0.2.0",
"create-react-class": "15.6.2",
"css-loader": "0.23.1", "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-json": "1.2.0",
"eslint-plugin-react": "3.3.1", "eslint-plugin-react": "7.4.0",
"exenv": "1.2.0", "exenv": "1.2.0",
"fastly": "1.2.1", "fastly": "1.2.1",
"file-loader": "0.8.4", "file-loader": "0.8.4",
"formsy-react": "0.18.0", "formsy-react": "0.19.5",
"formsy-react-components": "0.7.1", "formsy-react-components": "0.11.1",
"git-bundle-sha": "0.0.2", "git-bundle-sha": "0.0.2",
"glob": "5.0.15", "glob": "5.0.15",
"google-libphonenumber": "1.0.21", "google-libphonenumber": "1.0.21",
@ -59,6 +62,7 @@
"json-loader": "0.5.2", "json-loader": "0.5.2",
"json2po-stream": "1.0.3", "json2po-stream": "1.0.3",
"keymirror": "0.1.1", "keymirror": "0.1.1",
"lodash.bindall": "4.4.0",
"lodash.clone": "3.0.3", "lodash.clone": "3.0.3",
"lodash.defaultsdeep": "3.10.0", "lodash.defaultsdeep": "3.10.0",
"lodash.isarray": "3.0.4", "lodash.isarray": "3.0.4",
@ -70,21 +74,22 @@
"node-sass": "4.6.1", "node-sass": "4.6.1",
"pako": "0.2.8", "pako": "0.2.8",
"po2icu": "0.0.2", "po2icu": "0.0.2",
"postcss-loader": "0.8.2", "postcss-loader": "2.0.10",
"prop-types": "15.6.0",
"raven-js": "3.0.4", "raven-js": "3.0.4",
"react": "15.1.0", "react": "15.5.4",
"react-dom": "15.0.1", "react-dom": "15.5.4",
"react-intl": "2.1.2", "react-intl": "2.1.2",
"react-modal": "1.5.2", "react-modal": "3.1.11",
"react-onclickoutside": "4.1.1", "react-onclickoutside": "6.7.1",
"react-redux": "4.4.5", "react-redux": "4.4.5",
"react-responsive": "1.1.4", "react-responsive": "3.0.0",
"react-slick": "0.12.2", "react-slick": "0.12.2",
"react-telephone-input": "3.4.5", "react-telephone-input": "3.8.6",
"redux": "3.5.2", "redux": "3.5.2",
"redux-thunk": "2.0.1", "redux-thunk": "2.0.1",
"sass-lint": "1.5.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", "scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master",
"slick-carousel": "1.5.8", "slick-carousel": "1.5.8",
"source-map-support": "0.3.2", "source-map-support": "0.3.2",
@ -92,8 +97,8 @@
"tap": "7.1.2", "tap": "7.1.2",
"url-loader": "0.5.6", "url-loader": "0.5.6",
"watch": "0.16.0", "watch": "0.16.0",
"webpack": "1.12.14", "webpack": "2.7.0",
"webpack-dev-middleware": "1.2.0", "webpack-dev-middleware": "2.0.4",
"xhr": "2.2.0" "xhr": "2.2.0"
}, },
"nyc": { "nyc": {

11
src/.eslintrc.js Normal file
View file

@ -0,0 +1,11 @@
module.exports = {
root: true,
extends: ['scratch', 'scratch/es6', 'scratch/react'],
env: {
browser: true
},
globals: {
process: true
},
plugins: ['json']
};

View file

@ -1,33 +1,35 @@
var classNames = require('classnames'); const bindAll = require('lodash.bindall');
var React = require('react'); const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./accordion.scss'); require('./accordion.scss');
var Accordion = React.createClass({ class Accordion extends React.Component {
type: 'Accordion', constructor (props) {
getDefaultProps: function () { super(props);
return { bindAll(this, [
titleAs: 'div', 'handleClick'
contentAs: 'div' ]);
}; this.state = {
},
getInitialState: function () {
return {
isOpen: false isOpen: false
}; };
}, }
toggleContent: function () { handleClick (e) {
e.preventDefault();
this.setState({isOpen: !this.state.isOpen}); this.setState({isOpen: !this.state.isOpen});
}, }
render: function () { render () {
var classes = classNames({ const classes = classNames({
'content': true, content: true,
'open': this.state.isOpen open: this.state.isOpen
}); });
return ( return (
<div className="accordion"> <div className="accordion">
<this.props.titleAs className="title" <this.props.titleAs
onClick={this.toggleContent}> className="title"
onClick={this.handleClick}
>
{this.props.title} {this.props.title}
</this.props.titleAs> </this.props.titleAs>
<this.props.contentAs className={classes}> <this.props.contentAs className={classes}>
@ -36,6 +38,16 @@ var Accordion = React.createClass({
</div> </div>
); );
} }
}); }
Accordion.propTypes = {
content: PropTypes.node,
title: PropTypes.string
};
Accordion.defaultProps = {
contentAs: 'div',
titleAs: 'div'
};
module.exports = Accordion; module.exports = Accordion;

View file

@ -1,37 +1,39 @@
var React = require('react'); const bindAll = require('lodash.bindall');
var connect = require('react-redux').connect; 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'); require('./adminpanel.scss');
var AdminPanel = React.createClass({ class AdminPanel extends React.Component {
type: 'AdminPanel', constructor (props) {
getInitialState: function () { super(props);
return { bindAll(this, [
'handleToggleVisibility'
]);
this.state = {
showPanel: false showPanel: false
}; };
}, }
handleToggleVisibility: function (e) { handleToggleVisibility (e) {
e.preventDefault(); e.preventDefault();
this.setState({showPanel: !this.state.showPanel}); 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;
} }
render () {
if (!showAdmin) return false; if (!this.props.isAdmin) return false;
if (this.state.showPanel) { if (this.state.showPanel) {
return ( return (
<div id="admin-panel" className="visible"> <div
className="visible"
id="admin-panel"
>
<span <span
className="toggle" className="toggle"
onClick={this.handleToggleVisibility}> onClick={this.handleToggleVisibility}
>
x x
</span> </span>
<div className="admin-header"> <div className="admin-header">
@ -44,8 +46,15 @@ var AdminPanel = React.createClass({
<dd> <dd>
<ul className="cache-list"> <ul className="cache-list">
<li> <li>
<form method="post" action="/scratch_admin/page/clear-anon-cache/"> <form
<input type="hidden" name="path" value="/" /> action="/scratch_admin/page/clear-anon-cache/"
method="post"
>
<input
name="path"
type="hidden"
value="/"
/>
<div className="button-row"> <div className="button-row">
<span>For anonymous users:</span> <span>For anonymous users:</span>
<Button type="submit"> <Button type="submit">
@ -60,27 +69,32 @@ var AdminPanel = React.createClass({
</div> </div>
</div> </div>
); );
} else { }
return ( return (
<div id="admin-panel" className="hidden"> <div
className="hidden"
id="admin-panel"
>
<span <span
className="toggle" className="toggle"
onClick={this.handleToggleVisibility}> onClick={this.handleToggleVisibility}
>
&gt; &gt;
</span> </span>
</div> </div>
); );
} }
} }
AdminPanel.propTypes = {
children: PropTypes.node,
isAdmin: PropTypes.bool
};
const mapStateToProps = state => ({
isAdmin: state.permissions.admin
}); });
var mapStateToProps = function (state) { const ConnectedAdminPanel = connect(mapStateToProps)(AdminPanel);
return {
session: state.session
};
};
var ConnectedAdminPanel = connect(mapStateToProps)(AdminPanel);
module.exports = ConnectedAdminPanel; module.exports = ConnectedAdminPanel;

View file

@ -1,23 +1,22 @@
var React = require('react'); const classNames = require('classnames');
var classNames = require('classnames'); const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
var Avatar = React.createClass({ const Avatar = props => (
type: 'Avatar', <img
propTypes: { className={classNames('avatar', props.className)}
src: React.PropTypes.string {...omit(props, ['className'])}
}, />
getDefaultProps: function () { );
return {
Avatar.propTypes = {
className: PropTypes.string,
src: PropTypes.string
};
Avatar.defaultProps = {
src: '//cdn2.scratch.mit.edu/get_image/user/2584924_24x24.png?v=1438702210.96' src: '//cdn2.scratch.mit.edu/get_image/user/2584924_24x24.png?v=1438702210.96'
}; };
},
render: function () {
var classes = classNames(
'avatar',
this.props.className
);
return <img {... this.props} className={classes} />;
}
});
module.exports = Avatar; module.exports = Avatar;

View file

@ -1,40 +1,38 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./box.scss'); require('./box.scss');
var Box = React.createClass({ const Box = props => (
type: 'Box', <div className={classNames('box', props.className)}>
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 (
<div className={classes}>
<div className="box-header"> <div className="box-header">
<h4>{this.props.title}</h4> <h4>{props.title}</h4>
<h5>{this.props.subtitle}</h5> <h5>{props.subtitle}</h5>
<p> <p>
<a href={this.props.moreHref} {...this.props.moreProps}> <a
{this.props.moreTitle} href={props.moreHref}
{...props.moreProps}
>
{props.moreTitle}
</a> </a>
</p> </p>
</div> </div>
<div className="box-content"> <div className="box-content">
{this.props.children} {props.children}
</div> </div>
</div> </div>
); );
}
}); 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; module.exports = Box;

View file

@ -1,17 +1,18 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./card.scss'); require('./card.scss');
var Card = React.createClass({ const Card = props => (
displayName: 'Card', <div className={classNames(['card', props.className])}>
render: function () { {props.children}
return (
<div className={classNames(['card', this.props.className])}>
{this.props.children}
</div> </div>
); );
}
}); Card.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = Card; module.exports = Card;

View file

@ -1,35 +1,19 @@
var classNames = require('classnames'); const classNames = require('classnames');
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var React = require('react'); const PropTypes = require('prop-types');
var Slider = require('react-slick'); 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.scss');
require('slick-carousel/slick/slick-theme.scss'); require('slick-carousel/slick/slick-theme.scss');
require('./carousel.scss'); require('./carousel.scss');
/** const Carousel = props => {
* Displays content in horizontal scrolling box. Example usage: splash page rows. defaults(props.settings, {
*/
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, centerMode: false,
dots: false, dots: false,
infinite: false, infinite: false,
@ -38,58 +22,94 @@ var Carousel = React.createClass({
slidesToScroll: 5, slidesToScroll: 5,
variableWidth: true, variableWidth: true,
responsive: [ responsive: [
{breakpoint: frameless.mobile, settings: { {
breakpoint: frameless.mobile,
settings: {
arrows: true, arrows: true,
slidesToScroll: 1, slidesToScroll: 1,
slidesToShow: 1, slidesToShow: 1,
centerMode: true centerMode: true
}}, }
{breakpoint: frameless.tablet, settings: { },
{
breakpoint: frameless.tablet,
settings: {
slidesToScroll: 2, slidesToScroll: 2,
slidesToShow: 2 slidesToShow: 2
}}, }
{breakpoint: frameless.desktop, settings: { },
{
breakpoint: frameless.desktop,
settings: {
slidesToScroll: 4, slidesToScroll: 4,
slidesToShow: 4 slidesToShow: 4
}} }
}
] ]
}); });
var arrows = this.props.items.length > settings.slidesToShow; const arrows = props.items.length > props.settings.slidesToShow;
var classes = classNames(
'carousel',
this.props.className
);
return ( return (
<Slider className={classes} arrows={arrows} {... settings}> <Slider
{this.props.items.map(function (item) { arrows={arrows}
var href = ''; className={classNames('carousel', props.className)}
switch (this.props.type) { {... props.settings}
>
{props.items.map(item => {
let href = '';
switch (props.type) {
case 'gallery': case 'gallery':
href = '/studios/' + item.id + '/'; href = `/studios/${item.id}/`;
break; break;
case 'project': case 'project':
href = '/projects/' + item.id + '/'; href = `/projects/${item.id}/`;
break; break;
default: default:
href = '/' + item.type + '/' + item.id + '/'; href = `/${item.type}/${item.id}/`;
} }
return ( return (
<Thumbnail key={[this.key, item.id].join('.')} <Thumbnail
showLoves={this.props.showLoves}
showRemixes={this.props.showRemixes}
type={this.props.type}
href={href}
title={item.title}
src={item.image}
creator={item.author.username} creator={item.author.username}
href={href}
key={[props.type, item.id].join('.')}
loves={item.stats.loves}
remixes={item.stats.remixes} remixes={item.stats.remixes}
loves={item.stats.loves} /> showLoves={props.showLoves}
showRemixes={props.showRemixes}
src={item.image}
title={item.title}
type={props.type}
/>
); );
}.bind(this))} })}
</Slider> </Slider>
); );
} };
});
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; module.exports = Carousel;

View file

@ -1,37 +1,22 @@
// This component handles json returned via proxy from a django server, // This component handles json returned via proxy from a django server,
// or directly from a django server, and the model structure that system // or directly from a django server, and the model structure that system
// has. // has.
var classNames = require('classnames'); const classNames = require('classnames');
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var React = require('react'); const PropTypes = require('prop-types');
var Slider = require('react-slick'); 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.scss');
require('slick-carousel/slick/slick-theme.scss'); require('slick-carousel/slick/slick-theme.scss');
require('./carousel.scss'); require('./carousel.scss');
/** const Carousel = props => {
* Displays content in horizontal scrolling box. Example usage: splash page rows. defaults(props.settings, {
*/
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, centerMode: false,
dots: false, dots: false,
infinite: false, infinite: false,
@ -40,58 +25,94 @@ var LegacyCarousel = React.createClass({
slidesToScroll: 5, slidesToScroll: 5,
variableWidth: true, variableWidth: true,
responsive: [ responsive: [
{breakpoint: frameless.mobile, settings: { {
breakpoint: frameless.mobile,
settings: {
arrows: true, arrows: true,
slidesToScroll: 1, slidesToScroll: 1,
slidesToShow: 1, slidesToShow: 1,
centerMode: true centerMode: true
}}, }
{breakpoint: frameless.tablet, settings: { },
{
breakpoint: frameless.tablet,
settings: {
slidesToScroll: 2, slidesToScroll: 2,
slidesToShow: 2 slidesToShow: 2
}}, }
{breakpoint: frameless.desktop, settings: { },
{
breakpoint: frameless.desktop,
settings: {
slidesToScroll: 4, slidesToScroll: 4,
slidesToShow: 4 slidesToShow: 4
}} }
}
] ]
}); });
var arrows = this.props.items.length > settings.slidesToShow; const arrows = props.items.length > props.settings.slidesToShow;
var classes = classNames(
'carousel',
this.props.className
);
return ( return (
<Slider className={classes} arrows={arrows} {... settings}> <Slider
{this.props.items.map(function (item) { arrows={arrows}
var href = ''; className={classNames('carousel', props.className)}
{... props.settings}
>
{props.items.map(item => {
let href = '';
switch (item.type) { switch (item.type) {
case 'gallery': case 'gallery':
href = '/studios/' + item.id + '/'; href = `/studios/${item.id}/`;
break; break;
case 'project': case 'project':
href = '/projects/' + item.id + '/'; href = `/projects/${item.id}/`;
break; break;
default: default:
href = '/' + item.type + '/' + item.id + '/'; href = `/${item.type}/${item.id}/`;
} }
return ( return (
<Thumbnail key={[this.key, item.id].join('.')} <Thumbnail
showLoves={this.props.showLoves}
showRemixes={this.props.showRemixes}
type={item.type}
href={href}
title={item.title}
src={item.thumbnail_url}
creator={item.creator} creator={item.creator}
href={href}
key={[props.type, item.id].join('.')}
loves={item.love_count}
remixes={item.remixers_count} remixes={item.remixers_count}
loves={item.love_count} /> showLoves={props.showLoves}
showRemixes={props.showRemixes}
src={item.thumbnail_url}
title={item.title}
type={item.type}
/>
); );
}.bind(this))} })}
</Slider> </Slider>
); );
} };
});
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;

View file

@ -1,34 +1,33 @@
var classNames = require('classnames'); const classNames = require('classnames');
var FormattedRelative = require('react-intl').FormattedRelative; const FormattedRelative = require('react-intl').FormattedRelative;
var React = require('react'); 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'); require('./comment.scss');
var CommentText = React.createClass({ const CommentText = props => (
type: 'CommentText', <div className={classNames('comment-text', props.className)}>
propTypes: { <EmojiText
comment: React.PropTypes.string.isRequired, className="mod-comment"
datetimeCreated: React.PropTypes.string, text={props.comment}
className: React.PropTypes.string />
}, {typeof props.datetimeCreated === 'undefined' ? [] : [
render: function () { <p
var classes = classNames( className="comment-text-timestamp"
'comment-text', key="comment-text-timestamp"
this.props.class >
); <FormattedRelative value={new Date(props.datetimeCreated)} />
return (
<div className={classes}>
<EmojiText className="mod-comment" text={this.props.comment} />
{typeof this.props.datetimeCreated !== 'undefined' ? [
<p className="comment-text-timestamp">
<FormattedRelative value={new Date(this.props.datetimeCreated)} />
</p> </p>
] : []} ]}
</div> </div>
); );
}
}); CommentText.propTypes = {
className: PropTypes.string,
comment: PropTypes.string.isRequired,
datetimeCreated: PropTypes.string
};
module.exports = CommentText; module.exports = CommentText;

View file

@ -1,22 +1,29 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./deck.scss'); require('./deck.scss');
var Deck = React.createClass({ const Deck = props => (
displayName: 'Deck', <div className={classNames(['deck', props.className])}>
render: function () {
return (
<div className={classNames(['deck', this.props.className])}>
<div className="inner"> <div className="inner">
<a href="/" aria-label="Scratch"> <a
<img className="logo" src="/images/logo_sm.png" /> aria-label="Scratch"
href="/"
>
<img
className="logo"
src="/images/logo_sm.png"
/>
</a> </a>
{this.props.children} {props.children}
</div> </div>
</div> </div>
); );
}
}); Deck.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = Deck; module.exports = Deck;

View file

@ -1,33 +1,29 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./banner.scss'); require('./banner.scss');
/** const Banner = props => (
* Container for messages displayed below the nav bar that can be dismissed <div className={classNames('banner', props.className)}>
* (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 (
<div className={classes}>
<div className="inner"> <div className="inner">
{this.props.children} {props.children}
{this.props.onRequestDismiss ? [ {props.onRequestDismiss ? [
<a className="close" key="close" href="#" onClick={this.props.onRequestDismiss}>x</a> <a
className="close"
href="#"
key="close"
onClick={props.onRequestDismiss}
>x</a>
] : []} ] : []}
</div> </div>
</div> </div>
); );
}
}); Banner.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
onRequestDismiss: PropTypes.func
};
module.exports = Banner; module.exports = Banner;

View file

@ -1,40 +1,46 @@
var React = require('react'); const bindAll = require('lodash.bindall');
var classNames = require('classnames'); const classNames = require('classnames');
const onClickOutside = require('react-onclickoutside').default;
const PropTypes = require('prop-types');
const React = require('react');
require('./dropdown.scss'); require('./dropdown.scss');
var Dropdown = React.createClass({ class Dropdown extends React.Component {
type: 'Dropdown', constructor (props) {
mixins: [ super(props);
require('react-onclickoutside') bindAll(this, [
], 'handleClickOutside'
propTypes: { ]);
onRequestClose: React.PropTypes.func, }
isOpen: React.PropTypes.bool handleClickOutside () {
},
getDefaultProps: function () {
return {
as: 'div',
isOpen: false
};
},
handleClickOutside: function () {
if (this.props.isOpen) { if (this.props.isOpen) {
this.props.onRequestClose(); this.props.onRequestClose();
} }
}, }
render: function () { render () {
var classes = classNames(
'dropdown',
this.props.className,
{open: this.props.isOpen}
);
return ( return (
<this.props.as className={classes}> <this.props.as
className={classNames('dropdown', this.props.className, {
open: this.props.isOpen
})}
>
{this.props.children} {this.props.children}
</this.props.as> </this.props.as>
); );
} }
}); }
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);

View file

@ -24,9 +24,13 @@
} }
a { a {
&:link,
&:visited,
&:active {
background-color: transparent; background-color: transparent;
color: $type-white; color: $type-white;
} }
}
input { input {
// 100% minus border and padding // 100% minus border and padding

View file

@ -1,33 +1,25 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./emoji-text.scss'); require('./emoji-text.scss');
var EmojiText = React.createClass({ const EmojiText = props => (
type: 'EmojiText', <props.as
propTyes: { className={classNames('emoji-text', props.className)}
text: React.PropTypes.string.isRequired, dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger
className: React.PropTypes.string __html: props.text
},
getDefaultProps: function () {
return {
as: 'p'
};
},
render: function () {
var classes = classNames(
'emoji-text',
this.props.className
);
return (
<this.props.as
className={classes}
dangerouslySetInnerHTML={{
__html: this.props.text
}} }}
/> />
); );
}
}); EmojiText.propTypes = {
className: PropTypes.string,
text: PropTypes.string.isRequired
};
EmojiText.defaultProps = {
as: 'p'
};
module.exports = EmojiText; module.exports = EmojiText;

View file

@ -1,26 +1,22 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./flex-row.scss'); require('./flex-row.scss');
var FlexRow = React.createClass({ const FlexRow = props => (
type: 'FlexRow', <props.as className={classNames('flex-row', props.className)}>
getDefaultProps: function () { {props.children}
return { </props.as>
);
FlexRow.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
FlexRow.defaultProps = {
as: 'div' as: 'div'
}; };
},
render: function () {
var classes = classNames(
'flex-row',
this.props.className
);
return (
<this.props.as className={classes}>
{this.props.children}
</this.props.as>
);
}
});
module.exports = FlexRow; module.exports = FlexRow;

View file

@ -1,58 +1,69 @@
var React = require('react'); const React = require('react');
var FlexRow = require('../../../flex-row/flex-row.jsx'); const FlexRow = require('../../../flex-row/flex-row.jsx');
var FooterBox = require('../../container/footer.jsx'); const FooterBox = require('../../container/footer.jsx');
require('../footer.scss'); require('../footer.scss');
var ConferenceFooter = React.createClass({ const ConferenceFooter = () => (
type: 'ConferenceFooter',
render: function () {
return (
<FooterBox> <FooterBox>
<div className="collaborators"> <div className="collaborators">
<h4>Sponsors</h4> <h4>Sponsors</h4>
<FlexRow as="ul"> <FlexRow as="ul">
<li className="odl"> <li className="odl">
<a href="https://odl.mit.edu/"> <a href="https://odl.mit.edu/">
<img src="/images/conference/footer/mit-odl.png" <img
alt="MIT Office of Digital Learning" /> alt="MIT Office of Digital Learning"
src="/images/conference/footer/mit-odl.png"
/>
</a> </a>
</li> </li>
<li className="intel"> <li className="intel">
<a href="http://www.intel.com/content/www/us/en/homepage.html"> <a href="http://www.intel.com/content/www/us/en/homepage.html">
<img src="/images/conference/footer/intel.png" <img
alt="Intel" /> alt="Intel"
src="/images/conference/footer/intel.png"
/>
</a> </a>
</li> </li>
<li className="lego"> <li className="lego">
<a href="http://www.legofoundation.com/"> <a href="http://www.legofoundation.com/">
<img src="/images/conference/footer/lego-foundation.png" <img
alt="LEGO Foundation" /> alt="LEGO Foundation"
src="/images/conference/footer/lego-foundation.png"
/>
</a> </a>
</li> </li>
<li className="google"> <li className="google">
<a href="http://www.google.com/"> <a href="http://www.google.com/">
<img src="/images/conference/footer/google.png" <img
alt="Google" /> alt="Google"
src="/images/conference/footer/google.png"
/>
</a> </a>
</li> </li>
<li className="siegel"> <li className="siegel">
<a href="http://www.siegelendowment.org/"> <a href="http://www.siegelendowment.org/">
<img src="/images/conference/footer/siegel-endowment.png" <img
alt="Siegel Family Endowment" /> alt="Siegel Family Endowment"
src="/images/conference/footer/siegel-endowment.png"
/>
</a> </a>
</li> </li>
<li className="nostarch"> <li className="nostarch">
<a href="https://www.nostarch.com/"> <a href="https://www.nostarch.com/">
<img src="/images/conference/footer/no-starch.png" <img
alt="No Starch Press" /> alt="No Starch Press"
src="/images/conference/footer/no-starch.png"
/>
</a> </a>
</li> </li>
<li className="scratchfoundation"> <li className="scratchfoundation">
<a href="http://www.scratchfoundation.org/"> <a href="http://www.scratchfoundation.org/">
<img src="/images/conference/footer/scratch-foundation.png" <img
alt="Scratch Foundation" /> alt="Scratch Foundation"
src="/images/conference/footer/scratch-foundation.png"
/>
</a> </a>
</li> </li>
</FlexRow> </FlexRow>
@ -61,25 +72,34 @@ var ConferenceFooter = React.createClass({
<div className="family"> <div className="family">
<h4>Scratch Family</h4> <h4>Scratch Family</h4>
<FlexRow> <FlexRow>
<FlexRow as="ul" className="column"> <FlexRow
as="ul"
className="column"
>
<li> <li>
<a href="https://scratch.mit.edu" target="_blank">Scratch</a> <a href="https://scratch.mit.edu">Scratch</a>
</li> </li>
<li> <li>
<a href="http://www.scratchjr.org/" target="_blank">ScratchJr</a> <a href="http://www.scratchjr.org/">ScratchJr</a>
</li> </li>
</FlexRow> </FlexRow>
<FlexRow as="ul" className="column"> <FlexRow
as="ul"
className="column"
>
<li> <li>
<a href="http://www.scratchfoundation.org/" target="_blank">Scratch Foundation</a> <a href="http://www.scratchfoundation.org/">Scratch Foundation</a>
</li> </li>
<li> <li>
<a href="http://scratched.gse.harvard.edu/" target="_blank">ScratchEd</a> <a href="http://scratched.gse.harvard.edu/">ScratchEd</a>
</li> </li>
</FlexRow> </FlexRow>
<FlexRow as="ul" className="column"> <FlexRow
as="ul"
className="column"
>
<li> <li>
<a href="http://day.scratch.mit.edu" target="_blank">Scratch Day</a> <a href="http://day.scratch.mit.edu">Scratch Day</a>
</li> </li>
</FlexRow> </FlexRow>
</FlexRow> </FlexRow>
@ -91,7 +111,7 @@ var ConferenceFooter = React.createClass({
<div className="contact-us"> <div className="contact-us">
<h4>Contact</h4> <h4>Contact</h4>
<p> <p>
<a href="mailto:help@scratch.mit.edu" target="_blank"> <a href="mailto:help@scratch.mit.edu">
Email Us Email Us
</a> </a>
</p> </p>
@ -99,18 +119,27 @@ var ConferenceFooter = React.createClass({
<div className="social"> <div className="social">
<FlexRow as="ul"> <FlexRow as="ul">
<li> <li>
<a href="//www.twitter.com/scratch" target="_blank"> <a href="//www.twitter.com/scratch">
<img src="/images/conference/footer/twitter.png" alt="scratch twitter" /> <img
alt="scratch twitter"
src="/images/conference/footer/twitter.png"
/>
</a> </a>
</li> </li>
<li> <li>
<a href="//www.facebook.com/scratchteam" target="_blank"> <a href="//www.facebook.com/scratchteam">
<img src="/images/conference/footer/facebook.png" alt="scratch facebook" /> <img
alt="scratch facebook"
src="/images/conference/footer/facebook.png"
/>
</a> </a>
</li> </li>
<li> <li>
<a href="http://medium.com/scratchfoundation-blog" target="_blank"> <a href="http://medium.com/scratchfoundation-blog">
<img src="/images/conference/footer/medium.png" alt="scratch foundation blog" /> <img
alt="scratch foundation blog"
src="/images/conference/footer/medium.png"
/>
</a> </a>
</li> </li>
</FlexRow> </FlexRow>
@ -119,7 +148,5 @@ var ConferenceFooter = React.createClass({
</FlexRow> </FlexRow>
</FooterBox> </FooterBox>
); );
}
});
module.exports = ConferenceFooter; module.exports = ConferenceFooter;

View file

@ -1,55 +1,60 @@
var React = require('react'); const injectIntl = require('react-intl').injectIntl;
var ReactIntl = require('react-intl'); const intlShape = require('react-intl').intlShape;
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
var injectIntl = ReactIntl.injectIntl; const FlexRow = require('../../../flex-row/flex-row.jsx');
var FormattedMessage = ReactIntl.FormattedMessage; const FooterBox = require('../../container/footer.jsx');
const LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
var FlexRow = require('../../../flex-row/flex-row.jsx');
var FooterBox = require('../../container/footer.jsx');
var LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
require('../footer.scss'); require('../footer.scss');
var ConferenceFooter = React.createClass({ const ConferenceFooter = props => (
type: 'ConferenceFooter',
render: function () {
return (
<FooterBox> <FooterBox>
<FlexRow className="scratch-links"> <FlexRow className="scratch-links">
<div className="family"> <div className="family">
<h4><FormattedMessage id='footer.scratchFamily' /></h4> <h4><FormattedMessage id="footer.scratchFamily" /></h4>
<FlexRow> <FlexRow>
<FlexRow as="ul" className="column"> <FlexRow
as="ul"
className="column"
>
<li> <li>
<a href="https://scratch.mit.edu" target="_blank">Scratch</a> <a href="https://scratch.mit.edu">Scratch</a>
</li> </li>
<li> <li>
<a href="http://www.scratchjr.org/" target="_blank">ScratchJr</a> <a href="http://www.scratchjr.org/">ScratchJr</a>
</li> </li>
</FlexRow> </FlexRow>
<FlexRow as="ul" className="column"> <FlexRow
as="ul"
className="column"
>
<li> <li>
<a href="http://www.scratchfoundation.org/" target="_blank">Scratch Foundation</a> <a href="http://www.scratchfoundation.org/">Scratch Foundation</a>
</li> </li>
<li> <li>
<a href="http://scratched.gse.harvard.edu/" target="_blank">ScratchEd</a> <a href="http://scratched.gse.harvard.edu/">ScratchEd</a>
</li> </li>
</FlexRow> </FlexRow>
<FlexRow as="ul" className="column"> <FlexRow
as="ul"
className="column"
>
<li> <li>
<a href="http://day.scratch.mit.edu" target="_blank">Scratch Day</a> <a href="http://day.scratch.mit.edu">Scratch Day</a>
</li> </li>
</FlexRow> </FlexRow>
</FlexRow> </FlexRow>
<p className="legal"> <p className="legal">
<FormattedMessage id='general.copyright' /> <FormattedMessage id="general.copyright" />
</p> </p>
</div> </div>
<div className="media"> <div className="media">
<div className="contact-us"> <div className="contact-us">
<h4>Contact</h4> <h4>Contact</h4>
<p> <p>
<a href="mailto:help@scratch.mit.edu" target="_blank"> <a href="mailto:help@scratch.mit.edu">
Email Us Email Us
</a> </a>
</p> </p>
@ -57,28 +62,39 @@ var ConferenceFooter = React.createClass({
<div className="social"> <div className="social">
<FlexRow as="ul"> <FlexRow as="ul">
<li> <li>
<a href="//www.twitter.com/scratch" target="_blank"> <a href="//www.twitter.com/scratch">
<img src="/images/conference/footer/twitter.png" alt="scratch twitter" /> <img
alt="scratch twitter"
src="/images/conference/footer/twitter.png"
/>
</a> </a>
</li> </li>
<li> <li>
<a href="//www.facebook.com/scratchteam" target="_blank"> <a href="//www.facebook.com/scratchteam">
<img src="/images/conference/footer/facebook.png" alt="scratch facebook" /> <img
alt="scratch facebook"
src="/images/conference/footer/facebook.png"
/>
</a> </a>
</li> </li>
<li> <li>
<a href="http://medium.com/scratchfoundation-blog" target="_blank"> <a href="http://medium.com/scratchfoundation-blog">
<img src="/images/conference/footer/medium.png" alt="scratch foundation blog" /> <img
alt="scratch foundation blog"
src="/images/conference/footer/medium.png"
/>
</a> </a>
</li> </li>
</FlexRow> </FlexRow>
</div> </div>
</div> </div>
</FlexRow> </FlexRow>
<LanguageChooser locale={this.props.intl.locale} /> <LanguageChooser locale={props.intl.locale} />
</FooterBox> </FooterBox>
); );
}
}); ConferenceFooter.propTypes = {
intl: intlShape
};
module.exports = injectIntl(ConferenceFooter); module.exports = injectIntl(ConferenceFooter);

View file

@ -1,55 +1,94 @@
var React = require('react'); const FormattedMessage = require('react-intl').FormattedMessage;
var ReactIntl = require('react-intl'); const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const React = require('react');
var injectIntl = ReactIntl.injectIntl; const FlexRow = require('../../../flex-row/flex-row.jsx');
var FormattedMessage = ReactIntl.FormattedMessage; const FooterBox = require('../../container/footer.jsx');
const LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
var FlexRow = require('../../../flex-row/flex-row.jsx');
var FooterBox = require('../../container/footer.jsx');
var LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
require('../footer.scss'); require('../footer.scss');
var ConferenceFooter = React.createClass({ const ConferenceFooter = props => (
type: 'ConferenceFooter',
render: function () {
return (
<FooterBox> <FooterBox>
<FlexRow className="scratch-links"> <FlexRow className="scratch-links">
<div className="family"> <div className="family">
<h4><FormattedMessage id='footer.scratchFamily' /></h4> <h4><FormattedMessage id="footer.scratchFamily" /></h4>
<FlexRow> <FlexRow>
<FlexRow as="ul" className="column"> <FlexRow
as="ul"
className="column"
>
<li> <li>
<a href="https://scratch.mit.edu" target="_blank">Scratch</a> <a
href="https://scratch.mit.edu"
rel="noopener noreferrer"
target="_blank"
>
Scratch
</a>
</li> </li>
<li> <li>
<a href="http://www.scratchjr.org/" target="_blank">ScratchJr</a> <a
href="http://www.scratchjr.org/"
rel="noopener noreferrer"
target="_blank"
>
ScratchJr
</a>
</li> </li>
</FlexRow> </FlexRow>
<FlexRow as="ul" className="column"> <FlexRow
as="ul"
className="column"
>
<li> <li>
<a href="http://www.scratchfoundation.org/" target="_blank">Scratch Foundation</a> <a
href="http://www.scratchfoundation.org/"
rel="noopener noreferrer"
target="_blank"
>
Scratch Foundation
</a>
</li> </li>
<li> <li>
<a href="http://scratched.gse.harvard.edu/" target="_blank">ScratchEd</a> <a
href="http://scratched.gse.harvard.edu/"
rel="noopener noreferrer"
target="_blank"
>
ScratchEd
</a>
</li> </li>
</FlexRow> </FlexRow>
<FlexRow as="ul" className="column"> <FlexRow
as="ul"
className="column"
>
<li> <li>
<a href="http://day.scratch.mit.edu" target="_blank">Scratch Day</a> <a
href="http://day.scratch.mit.edu"
rel="noopener noreferrer"
target="_blank"
>
Scratch Day
</a>
</li> </li>
</FlexRow> </FlexRow>
</FlexRow> </FlexRow>
<p className="legal"> <p className="legal">
<FormattedMessage id='general.copyright' /> <FormattedMessage id="general.copyright" />
</p> </p>
</div> </div>
<div className="media"> <div className="media">
<div className="contact-us"> <div className="contact-us">
<h4>Contact</h4> <h4>Contact</h4>
<p> <p>
<a href="mailto:conference@scratch.mit.edu" target="_blank"> <a
href="mailto:conference@scratch.mit.edu"
rel="noopener noreferrer"
target="_blank"
>
Email Us Email Us
</a> </a>
</p> </p>
@ -57,28 +96,51 @@ var ConferenceFooter = React.createClass({
<div className="social"> <div className="social">
<FlexRow as="ul"> <FlexRow as="ul">
<li> <li>
<a href="//www.twitter.com/scratch" target="_blank"> <a
<img src="/images/conference/footer/twitter.png" alt="scratch twitter" /> href="//www.twitter.com/scratch"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="scratch twitter"
src="/images/conference/footer/twitter.png"
/>
</a> </a>
</li> </li>
<li> <li>
<a href="//www.facebook.com/scratchteam" target="_blank"> <a
<img src="/images/conference/footer/facebook.png" alt="scratch facebook" /> href="//www.facebook.com/scratchteam"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="scratch facebook"
src="/images/conference/footer/facebook.png"
/>
</a> </a>
</li> </li>
<li> <li>
<a href="http://medium.com/scratchfoundation-blog" target="_blank"> <a
<img src="/images/conference/footer/medium.png" alt="scratch foundation blog" /> href="http://medium.com/scratchfoundation-blog"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="scratch foundation blog"
src="/images/conference/footer/medium.png"
/>
</a> </a>
</li> </li>
</FlexRow> </FlexRow>
</div> </div>
</div> </div>
</FlexRow> </FlexRow>
<LanguageChooser locale={this.props.intl.locale} /> <LanguageChooser locale={props.intl.locale} />
</FooterBox> </FooterBox>
); );
}
}); ConferenceFooter.propTypes = {
intl: intlShape
};
module.exports = injectIntl(ConferenceFooter); module.exports = injectIntl(ConferenceFooter);

View file

@ -1,16 +1,16 @@
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./footer.scss'); require('./footer.scss');
var FooterBox = React.createClass({ const FooterBox = props => (
type: 'FooterBox',
render: function () {
return (
<div className="inner"> <div className="inner">
{this.props.children} {props.children}
</div> </div>
); );
}
}); FooterBox.propTypes = {
children: PropTypes.node
};
module.exports = FooterBox; module.exports = FooterBox;

View file

@ -1,54 +1,51 @@
var React = require('react'); const FormattedMessage = require('react-intl').FormattedMessage;
var ReactIntl = require('react-intl'); const injectIntl = require('react-intl').injectIntl;
var FormattedMessage = ReactIntl.FormattedMessage; const intlShape = require('react-intl').intlShape;
var injectIntl = ReactIntl.injectIntl; const MediaQuery = require('react-responsive').default;
const React = require('react');
var FooterBox = require('../container/footer.jsx'); const FooterBox = require('../container/footer.jsx');
var LanguageChooser = require('../../languagechooser/languagechooser.jsx'); const LanguageChooser = require('../../languagechooser/languagechooser.jsx');
var MediaQuery = require('react-responsive'); const frameless = require('../../../lib/frameless');
var frameless = require('../../../lib/frameless');
require('./footer.scss'); require('./footer.scss');
var Footer = React.createClass({ const Footer = props => (
type: 'Footer',
render: function () {
return (
<FooterBox> <FooterBox>
<MediaQuery maxWidth={frameless.tablet - 1}> <MediaQuery maxWidth={frameless.tablet - 1}>
<div className="lists"> <div className="lists">
<dl> <dl>
<dd> <dd>
<a href="/about"> <a href="/about">
<FormattedMessage id='general.aboutScratch' /> <FormattedMessage id="general.aboutScratch" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/jobs"> <a href="/jobs">
<FormattedMessage id='general.jobs' /> <FormattedMessage id="general.jobs" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/contact-us/"> <a href="/contact-us/">
<FormattedMessage id='general.contactUs' /> <FormattedMessage id="general.contactUs" />
</a> </a>
</dd> </dd>
</dl> </dl>
<dl> <dl>
<dd> <dd>
<a href="/terms_of_use"> <a href="/terms_of_use">
<FormattedMessage id='general.termsOfUse' /> <FormattedMessage id="general.termsOfUse" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/privacy_policy"> <a href="/privacy_policy">
<FormattedMessage id='general.privacyPolicy' /> <FormattedMessage id="general.privacyPolicy" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/community_guidelines"> <a href="/community_guidelines">
<FormattedMessage id='general.guidelines' /> <FormattedMessage id="general.guidelines" />
</a> </a>
</dd> </dd>
</dl> </dl>
@ -58,139 +55,139 @@ var Footer = React.createClass({
<div className="lists"> <div className="lists">
<dl> <dl>
<dt> <dt>
<FormattedMessage id='general.about' /> <FormattedMessage id="general.about" />
</dt> </dt>
<dd> <dd>
<a href="/about"> <a href="/about">
<FormattedMessage id='general.aboutScratch' /> <FormattedMessage id="general.aboutScratch" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/parents/"> <a href="/parents/">
<FormattedMessage id='general.forParents' /> <FormattedMessage id="general.forParents" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/educators"> <a href="/educators">
<FormattedMessage id='general.forEducators' /> <FormattedMessage id="general.forEducators" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/developers"> <a href="/developers">
<FormattedMessage id='general.forDevelopers' /> <FormattedMessage id="general.forDevelopers" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/info/credits"> <a href="/info/credits">
<FormattedMessage id='general.credits' /> <FormattedMessage id="general.credits" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/jobs"> <a href="/jobs">
<FormattedMessage id='general.jobs' /> <FormattedMessage id="general.jobs" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="http://wiki.scratch.mit.edu/wiki/Scratch_Press"> <a href="http://wiki.scratch.mit.edu/wiki/Scratch_Press">
<FormattedMessage id='general.press' /> <FormattedMessage id="general.press" />
</a> </a>
</dd> </dd>
</dl> </dl>
<dl> <dl>
<dt> <dt>
<FormattedMessage id='general.community' /> <FormattedMessage id="general.community" />
</dt> </dt>
<dd> <dd>
<a href="/community_guidelines"> <a href="/community_guidelines">
<FormattedMessage id='general.guidelines' /> <FormattedMessage id="general.guidelines" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/discuss/"> <a href="/discuss/">
<FormattedMessage id='footer.discuss' /> <FormattedMessage id="footer.discuss" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="https://wiki.scratch.mit.edu/"> <a href="https://wiki.scratch.mit.edu/">
<FormattedMessage id='general.wiki' /> <FormattedMessage id="general.wiki" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/statistics/"> <a href="/statistics/">
<FormattedMessage id='general.statistics' /> <FormattedMessage id="general.statistics" />
</a> </a>
</dd> </dd>
</dl> </dl>
<dl> <dl>
<dt> <dt>
<FormattedMessage id='general.support' /> <FormattedMessage id="general.support" />
</dt> </dt>
<dd> <dd>
<a href="/tips"> <a href="/tips">
<FormattedMessage id='general.tips' /> <FormattedMessage id="general.tips" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/info/faq"> <a href="/info/faq">
<FormattedMessage id='general.faq' /> <FormattedMessage id="general.faq" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/download"> <a href="/download">
<FormattedMessage id='general.offlineEditor' /> <FormattedMessage id="general.offlineEditor" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/contact-us/"> <a href="/contact-us/">
<FormattedMessage id='general.contactUs' /> <FormattedMessage id="general.contactUs" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/store"> <a href="/store">
<FormattedMessage id='general.scratchStore' /> <FormattedMessage id="general.scratchStore" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="https://secure.donationpay.org/scratchfoundation/"> <a href="https://secure.donationpay.org/scratchfoundation/">
<FormattedMessage id='general.donate'/> <FormattedMessage id="general.donate" />
</a> </a>
</dd> </dd>
</dl> </dl>
<dl> <dl>
<dt> <dt>
<FormattedMessage id='general.legal'/> <FormattedMessage id="general.legal" />
</dt> </dt>
<dd> <dd>
<a href="/terms_of_use"> <a href="/terms_of_use">
<FormattedMessage id='general.termsOfUse' /> <FormattedMessage id="general.termsOfUse" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/privacy_policy"> <a href="/privacy_policy">
<FormattedMessage id='general.privacyPolicy' /> <FormattedMessage id="general.privacyPolicy" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="/DMCA"> <a href="/DMCA">
<FormattedMessage id='general.dmca' /> <FormattedMessage id="general.dmca" />
</a> </a>
</dd> </dd>
</dl> </dl>
<dl> <dl>
<dt> <dt>
<FormattedMessage id='footer.scratchFamily' /> <FormattedMessage id="footer.scratchFamily" />
</dt> </dt>
<dd> <dd>
<a href="http://scratched.gse.harvard.edu/"> <a href="http://scratched.gse.harvard.edu/">
<FormattedMessage id='general.scratchEd' /> <FormattedMessage id="general.scratchEd" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="http://www.scratchjr.org/"> <a href="http://www.scratchjr.org/">
<FormattedMessage id='general.scratchJr' /> <FormattedMessage id="general.scratchJr" />
</a> </a>
</dd> </dd>
<dd> <dd>
@ -200,27 +197,29 @@ var Footer = React.createClass({
</dd> </dd>
<dd> <dd>
<a href="/conference"> <a href="/conference">
<FormattedMessage id='general.scratchConference' /> <FormattedMessage id="general.scratchConference" />
</a> </a>
</dd> </dd>
<dd> <dd>
<a href="http://www.scratchfoundation.org/"> <a href="http://www.scratchfoundation.org/">
<FormattedMessage id='general.scratchFoundation' /> <FormattedMessage id="general.scratchFoundation" />
</a> </a>
</dd> </dd>
</dl> </dl>
</div> </div>
</MediaQuery> </MediaQuery>
<LanguageChooser locale={this.props.intl.locale} /> <LanguageChooser locale={props.intl.locale} />
<div className="copyright"> <div className="copyright">
<p> <p>
<FormattedMessage id='general.copyright' /> <FormattedMessage id="general.copyright" />
</p> </p>
</div> </div>
</FooterBox> </FooterBox>
); );
}
}); Footer.propTypes = {
intl: intlShape.isRequired
};
module.exports = injectIntl(Footer); module.exports = injectIntl(Footer);

View file

@ -1,22 +1,26 @@
var React = require('react'); const classNames = require('classnames');
var classNames = require('classnames'); const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
require('./button.scss'); require('./button.scss');
var Button = React.createClass({ const Button = props => {
type: 'Button', const classes = classNames('button', props.className);
propTypes: {
},
render: function () {
var classes = classNames(
'button',
this.props.className
);
return ( return (
<button {... this.props} className={classes} >{this.props.children}</button> <button
className={classes}
{...omit(props, ['className', 'children'])}
>
{props.children}
</button>
); );
} };
});
Button.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = Button; module.exports = Button;

View file

@ -1,28 +1,28 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./charcount.scss'); require('./charcount.scss');
var CharCount = React.createClass({ const CharCount = props => (
type: 'CharCount', <p
getDefaultProps: function () { className={classNames('char-count', props.className, {
return { overmax: (props.currentCharacters > props.maxCharacters)
maxCharacters: 0, })}
currentCharacters: 0 >
}; {props.currentCharacters}/{props.maxCharacters}
},
render: function () {
var classes = classNames(
'char-count',
this.props.className,
{overmax: (this.props.currentCharacters > this.props.maxCharacters)}
);
return (
<p className={classes}>
{this.props.currentCharacters}/{this.props.maxCharacters}
</p> </p>
); );
}
}); CharCount.propTypes = {
className: PropTypes.string,
currentCharacters: PropTypes.number,
maxCharacters: PropTypes.number
};
CharCount.defaultProps = {
currentCharacters: 0,
maxCharacters: 0
};
module.exports = CharCount; module.exports = CharCount;

View file

@ -1,25 +1,25 @@
var classNames = require('classnames'); const classNames = require('classnames');
var FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup; const FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup;
var React = require('react'); const PropTypes = require('prop-types');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; const React = require('react');
var inputHOC = require('./input-hoc.jsx');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./row.scss'); require('./row.scss');
require('./checkbox-group.scss'); require('./checkbox-group.scss');
var CheckboxGroup = React.createClass({ const CheckboxGroup = props => (
type: 'CheckboxGroup', <div className={classNames('checkbox-group', props.className)}>
render: function () { <FRCCheckboxGroup
var classes = classNames( className={classNames('checkbox-group', props.className)}
'checkbox-group', {... props}
this.props.className />
);
return (
<div className={classes}>
<FRCCheckboxGroup {... this.props} className={classes} />
</div> </div>
); );
}
}); CheckboxGroup.propTypes = {
className: PropTypes.string
};
module.exports = inputHOC(defaultValidationHOC(CheckboxGroup)); module.exports = inputHOC(defaultValidationHOC(CheckboxGroup));

View file

@ -1,23 +1,23 @@
var classNames = require('classnames'); const classNames = require('classnames');
var FRCCheckbox = require('formsy-react-components').Checkbox; const FRCCheckbox = require('formsy-react-components').Checkbox;
var React = require('react'); const PropTypes = require('prop-types');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; const React = require('react');
var inputHOC = require('./input-hoc.jsx');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./row.scss'); require('./row.scss');
require('./checkbox.scss'); require('./checkbox.scss');
var Checkbox = React.createClass({ const Checkbox = props => (
type: 'Checkbox', <FRCCheckbox
render: function () { rowClassName={classNames('checkbox-row', props.className)}
var classes = classNames( {...props}
'checkbox-row', />
this.props.className
); );
return (
<FRCCheckbox rowClassName={classes} {... this.props} /> Checkbox.propTypes = {
); className: PropTypes.string
} };
});
module.exports = inputHOC(defaultValidationHOC(Checkbox)); module.exports = inputHOC(defaultValidationHOC(Checkbox));

View file

@ -1,47 +1,61 @@
var classNames = require('classnames'); const bindAll = require('lodash.bindall');
var Formsy = require('formsy-react'); const classNames = require('classnames');
var omit = require('lodash.omit'); const Formsy = require('formsy-react');
var React = require('react'); const omit = require('lodash.omit');
var validations = require('./validations.jsx').validations; 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]); Formsy.addValidationRule(validation, validations[validation]);
} }
var Form = React.createClass({ class Form extends React.Component {
getDefaultProps: function () { constructor (props) {
return { super(props);
noValidate: true, bindAll(this, [
onChange: function () {} 'handleChange'
}; ]);
}, this.state = {
getInitialState: function () {
return {
allValues: {} allValues: {}
}; };
}, }
onChange: function (currentValues, isChanged) { handleChange (currentValues, isChanged) {
this.setState({allValues: omit(currentValues, 'all')}); this.setState({allValues: omit(currentValues, 'all')});
this.props.onChange(currentValues, isChanged); this.props.onChange(currentValues, isChanged);
}, }
render: function () { render () {
var classes = classNames(
'form',
this.props.className
);
return ( return (
<Formsy.Form {... this.props} className={classes} ref="formsy" onChange={this.onChange}> <Formsy.Form
{React.Children.map(this.props.children, function (child) { className={classNames('form', this.props.className)}
ref={form => {
this.formsy = form;
}}
onChange={this.handleChange}
{...this.props}
>
{React.Children.map(this.props.children, child => {
if (!child) return child; if (!child) return child;
if (child.props.name === 'all') { if (child.props.name === 'all') {
return React.cloneElement(child, {value: this.state.allValues}); return React.cloneElement(child, {value: this.state.allValues});
} else {
return child;
} }
}.bind(this))} return child;
})}
</Formsy.Form> </Formsy.Form>
); );
} }
}); }
Form.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
onChange: PropTypes.func
};
Form.defaultProps = {
noValidate: true,
onChange: function () {}
};
module.exports = Form; module.exports = Form;

View file

@ -1,5 +1,6 @@
var Formsy = require('formsy-react'); const Formsy = require('formsy-react');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./general-error.scss'); require('./general-error.scss');
@ -10,13 +11,18 @@ require('./general-error.scss');
* give it a name, and apply your validation error to * give it a name, and apply your validation error to
* the name of the GeneralError component. * the name of the GeneralError component.
*/ */
module.exports = Formsy.HOC(React.createClass({ const GeneralError = props => {
render: function () { if (!props.showError()) return null;
if (!this.props.showError()) return null;
return ( return (
<p className="general-error"> <p className="general-error">
{this.props.getErrorMessage()} {props.getErrorMessage()}
</p> </p>
); );
} };
}));
GeneralError.propTypes = {
getErrorMessage: PropTypes.func,
showError: PropTypes.func
};
module.exports = Formsy.HOC(GeneralError);

View file

@ -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({ * Higher-order component for building an input field
getDefaultProps: function () { * @param {React.Component} Component an input component
return { * @return {React.Component} a wrapped input component
*/
module.exports = Component => {
const InputComponent = props => (
<Component
help={props.required ? null : props.messages['general.notRequired']}
{...omit(props, ['messages'])}
/>
);
InputComponent.propTypes = {
messages: PropTypes.shape({
'general.notRequired': PropTypes.string
}),
required: PropTypes.oneOfType([PropTypes.bool, PropTypes.string])
};
InputComponent.defaultProps = {
messages: { messages: {
'general.notRequired': 'Not Required' 'general.notRequired': 'Not Required'
} }
}; };
},
render: function () {
return (
<Component help={this.props.required ? null : this.props.messages['general.notRequired']}
{...this.props} />
);
}
});
return InputComponent; return InputComponent;
}; };

View file

@ -1,46 +1,57 @@
var classNames = require('classnames'); const bindAll = require('lodash.bindall');
var FRCInput = require('formsy-react-components').Input; const classNames = require('classnames');
var React = require('react'); const FRCInput = require('formsy-react-components').Input;
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; const omit = require('lodash.omit');
var inputHOC = require('./input-hoc.jsx'); 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('./input.scss');
require('./row.scss'); require('./row.scss');
var Input = React.createClass({ class Input extends React.Component {
type: 'Input', constructor (props) {
getDefaultProps: function () { super(props);
return {}; bindAll(this, [
}, 'handleInvalid',
getInitialState: function () { 'handleValid'
return { ]);
this.state = {
status: '' status: ''
}; };
}, }
onValid: function () { handleValid () {
this.setState({ this.setState({
status: 'pass' status: 'pass'
}); });
}, }
onInvalid: function () { handleInvalid () {
this.setState({ this.setState({
status: 'fail' status: 'fail'
}); });
}, }
render: function () { render () {
var classes = classNames( return (
<FRCInput
className="input"
rowClassName={classNames(
this.state.status, this.state.status,
this.props.className, this.props.className,
{'no-label': (typeof this.props.label === 'undefined')} {'no-label': (typeof this.props.label === 'undefined')}
); )}
return ( onInvalid={this.handleInvalid}
<FRCInput {... this.props} onValid={this.handleValid}
className="input" {...omit(this.props, ['className'])}
rowClassName={classes} />
onValid={this.onValid}
onInvalid={this.onInvalid} />
); );
} }
}); }
Input.propTypes = {
className: PropTypes.string,
label: PropTypes.string
};
module.exports = inputHOC(defaultValidationHOC(Input)); module.exports = inputHOC(defaultValidationHOC(Input));

View file

@ -1,23 +1,33 @@
var allCountries = require('react-telephone-input/lib/country_data').allCountries; const allCountries = require('react-telephone-input/lib/country_data').allCountries;
var classNames = require('classnames'); const classNames = require('classnames');
var ComponentMixin = require('formsy-react-components').ComponentMixin; const ComponentMixin = require('formsy-react-components').ComponentMixin;
var FormsyMixin = require('formsy-react').Mixin; const createReactClass = require('create-react-class');
var React = require('react'); const FormsyMixin = require('formsy-react').Mixin;
var ReactPhoneInput = require('react-telephone-input/lib/withStyles'); const omit = require('lodash.omit');
var Row = require('formsy-react-components').Row; 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; const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
var inputHOC = require('./input-hoc.jsx'); const inputHOC = require('./input-hoc.jsx');
var intl = require('../../lib/intl.jsx'); const intl = require('../../lib/intl.jsx');
var validationHOCFactory = require('./validations.jsx').validationHOCFactory; 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('./row.scss');
require('./phone-input.scss'); require('./phone-input.scss');
var PhoneInput = React.createClass({ const PhoneInput = createReactClass({ // eslint-disable-line react/prefer-es6-class
displayName: 'PhoneInput', displayName: 'PhoneInput',
propTypes: {
className: PropTypes.string,
defaultCountry: PropTypes.string,
disabled: PropTypes.bool,
name: PropTypes.string,
onChange: PropTypes.func
},
mixins: [ mixins: [
FormsyMixin, FormsyMixin,
ComponentMixin ComponentMixin
@ -31,29 +41,34 @@ var PhoneInput = React.createClass({
defaultCountry: 'us' defaultCountry: 'us'
}; };
}, },
onChangeInput: function (number, country) { handleChangeInput: function (number, country) {
var value = {national_number: number, country_code: country}; const value = {
national_number: number,
country_code: country
};
this.setValue(value); this.setValue(value);
this.props.onChange(this.props.name, value); this.props.onChange(this.props.name, value);
}, },
render: function () { render: function () {
var defaultCountry = PhoneInput.getDefaultProps().defaultCountry; let defaultCountry = PhoneInput.getDefaultProps().defaultCountry;
if (allIso2.indexOf(this.props.defaultCountry.toLowerCase()) !== -1) { if (allIso2.indexOf(this.props.defaultCountry.toLowerCase()) !== -1) {
defaultCountry = this.props.defaultCountry.toLowerCase(); defaultCountry = this.props.defaultCountry.toLowerCase();
} }
return ( return (
<Row {... this.getRowProperties()} <Row
htmlFor={this.getId()} htmlFor={this.getId()}
rowClassName={classNames('phone-input', this.props.className)} rowClassName={classNames('phone-input', this.props.className)}
{...this.getRowProperties()}
> >
<div className="input-group"> <div className="input-group">
<ReactPhoneInput className="form-control" <ReactPhoneInput
{... this.props} className="form-control"
defaultCountry={defaultCountry} defaultCountry={defaultCountry}
onChange={this.onChangeInput} disabled={this.isFormDisabled() || this.props.disabled}
id={this.getId()} id={this.getId()}
label={null} label={null}
disabled={this.isFormDisabled() || this.props.disabled} onChange={this.handleChangeInput}
{...omit(this.props, ['className', 'disabled', 'onChange'])}
/> />
{this.renderHelp()} {this.renderHelp()}
{this.renderErrorMessage()} {this.renderErrorMessage()}
@ -63,7 +78,7 @@ var PhoneInput = React.createClass({
} }
}); });
var phoneValidationHOC = validationHOCFactory({ const phoneValidationHOC = validationHOCFactory({
isPhone: <intl.FormattedMessage id="teacherRegistration.validationPhoneNumber" /> isPhone: <intl.FormattedMessage id="teacherRegistration.validationPhoneNumber" />
}); });

View file

@ -1,23 +1,23 @@
var classNames = require('classnames'); const classNames = require('classnames');
var FRCRadioGroup = require('formsy-react-components').RadioGroup; const FRCRadioGroup = require('formsy-react-components').RadioGroup;
var React = require('react'); const PropTypes = require('prop-types');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; const React = require('react');
var inputHOC = require('./input-hoc.jsx');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./row.scss'); require('./row.scss');
require('./radio-group.scss'); require('./radio-group.scss');
var RadioGroup = React.createClass({ const RadioGroup = props => (
type: 'RadioGroup', <FRCRadioGroup
render: function () { className={classNames('radio-group', props.className)}
var classes = classNames( {... props}
'radio-group', />
this.props.className
); );
return (
<FRCRadioGroup {... this.props} className={classes} /> RadioGroup.propTypes = {
); className: PropTypes.string
} };
});
module.exports = inputHOC(defaultValidationHOC(RadioGroup)); module.exports = inputHOC(defaultValidationHOC(RadioGroup));

View file

@ -1,33 +1,31 @@
var classNames = require('classnames'); const classNames = require('classnames');
var defaults = require('lodash.defaultsdeep'); const defaults = require('lodash.defaultsdeep');
var FRCSelect = require('formsy-react-components').Select; const FRCSelect = require('formsy-react-components').Select;
var React = require('react'); const PropTypes = require('prop-types');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; const React = require('react');
var inputHOC = require('./input-hoc.jsx');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./row.scss'); require('./row.scss');
require('./select.scss'); require('./select.scss');
var Select = React.createClass({ const Select = props => {
type: 'Select', if (props.required && !props.value) {
propTypes: { props = defaults({}, props, {value: props.options[0].value});
},
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 ( return (
<div className={classes}> <div className={classNames('select', props.className)}>
<FRCSelect {...props} /> <FRCSelect {...props} />
</div> </div>
); );
} };
});
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)); module.exports = inputHOC(defaultValidationHOC(Select));

View file

@ -1,25 +1,25 @@
var classNames = require('classnames'); const classNames = require('classnames');
var FRCTextarea = require('formsy-react-components').Textarea; const FRCTextarea = require('formsy-react-components').Textarea;
var React = require('react'); const omit = require('lodash.omit');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; const PropTypes = require('prop-types');
var inputHOC = require('./input-hoc.jsx'); const React = require('react');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./row.scss'); require('./row.scss');
require('./textarea.scss'); require('./textarea.scss');
var TextArea = React.createClass({ const TextArea = props => (
type: 'TextArea', <FRCTextarea
render: function () {
var classes = classNames(
'textarea-row',
this.props.className
);
return (
<FRCTextarea {... this.props}
className="textarea" className="textarea"
rowClassName={classes} /> rowClassName={classNames('textarea-row', props.className)}
{...omit(props, ['className'])}
/>
); );
}
}); TextArea.propTypes = {
className: PropTypes.string
};
module.exports = inputHOC(defaultValidationHOC(TextArea)); module.exports = inputHOC(defaultValidationHOC(TextArea));

View file

@ -1,48 +1,46 @@
var defaults = require('lodash.defaultsdeep'); const defaults = require('lodash.defaultsdeep');
var intl = require('../../lib/intl.jsx'); const intl = require('../../lib/intl.jsx');
var libphonenumber = require('google-libphonenumber'); const libphonenumber = require('google-libphonenumber');
var phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance(); const omit = require('lodash.omit');
var React = require('react'); const phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance();
const PropTypes = require('prop-types');
module.exports = {}; const React = require('react');
module.exports.validations = { module.exports.validations = {
notEquals: function (values, value, neq) { notEquals: (values, value, neq) => (value !== neq),
return value !== neq; notEqualsField: (values, value, field) => (value !== values[field]),
}, isPhone: (values, value) => {
notEqualsField: function (values, value, field) {
return value !== values[field];
},
isPhone: function (values, value) {
if (typeof value === 'undefined') return true; if (typeof value === 'undefined') return true;
if (value && value.national_number === '+') return true; if (value && value.national_number === '+') return true;
try { 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) { } catch (err) {
return false; return false;
} }
return phoneNumberUtil.isValidNumber(parsed);
} }
}; };
module.exports.validations.notEqualsUsername = module.exports.validations.notEquals; module.exports.validations.notEqualsUsername = module.exports.validations.notEquals;
module.exports.validationHOCFactory = function (defaultValidationErrors) { module.exports.validationHOCFactory = defaultValidationErrors => (Component => {
return function (Component) { const ValidatedComponent = props => (
var ValidatedComponent = React.createClass({ <Component
render: function () { validationErrors={defaults(
var validationErrors = defaults(
{}, {},
defaultValidationErrors, defaultValidationErrors,
this.props.validationErrors props.validationErrors
)}
{...omit(props, ['validationErrors'])}
/>
); );
return (
<Component {...this.props} validationErrors={validationErrors} /> ValidatedComponent.propTypes = {
); validationErrors: PropTypes.object // eslint-disable-line react/forbid-prop-types
} };
});
return ValidatedComponent; return ValidatedComponent;
}; });
};
module.exports.defaultValidationHOC = module.exports.validationHOCFactory({ module.exports.defaultValidationHOC = module.exports.validationHOCFactory({
isDefaultRequiredValue: <intl.FormattedMessage id="form.validationRequired" /> isDefaultRequiredValue: <intl.FormattedMessage id="form.validationRequired" />

View file

@ -1,15 +1,61 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var Thumbnail = require('../thumbnail/thumbnail.jsx'); const Thumbnail = require('../thumbnail/thumbnail.jsx');
var FlexRow = require('../flex-row/flex-row.jsx'); const FlexRow = require('../flex-row/flex-row.jsx');
require('./grid.scss'); require('./grid.scss');
var Grid = React.createClass({ const Grid = props => (
type: 'Grid', <div className={classNames('grid', props.className)}>
getDefaultProps: function () { <FlexRow>
return { {props.items.map((item, key) => {
const href = `/${props.itemType}/${item.id}/`;
if (props.itemType === 'projects') {
return (
<Thumbnail
avatar={`https://cdn2.scratch.mit.edu/get_image/user/${item.author.id}_32x32.png`}
creator={item.author.username}
favorites={item.stats.favorites}
href={href}
key={key}
loves={item.stats.loves}
remixes={item.stats.remixes}
showAvatar={props.showAvatar}
showFavorites={props.showFavorites}
showLoves={props.showLoves}
showRemixes={props.showRemixes}
showViews={props.showViews}
src={item.image}
title={item.title}
type={'project'}
views={item.stats.views}
/>
);
}
return (
<Thumbnail
href={href}
key={key}
owner={item.owner}
src={item.image}
title={item.title}
type={'gallery'}
/>
);
})}
</FlexRow>
</div>
);
Grid.propTypes = {
className: PropTypes.string,
itemType: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.object)
};
Grid.defaultProps = {
items: require('./grid.json'), items: require('./grid.json'),
itemType: 'projects', itemType: 'projects',
showLoves: false, showLoves: false,
@ -18,62 +64,5 @@ var Grid = React.createClass({
showViews: false, showViews: false,
showAvatar: false showAvatar: false
}; };
},
render: function () {
var classes = classNames(
'grid',
this.props.className
);
return (
<div className={classes}>
<FlexRow>
{this.props.items.map(function (item, key) {
var href = '/' + this.props.itemType + '/' + item.id + '/';
if (this.props.itemType == 'projects') {
return (
<Thumbnail
key={key}
showLoves={this.props.showLoves}
showFavorites={this.props.showFavorites}
showRemixes={this.props.showRemixes}
showViews={this.props.showViews}
showAvatar={this.props.showAvatar}
type={'project'}
href={href}
title={item.title}
src={item.image}
avatar={
'https://uploads.scratch.mit.edu/users/avatars/' +
item.author.id +
'.png'
}
creator={item.author.username}
loves={item.stats.loves}
favorites={item.stats.favorites}
remixes={item.stats.remixes}
views={item.stats.views}
/>
);
}
else {
return (
<Thumbnail
key={key}
type={'gallery'}
href={href}
title={item.title}
src={item.image}
srcDefault={'https://uploads.scratch.mit.edu/galleries/thumbnails/default.png'}
owner={item.owner}
/>
);
}
}.bind(this))}
</FlexRow>
</div>
);
}
});
module.exports = Grid; module.exports = Grid;

View file

@ -1,39 +1,34 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
var TitleBanner = require('../../components/title-banner/title-banner.jsx'); const React = require('react');
const TitleBanner = require('../../components/title-banner/title-banner.jsx');
require('./informationpage.scss'); require('./informationpage.scss');
/** /*
* Container for a table of contents * Container for a table of contents
* alongside a long body of text * alongside a long body of text
*/ */
var InformationPage = React.createClass({ const InformationPage = props => (
type: 'InformationPage',
propTypes: {
title: React.PropTypes.string.isRequired
},
render: function () {
var classes = classNames(
'info-outer',
'inner',
this.props.className
);
return (
<div className="information-page"> <div className="information-page">
<TitleBanner className="masthead"> <TitleBanner className="masthead">
<div className="inner"> <div className="inner">
<h1 className="title-banner-h1"> <h1 className="title-banner-h1">
{this.props.title} {props.title}
</h1> </h1>
</div> </div>
</TitleBanner> </TitleBanner>
<div className={classes}> <div className={classNames('info-outer', 'inner', props.className)}>
{this.props.children} {props.children}
</div> </div>
</div> </div>
); );
}
}); InformationPage.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
title: PropTypes.string.isRequired
};
module.exports = InformationPage; module.exports = InformationPage;

View file

@ -1,17 +1,180 @@
var connect = require('react-redux').connect; const bindAll = require('lodash.bindall');
var React = require('react'); 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'); const IframeModal = require('../modal/iframe/modal.jsx');
var Registration = require('../registration/registration.jsx'); const Registration = require('../registration/registration.jsx');
require('./intro.scss'); require('./intro.scss');
var Intro = React.createClass({ class Intro extends React.Component {
type: 'Intro', constructor (props) {
getDefaultProps: function () { super(props);
return { bindAll(this, [
'handleShowVideo',
'handleCloseVideo',
'handleJoinClick',
'handleCloseRegistration',
'handleCompleteRegistration'
]);
this.state = {
videoOpen: false
};
}
handleShowVideo () {
this.setState({videoOpen: true});
}
handleCloseVideo () {
this.setState({videoOpen: false});
}
handleJoinClick (e) {
e.preventDefault();
this.setState({registrationOpen: true});
}
handleCloseRegistration () {
this.setState({registrationOpen: false});
}
handleCompleteRegistration () {
this.props.dispatch(sessionActions.refreshSession());
this.closeRegistration();
}
render () {
return (
<div className="intro">
<div className="content">
<h1
dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger
__html: this.props.messages['intro.tagLine']
}}
/>
<div className="sprites">
<a
className="sprite sprite-1"
href="/projects/editor/?tip_bar=getStarted"
>
<img
alt="Scratch Cat"
className="costume costume-1"
src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-a.png"
/>
<img
alt="Scratch Cat"
className="costume costume-2"
src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-b.png"
/>
<div className="circle" />
<div className="text">
{this.props.messages['intro.tryItOut']}
</div>
</a>
<a
className="sprite sprite-2"
href="/starter_projects/"
>
<img
alt="Tera"
className="costume costume-1"
src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-a.png"
/>
<img
alt="Tera"
className="costume costume-2"
src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-b.png"
/>
<div className="circle" />
<div className="text">
{this.props.messages['intro.seeExamples']}
</div>
</a>
<a
className="sprite sprite-3"
href="#"
onClick={this.handleJoinClick}
>
<img
alt="Gobo"
className="costume costume-1"
src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-a.png"
/>
<img
alt="Gobo"
className="costume costume-2"
src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-b.png"
/>
<div className="circle" />
<div className="text">
{this.props.messages['intro.joinScratch']}
</div>
<div className="text subtext">{this.props.messages['intro.itsFree']}</div>
</a>
<Registration
isOpen={this.state.registrationOpen}
key="registration"
onRegistrationDone={this.handleCompleteRegistration}
onRequestClose={this.handleCloseRegistration}
/>
</div>
<div
className="description"
dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger
__html: this.props.messages['intro.description']
}}
/>
<div className="links">
<a href="/about/">
{this.props.messages['intro.aboutScratch']}
</a>
<a href="/educators/">
{this.props.messages['intro.forEducators']}
</a>
<a
className="last"
href="/parents/"
>
{this.props.messages['intro.forParents']}
</a>
</div>
</div>
<div className="video">
<div
className="play-button"
onClick={this.handleShowVideo}
/>
<img
alt="Intro Video"
src="//cdn.scratch.mit.edu/scratchr2/static/images/hp-video-screenshot.png"
/>
</div>
<IframeModal
className="mod-intro-video"
isOpen={this.state.videoOpen}
src="//player.vimeo.com/video/65583694?title=0&amp;byline=0&amp;portrait=0"
onRequestClose={this.handleCloseVideo}
/>
</div>
);
}
}
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
})
};
Intro.defaultProps = {
messages: { messages: {
'intro.aboutScratch': 'ABOUT SCRATCH', 'intro.aboutScratch': 'ABOUT SCRATCH',
'intro.forEducators': 'FOR EDUCATORS', 'intro.forEducators': 'FOR EDUCATORS',
@ -26,120 +189,11 @@ var Intro = React.createClass({
}, },
session: {} session: {}
}; };
},
getInitialState: function () { const mapStateToProps = state => ({
return { session: state.session
videoOpen: false
};
},
showVideo: function () {
this.setState({videoOpen: true});
},
closeVideo: function () {
this.setState({videoOpen: false});
},
handleJoinClick: function (e) {
e.preventDefault();
this.setState({'registrationOpen': true});
},
closeRegistration: function () {
this.setState({'registrationOpen': false});
},
completeRegistration: function () {
this.props.dispatch(sessionActions.refreshSession());
this.closeRegistration();
},
render: function () {
return (
<div className="intro">
<div className="content">
<h1 dangerouslySetInnerHTML={{__html: this.props.messages['intro.tagLine']}}>
</h1>
<div className="sprites">
<a className="sprite sprite-1" href="/projects/editor/?tip_bar=getStarted">
<img
className="costume costume-1"
src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-a.png"
alt="Scratch Cat" />
<img
className="costume costume-2"
src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-b.png"
alt="Scratch Cat" />
<div className="circle"></div>
<div className="text">
{this.props.messages['intro.tryItOut']}
</div>
</a>
<a className="sprite sprite-2" href="/starter_projects/">
<img
className="costume costume-1"
src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-a.png"
alt="Tera" />
<img
className="costume costume-2"
src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-b.png"
alt="Tera" />
<div className="circle"></div>
<div className="text">
{this.props.messages['intro.seeExamples']}
</div>
</a>
<a className="sprite sprite-3" href="#" onClick={this.handleJoinClick}>
<img
className="costume costume-1"
src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-a.png"
alt="Gobo" />
<img
className="costume costume-2"
src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-b.png"
alt="Gobo" />
<div className="circle"></div>
<div className="text">
{this.props.messages['intro.joinScratch']}
</div>
<div className="text subtext">{this.props.messages['intro.itsFree']}</div>
</a>
<Registration key="registration"
isOpen={this.state.registrationOpen}
onRequestClose={this.closeRegistration}
onRegistrationDone={this.completeRegistration} />
</div>
<div className="description"
dangerouslySetInnerHTML={{__html: this.props.messages['intro.description']}}></div>
<div className="links">
<a href="/about/">
{this.props.messages['intro.aboutScratch']}
</a>
<a href="/educators/">
{this.props.messages['intro.forEducators']}
</a>
<a className="last" href="/parents/">
{this.props.messages['intro.forParents']}
</a>
</div>
</div>
<div className="video">
<div className="play-button" onClick={this.showVideo}></div>
<img src="//cdn.scratch.mit.edu/scratchr2/static/images/hp-video-screenshot.png"
alt="Intro Video" />
</div>
<IframeModal
className="mod-intro-video"
isOpen={this.state.videoOpen}
onRequestClose={this.closeVideo}
src="//player.vimeo.com/video/65583694?title=0&amp;byline=0&amp;portrait=0"
/>
</div>
);
}
}); });
var mapStateToProps = function (state) { const ConnectedIntro = connect(mapStateToProps)(Intro);
return {
session: state.session
};
};
var ConnectedIntro = connect(mapStateToProps)(Intro);
module.exports = ConnectedIntro; module.exports = ConnectedIntro;

View file

@ -1,46 +1,57 @@
var classNames = require('classnames'); const bindAll = require('lodash.bindall');
var React = require('react'); const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
var jar = require('../../lib/jar.js'); const jar = require('../../lib/jar.js');
var languages = require('../../../languages.json'); const languages = require('../../../languages.json');
var Form = require('../forms/form.jsx'); const Form = require('../forms/form.jsx');
var Select = require('../forms/select.jsx'); const Select = require('../forms/select.jsx');
require('./languagechooser.scss'); require('./languagechooser.scss');
/** /**
* Footer dropdown menu that allows one to change their language. * Footer dropdown menu that allows one to change their language.
*/ */
var LanguageChooser = React.createClass({ class LanguageChooser extends React.Component {
type: 'LanguageChooser', constructor (props) {
getDefaultProps: function () { super(props);
return { bindAll(this, [
languages: languages, 'handleSetLanguage'
locale: 'en' ]);
}; }
}, handleSetLanguage (name, value) {
onSetLanguage: function (name, value) {
jar.set('scratchlanguage', value); jar.set('scratchlanguage', value);
window.location.reload(); window.location.reload();
}, }
render: function () { render () {
var classes = classNames( const languageOptions = Object.keys(this.props.languages).map(value => ({
'language-chooser', value: value,
this.props.className label: this.props.languages[value]
); }));
var languageOptions = Object.keys(this.props.languages).map(function (value) {
return {value: value, label: this.props.languages[value]};
}.bind(this));
return ( return (
<Form className={classes}> <Form className={classNames('language-chooser', this.props.className)}>
<Select name="language" <Select
required
name="language"
options={languageOptions} options={languageOptions}
value={this.props.locale} value={this.props.locale}
onChange={this.onSetLanguage} onChange={this.handleSetLanguage}
required /> />
</Form> </Form>
); );
} }
}); }
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; module.exports = LanguageChooser;

View file

@ -1,66 +1,108 @@
var React = require('react'); const bindAll = require('lodash.bindall');
var FormattedMessage = require('react-intl').FormattedMessage; 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'); const Form = require('../forms/form.jsx');
var Input = require('../forms/input.jsx'); const Input = require('../forms/input.jsx');
var Button = require('../forms/button.jsx'); const Button = require('../forms/button.jsx');
var Spinner = require('../spinner/spinner.jsx'); const Spinner = require('../spinner/spinner.jsx');
require('./login.scss'); require('./login.scss');
var Login = React.createClass({ class Login extends React.Component {
type: 'Login', constructor (props) {
propTypes: { super(props);
onLogIn: React.PropTypes.func, bindAll(this, [
error: React.PropTypes.string 'handleSubmit'
}, ]);
getInitialState: function () { this.state = {
return {
waiting: false waiting: false
}; };
}, }
handleSubmit: function (formData) { handleSubmit (formData) {
this.setState({waiting: true}); this.setState({waiting: true});
this.props.onLogIn(formData, function (err) { this.props.onLogIn(formData, err => {
if (err) log.error(err); if (err) log.error(err);
this.setState({waiting: false}); this.setState({waiting: false});
}.bind(this)); });
}, }
render: function () { render () {
var error; let error;
if (this.props.error) { if (this.props.error) {
error = <div className="error">{this.props.error}</div>; error = <div className="error">{this.props.error}</div>;
} }
return ( return (
<div className="login"> <div className="login">
<Form onSubmit={this.handleSubmit}> <Form onSubmit={this.handleSubmit}>
<label htmlFor="username" key="usernameLabel"> <label
<FormattedMessage id='general.username' /> htmlFor="username"
key="usernameLabel"
>
<FormattedMessage id="general.username" />
</label> </label>
<Input type="text" ref="username" name="username" maxLength="30" key="usernameInput" required /> <Input
<label htmlFor="password" key="passwordLabel"> required
<FormattedMessage id='general.password' /> key="usernameInput"
maxLength="30"
name="username"
ref={input => {
this.username = input;
}}
type="text"
/>
<label
htmlFor="password"
key="passwordLabel"
>
<FormattedMessage id="general.password" />
</label> </label>
<Input type="password" ref="password" name="password" key="passwordInput" required /> <Input
required
key="passwordInput"
name="password"
ref={input => {
this.password = input;
}}
type="password"
/>
{this.state.waiting ? [ {this.state.waiting ? [
<Button className="submit-button white" type="submit" disabled="disabled" key="submitButton"> <Button
className="submit-button white"
disabled="disabled"
key="submitButton"
type="submit"
>
<Spinner /> <Spinner />
</Button> </Button>
] : [ ] : [
<Button className="submit-button white" type="submit" key="submitButton"> <Button
<FormattedMessage id='general.signIn' /> className="submit-button white"
key="submitButton"
type="submit"
>
<FormattedMessage id="general.signIn" />
</Button> </Button>
]} ]}
<a className="right" href="/accounts/password_reset/" key="passwordResetLink"> <a
<FormattedMessage id='login.needHelp' /> className="right"
href="/accounts/password_reset/"
key="passwordResetLink"
>
<FormattedMessage id="login.needHelp" />
</a> </a>
{error} {error}
</Form> </Form>
</div> </div>
); );
} }
}); }
Login.propTypes = {
error: PropTypes.string,
onLogIn: PropTypes.func
};
module.exports = Login; module.exports = Login;

View file

@ -1,52 +1,51 @@
var classNames = require('classnames'); const bindAll = require('lodash.bindall');
var React = require('react'); const classNames = require('classnames');
var MediaQuery = require('react-responsive'); const MediaQuery = require('react-responsive').default;
var frameless = require('../../lib/frameless'); const PropTypes = require('prop-types');
const React = require('react');
const frameless = require('../../lib/frameless');
require('./masonrygrid.scss'); require('./masonrygrid.scss');
var MasonryGrid = React.createClass({ class MasonryGrid extends React.Component {
type: 'MasonryGrid', constructor (props) {
getDefaultProps: function () { super(props);
return { bindAll(this, [
as: 'div' 'reorderColumns'
}; ]);
}, }
reorderColumns: function (items, cols) { reorderColumns (items, cols) {
var a1 = []; const a1 = [];
var a2 = []; const a2 = [];
var a3 = []; const a3 = [];
var i = 0; let i = 0;
// only implemented for 2 and 3 columns so far - easy to extend if needed // only implemented for 2 and 3 columns so far - easy to extend if needed
if (cols > 1 && cols < 4) { if (cols > 1 && cols < 4) {
for (i = 0; i < items.length; i++){ for (i = 0; i < items.length; i++){
var col = (i+cols)%cols; const col = (i + cols) % cols;
if (col === 0) { if (col === 0) {
a1.push(items[i]); a1.push(items[i]);
} } else if (col === 1) {
else if (col === 1) {
a2.push(items[i]); a2.push(items[i]);
} } else if (col === 2) {
else if (col === 2) {
a3.push(items[i]); a3.push(items[i]);
} }
} }
return a1.concat(a2, a3); return a1.concat(a2, a3);
} else { }
return items; return items;
} }
}, render () {
render: function () {
var classes = classNames(
'masonry',
this.props.className
);
return ( return (
<this.props.as className={classes}> <this.props.as className={classNames('masonry', this.props.className)}>
<MediaQuery maxWidth={frameless.tablet - 1}> <MediaQuery maxWidth={frameless.tablet - 1}>
{this.props.children} {this.props.children}
</MediaQuery> </MediaQuery>
<MediaQuery minWidth={frameless.tablet} maxWidth={frameless.desktop - 1} > <MediaQuery
maxWidth={frameless.desktop - 1}
minWidth={frameless.tablet}
>
{this.reorderColumns(this.props.children, 2)} {this.reorderColumns(this.props.children, 2)}
</MediaQuery> </MediaQuery>
<MediaQuery minWidth={frameless.desktop}> <MediaQuery minWidth={frameless.desktop}>
@ -55,6 +54,15 @@ var MasonryGrid = React.createClass({
</this.props.as> </this.props.as>
); );
} }
}); }
MasonryGrid.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
MasonryGrid.defaultProps = {
as: 'div'
};
module.exports = MasonryGrid; module.exports = MasonryGrid;

View file

@ -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'); require('./microworld.scss');
var Box = require('../box/box.jsx'); class Microworld extends React.Component {
var LegacyCarousel = require('../carousel/legacy-carousel.jsx'); constructor (props) {
var IframeModal = require('../modal/iframe/modal.jsx'); super(props);
var NestedCarousel = require('../nestedcarousel/nestedcarousel.jsx'); bindAll(this, [
'markVideoOpen',
var Microworld = React.createClass({ 'markVideoClosed',
type: 'Microworld', 'renderVideos',
propTypes: { 'renderVideo',
microworldData: React.PropTypes.node.isRequired 'renderEditorWindow',
}, 'renderTips',
markVideoOpen: function (key) { 'renderStarterProject',
{/* When a video is clicked, mark it as an open video, so the video Modal will open. 'renderProjectIdeasBox',
Key is the number of the video, so distinguish between different videos on the page */} 'renderForum',
'renderDesignStudio'
var videoOpenArr = this.state.videoOpen; ]);
videoOpenArr[key] = true; this.state = {
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 {
videoOpen: {} videoOpen: {}
}; };
}, }
renderVideos: function () { markVideoOpen (key) {
var videos = this.props.microworldData.videos; /*
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) { if (!videos || videos.length <= 0) {
return null; return null;
} }
@ -48,26 +62,32 @@ var Microworld = React.createClass({
</div> </div>
</div> </div>
); );
}, }
renderVideo: function (video, key) { renderVideo (video, key) {
return ( return (
<div> <div>
<div className="video"> <div className="video">
<div className="play-button" onClick={this.markVideoOpen.bind(this, key)}> <div
</div> className="play-button"
onClick={() => { // eslint-disable-line react/jsx-no-bind
this.markVideoOpen(key);
}}
/>
<img src={video.image} /> <img src={video.image} />
</div> </div>
<IframeModal <IframeModal
className="mod-microworld-video" className="mod-microworld-video"
isOpen={this.state.videoOpen[key]} isOpen={this.state.videoOpen[key]}
onRequestClose={this.markVideoClosed.bind(this, key)}
src={video.link} src={video.link}
onRequestClose={() => { // eslint-disable-line react/jsx-no-bind
this.markVideoClosed(key);
}}
/> />
</div> </div>
); );
}, }
renderEditorWindow: function () { renderEditorWindow () {
var projectId = this.props.microworldData.microworld_project_id; const projectId = this.props.microworldData.microworld_project_id;
if (!projectId) { if (!projectId) {
return null; return null;
@ -75,30 +95,37 @@ var Microworld = React.createClass({
return ( return (
<div className="editor section"> <div className="editor section">
<h1 className="sectionheader">Start Creating!</h1> <h1 className="sectionheader">Start Creating!</h1>
<iframe src={'//scratch.mit.edu/projects/embed-editor/' + projectId + '/?isMicroworld=true'} <iframe
frameBorder="0"> </iframe> frameBorder="0"
src={`//scratch.mit.edu/projects/embed-editor/${projectId}/?isMicroworld=true`}
/>
{this.renderTips()} {this.renderTips()}
</div> </div>
); );
}, }
renderTips: function () { renderTips () {
var tips = this.props.microworldData.tips; const tips = this.props.microworldData.tips;
if (!tips || tips.length <= 0) { if (!tips || tips.length <= 0) {
return null; return null;
} }
return ( return (
<div className="box nestedcarousel"> <div className="box nestedcarousel">
<div className="box-header"> <div className="box-header" />
</div>
<div className="box-content"> <div className="box-content">
<NestedCarousel items={tips} settings={{slidesToShow:1,slidesToScroll:1}}/> <NestedCarousel
items={tips}
settings={{
slidesToShow: 1,
slidesToScroll: 1
}}
/>
</div> </div>
</div> </div>
); );
}, }
renderStarterProject: function () { renderStarterProject () {
var starterProjects = this.props.microworldData.starter_projects; const starterProjects = this.props.microworldData.starter_projects;
if (!starterProjects || starterProjects.length <= 0){ if (!starterProjects || starterProjects.length <= 0){
return null; return null;
} }
@ -107,28 +134,30 @@ var Microworld = React.createClass({
<div className="project-ideas"> <div className="project-ideas">
<h1 className="sectionheader">Check out ideas for more projects</h1> <h1 className="sectionheader">Check out ideas for more projects</h1>
<Box <Box
key="starter_projects"
title="More Starter Projects" title="More Starter Projects"
key="starter_projects"> >
<LegacyCarousel items={starterProjects} /> <LegacyCarousel items={starterProjects} />
</Box> </Box>
</div> </div>
); );
}, }
renderProjectIdeasBox: function () { renderProjectIdeasBox () {
var communityProjects = this.props.microworldData.community_projects; const communityProjects = this.props.microworldData.community_projects;
if (!communityProjects || communityProjects.size <= 0) { if (!communityProjects || communityProjects.size <= 0) {
return null; return null;
} }
var featured = communityProjects.featured_projects; const featured = communityProjects.featured_projects;
var all = communityProjects.newest_projects; const all = communityProjects.newest_projects;
var rows = []; const rows = [];
if (featured && featured.length > 0){ if (featured && featured.length > 0){
rows.push( rows.push(
<Box <Box
key="community_featured_projects"
title="Featured Community Projects" title="Featured Community Projects"
key="community_featured_projects"> >
<LegacyCarousel items={featured} /> <LegacyCarousel items={featured} />
</Box> </Box>
); );
@ -136,8 +165,9 @@ var Microworld = React.createClass({
if (all && all.length > 0) { if (all && all.length > 0) {
rows.push( rows.push(
<Box <Box
key="community_all_projects"
title="All Community Projects" title="All Community Projects"
key="community_all_projects"> >
<LegacyCarousel items={all} /> <LegacyCarousel items={all} />
</Box> </Box>
); );
@ -151,8 +181,8 @@ var Microworld = React.createClass({
{rows} {rows}
</div> </div>
); );
}, }
renderForum: function () { renderForum () {
if (!this.props.microworldData.show_forum) { if (!this.props.microworldData.show_forum) {
return null; return null;
} }
@ -163,55 +193,76 @@ var Microworld = React.createClass({
<img src="/images/forum-image.png" /> <img src="/images/forum-image.png" />
</div> </div>
); );
}, }
renderDesignStudio: function () { renderDesignStudio () {
var designChallenge = this.props.microworldData.design_challenge; const designChallenge = this.props.microworldData.design_challenge;
if (!designChallenge) { if (!designChallenge) {
return null; return null;
} }
let studioHref = '';
if (designChallenge.studio_id) { if (designChallenge.studio_id) {
var studioHref = 'https://scratch.mit.edu//studios/' + designChallenge.studio_id + '/'; studioHref = `https://scratch.mit.edu//studios/${designChallenge.studio_id}/`;
} }
if (designChallenge.project_id) { if (designChallenge.project_id) {
return ( return (
<div className="side-by-side section"> <div className="side-by-side section">
<h1 className="sectionheader">Join our Design Challenge!</h1> <h1 className="sectionheader">Join our Design Challenge!</h1>
<div className="design-studio"> <div className="design-studio">
<iframe src={'https://scratch.mit.edu/projects/' + designChallenge.project_id + <iframe
'/#fullscreen'} frameBorder="0"> </iframe> frameBorder="0"
src={`https://scratch.mit.edu/projects/${designChallenge.project_id}/#fullscreen`}
/>
</div> </div>
<div className="design-studio-projects"> <div className="design-studio-projects">
<Box title="Examples" <Box
key="scratch_design_studio" key="scratch_design_studio"
moreHref={studioHref ? studioHref : null}
moreTitle={studioHref ? 'Visit the studio' : null} moreTitle={studioHref ? 'Visit the studio' : null}
moreHref={studioHref ? studioHref : null}> title="Examples"
>
{/* The two carousels are used to show two rows of projects, one above the {/* The two carousels are used to show two rows of projects, one above the
other. This should be probably be changed, to allow better scrolling. */} other. This should be probably be changed, to allow better scrolling. */}
<LegacyCarousel settings={{slidesToShow:2,slidesToScroll:2}} <LegacyCarousel
items={this.props.microworldData.design_challenge.studio1} /> items={this.props.microworldData.design_challenge.studio1}
<LegacyCarousel settings={{slidesToShow:2,slidesToScroll:2}} settings={{
items={this.props.microworldData.design_challenge.studio2} /> slidesToShow: 2,
slidesToScroll: 2
}}
/>
<LegacyCarousel
items={this.props.microworldData.design_challenge.studio2}
settings={{
slidesToShow: 2,
slidesToScroll: 2
}}
/>
</Box> </Box>
</div> </div>
</div> </div>
); );
} else { }
return ( return (
<div className="section"> <div className="section">
<h1 className="sectionheader">Join our Design Challenge!</h1> <h1 className="sectionheader">Join our Design Challenge!</h1>
<Box <Box
title="design Challenge Projects"
key="scratch_design_studio" key="scratch_design_studio"
moreHref={studioHref ? studioHref : null}
moreTitle={studioHref ? 'Visit the studio' : null} moreTitle={studioHref ? 'Visit the studio' : null}
moreHref={studioHref ? studioHref : null}> title="design Challenge Projects"
<LegacyCarousel items={this.props.microworldData.design_challenge.studio1.concat( >
this.props.microworldData.design_challenge.studio2)} /> <LegacyCarousel
</Box> items={
this.props.microworldData.design_challenge.studio1.concat(
this.props.microworldData.design_challenge.studio2
)
}
/>
`</Box>
</div> </div>
); );
} }
}, render () {
render: function () {
return ( return (
<div className="inner microworld"> <div className="inner microworld">
<div className="top-banner section"> <div className="top-banner section">
@ -231,6 +282,10 @@ var Microworld = React.createClass({
); );
} }
}); }
Microworld.propTypes = {
microworldData: PropTypes.node.isRequired
};
module.exports = Microworld; module.exports = Microworld;

View file

@ -1,7 +1,9 @@
var classNames = require('classnames'); const bindAll = require('lodash.bindall');
var omit = require('lodash.omit'); const classNames = require('classnames');
var React = require('react'); const omit = require('lodash.omit');
var ReactModal = require('react-modal'); const PropTypes = require('prop-types');
const React = require('react');
const ReactModal = require('react-modal');
require('./modal.scss'); require('./modal.scss');
@ -10,47 +12,55 @@ ReactModal.setAppElement(document.getElementById('view'));
/** /**
* Container for pop up windows (See: registration window) * Container for pop up windows (See: registration window)
*/ */
var Modal = React.createClass({ class Modal extends React.Component {
type: 'Modal', constructor (props) {
propTypes: { super(props);
className: React.PropTypes.string, bindAll(this, [
overlayClassName: React.PropTypes.string 'handleRequestClose'
}, ]);
requestClose: function () { }
handleRequestClose () {
return this.modal.portal.requestClose(); return this.modal.portal.requestClose();
}, }
render: function () { render () {
var modalClasses = classNames(
'modal-content',
this.props.className
);
var overlayClasses = classNames(
'modal-overlay',
this.props.overlayClassName
);
return ( return (
<ReactModal <ReactModal
ref={ appElement={document.getElementById('view')}
function (component) { className={{
base: classNames('modal-content', this.props.className),
afterOpen: classNames('modal-content', this.props.className),
beforeClose: classNames('modal-content', this.props.className)
}}
overlayClassName={{
base: classNames('modal-overlay', this.props.overlayClassName),
afterOpen: classNames('modal-overlay', this.props.overlayClassName),
beforeClose: classNames('modal-overlay', this.props.overlayClassName)
}}
ref={component => {
this.modal = component; this.modal = component;
}.bind(this) }}
}
className={modalClasses}
overlayClassName={overlayClasses}
{...omit(this.props, ['className', 'overlayClassName'])} {...omit(this.props, ['className', 'overlayClassName'])}
> >
<div className="modal-content-close" onClick={this.requestClose}> <div
className="modal-content-close"
onClick={this.handleRequestClose}
>
<img <img
alt="close-icon"
className="modal-content-close-img" className="modal-content-close-img"
src="/svgs/modal/close-x.svg" src="/svgs/modal/close-x.svg"
alt="close-icon"
/> />
</div> </div>
{this.props.children} {this.props.children}
</ReactModal> </ReactModal>
); );
} }
}); }
Modal.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
overlayClassName: PropTypes.string
};
module.exports = Modal; module.exports = Modal;

View file

@ -21,7 +21,7 @@
background-color: transparentize($ui-blue, .3); background-color: transparentize($ui-blue, .3);
} }
.ReactModal__Content:focus { .modal-content:focus {
outline: none; outline: none;
} }

View file

@ -1,34 +1,28 @@
var classNames = require('classnames'); const classNames = require('classnames');
var omit = require('lodash.omit'); const omit = require('lodash.omit');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var Modal = require('../base/modal.jsx'); const Modal = require('../base/modal.jsx');
require('./modal.scss'); require('./modal.scss');
var IframeModal = React.createClass({ const IframeModal = props => (
propTypes: { <Modal {...omit(props, ['src'])}>
isOpen: React.PropTypes.bool,
onRequestClose: React.PropTypes.func,
className: React.PropTypes.string,
componentRef: React.PropTypes.func,
src: React.PropTypes.string
},
render: function () {
var iframeClasses = classNames(
'modal-content-iframe',
this.props.className
);
return (
<Modal {...omit(this.props, ['src'])}>
<iframe <iframe
ref={this.props.componentRef} className={classNames('modal-content-iframe', props.className)}
src={this.props.src} ref={props.componentRef}
className={iframeClasses} src={props.src}
/> />
</Modal> </Modal>
); );
}
}); IframeModal.propTypes = {
className: PropTypes.string,
componentRef: PropTypes.func,
isOpen: PropTypes.bool,
onRequestClose: PropTypes.func,
src: PropTypes.string
};
module.exports = IframeModal; module.exports = IframeModal;

View file

@ -1,28 +1,23 @@
var FormattedMessage = require('react-intl').FormattedMessage; const FormattedMessage = require('react-intl').FormattedMessage;
var MediaQuery = require('react-responsive'); const MediaQuery = require('react-responsive').default;
var omit = require('lodash.omit'); const omit = require('lodash.omit');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var FlexRow = require('../../flex-row/flex-row.jsx'); const FlexRow = require('../../flex-row/flex-row.jsx');
var frameless = require('../../../lib/frameless'); const frameless = require('../../../lib/frameless');
var Modal = require('../base/modal.jsx'); const Modal = require('../base/modal.jsx');
var TitleBanner = require('../../title-banner/title-banner.jsx'); const TitleBanner = require('../../title-banner/title-banner.jsx');
require('../../forms/button.scss'); require('../../forms/button.scss');
require('./modal.scss'); require('./modal.scss');
var TTTModal = React.createClass({ const TTTModal = props => (
propTypes: { <Modal
title: React.PropTypes.string.isRequired, className="mod-ttt"
description: React.PropTypes.string.isRequired, {...omit(
tutorialLoc: React.PropTypes.string.isRequired, props,
activityLoc: React.PropTypes.string.isRequired, [
guideLoc: React.PropTypes.string.isRequired,
thumbUrl: React.PropTypes.string.isRequired,
bannerUrl: React.PropTypes.string.isRequired
},
render: function () {
var modalOmit = [
'title', 'title',
'description', 'description',
'tutorialLoc', 'tutorialLoc',
@ -30,32 +25,40 @@ var TTTModal = React.createClass({
'guideLoc', 'guideLoc',
'thumbUrl', 'thumbUrl',
'bannerUrl' 'bannerUrl'
]; ]
return ( )}
<Modal
className="mod-ttt"
{...omit(this.props, modalOmit)}
> >
<TitleBanner className="mod-ttt"> <TitleBanner className="mod-ttt">
<MediaQuery minWidth={frameless.mobile}> <MediaQuery minWidth={frameless.mobile}>
<img className="mod-ttt-img" src={this.props.bannerUrl} alt="" /> <img
alt=""
className="mod-ttt-img"
src={props.bannerUrl}
/>
</MediaQuery> </MediaQuery>
<MediaQuery maxWidth={frameless.mobile - 1}> <MediaQuery maxWidth={frameless.mobile - 1}>
<img className="mod-ttt-img" src={this.props.thumbUrl} alt="" /> <img
alt=""
className="mod-ttt-img"
src={props.thumbUrl}
/>
</MediaQuery> </MediaQuery>
</TitleBanner> </TitleBanner>
<div className="ttt-title"> <div className="ttt-title">
<h2>{this.props.title}</h2> <h2>{props.title}</h2>
<p className="ttt-description">{this.props.description}</p> <p className="ttt-description">{props.description}</p>
</div> </div>
<ul className="modal-content-ttt"> <ul className="modal-content-ttt">
<FlexRow as="li" className="mod-ttt-item"> <FlexRow
as="li"
className="mod-ttt-item"
>
<div className="modal-content-ttt-text"> <div className="modal-content-ttt-text">
<div className="modal-content-ttt-title"> <div className="modal-content-ttt-title">
<img <img
alt="tutorial-icon"
className="modal-content-ttt-title-img" className="modal-content-ttt-title-img"
src="/svgs/ttt/tutorial.svg" src="/svgs/ttt/tutorial.svg"
alt="tutorial-icon"
/> />
<FormattedMessage id="ttt.tutorial" /> <FormattedMessage id="ttt.tutorial" />
</div> </div>
@ -64,19 +67,22 @@ var TTTModal = React.createClass({
</p> </p>
</div> </div>
<a <a
href={this.props.tutorialLoc}
className="button white mod-ttt-item" className="button white mod-ttt-item"
href={props.tutorialLoc}
> >
<FormattedMessage id="tile.tryIt" /> <FormattedMessage id="tile.tryIt" />
</a> </a>
</FlexRow> </FlexRow>
<FlexRow as="li" className="mod-ttt-item"> <FlexRow
as="li"
className="mod-ttt-item"
>
<div className="modal-content-ttt-text"> <div className="modal-content-ttt-text">
<div className="modal-content-ttt-title"> <div className="modal-content-ttt-title">
<img <img
alt="activity-cards-icon"
className="modal-content-ttt-title-img" className="modal-content-ttt-title-img"
src="/svgs/ttt/activity-cards.svg" src="/svgs/ttt/activity-cards.svg"
alt="activity-cards-icon"
/> />
<FormattedMessage id="ttt.activityTitle" /> <FormattedMessage id="ttt.activityTitle" />
</div> </div>
@ -85,19 +91,22 @@ var TTTModal = React.createClass({
</p> </p>
</div> </div>
<a <a
href={this.props.activityLoc}
className="button white mod-ttt-item" className="button white mod-ttt-item"
href={props.activityLoc}
> >
<FormattedMessage id="ttt.open" /> <FormattedMessage id="ttt.open" />
</a> </a>
</FlexRow> </FlexRow>
<FlexRow as="li" className="mod-ttt-item"> <FlexRow
as="li"
className="mod-ttt-item"
>
<div className="modal-content-ttt-text"> <div className="modal-content-ttt-text">
<div className="modal-content-ttt-title"> <div className="modal-content-ttt-title">
<img <img
alt="educator-guide-icon"
className="modal-content-ttt-title-img" className="modal-content-ttt-title-img"
src="/svgs/ttt/educator-guide.svg" src="/svgs/ttt/educator-guide.svg"
alt="educator-guide-icon"
/> />
<FormattedMessage id="ttt.educatorTitle" /> <FormattedMessage id="ttt.educatorTitle" />
</div> </div>
@ -106,8 +115,8 @@ var TTTModal = React.createClass({
</p> </p>
</div> </div>
<a <a
href={this.props.guideLoc}
className="button white mod-ttt-item" className="button white mod-ttt-item"
href={props.guideLoc}
> >
<FormattedMessage id="ttt.open" /> <FormattedMessage id="ttt.open" />
</a> </a>
@ -115,7 +124,15 @@ var TTTModal = React.createClass({
</ul> </ul>
</Modal> </Modal>
); );
}
}); TTTModal.propTypes = {
activityLoc: PropTypes.string.isRequired,
bannerUrl: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
guideLoc: PropTypes.string.isRequired,
thumbUrl: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
tutorialLoc: PropTypes.string.isRequired
};
module.exports = TTTModal; module.exports = TTTModal;

View file

@ -1,21 +1,18 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./navigation.scss'); require('./navigation.scss');
var NavigationBox = React.createClass({ const NavigationBox = props => (
type: 'NavigationBox', <div className={classNames('inner', props.className)}>
render: function () { {props.children}
var classes = classNames(
'inner',
this.props.className
);
return (
<div className={classes}>
{this.props.children}
</div> </div>
); );
}
}); NavigationBox.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = NavigationBox; module.exports = NavigationBox;

View file

@ -1,21 +1,21 @@
var React = require('react'); const React = require('react');
var NavigationBox = require('../../base/navigation.jsx'); const NavigationBox = require('../../base/navigation.jsx');
require('./navigation.scss'); require('./navigation.scss');
var Navigation = React.createClass({ const Navigation = () => (
type: 'Navigation',
render: function () {
return (
<NavigationBox> <NavigationBox>
<ul className="ul mod-2016"> <ul className="ul mod-2016">
<li className="li-left mod-logo mod-2016"> <li className="li-left mod-logo mod-2016">
<a href="/conference/2016" className="logo-a"> <a
className="logo-a"
href="/conference/2016"
>
<img <img
src="/images/logo_sm.png"
alt="Scratch Logo" alt="Scratch Logo"
className="logo-a-image" className="logo-a-image"
src="/images/logo_sm.png"
/> />
<p className="logo-a-title">Conference</p> <p className="logo-a-title">Conference</p>
</a> </a>
@ -23,20 +23,33 @@ var Navigation = React.createClass({
<li className="li-right mod-2016"> <li className="li-right mod-2016">
<ul className="li-right-ul mod-2016"> <ul className="li-right-ul mod-2016">
<li className="link expect"> <li className="link expect">
<a href="/conference/2016/expect" className="link-a">What to Expect</a> <a
className="link-a"
href="/conference/2016/expect"
>
What to Expect
</a>
</li> </li>
<li className="link plan"> <li className="link plan">
<a href="/conference/2016/plan" className="link-a">Plan Your Visit</a> <a
className="link-a"
href="/conference/2016/plan"
>
Plan Your Visit
</a>
</li> </li>
<li className="link schedule"> <li className="link schedule">
<a href="/conference/2016/schedule" className="link-a">Schedule</a> <a
className="link-a"
href="/conference/2016/schedule"
>
Schedule
</a>
</li> </li>
</ul> </ul>
</li> </li>
</ul> </ul>
</NavigationBox> </NavigationBox>
); );
}
});
module.exports = Navigation; module.exports = Navigation;

View file

@ -1,21 +1,21 @@
var React = require('react'); const React = require('react');
var NavigationBox = require('../../base/navigation.jsx'); const NavigationBox = require('../../base/navigation.jsx');
require('./navigation.scss'); require('./navigation.scss');
var Navigation = React.createClass({ const Navigation = () => (
type: 'Navigation',
render: function () {
return (
<NavigationBox> <NavigationBox>
<ul className="ul mod-2017"> <ul className="ul mod-2017">
<li className="li-left mod-logo mod-2017"> <li className="li-left mod-logo mod-2017">
<a href="/conference" className="logo-a"> <a
className="logo-a"
href="/conference"
>
<img <img
src="/images/logo_sm.png"
alt="Scratch Logo" alt="Scratch Logo"
className="logo-a-image" className="logo-a-image"
src="/images/logo_sm.png"
/> />
<p className="logo-a-title">Conferences</p> <p className="logo-a-title">Conferences</p>
</a> </a>
@ -23,7 +23,5 @@ var Navigation = React.createClass({
</ul> </ul>
</NavigationBox> </NavigationBox>
); );
}
});
module.exports = Navigation; module.exports = Navigation;

View file

@ -1,21 +1,21 @@
var React = require('react'); const React = require('react');
var NavigationBox = require('../../base/navigation.jsx'); const NavigationBox = require('../../base/navigation.jsx');
require('./navigation.scss'); require('./navigation.scss');
var Navigation = React.createClass({ const Navigation = () => (
type: 'Navigation',
render: function () {
return (
<NavigationBox> <NavigationBox>
<ul className="ul mod-2018"> <ul className="ul mod-2018">
<li className="li-left mod-logo mod-2018"> <li className="li-left mod-logo mod-2018">
<a href="/" className="logo-a"> <a
className="logo-a"
href="/"
>
<img <img
src="/images/logo_sm.png"
alt="Scratch Logo" alt="Scratch Logo"
className="logo-a-image" className="logo-a-image"
src="/images/logo_sm.png"
/> />
<p className="logo-a-title">Conferences</p> <p className="logo-a-title">Conferences</p>
</a> </a>
@ -23,7 +23,5 @@ var Navigation = React.createClass({
</ul> </ul>
</NavigationBox> </NavigationBox>
); );
}
});
module.exports = Navigation; module.exports = Navigation;

View file

@ -1,31 +1,48 @@
var classNames = require('classnames'); const bindAll = require('lodash.bindall');
var connect = require('react-redux').connect; const classNames = require('classnames');
var React = require('react'); const connect = require('react-redux').connect;
var ReactIntl = require('react-intl'); const FormattedMessage = require('react-intl').FormattedMessage;
var FormattedMessage = ReactIntl.FormattedMessage; const injectIntl = require('react-intl').injectIntl;
var injectIntl = ReactIntl.injectIntl; const intlShape = require('react-intl').intlShape;
const PropTypes = require('prop-types');
const React = require('react');
var messageCountActions = require('../../../redux/message-count.js'); const messageCountActions = require('../../../redux/message-count.js');
var sessionActions = require('../../../redux/session.js'); const sessionActions = require('../../../redux/session.js');
var api = require('../../../lib/api'); const api = require('../../../lib/api');
var Avatar = require('../../avatar/avatar.jsx'); const Avatar = require('../../avatar/avatar.jsx');
var Button = require('../../forms/button.jsx'); const Button = require('../../forms/button.jsx');
var Dropdown = require('../../dropdown/dropdown.jsx'); const Dropdown = require('../../dropdown/dropdown.jsx');
var Form = require('../../forms/form.jsx'); const Form = require('../../forms/form.jsx');
var Input = require('../../forms/input.jsx'); const Input = require('../../forms/input.jsx');
var log = require('../../../lib/log.js'); const log = require('../../../lib/log.js');
var Login = require('../../login/login.jsx'); const Login = require('../../login/login.jsx');
var Modal = require('../../modal/base/modal.jsx'); const Modal = require('../../modal/base/modal.jsx');
var NavigationBox = require('../base/navigation.jsx'); const NavigationBox = require('../base/navigation.jsx');
var Registration = require('../../registration/registration.jsx'); const Registration = require('../../registration/registration.jsx');
require('./navigation.scss'); require('./navigation.scss');
var Navigation = React.createClass({ class Navigation extends React.Component {
type: 'Navigation', constructor (props) {
getInitialState: function () { super(props);
return { bindAll(this, [
'getProfileUrl',
'handleJoinClick',
'handleLoginClick',
'handleCloseLogin',
'handleLogIn',
'handleLogOut',
'handleAccountNavClick',
'handleCloseAccountNav',
'showCanceledDeletion',
'handleCloseCanceledDeletion',
'handleCloseRegistration',
'handleCompleteRegistration',
'handleSearchSubmit'
]);
this.state = {
accountNavOpen: false, accountNavOpen: false,
canceledDeletionOpen: false, canceledDeletionOpen: false,
loginOpen: false, loginOpen: false,
@ -33,155 +50,153 @@ var Navigation = React.createClass({
registrationOpen: false, registrationOpen: false,
messageCountIntervalId: -1 // javascript method interval id for getting messsage count. messageCountIntervalId: -1 // javascript method interval id for getting messsage count.
}; };
},
getDefaultProps: function () {
return {
session: {},
unreadMessageCount: 0, // bubble number to display how many notifications someone has.
searchTerm: ''
};
},
componentDidMount: function () {
if (this.props.session.session.user) {
var intervalId = setInterval(function () {
this.props.dispatch(messageCountActions.getCount(this.props.session.session.user.username));
}.bind(this), 120000); // check for new messages every 2 mins.
this.setState({'messageCountIntervalId': intervalId});
} }
}, componentDidMount () {
componentDidUpdate: function (prevProps) { if (this.props.session.session.user) {
if (prevProps.session.session.user != this.props.session.session.user) { const intervalId = setInterval(() => {
this.setState({ this.props.dispatch(
'loginOpen': false, messageCountActions.getCount(this.props.session.session.user.username)
'accountNavOpen': false );
}, 120000); // check for new messages every 2 mins.
this.setState({ // eslint-disable-line react/no-did-mount-set-state
messageCountIntervalId: intervalId
});
}
}
componentDidUpdate (prevProps) {
if (prevProps.session.session.user !== this.props.session.session.user) {
this.setState({ // eslint-disable-line react/no-did-update-set-state
loginOpen: false,
accountNavOpen: false
}); });
if (this.props.session.session.user) { if (this.props.session.session.user) {
var intervalId = setInterval(function () { const intervalId = setInterval(() => {
this.props.dispatch(messageCountActions.getCount(this.props.session.session.user.username)); this.props.dispatch(
}.bind(this), 120000); // check for new messages every 2 mins. messageCountActions.getCount(this.props.session.session.user.username)
this.setState({'messageCountIntervalId': intervalId}); );
}, 120000); // check for new messages every 2 mins.
this.setState({ // eslint-disable-line react/no-did-update-set-state
messageCountIntervalId: intervalId
});
} else { } else {
// clear message count check, and set to default id. // clear message count check, and set to default id.
clearInterval(this.state.messageCountIntervalId); clearInterval(this.state.messageCountIntervalId);
this.props.dispatch(messageCountActions.setCount(0)); this.props.dispatch(messageCountActions.setCount(0));
this.setState({ this.setState({ // eslint-disable-line react/no-did-update-set-state
'messageCountIntervalId': -1 messageCountIntervalId: -1
}); });
} }
} }
}, }
componentWillUnmount: function () { componentWillUnmount () {
// clear message interval if it exists // clear message interval if it exists
if (this.state.messageCountIntervalId != -1) { if (this.state.messageCountIntervalId !== -1) {
clearInterval(this.state.messageCountIntervalId); clearInterval(this.state.messageCountIntervalId);
this.props.dispatch(messageCountActions.setCount(0)); this.props.dispatch(messageCountActions.setCount(0));
this.setState({ this.setState({
'messageCountIntervalId': -1 messageCountIntervalId: -1
}); });
} }
}, }
getProfileUrl: function () { getProfileUrl () {
if (!this.props.session.session.user) return; if (!this.props.session.session.user) return;
return '/users/' + this.props.session.session.user.username + '/'; return `/users/${this.props.session.session.user.username}/`;
}, }
handleJoinClick: function (e) { handleJoinClick (e) {
e.preventDefault(); e.preventDefault();
this.setState({'registrationOpen': true}); this.setState({registrationOpen: true});
}, }
handleLoginClick: function (e) { handleLoginClick (e) {
e.preventDefault(); e.preventDefault();
this.setState({'loginOpen': !this.state.loginOpen}); this.setState({loginOpen: !this.state.loginOpen});
}, }
closeLogin: function () { handleCloseLogin () {
this.setState({'loginOpen': false}); this.setState({loginOpen: false});
}, }
handleLogIn: function (formData, callback) { handleLogIn (formData, callback) {
this.setState({'loginError': null}); this.setState({loginError: null});
formData['useMessages'] = true; formData.useMessages = true;
api({ api({
method: 'post', method: 'post',
host: '', host: '',
uri: '/accounts/login/', uri: '/accounts/login/',
json: formData, json: formData,
useCsrf: true useCsrf: true
}, function (err, body) { }, (err, body) => {
if (err) this.setState({'loginError': err.message}); if (err) this.setState({loginError: err.message});
if (body) { if (body) {
body = body[0]; body = body[0];
if (!body.success) { if (body.success) {
this.handleCloseLogin();
body.messages.map(message => { // eslint-disable-line array-callback-return
if (message.message === 'canceled-deletion') {
this.showCanceledDeletion();
}
});
this.props.dispatch(sessionActions.refreshSession());
} else {
if (body.redirect) { if (body.redirect) {
window.location = body.redirect; window.location = body.redirect;
} }
// Update login error message to a friendlier one if it exists // Update login error message to a friendlier one if it exists
this.setState({'loginError': body.msg}); this.setState({loginError: body.msg});
} else {
this.closeLogin();
body.messages.map(function (message) {
if (message.message == 'canceled-deletion') {
this.showCanceledDeletion();
}
}.bind(this));
this.props.dispatch(sessionActions.refreshSession());
} }
} }
// JS error already logged by api mixin // JS error already logged by api mixin
callback(); callback();
}.bind(this)); });
}, }
handleLogOut: function (e) { handleLogOut (e) {
e.preventDefault(); e.preventDefault();
api({ api({
host: '', host: '',
method: 'post', method: 'post',
uri: '/accounts/logout/', uri: '/accounts/logout/',
useCsrf: true useCsrf: true
}, function (err) { }, err => {
if (err) log.error(err); if (err) log.error(err);
this.closeLogin(); this.handleCloseLogin();
window.location = '/'; window.location = '/';
}.bind(this)); });
}, }
handleAccountNavClick: function (e) { handleAccountNavClick (e) {
e.preventDefault(); e.preventDefault();
this.setState({'accountNavOpen': true}); this.setState({accountNavOpen: true});
}, }
closeAccountNav: function () { handleCloseAccountNav () {
this.setState({'accountNavOpen': false}); this.setState({accountNavOpen: false});
}, }
showCanceledDeletion: function () { showCanceledDeletion () {
this.setState({'canceledDeletionOpen': true}); this.setState({canceledDeletionOpen: true});
}, }
closeCanceledDeletion: function () { handleCloseCanceledDeletion () {
this.setState({'canceledDeletionOpen': false}); this.setState({canceledDeletionOpen: false});
}, }
closeRegistration: function () { handleCloseRegistration () {
this.setState({'registrationOpen': false}); this.setState({registrationOpen: false});
}, }
completeRegistration: function () { handleCompleteRegistration () {
this.props.dispatch(sessionActions.refreshSession()); this.props.dispatch(sessionActions.refreshSession());
this.closeRegistration(); this.handleCloseRegistration();
}, }
onSearchSubmit: function (formData) { handleSearchSubmit (formData) {
window.location.href = '/search/projects?q=' + encodeURIComponent(formData.q); window.location.href = `/search/projects?q=${encodeURIComponent(formData.q)}`;
}, }
render: function () { render () {
var classes = classNames({ const createLink = this.props.session.session.user ? '/projects/editor/' : '/projects/editor/?tip_bar=home';
'logged-in': this.props.session.session.user
});
var messageClasses = classNames({
'message-count': true,
'show': this.props.unreadMessageCount > 0
});
var dropdownClasses = classNames({
'user-info': true,
'open': this.state.accountNavOpen
});
var formatMessage = this.props.intl.formatMessage;
var createLink = this.props.session.session.user ? '/projects/editor/' : '/projects/editor/?tip_bar=home';
return ( return (
<NavigationBox className={classes}> <NavigationBox
className={classNames({
'logged-in': this.props.session.session.user
})}
>
<ul> <ul>
<li className="logo"><a href="/" aria-label="Scratch"></a></li> <li className="logo">
<a
aria-label="Scratch"
href="/"
/>
</li>
<li className="link create"> <li className="link create">
<a href={createLink}> <a href={createLink}>
@ -205,47 +220,76 @@ var Navigation = React.createClass({
</li> </li>
<li className="search"> <li className="search">
<Form onSubmit={this.onSearchSubmit}> <Form onSubmit={this.handleSearchSubmit}>
<Button type="submit" className="btn-search" /> <Button
<Input type="text" className="btn-search"
type="submit"
/>
<Input
aria-label={this.props.intl.formatMessage({id: 'general.search'})}
name="q"
placeholder={this.props.intl.formatMessage({id: 'general.search'})}
type="text"
value={this.props.searchTerm} value={this.props.searchTerm}
aria-label={formatMessage({id: 'general.search'})} />
placeholder={formatMessage({id: 'general.search'})}
name="q" />
</Form> </Form>
</li> </li>
{this.props.session.status === sessionActions.Status.FETCHED ? ( {this.props.session.status === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [ this.props.session.session.user ? [
<li className="link right messages" key="messages"> <li
className="link right messages"
key="messages"
>
<a <a
href="/messages/" href="/messages/"
title={formatMessage({id: 'general.messages'})}> title={this.props.intl.formatMessage({id: 'general.messages'})}
>
<span className={messageClasses}>{this.props.unreadMessageCount}</span> <span
className={classNames({
'message-count': true,
'show': this.props.unreadMessageCount > 0
})}
>{this.props.unreadMessageCount}</span>
<FormattedMessage id="general.messages" /> <FormattedMessage id="general.messages" />
</a> </a>
</li>, </li>,
<li className="link right mystuff" key="mystuff"> <li
className="link right mystuff"
key="mystuff"
>
<a <a
href="/mystuff/" href="/mystuff/"
title={formatMessage({id: 'general.myStuff'})}> title={this.props.intl.formatMessage({id: 'general.myStuff'})}
>
<FormattedMessage id="general.myStuff" /> <FormattedMessage id="general.myStuff" />
</a> </a>
</li>, </li>,
<li className="link right account-nav" key="account-nav"> <li
<a className={dropdownClasses} className="link right account-nav"
href="#" onClick={this.handleAccountNavClick}> key="account-nav"
<Avatar src={this.props.session.session.user.thumbnailUrl} alt="" /> >
<span className='profile-name'> <a
className={classNames({
'user-info': true,
'open': this.state.accountNavOpen
})}
href="#"
onClick={this.handleAccountNavClick}
>
<Avatar
alt=""
src={this.props.session.session.user.thumbnailUrl}
/>
<span className="profile-name">
{this.props.session.session.user.username} {this.props.session.session.user.username}
</span> </span>
</a> </a>
<Dropdown <Dropdown
as="ul" as="ul"
className={process.env.SCRATCH_ENV}
isOpen={this.state.accountNavOpen} isOpen={this.state.accountNavOpen}
onRequestClose={this.closeAccountNav} onRequestClose={this.handleCloseAccountNav}
className={process.env.SCRATCH_ENV}> >
<li> <li>
<a href={this.getProfileUrl()}> <a href={this.getProfileUrl()}>
<FormattedMessage id="general.profile" /> <FormattedMessage id="general.profile" />
@ -264,8 +308,8 @@ var Navigation = React.createClass({
</li> </li>
] : []} ] : []}
{this.props.permissions.student ? [ {this.props.permissions.student ? [
<li> <li key="my-class-li">
<a href={'/classes/' + this.props.session.session.user.classroomId + '/'}> <a href={`/classes/${this.props.session.session.user.classroomId}/`}>
<FormattedMessage id="general.myClass" /> <FormattedMessage id="general.myClass" />
</a> </a>
</li> </li>
@ -276,69 +320,123 @@ var Navigation = React.createClass({
</a> </a>
</li> </li>
<li className="divider"> <li className="divider">
<a href="#" onClick={this.handleLogOut}> <a
href="#"
onClick={this.handleLogOut}
>
<FormattedMessage id="navigation.signOut" /> <FormattedMessage id="navigation.signOut" />
</a> </a>
</li> </li>
</Dropdown> </Dropdown>
</li> </li>
] : [ ] : [
<li className="link right join" key="join"> <li
<a href="#" onClick={this.handleJoinClick}> className="link right join"
key="join"
>
<a
href="#"
onClick={this.handleJoinClick}
>
<FormattedMessage id="general.joinScratch" /> <FormattedMessage id="general.joinScratch" />
</a> </a>
</li>, </li>,
<Registration <Registration
key="registration"
isOpen={this.state.registrationOpen} isOpen={this.state.registrationOpen}
onRequestClose={this.closeRegistration} key="registration"
onRegistrationDone={this.completeRegistration} />, onRegistrationDone={this.handleCompleteRegistration}
<li className="link right login-item" key="login"> onRequestClose={this.handleCloseRegistration}
/>,
<li
className="link right login-item"
key="login"
>
<a <a
href="#"
onClick={this.handleLoginClick}
className="ignore-react-onclickoutside" className="ignore-react-onclickoutside"
key="login-link"> href="#"
key="login-link"
onClick={this.handleLoginClick}
>
<FormattedMessage id="general.signIn" /> <FormattedMessage id="general.signIn" />
</a> </a>
<Dropdown <Dropdown
className="login-dropdown with-arrow" className="login-dropdown with-arrow"
isOpen={this.state.loginOpen} isOpen={this.state.loginOpen}
onRequestClose={this.closeLogin} key="login-dropdown"
key="login-dropdown"> onRequestClose={this.handleCloseLogin}
>
<Login <Login
error={this.state.loginError}
onLogIn={this.handleLogIn} onLogIn={this.handleLogIn}
error={this.state.loginError} /> />
</Dropdown> </Dropdown>
</li> </li>
]) : [ ]) : []}
]}
</ul> </ul>
<Modal isOpen={this.state.canceledDeletionOpen} <Modal
onRequestClose={this.closeCanceledDeletion} isOpen={this.state.canceledDeletionOpen}
style={{content:{padding: 15}}}> style={{
content: {
padding: 15
}
}}
onRequestClose={this.handleCloseCanceledDeletion}
>
<h4>Your Account Will Not Be Deleted</h4> <h4>Your Account Will Not Be Deleted</h4>
<h4><FormattedMessage id="general.noDeletionTitle" /></h4>
<p> <p>
Your account was scheduled for deletion but you logged in. Your account has been reactivated. <FormattedMessage
If you didnt request for your account to be deleted, you should id="general.noDeletionDescription"
{' '}<a href="/accounts/password_reset/">change your password</a>{' '} values={{
to make sure your account is secure. resetLink: <a href="/accounts/password_reset/">
{this.props.intl.formatMessage({id: 'general.noDeletionLink'})}
</a>
}}
/>
</p> </p>
</Modal> </Modal>
</NavigationBox> </NavigationBox>
); );
} }
}); }
var mapStateToProps = function (state) { Navigation.propTypes = {
return { dispatch: PropTypes.func,
intl: intlShape,
permissions: PropTypes.shape({
admin: PropTypes.bool,
social: PropTypes.bool,
educator: PropTypes.bool,
educator_invitee: PropTypes.bool,
student: PropTypes.bool
}),
searchTerm: PropTypes.string,
session: PropTypes.shape({
session: PropTypes.shape({
user: PropTypes.shape({
classroomId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
thumbnailUrl: PropTypes.string,
username: PropTypes.string
})
}),
status: PropTypes.string
}),
unreadMessageCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
};
Navigation.defaultProps = {
session: {},
unreadMessageCount: 0, // bubble number to display how many notifications someone has.
searchTerm: ''
};
const mapStateToProps = state => ({
session: state.session, session: state.session,
permissions: state.permissions, permissions: state.permissions,
unreadMessageCount: state.messageCount.messageCount, unreadMessageCount: state.messageCount.messageCount,
searchTerm: state.navigation searchTerm: state.navigation
}; });
};
var ConnectedNavigation = connect(mapStateToProps)(Navigation); const ConnectedNavigation = connect(mapStateToProps)(Navigation);
module.exports = injectIntl(ConnectedNavigation); module.exports = injectIntl(ConnectedNavigation);

View file

@ -1,35 +1,25 @@
var classNames = require('classnames'); const classNames = require('classnames');
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var React = require('react'); const PropTypes = require('prop-types');
var Slider = require('react-slick'); const React = require('react');
const Slider = require('react-slick');
var Thumbnail = require('../thumbnail/thumbnail.jsx'); const Thumbnail = require('../thumbnail/thumbnail.jsx');
require('slick-carousel/slick/slick.scss'); require('slick-carousel/slick/slick.scss');
require('slick-carousel/slick/slick-theme.scss'); require('slick-carousel/slick/slick-theme.scss');
require('./nestedcarousel.scss'); require('./nestedcarousel.scss');
{/* /*
NestedCarousel is used to show a carousel, where each slide is composed of a few NestedCarousel is used to show a carousel, where each slide is composed of a few
thumbnails (for example, to show step-by-syep tips, where each stage has a few steps). thumbnails (for example, to show step-by-syep tips, where each stage has a few steps).
It creates the thumbnails without links. It creates the thumbnails without links.
Each slide has a title, and then a list of thumbnails, that will be shown together. Each slide has a title, and then a list of thumbnails, that will be shown together.
*/} */
var NestedCarousel = React.createClass({ const NestedCarousel = props => {
type: 'NestedCarousel', defaults(props.settings, {
propTypes: {
items: React.PropTypes.array
},
getDefaultProps: function () {
return {
items: require('./nestedcarousel.json')
};
},
render: function () {
var settings = this.props.settings || {};
defaults(settings, {
dots: true, dots: true,
infinite: false, infinite: false,
lazyLoad: true, lazyLoad: true,
@ -38,38 +28,57 @@ var NestedCarousel = React.createClass({
variableWidth: false variableWidth: false
}); });
var arrows = this.props.items.length > settings.slidesToShow; const arrows = props.items.length > props.settings.slidesToShow;
const stages = [];
var classes = classNames( for (let i = 0; i < props.items.length; i++) {
'nestedcarousel', const items = props.items[i].thumbnails;
'carousel', const thumbnails = [];
this.props.className for (let j = 0; j < items.length; j++) {
); const item = items[j];
var stages = [];
for (var i=0; i < this.props.items.length; i++) {
var items = this.props.items[i].thumbnails;
var thumbnails = [];
for (var j=0; j < items.length; j++) {
var item = items[j];
thumbnails.push( thumbnails.push(
<Thumbnail key={'inner_' + i + '_' + j} <Thumbnail
title={item.title} key={`inner_${i}_${j}`}
linkTitle={false}
src={item.thumbnailUrl} src={item.thumbnailUrl}
linkTitle = {false} />); title={item.title}
/>
);
} }
stages.push( stages.push(
<div key={'outer_' + i}> <div key={`outer_${i}`}>
<h3>{this.props.items[i].title}</h3> <h3>{props.items[i].title}</h3>
{thumbnails} {thumbnails}
</div>); </div>
);
} }
return ( return (
<Slider className={classes} arrows={arrows} {... settings}> <Slider
arrows={arrows}
className={classNames('nestedcarousel', 'carousel', props.className)}
{...props.settings}
>
{stages} {stages}
</Slider> </Slider>
); );
} };
});
NestedCarousel.propTypes = {
className: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.object),
settings: PropTypes.shape({
dots: PropTypes.bool,
infinite: PropTypes.bool,
lazyLoad: PropTypes.bool,
slidesToShow: PropTypes.number,
slidesToScroll: PropTypes.number,
variableWidth: PropTypes.bool
})
};
NestedCarousel.defaultProps = {
settings: {},
items: require('./nestedcarousel.json')
};
module.exports = NestedCarousel; module.exports = NestedCarousel;

View file

@ -1,41 +1,27 @@
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var Box = require('../box/box.jsx'); const Box = require('../box/box.jsx');
require('./news.scss'); require('./news.scss');
var News = React.createClass({ const News = props => (
type: 'News',
propTypes: {
items: React.PropTypes.array
},
getDefaultProps: function () {
return {
items: require('./news.json'),
messages: {
'general.viewAll': 'View All',
'news.scratchNews': 'Scratch News'
}
};
},
render: function () {
return (
<Box <Box
className="news" className="news"
title={this.props.messages['news.scratchNews']} moreHref="/discuss/5/"
moreTitle={this.props.messages['general.viewAll']} moreTitle={props.messages['general.viewAll']}
moreHref="/discuss/5/"> title={props.messages['news.scratchNews']}
>
<ul> <ul>
{this.props.items.map(function (item) { {props.items.map(item => (
return (
<li key={item.id}> <li key={item.id}>
<a href={item.url}> <a href={item.url}>
<img src={item.image} <img
className="news-image"
width="53"
height="53"
alt="" alt=""
className="news-image"
height="53"
src={item.image}
width="53"
/> />
<div className="news-description"> <div className="news-description">
<h4>{item.headline}</h4> <h4>{item.headline}</h4>
@ -43,12 +29,25 @@ var News = React.createClass({
</div> </div>
</a> </a>
</li> </li>
); ))}
})}
</ul> </ul>
</Box> </Box>
); );
News.propTypes = {
items: PropTypes.arrayOf(PropTypes.object),
messages: PropTypes.shape({
'general.viewAll': PropTypes.string,
'news.scratchNews': PropTypes.string
})
};
News.defaultProps = {
items: require('./news.json'),
messages: {
'general.viewAll': 'View All',
'news.scratchNews': 'Scratch News'
} }
}); };
module.exports = News; module.exports = News;

View file

@ -1,27 +1,27 @@
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var Navigation = require('../../../navigation/conference/2016/navigation.jsx'); const Navigation = require('../../../navigation/conference/2016/navigation.jsx');
var Footer = require('../../../footer/conference/2016/footer.jsx'); const Footer = require('../../../footer/conference/2016/footer.jsx');
require('../page.scss'); require('../page.scss');
var Page = React.createClass({ const Page = props => (
type: 'Page',
render: function () {
return (
<div className="page mod-conference"> <div className="page mod-conference">
<div id="navigation"> <div id="navigation">
<Navigation /> <Navigation />
</div> </div>
<div id="view"> <div id="view">
{this.props.children} {props.children}
</div> </div>
<div id="footer"> <div id="footer">
<Footer /> <Footer />
</div> </div>
</div> </div>
); );
}
}); Page.propTypes = {
children: PropTypes.node
};
module.exports = Page; module.exports = Page;

View file

@ -1,27 +1,27 @@
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var Navigation = require('../../../navigation/conference/2017/navigation.jsx'); const Navigation = require('../../../navigation/conference/2017/navigation.jsx');
var Footer = require('../../../footer/conference/2017/footer.jsx'); const Footer = require('../../../footer/conference/2017/footer.jsx');
require('../page.scss'); require('../page.scss');
var Page = React.createClass({ const Page = props => (
type: 'Page',
render: function () {
return (
<div className="page mod-conference"> <div className="page mod-conference">
<div id="navigation"> <div id="navigation">
<Navigation /> <Navigation />
</div> </div>
<div id="view"> <div id="view">
{this.props.children} {props.children}
</div> </div>
<div id="footer"> <div id="footer">
<Footer /> <Footer />
</div> </div>
</div> </div>
); );
}
}); Page.propTypes = {
children: PropTypes.node
};
module.exports = Page; module.exports = Page;

View file

@ -1,27 +1,27 @@
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var Navigation = require('../../../navigation/conference/2018/navigation.jsx'); const Navigation = require('../../../navigation/conference/2018/navigation.jsx');
var Footer = require('../../../footer/conference/2018/footer.jsx'); const Footer = require('../../../footer/conference/2018/footer.jsx');
require('../page.scss'); require('../page.scss');
var Page = React.createClass({ const Page = props => (
type: 'Page',
render: function () {
return (
<div className="page mod-conference"> <div className="page mod-conference">
<div id="navigation"> <div id="navigation">
<Navigation /> <Navigation />
</div> </div>
<div id="view"> <div id="view">
{this.props.children} {props.children}
</div> </div>
<div id="footer"> <div id="footer">
<Footer /> <Footer />
</div> </div>
</div> </div>
); );
}
}); Page.propTypes = {
children: PropTypes.node
};
module.exports = Page; module.exports = Page;

View file

@ -1,29 +1,31 @@
var React = require('react'); const classNames = require('classnames');
var classNames = require('classnames'); const PropTypes = require('prop-types');
const React = require('react');
var Navigation = require('../../navigation/www/navigation.jsx'); const Navigation = require('../../navigation/www/navigation.jsx');
var Footer = require('../../footer/www/footer.jsx'); const Footer = require('../../footer/www/footer.jsx');
var Page = React.createClass({ const Page = props => (
type: 'Page',
render: function () {
var classes = classNames({
'staging': process.env.SCRATCH_ENV == 'staging'
});
return (
<div className="page"> <div className="page">
<div id="navigation" className={classes}> <div
className={classNames({
staging: process.env.SCRATCH_ENV === 'staging'
})}
id="navigation"
>
<Navigation /> <Navigation />
</div> </div>
<div id="view"> <div id="view">
{this.props.children} {props.children}
</div> </div>
<div id="footer"> <div id="footer">
<Footer /> <Footer />
</div> </div>
</div> </div>
); );
}
}); Page.propTypes = {
children: PropTypes.node
};
module.exports = Page; module.exports = Page;

View file

@ -1,42 +1,45 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
module.exports = React.createClass({ const Progression = props => {
displayName: 'Progression', const childProps = {
propTypes: { activeStep: props.step,
step: function (props, propName, componentName) { totalSteps: React.Children.count(props.children)
var stepValidator = function (props, propName) {
if (props[propName] > -1 && props[propName] < props.children.length) {
return null;
} else {
return new Error('Prop `step` out of range');
}
}; };
return ( return (
React.PropTypes.number.isRequired(props, propName, componentName) || <div
className={classNames('progression', props.className)}
{...props}
>
{React.Children.map(props.children, (child, id) => {
if (id === props.step) {
return React.cloneElement(child, childProps);
}
})}
</div>
);
};
Progression.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
step: function (props, propName, componentName) {
const stepValidator = (propz, name) => {
if (propz[name] > -1 && propz[name] < propz.children.length) {
return null;
}
return new Error('Prop `step` out of range');
};
return (
(typeof props[propName] === 'number' ? null : new Error('Not a number')) ||
stepValidator(props, propName, componentName) stepValidator(props, propName, componentName)
); );
} }
}, };
getDefaultProps: function () {
return { Progression.defaultProps = {
step: 0 step: 0
}; };
},
render: function () { module.exports = Progression;
var childProps = {
activeStep: this.props.step,
totalSteps: React.Children.count(this.props.children)
};
return (
<div {... this.props}
className={classNames('progression', this.props.className)}>
{React.Children.map(this.props.children, function (child, id) {
if (id === this.props.step) {
return React.cloneElement(child, childProps);
}
}, this)}
</div>
);
}
});

View file

@ -1,53 +1,62 @@
var React = require('react'); const bindAll = require('lodash.bindall');
var IframeModal = require('../modal/iframe/modal.jsx'); const PropTypes = require('prop-types');
const React = require('react');
const IframeModal = require('../modal/iframe/modal.jsx');
require('./registration.scss'); require('./registration.scss');
var Registration = React.createClass({ class Registration extends React.Component {
propTypes: { constructor (props) {
isOpen: React.PropTypes.bool, super(props);
onRegistrationDone: React.PropTypes.func, bindAll(this, [
onRequestClose: React.PropTypes.func 'handleMessage',
}, 'toggleMessageListener'
onMessage: function (e) { ]);
if (e.origin != window.location.origin) return; }
if (e.source != this.registrationIframe.contentWindow) return; componentDidMount () {
if (e.data == 'registration-done') this.props.onRegistrationDone(); if (this.props.isOpen) this.toggleMessageListener(true);
if (e.data == 'registration-relaunch') { }
componentDidUpdate (prevProps) {
this.toggleMessageListener(this.props.isOpen && !prevProps.isOpen);
}
componentWillUnmount () {
this.toggleMessageListener(false);
}
handleMessage (e) {
if (e.origin !== window.location.origin) return;
if (e.source !== this.registrationIframe.contentWindow) return;
if (e.data === 'registration-done') this.props.onRegistrationDone();
if (e.data === 'registration-relaunch') {
this.registrationIframe.contentWindow.location.reload(); this.registrationIframe.contentWindow.location.reload();
} }
},
toggleMessageListener: function (present) {
if (present) {
window.addEventListener('message', this.onMessage);
} else {
window.removeEventListener('message', this.onMessage);
} }
}, toggleMessageListener (present) {
componentDidMount: function () { if (present) {
if (this.props.isOpen) this.toggleMessageListener(true); window.addEventListener('message', this.handleMessage);
}, } else {
componentDidUpdate: function (prevProps) { window.removeEventListener('message', this.handleMessage);
this.toggleMessageListener(this.props.isOpen && !prevProps.isOpen); }
}, }
componentWillUnmount: function () { render () {
this.toggleMessageListener(false);
},
render: function () {
return ( return (
<IframeModal <IframeModal
isOpen={this.props.isOpen}
onRequestClose={this.props.onRequestClose}
className="mod-registration" className="mod-registration"
componentRef={ componentRef={iframe => { // eslint-disable-line react/jsx-no-bind
function (iframe) {
this.registrationIframe = iframe; this.registrationIframe = iframe;
}.bind(this) }}
} isOpen={this.props.isOpen}
src="/accounts/standalone-registration/" src="/accounts/standalone-registration/"
onRequestClose={this.props.onRequestClose}
/> />
); );
} }
}); }
Registration.propTypes = {
isOpen: PropTypes.bool,
onRegistrationDone: PropTypes.func,
onRequestClose: PropTypes.func
};
module.exports = Registration; module.exports = Registration;

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,18 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./slide.scss'); require('./slide.scss');
var Slide = React.createClass({ const Slide = props => (
displayName: 'Slide', <div className={classNames(['slide', props.className])}>
render: function () { {props.children}
return (
<div className={classNames(['slide', this.props.className])}>
{this.props.children}
</div> </div>
); );
}
}); Slide.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = Slide; module.exports = Slide;

View file

@ -1,57 +1,47 @@
var classNames = require('classnames'); const classNames = require('classnames');
var FormattedRelative = require('react-intl').FormattedRelative; const FormattedRelative = require('react-intl').FormattedRelative;
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var FlexRow = require('../flex-row/flex-row.jsx'); const FlexRow = require('../flex-row/flex-row.jsx');
require('./social-message.scss'); require('./social-message.scss');
var SocialMessage = React.createClass({ const SocialMessage = props => (
type: 'SocialMessage', <props.as className={classNames('social-message', props.className)}>
propTypes: {
as: React.PropTypes.string,
datetime: React.PropTypes.string.isRequired,
iconSrc: React.PropTypes.string,
iconAlt: React.PropTypes.string,
imgClassName: React.PropTypes.string
},
getDefaultProps: function () {
return {
as: 'li'
};
},
render: function () {
var classes = classNames(
'social-message',
this.props.className
);
var imgClass = classNames(
'social-message-icon',
this.props.imgClassName
);
return (
<this.props.as className={classes}>
<FlexRow className="mod-social-message"> <FlexRow className="mod-social-message">
<div className="social-message-content"> <div className="social-message-content">
{typeof this.props.iconSrc !== 'undefined' ? [ {typeof props.iconSrc === 'undefined' ? [] : [
<img <img
alt={props.iconAlt}
className={classNames('social-message-icon', props.imgClassName)}
key="social-message-icon" key="social-message-icon"
className={imgClass} src={props.iconSrc}
src={this.props.iconSrc}
alt={this.props.iconAlt}
/> />
] : []} ]}
<div> <div>
{this.props.children} {props.children}
</div> </div>
</div> </div>
<span className="social-message-date"> <span className="social-message-date">
<FormattedRelative value={new Date(this.props.datetime)} /> <FormattedRelative value={new Date(props.datetime)} />
</span> </span>
</FlexRow> </FlexRow>
</this.props.as> </props.as>
); );
}
}); SocialMessage.propTypes = {
as: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
children: PropTypes.node,
className: PropTypes.string,
datetime: PropTypes.string.isRequired,
iconAlt: PropTypes.string,
iconSrc: PropTypes.string,
imgClassName: PropTypes.string
};
SocialMessage.defaultProps = {
as: 'li'
};
module.exports = SocialMessage; module.exports = SocialMessage;

View file

@ -1,20 +1,18 @@
var range = require('lodash.range'); const range = require('lodash.range');
var React = require('react'); const React = require('react');
require('./spinner.scss'); require('./spinner.scss');
var Spinner = React.createClass({
// Adapted from http://tobiasahlin.com/spinkit/ // Adapted from http://tobiasahlin.com/spinkit/
type: 'Spinner', const Spinner = () => (
render: function () {
return (
<div className="spinner"> <div className="spinner">
{range(1,13).map(function (id) { {range(1, 13).map(id => (
return <div className={'circle' + id + ' circle'}></div>; <div
})} className={`circle${id} circle`}
key={`circle${id}`}
/>
))}
</div> </div>
); );
}
});
module.exports = Spinner; module.exports = Spinner;

View file

@ -1,28 +1,29 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./stepnavigation.scss'); require('./stepnavigation.scss');
var StepNavigation = React.createClass({ const StepNavigation = props => (
type: 'Navigation', <ul className={classNames('step-navigation', props.className)}>
render: function () { {Array.apply(null, Array(props.steps)).map((v, step) => (
return ( <li
<ul className={classNames('step-navigation', this.props.className)}>
{Array.apply(null, Array(this.props.steps)).map(function (v, step) {
return (
<li key={step}
className={classNames({ className={classNames({
active: step < this.props.active, active: step < props.active,
selected: step === this.props.active selected: step === props.active
})} })}
key={step}
> >
<div className="indicator" /> <div className="indicator" />
</li> </li>
); ))}
}.bind(this))}
</ul> </ul>
); );
}
}); StepNavigation.propTypes = {
active: PropTypes.number,
className: PropTypes.string,
steps: PropTypes.number
};
module.exports = StepNavigation; module.exports = StepNavigation;

View file

@ -1,36 +1,38 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./subnavigation.scss'); require('./subnavigation.scss');
/** /*
* Container for a custom, horizontal list of navigation elements * Container for a custom, horizontal list of navigation elements
* that can be displayed within a view or component. * that can be displayed within a view or component.
*/ */
var SubNavigation = React.createClass({ const SubNavigation = props => (
type: 'SubNavigation', <div
getDefaultProps: function () { className={classNames(
return {
align: 'middle'
};
},
render: function () {
var classes = classNames(
[ [
'sub-nav', 'sub-nav',
this.props.className props.className
], ],
{ {
'sub-nav-align-left': this.props.align === 'left', 'sub-nav-align-left': props.align === 'left',
'sub-nav-align-right': this.props.align === 'right' 'sub-nav-align-right': props.align === 'right'
} }
); )}
return ( >
<div className={classes}> {props.children}
{this.props.children}
</div> </div>
); );
}
}); SubNavigation.propTypes = {
align: PropTypes.string,
children: PropTypes.node,
className: PropTypes.string
};
SubNavigation.defaultProps = {
align: 'middle'
};
module.exports = SubNavigation; module.exports = SubNavigation;

View file

@ -1,28 +1,26 @@
var classNames = require('classnames'); const classNames = require('classnames');
var SubNavigation = require('../../components/subnavigation/subnavigation.jsx'); const PropTypes = require('prop-types');
var React = require('react'); const React = require('react');
const SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
require('./tabs.scss'); require('./tabs.scss');
/** /*
* Container for a custom, horizontal list of navigation elements * Container for a custom, horizontal list of navigation elements
* that can be displayed within a view or component. * that can be displayed within a view or component.
*/ */
var Tabs = React.createClass({ const Tabs = props => (
type: 'Tabs', <div className="tab-background">
render: function () { <SubNavigation className={classNames('tabs', props.className)}>
var classes = classNames( {props.children}
'tabs',
this.props.className
);
return (
<div className='tab-background'>
<SubNavigation className={classes}>
{this.props.children}
</SubNavigation> </SubNavigation>
</div> </div>
); );
}
}); Tabs.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = Tabs; module.exports = Tabs;

View file

@ -1,69 +1,60 @@
var classNames = require('classnames'); const classNames = require('classnames');
var connect = require('react-redux').connect; const connect = require('react-redux').connect;
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var sessionActions = require('../../redux/session.js'); const sessionActions = require('../../redux/session.js');
var TitleBanner = require('../title-banner/title-banner.jsx'); const TitleBanner = require('../title-banner/title-banner.jsx');
var Button = require('../forms/button.jsx'); const Button = require('../forms/button.jsx');
var FlexRow = require('../flex-row/flex-row.jsx'); const FlexRow = require('../flex-row/flex-row.jsx');
require('./teacher-banner.scss'); require('./teacher-banner.scss');
var TeacherBanner = React.createClass({ const TeacherBanner = props => (
type: 'TeacherBanner', <TitleBanner className={classNames('teacher-banner', props.className)}>
getDefaultProps: function () {
return {
messages: {
'teacherbanner.greeting': 'Hi',
'teacherbanner.subgreeting': 'Teacher Account',
'teacherbanner.classesButton': 'My Classes',
'teacherbanner.resourcesButton': 'Educator Resources',
'teacherbanner.faqButton': 'Teacher Account FAQ'
},
session: {}
};
},
render: function () {
var classes = classNames(
'teacher-banner',
this.props.className
);
return (
<TitleBanner className={classes}>
<FlexRow className="inner"> <FlexRow className="inner">
<div className="welcome"> <div className="welcome">
{this.props.session.status === sessionActions.Status.FETCHED ? ( {props.sessionStatus === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [ props.user ? [
<h3 key="greeting"> <h3 key="greeting">
{this.props.messages['teacherbanner.greeting']},{' '} {props.messages['teacherbanner.greeting']},{' '}
{this.props.session.session.user.username} {props.user.username}
</h3>, </h3>,
<p <p
key="subgreeting"
className="title-banner-p" className="title-banner-p"
key="subgreeting"
> >
{this.props.messages['teacherbanner.subgreeting']} {props.messages['teacherbanner.subgreeting']}
</p> </p>
] : [] ] : []
) : []} ) : []}
</div> </div>
<FlexRow className="quick-links"> <FlexRow className="quick-links">
{this.props.session.status === sessionActions.Status.FETCHED ? ( {props.sessionStatus === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [ props.user ? [
<a href="/educators/classes" key="classes-button"> <a
href="/educators/classes"
key="classes-button"
>
<Button> <Button>
{this.props.messages['teacherbanner.classesButton']} {props.messages['teacherbanner.classesButton']}
</Button> </Button>
</a>, </a>,
<a href="/info/educators" key="resources-button"> <a
href="/info/educators"
key="resources-button"
>
<Button> <Button>
{this.props.messages['teacherbanner.resourcesButton']} {props.messages['teacherbanner.resourcesButton']}
</Button> </Button>
</a>, </a>,
<a href="/educators/faq" key="faq-button"> <a
href="/educators/faq"
key="faq-button"
>
<Button> <Button>
{this.props.messages['teacherbanner.faqButton']} {props.messages['teacherbanner.faqButton']}
</Button> </Button>
</a> </a>
] : [] ] : []
@ -72,15 +63,38 @@ var TeacherBanner = React.createClass({
</FlexRow> </FlexRow>
</TitleBanner> </TitleBanner>
); );
}
TeacherBanner.propTypes = {
className: PropTypes.string,
messages: PropTypes.shape({
'teacherbanner.greeting': PropTypes.string,
'teacherbanner.subgreeting': PropTypes.string,
'teacherbanner.classesButton': PropTypes.string,
'teacherbanner.resourcesButton': PropTypes.string,
'teacherbanner.faqButton': PropTypes.string
}),
sessionStatus: PropTypes.string,
user: PropTypes.shape({
username: PropTypes.string
})
};
TeacherBanner.defaultProps = {
messages: {
'teacherbanner.greeting': 'Hi',
'teacherbanner.subgreeting': 'Teacher Account',
'teacherbanner.classesButton': 'My Classes',
'teacherbanner.resourcesButton': 'Educator Resources',
'teacherbanner.faqButton': 'Teacher Account FAQ'
},
user: {}
};
const mapStateToProps = state => ({
sessionStatus: state.session.status,
user: state.session.session.user
}); });
var mapStateToProps = function (state) { const ConnectedTeacherBanner = connect(mapStateToProps)(TeacherBanner);
return {
session: state.session
};
};
var ConnectedTeacherBanner = connect(mapStateToProps)(TeacherBanner);
module.exports = ConnectedTeacherBanner; module.exports = ConnectedTeacherBanner;

View file

@ -1,175 +1,122 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./thumbnail.scss'); require('./thumbnail.scss');
var Thumbnail = React.createClass({ const Thumbnail = props => {
type: 'Thumbnail', const extra = [];
propTypes: { const info = [];
src: React.PropTypes.string
},
getInitialState: function () {
return {
srcFallback: false,
avatarFallback: false
};
},
getDefaultProps: function () {
return {
href: '#',
title: 'Project',
src: '',
srcDefault: 'https://uploads.scratch.mit.edu/projects/thumbnails/default.png',
avatar: '',
avatarDefault: 'https://uploads.scratch.mit.edu/users/avatars/default.png',
type: 'project',
showLoves: false,
showFavorites: false,
showRemixes: false,
showViews: false,
showAvatar: false,
linkTitle: true,
alt: ''
};
},
handleSrcError: function () {
this.setState({srcFallback: true});
},
handleAvatarError: function () {
this.setState({avatarFallback: true});
},
render: function () {
var classes = classNames(
'thumbnail',
this.props.type,
this.props.className
);
var extra = [];
var info = [];
if (this.props.loves && this.props.showLoves) { if (props.loves && props.showLoves) {
extra.push( extra.push(
<div <div
key="loves"
className="thumbnail-loves" className="thumbnail-loves"
title={this.props.loves + ' loves'}> key="loves"
{this.props.loves} title={`${props.loves} loves`}
>
{props.loves}
</div> </div>
); );
} }
if (this.props.favorites && this.props.showFavorites) { if (props.favorites && props.showFavorites) {
extra.push( extra.push(
<div <div
key="favorites"
className="thumbnail-favorites" className="thumbnail-favorites"
title={this.favorites + ' favorites'}> key="favorites"
{this.props.favorites} title={`${props.favorites} favorites`}
>
{props.favorites}
</div> </div>
); );
} }
if (this.props.remixes && this.props.showRemixes) { if (props.remixes && props.showRemixes) {
extra.push( extra.push(
<div <div
key="remixes"
className="thumbnail-remixes" className="thumbnail-remixes"
title={this.props.remixes + ' remixes'} key="remixes"
title={`${props.remixes} remixes`}
> >
{this.props.remixes} {props.remixes}
</div> </div>
); );
} }
if (this.props.views && this.props.showViews) { if (props.views && props.showViews) {
extra.push( extra.push(
<div <div
key="views"
className="thumbnail-views" className="thumbnail-views"
title={this.props.views + ' views'} key="views"
title={`${props.views} views`}
> >
{this.props.views} {props.views}
</div> </div>
); );
} }
var imgElement, titleElement, avatarElement; let imgElement;
if (this.props.linkTitle) { let titleElement;
if (this.state.srcFallback) { let avatarElement;
if (props.linkTitle) {
imgElement = ( imgElement = (
<a <a
className="thumbnail-image" className="thumbnail-image"
href={this.props.href} href={props.href}
key="imgElement" key="imgElement"
> >
<img <img
alt={this.props.alt} alt={props.alt}
src={this.props.srcDefault} src={props.src}
/> />
</a> </a>
); );
} else {
imgElement = (
<a
className="thumbnail-image"
href={this.props.href}
key="imgElement"
>
<img
alt={this.props.alt}
src={this.props.src}
onError={this.handleSrcError}
/>
</a>
);
}
titleElement = ( titleElement = (
<a href={this.props.href} key="titleElement"> <a
{this.props.title} href={props.href}
key="titleElement"
>
{props.title}
</a> </a>
); );
} else { } else {
imgElement = <img src={this.props.src} />; imgElement = <img src={props.src} />;
titleElement = this.props.title; titleElement = props.title;
} }
info.push(titleElement); info.push(titleElement);
if (this.props.creator) { if (props.creator) {
info.push( info.push(
<div key="creator" className="thumbnail-creator"> <div
<a href={'/users/' + this.props.creator + '/'}>{this.props.creator}</a> className="thumbnail-creator"
key="creator"
>
<a href={`/users/${props.creator}/`}>{props.creator}</a>
</div> </div>
); );
} }
if (this.props.avatar && this.props.showAvatar) { if (props.avatar && props.showAvatar) {
if (this.state.avatarFallback) {
avatarElement = ( avatarElement = (
<a <a
className="creator-image" className="creator-image"
href={'/users/' + this.props.creator + '/'} href={`/users/${props.creator}/`}
> >
<img <img
alt={this.props.creator} alt={props.creator}
src={this.props.avatarDefault} src={props.avatar}
/> />
</a> </a>
); );
} else {
avatarElement = (
<a
className="creator-image"
href={'/users/' + this.props.creator + '/'}
>
<img
alt={this.props.creator}
src={this.props.avatar}
onError={this.handleAvatarError}
/>
</a>
);
}
} }
return ( return (
<div className={classes} > <div
className={classNames(
'thumbnail',
props.type,
props.className
)}
>
{imgElement} {imgElement}
<div className="thumbnail-info"> <div className="thumbnail-info">
{avatarElement} {avatarElement}
@ -180,7 +127,42 @@ var Thumbnail = React.createClass({
{extra} {extra}
</div> </div>
); );
} };
});
Thumbnail.propTypes = {
alt: PropTypes.string,
avatar: PropTypes.string,
className: PropTypes.string,
creator: PropTypes.string,
favorites: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
href: PropTypes.string,
linkTitle: PropTypes.bool,
loves: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
remixes: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
showAvatar: PropTypes.bool,
showFavorites: PropTypes.bool,
showLoves: PropTypes.bool,
showRemixes: PropTypes.bool,
showViews: PropTypes.bool,
src: PropTypes.string,
title: PropTypes.string,
type: PropTypes.string,
views: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
};
Thumbnail.defaultProps = {
alt: '',
avatar: '',
href: '#',
linkTitle: true,
showAvatar: false,
showFavorites: false,
showLoves: false,
showRemixes: false,
showViews: false,
src: '',
title: 'Project',
type: 'project'
};
module.exports = Thumbnail; module.exports = Thumbnail;

View file

@ -1,21 +1,18 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./title-banner.scss'); require('./title-banner.scss');
var TitleBanner = React.createClass({ const TitleBanner = props => (
type: 'TitleBanner', <div className={classNames('title-banner', props.className)}>
render: function () { {props.children}
var classes = classNames(
'title-banner',
this.props.className
);
return (
<div className={classes}>
{this.props.children}
</div> </div>
); );
}
}); TitleBanner.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = TitleBanner; module.exports = TitleBanner;

View file

@ -1,33 +1,39 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
require('./tooltip.scss'); require('./tooltip.scss');
var Tooltip = React.createClass({ const Tooltip = props => (
type: 'Tooltip', <span
getDefaultProps: function () { className={classNames(
return { 'tooltip',
props.className,
{overmax: (props.currentCharacters > props.maxCharacters)}
)}
>
<span className="tip">
<img
alt="info icon"
src="/svgs/tooltip/info.svg"
/>
</span>
<span className="expand">
{props.tipContent}
</span>
</span>
);
Tooltip.propTypes = {
className: PropTypes.string,
currentCharacters: PropTypes.number,
maxCharacters: PropTypes.number,
tipContent: PropTypes.node
};
Tooltip.defaultProps = {
title: '', title: '',
tipContent: '' tipContent: ''
}; };
},
render: function () {
var classes = classNames(
'tooltip',
this.props.className,
{overmax: (this.props.currentCharacters > this.props.maxCharacters)}
);
return (
<span className={classes}>
<span className="tip">
<img src="/svgs/tooltip/info.svg" alt="info icon" />
</span>
<span className="expand">
{this.props.tipContent}
</span>
</span>
);
}
});
module.exports = Tooltip; module.exports = Tooltip;

View file

@ -1,30 +1,21 @@
var classNames = require('classnames'); const classNames = require('classnames');
var React = require('react'); const FormattedMessage = require('react-intl').FormattedMessage;
var FormattedMessage = require('react-intl').FormattedMessage; const PropTypes = require('prop-types');
const React = require('react');
require('../forms/button.scss'); require('../forms/button.scss');
require('./ttt-tile.scss'); require('./ttt-tile.scss');
var TTTTile = React.createClass({ const TTTTile = props => (
type: 'TTTTile', <div className={classNames('ttt-tile', props.className)}>
propTypes: { <a href={props.tutorialLoc}>
title: React.PropTypes.string.isRequired,
description: React.PropTypes.string,
thumbUrl: React.PropTypes.string.isRequired,
tutorialLoc: React.PropTypes.string.isRequired,
onGuideClick: React.PropTypes.func
},
render: function () {
var classes = classNames(
'ttt-tile',
this.props.className
);
return (
<div className={classes} >
<a href={this.props.tutorialLoc}>
<div className="ttt-tile-tutorial"> <div className="ttt-tile-tutorial">
<div className="ttt-tile-image"> <div className="ttt-tile-image">
<img className="ttt-tile-image-img" src={this.props.thumbUrl} alt="" /> <img
alt=""
className="ttt-tile-image-img"
src={props.thumbUrl}
/>
<div className="ttt-tile-image-try"> <div className="ttt-tile-image-try">
<div className="button mod-ttt-try-button"> <div className="button mod-ttt-try-button">
<FormattedMessage id="tile.tryIt" /> <FormattedMessage id="tile.tryIt" />
@ -34,25 +25,44 @@ var TTTTile = React.createClass({
<div className="ttt-tile-info"> <div className="ttt-tile-info">
<div className="ttt-tile-tag"> <div className="ttt-tile-tag">
<FormattedMessage id='ttt.tutorial' defaultMessage='Tutorial'/> <FormattedMessage
defaultMessage="Tutorial"
id="ttt.tutorial"
/>
</div> </div>
<h4 className="ttt-tile-title">{this.props.title}</h4> <h4 className="ttt-tile-title">{props.title}</h4>
<p className="ttt-tile-description"> <p className="ttt-tile-description">
{this.props.description} {props.description}
</p> </p>
</div> </div>
</div> </div>
</a> </a>
{this.props.onGuideClick && ( {props.onGuideClick && (
<div className="ttt-tile-guides" onClick={this.props.onGuideClick}> <div
<FormattedMessage id='tile.guides' defaultMessage='See Cards and Guides'/> className="ttt-tile-guides"
<img className="ttt-tile-open-modal" src="/svgs/modal/open-blue.svg" /> onClick={props.onGuideClick}
>
<FormattedMessage
defaultMessage="See Cards and Guides"
id="tile.guides"
/>
<img
className="ttt-tile-open-modal"
src="/svgs/modal/open-blue.svg"
/>
</div> </div>
)} )}
</div> </div>
); );
}
}); TTTTile.propTypes = {
className: PropTypes.string,
description: PropTypes.string,
onGuideClick: PropTypes.func,
thumbUrl: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
tutorialLoc: PropTypes.string.isRequired
};
module.exports = TTTTile; module.exports = TTTTile;

View file

@ -1,16 +1,75 @@
var React = require('react'); const PropTypes = require('prop-types');
const React = require('react');
var Box = require('../box/box.jsx'); const Box = require('../box/box.jsx');
require('./welcome.scss'); require('./welcome.scss');
var Welcome = React.createClass({ const Welcome = props => (
type: 'Welcome', <Box
propTypes: { className="welcome"
onDismiss: React.PropTypes.func moreHref="#"
}, moreProps={{
getDefaultProps: function () { className: 'close',
return { title: 'Dismiss',
onClick: props.onDismiss
}}
moreTitle="x"
title={props.messages['welcome.welcomeToScratch']}
>
<div className="welcome-col blue">
<h4>
<a href="/projects/editor/?tip_bar=getStarted">
{props.messages['welcome.learn']}
</a>
</h4>
<a href="/projects/editor/?tip_bar=getStarted">
<img
alt="Get Started"
src="/images/welcome-learn.png"
/>
</a>
</div>
<div className="welcome-col green">
<h4>
<a href="/starter_projects/">
{props.messages['welcome.tryOut']}
</a>
</h4>
<a href="/starter_projects/">
<img
alt="Starter Projects"
src="/images/welcome-try.png"
/>
</a>
</div>
<div className="welcome-col pink">
<h4>
<a href="/studios/146521/">
{props.messages['welcome.connect']}
</a>
</h4>
<a href="/studios/146521/">
<img
alt="Connect"
src="/images/welcome-connect.png"
/>
</a>
</div>
</Box>
);
Welcome.propTypes = {
messages: PropTypes.shape({
'welcome.welcomeToScratch': PropTypes.string,
'welcome.learn': PropTypes.string,
'welcome.tryOut': PropTypes.string,
'welcome.connect': PropTypes.string
}),
onDismiss: PropTypes.func
};
Welcome.defaultProps = {
messages: { messages: {
'welcome.welcomeToScratch': 'Welcome to Scratch!', 'welcome.welcomeToScratch': 'Welcome to Scratch!',
'welcome.learn': 'Learn how to make a project in Scratch', 'welcome.learn': 'Learn how to make a project in Scratch',
@ -18,52 +77,5 @@ var Welcome = React.createClass({
'welcome.connect': 'Connect with other Scratchers' 'welcome.connect': 'Connect with other Scratchers'
} }
}; };
},
render: function () {
return (
<Box title={this.props.messages['welcome.welcomeToScratch']}
className="welcome"
moreTitle="x"
moreHref="#"
moreProps={{
className: 'close',
title: 'Dismiss',
onClick: this.props.onDismiss
}}>
<div className="welcome-col blue">
<h4>
<a href="/projects/editor/?tip_bar=getStarted">
{this.props.messages['welcome.learn']}
</a>
</h4>
<a href="/projects/editor/?tip_bar=getStarted">
<img src="/images/welcome-learn.png" alt="Get Started" />
</a>
</div>
<div className="welcome-col green">
<h4>
<a href="/starter_projects/">
{this.props.messages['welcome.tryOut']}
</a>
</h4>
<a href="/starter_projects/">
<img src="/images/welcome-try.png" alt="Starter Projects" />
</a>
</div>
<div className="welcome-col pink">
<h4>
<a href="/studios/146521/">
{this.props.messages['welcome.connect']}
</a>
</h4>
<a href="/studios/146521/">
<img src="/images/welcome-connect.png" alt="Connect" />
</a>
</div>
</Box>
);
}
});
module.exports = Welcome; module.exports = Welcome;

View file

@ -1,12 +1,12 @@
var jar = require('./lib/jar'); const jar = require('./lib/jar');
var Raven = require('raven-js'); const Raven = require('raven-js');
/** /**
* ----------------------------------------------------------------------------- * -----------------------------------------------------------------------------
* Error handling * Error handling
* ----------------------------------------------------------------------------- * -----------------------------------------------------------------------------
*/ */
(function () { (() => {
if (process.env.SENTRY_DSN !== '') { if (process.env.SENTRY_DSN !== '') {
Raven.config(process.env.SENTRY_DSN).install(); Raven.config(process.env.SENTRY_DSN).install();
} }
@ -17,14 +17,14 @@ var Raven = require('raven-js');
* L10N * L10N
* ----------------------------------------------------------------------------- * -----------------------------------------------------------------------------
*/ */
(function () { (() => {
/** /*
* Bind locale code from cookie if available. Uses navigator language API as a fallback. * Bind locale code from cookie if available. Uses navigator language API as a fallback.
* *
* @return {string} * @return {string}
*/ */
function updateLocale () { const updateLocale = () => {
var obj = jar.get('scratchlanguage'); let obj = jar.get('scratchlanguage');
if (typeof obj === 'undefined') { if (typeof obj === 'undefined') {
obj = window.navigator.userLanguage || window.navigator.language; obj = window.navigator.userLanguage || window.navigator.language;
if (['pt', 'pt-pt', 'PT', 'PT-PT'].indexOf(obj) !== -1) { if (['pt', 'pt-pt', 'PT', 'PT-PT'].indexOf(obj) !== -1) {
@ -32,7 +32,7 @@ var Raven = require('raven-js');
} }
} }
return obj; return obj;
} };
window._locale = updateLocale(); window._locale = updateLocale();
})(); })();

View file

@ -47,6 +47,9 @@
"general.myClass": "My Class", "general.myClass": "My Class",
"general.myClasses": "My Classes", "general.myClasses": "My Classes",
"general.myStuff": "My Stuff", "general.myStuff": "My Stuff",
"general.noDeletionTitle": "Your Account Will Not Be Deleted",
"general.noDeletionDescription": "Your account was scheduled for deletion but you logged in. Your account has been reactivated. If you didnt request for your account to be deleted, you should {resetLink} to make sure your account is secure.",
"general.noDeletionLink": "change your password",
"general.notRequired": "Not Required", "general.notRequired": "Not Required",
"general.other": "Other", "general.other": "Other",
"general.offlineEditor": "Offline Editor", "general.offlineEditor": "Offline Editor",

View file

@ -1,9 +1,9 @@
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var xhr = require('xhr'); const xhr = require('xhr');
var jar = require('./jar'); const jar = require('./jar');
var log = require('./log'); const log = require('./log');
var urlParams = require('./url-params'); const urlParams = require('./url-params');
/** /**
* Helper method that constructs requests to the scratch api. * Helper method that constructs requests to the scratch api.
@ -12,9 +12,11 @@ var urlParams = require('./url-params');
* CSRF forgeries (see: https://www.squarefree.com/securitytips/web-developers.html#CSRF) * CSRF forgeries (see: https://www.squarefree.com/securitytips/web-developers.html#CSRF)
* *
* It also takes in other arguments specified in the xhr library spec. * It also takes in other arguments specified in the xhr library spec.
*
* @param {object} opts optional xhr args (see above)
* @param {Function} callback [description]
*/ */
module.exports = (opts, callback) => {
module.exports = function (opts, callback) {
defaults(opts, { defaults(opts, {
host: process.env.API_HOST, host: process.env.API_HOST,
headers: {}, headers: {},
@ -40,26 +42,29 @@ module.exports = function (opts, callback) {
opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'; opts.headers['Content-Type'] = 'application/x-www-form-urlencoded';
} }
var apiRequest = function (opts) { const apiRequest = options => {
if (opts.host !== '') { if (options.host !== '') {
if ('withCredentials' in new XMLHttpRequest()) { if ('withCredentials' in new XMLHttpRequest()) {
opts.useXDR = false; options.useXDR = false;
} else { } else {
// For IE < 10, we must use XDR for cross-domain requests. XDR does not support // For IE < 10, we must use XDR for cross-domain requests. XDR does not support
// custom headers. // custom headers.
opts.useXDR = true; options.useXDR = true;
delete opts.headers; delete options.headers;
if (opts.authentication) { if (options.authentication) {
var authenticationParams = ['x-token=' + opts.authentication]; const authenticationParams = [`x-token=${options.authentication}`];
var parts = opts.uri.split('?'); const parts = options.uri.split('?');
var qs = (parts[1] || '').split('&').concat(authenticationParams).join('&'); const qs = (parts[1] || '')
opts.uri = parts[0] + '?' + qs; .split('&')
.concat(authenticationParams)
.join('&');
options.uri = `${parts[0]}?${qs}`;
} }
} }
} }
xhr(opts, function (err, res, body) { xhr(options, (err, res, body) => {
if (err) log.error(err); if (err) log.error(err);
if (opts.responseType === 'json' && typeof body === 'string') { if (options.responseType === 'json' && typeof body === 'string') {
// IE doesn't parse responses as JSON without the json attribute, // IE doesn't parse responses as JSON without the json attribute,
// even with responseType: 'json'. // even with responseType: 'json'.
// See https://github.com/Raynos/xhr/issues/123 // See https://github.com/Raynos/xhr/issues/123
@ -73,25 +78,25 @@ module.exports = function (opts, callback) {
// [{success: true, redirect: "/location/to/redirect"}] // [{success: true, redirect: "/location/to/redirect"}]
try { try {
if ('redirect' in body[0]) window.location = body[0].redirect; if ('redirect' in body[0]) window.location = body[0].redirect;
} catch (err) { } catch (e) {
// do nothing // do nothing
} }
callback(err, body, res); callback(err, body, res);
}); });
}.bind(this); };
if (typeof jar.get('scratchlanguage') !== 'undefined') { if (typeof jar.get('scratchlanguage') !== 'undefined') {
opts.headers['Accept-Language'] = jar.get('scratchlanguage') + ', en;q=0.8'; opts.headers['Accept-Language'] = `${jar.get('scratchlanguage')}, en;q=0.8`;
} }
if (opts.authentication) { if (opts.authentication) {
opts.headers['X-Token'] = opts.authentication; opts.headers['X-Token'] = opts.authentication;
} }
if (opts.useCsrf) { if (opts.useCsrf) {
jar.use('scratchcsrftoken', '/csrf_token/', function (err, csrftoken) { jar.use('scratchcsrftoken', '/csrf_token/', (err, csrftoken) => {
if (err) return log.error('Error while retrieving CSRF token', err); if (err) return log.error('Error while retrieving CSRF token', err);
opts.headers['X-CSRFToken'] = csrftoken; opts.headers['X-CSRFToken'] = csrftoken;
apiRequest(opts); apiRequest(opts);
}.bind(this)); });
} else { } else {
apiRequest(opts); apiRequest(opts);
} }

View file

@ -1,22 +1,28 @@
module.exports = {}; module.exports = {};
var countries = module.exports.data = require('iso-3166-2').data; const countries = module.exports.data = require('iso-3166-2').data;
module.exports.countryOptions = Object.keys(countries).map(function (code) { module.exports.countryOptions = Object.keys(countries).map(code => ({
return {value: code.toLowerCase(), label: countries[code].name}; value: code.toLowerCase(),
}).sort(function (a, b) { label: countries[code].name
return a.label < b.label ? -1 : 1; }))
.sort((a, b) => {
if (a.label < b.label) {
return -1;
}
return 1;
}); });
module.exports.subdivisionOptions = module.exports.subdivisionOptions = Object.keys(countries).reduce((subByCountry, code) => {
Object.keys(countries).reduce(function (subByCountry, code) { subByCountry[code.toLowerCase()] = Object.keys(countries[code].sub).map(subCode => ({
subByCountry[code.toLowerCase()] = Object.keys(countries[code].sub).map(function (subCode) {
return {
value: subCode.toLowerCase(), value: subCode.toLowerCase(),
label: countries[code].sub[subCode].name, label: countries[code].sub[subCode].name,
type: countries[code].sub[subCode].type type: countries[code].sub[subCode].type
}; }))
}).sort(function (a, b) { .sort((a, b) => {
return a.label < b.label ? -1 : 1; if (a.label < b.label) {
return -1;
}
return 1;
}); });
return subByCountry; return subByCountry;
}, {}); }, {});

View file

@ -1,34 +0,0 @@
/**
* Takes an ISO time and returns a string representing how long ago the date
* represents. For example, "2015-01-01T00:00:00" becomes "1 minute ago".
*
* Based on "JavaScript Pretty Date"
* Copyright (c) 2011 John Resig (ejohn.org)
* Licensed under the MIT and GPL licenses.
*/
var Format = {
date: function (stamp) {
stamp = (stamp || '').replace(/-/g,'/').replace(/[TZ]/g,' ');
var date = new Date(stamp);
var diff = (((new Date()).getTime() - date.getTime()) / 1000);
var day_diff = Math.floor(diff / 86400);
if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
return 'A while ago';
}
return day_diff == 0 && (
diff < 60 && 'Just now' ||
diff < 120 && '1 minute ago' ||
diff < 3600 && Math.floor( diff / 60 ) + ' minutes ago' ||
diff < 7200 && '1 hour ago' ||
diff < 86400 && Math.floor( diff / 3600 ) + ' hours ago') ||
day_diff == 1 && 'Yesterday' ||
day_diff < 7 && day_diff + ' days ago' ||
day_diff < 31 && Math.ceil( day_diff / 7 ) + ' weeks ago';
}
};
module.exports = Format;

View file

@ -1,9 +1,10 @@
/* This file contains breakpoints from _frameless.scss, to be used in MediaQuery elements. /* This file contains breakpoints from _frameless.scss, to be used in MediaQuery elements.
* All units are in px, as per _frameless.scss and the MediaQuery default arguments. * All units are in px, as per _frameless.scss and the MediaQuery default arguments.
*/ */
const frameless = {
module.exports = {
desktop: 942, desktop: 942,
tablet: 640, tablet: 640,
mobile: 480 mobile: 480
}; };
module.exports = frameless;

View file

@ -1,20 +1,20 @@
var requireAll = require('./require-all'); const requireAll = require('./require-all');
var ReactIntl = require('react-intl'); const ReactIntl = require('react-intl');
var allLocaleData = requireAll(require.context('react-intl/locale-data', true, /^\.\/.*\.js$/)); const allLocaleData = requireAll(require.context('react-intl/locale-data', true, /^\.\/.*\.js$/));
var customLocaleData = require('../../custom-locales.json'); const customLocaleData = require('../../custom-locales.json');
/** /**
* Add all locales * Add all locales
*/ */
for (var locale in allLocaleData) { for (const locale in allLocaleData) {
ReactIntl.addLocaleData(allLocaleData[locale]); ReactIntl.addLocaleData(allLocaleData[locale]);
} }
/** /**
* Add custom locales to react-intl if it doesn't have them. * Add custom locales to react-intl if it doesn't have them.
*/ */
for (var customLocale in customLocaleData) { for (const customLocale in customLocaleData) {
ReactIntl.addLocaleData(customLocaleData[customLocale]); ReactIntl.addLocaleData(customLocaleData[customLocale]);
} }

View file

@ -1,7 +1,7 @@
var cookie = require('cookie'); const cookie = require('cookie');
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var xhr = require('xhr'); const xhr = require('xhr');
var pako = require('pako'); const pako = require('pako');
/** /**
* Module that handles coookie interactions. * Module that handles coookie interactions.
@ -11,16 +11,16 @@ var pako = require('pako');
* set(name, value) synchronously sets the cookie * set(name, value) synchronously sets the cookie
* use(name, uri, callback) can by sync or async, gets cookie from the uri if not there. * use(name, uri, callback) can by sync or async, gets cookie from the uri if not there.
*/ */
var Jar = { const Jar = {
unsign: function (value, callback) { unsign: (value, callback) => {
// Return the usable content portion of a signed, compressed cookie generated by // Return the usable content portion of a signed, compressed cookie generated by
// Django's signing module // Django's signing module
// https://github.com/django/django/blob/stable/1.8.x/django/core/signing.py // https://github.com/django/django/blob/stable/1.8.x/django/core/signing.py
if (typeof value === 'undefined') return callback(null, value); if (typeof value === 'undefined') return callback(null, value);
try { try {
var b64Data = value.split(':')[0]; let b64Data = value.split(':')[0];
var decompress = false; let decompress = false;
if (b64Data[0] === '.') { if (b64Data[0] === '.') {
decompress = true; decompress = true;
b64Data = b64Data.substring(1); b64Data = b64Data.substring(1);
@ -29,24 +29,29 @@ var Jar = {
// Django makes its base64 strings url safe by replacing + and / with - and _ respectively // Django makes its base64 strings url safe by replacing + and / with - and _ respectively
// using base64.urlsafe_b64encode // using base64.urlsafe_b64encode
// https://docs.python.org/2/library/base64.html#base64.b64encode // https://docs.python.org/2/library/base64.html#base64.b64encode
b64Data = b64Data.replace(/[-_]/g, function (c) {return {'-':'+', '_':'/'}[c]; }); b64Data = b64Data.replace(
var strData = atob(b64Data); /[-_]/g,
c => ({
'-': '+',
'_': '/'
}[c])
);
let strData = atob(b64Data);
if (decompress) { if (decompress) {
var charData = strData.split('').map(function (c) { return c.charCodeAt(0); }); const charData = strData.split('').map(c => (c.charCodeAt(0)));
var binData = new Uint8Array(charData); const binData = new Uint8Array(charData);
var data = pako.inflate(binData); const data = pako.inflate(binData);
strData = String.fromCharCode.apply(null, new Uint16Array(data)); strData = String.fromCharCode.apply(null, new Uint16Array(data));
} }
return callback(null, strData); return callback(null, strData);
} catch (e) { } catch (e) {
return callback(e); return callback(e);
} }
}, },
get: function (name, callback) { get: (name, callback) => {
// Get cookie by name // Get cookie by name
var obj = cookie.parse(document.cookie) || {}; const obj = cookie.parse(document.cookie) || {};
// Handle optional callback // Handle optional callback
if (typeof callback === 'function') { if (typeof callback === 'function') {
@ -56,44 +61,44 @@ var Jar = {
return obj[name]; return obj[name];
}, },
use: function (name, uri, callback) { use: (name, uri, callback) => {
// Attempt to get cookie // Attempt to get cookie
Jar.get(name, function (err, obj) { Jar.get(name, (err, obj) => {
if (typeof obj !== 'undefined') return callback(null, obj); if (typeof obj !== 'undefined') return callback(null, obj);
// Make XHR request to cookie setter uri // Make XHR request to cookie setter uri
xhr({ xhr({
uri: uri uri: uri
}, function (err) { }, e => {
if (err) return callback(err); if (e) return callback(e);
Jar.get(name, callback); Jar.get(name, callback);
}); });
}); });
}, },
set: function (name, value, opts) { set: (name, value, opts) => {
opts = opts || {}; opts = opts || {};
defaults(opts, { defaults(opts, {
expires: new Date(new Date().setYear(new Date().getFullYear() + 1)) expires: new Date(new Date().setYear(new Date().getFullYear() + 1))
}); });
opts.path = '/'; opts.path = '/';
var obj = cookie.serialize(name, value, opts); const obj = cookie.serialize(name, value, opts);
document.cookie = obj; document.cookie = obj;
}, },
getUnsignedValue: function (cookieName, signedValue, callback) { getUnsignedValue: (cookieName, signedValue, callback) => {
// Get a value from a signed object // Get a value from a signed object
Jar.get(cookieName, function (err, value) { Jar.get(cookieName, (err, value) => {
if (err) return callback(err); if (err) return callback(err);
if (typeof value === 'undefined') return callback(null, value); if (typeof value === 'undefined') return callback(null, value);
Jar.unsign(value, function (err, contents) { Jar.unsign(value, (e, contents) => {
if (err) return callback(err); if (e) return callback(e);
try { try {
var data = JSON.parse(contents); const data = JSON.parse(contents);
} catch (err) {
return callback(err);
}
return callback(null, data[signedValue]); return callback(null, data[signedValue]);
} catch (error) {
return callback(error);
}
}); });
}); });
} }

View file

@ -1,4 +1,4 @@
var minilog = require('minilog'); const minilog = require('minilog');
minilog.enable(); minilog.enable();
module.exports = minilog('www'); module.exports = minilog('www');

View file

@ -1,21 +1,27 @@
var redux = require('redux'); const redux = require('redux');
var thunk = require('redux-thunk').default; const thunk = require('redux-thunk').default;
// JSX syntax transforms to React.createElement // JSX syntax transforms to React.createElement
var React = require('react'); // eslint-disable-line const React = require('react'); // eslint-disable-line
var ReactDOM = require('react-dom'); const ReactDOM = require('react-dom');
var StoreProvider = require('react-redux').Provider; const StoreProvider = require('react-redux').Provider;
var IntlProvider = require('./intl.jsx').IntlProvider; const IntlProvider = require('./intl.jsx').IntlProvider;
var permissionsActions = require('../redux/permissions.js'); const permissionsActions = require('../redux/permissions.js');
var sessionActions = require('../redux/session.js'); const sessionActions = require('../redux/session.js');
var reducer = require('../redux/reducer.js'); const reducer = require('../redux/reducer.js');
require('../main.scss'); require('../main.scss');
var render = function (jsx, element, reducers) { /**
* Function to render views into a full page
* @param {object} jsx jsx component of the view
* @param {object} element html element to render to on the template
* @param {array} reducers list of view-specific reducers
*/
const render = (jsx, element, reducers) => {
// Get locale and messages from global namespace (see "init.js") // Get locale and messages from global namespace (see "init.js")
var locale = window._locale || 'en'; let locale = window._locale || 'en';
var messages = {}; let messages = {};
if (typeof window._messages !== 'undefined') { if (typeof window._messages !== 'undefined') {
if (typeof window._messages[locale] === 'undefined') { if (typeof window._messages[locale] === 'undefined') {
// Fall back on the split // Fall back on the split
@ -28,8 +34,8 @@ var render = function (jsx, element, reducers) {
messages = window._messages[locale]; messages = window._messages[locale];
} }
var allReducers = reducer(reducers); const allReducers = reducer(reducers);
var store = redux.createStore( const store = redux.createStore(
allReducers, allReducers,
redux.applyMiddleware(thunk) redux.applyMiddleware(thunk)
); );
@ -37,7 +43,10 @@ var render = function (jsx, element, reducers) {
// Render view component // Render view component
ReactDOM.render( ReactDOM.render(
<StoreProvider store={store}> <StoreProvider store={store}>
<IntlProvider locale={locale} messages={messages}> <IntlProvider
locale={locale}
messages={messages}
>
{jsx} {jsx}
</IntlProvider> </IntlProvider>
</StoreProvider>, </StoreProvider>,

View file

@ -1,5 +1,5 @@
var requireAll = function (requireContext) { const requireAll = requireContext => (
return requireContext.keys().map(requireContext); requireContext.keys().map(requireContext)
}; );
module.exports = requireAll; module.exports = requireAll;

View file

@ -1,16 +1,12 @@
/* /*
* Function that shuffles an array using a Fisher-Yates shuffle. * Function that shuffles an array using a Fisher-Yates shuffle.
*/ */
module.exports.shuffle = arr => {
module.exports.shuffle = function (arr) { let i = 0;
var i, j = 0; let j = 0;
var temp = null; let temp = null;
if (arr) { if (arr) {
var tempArray = arr.slice(0); const tempArray = arr.slice(0);
} else {
return arr;
}
for (i = arr.length - 1; i > 0; i -= 1) { for (i = arr.length - 1; i > 0; i -= 1) {
j = Math.floor(Math.random() * (i + 1)); j = Math.floor(Math.random() * (i + 1));
temp = tempArray[i]; temp = tempArray[i];
@ -18,4 +14,6 @@ module.exports.shuffle = function (arr) {
tempArray[j] = temp; tempArray[j] = temp;
} }
return tempArray; return tempArray;
}
return arr;
}; };

View file

@ -2,21 +2,18 @@
* urlParams({a: 1, b: 2, c: 3}) * urlParams({a: 1, b: 2, c: 3})
* // a=1&b=2&c=3 * // a=1&b=2&c=3
*/ */
module.exports = function urlParams (values) { const params = values => (
return Object Object.keys(values).map(key => {
.keys(values) const value = typeof values[key] === 'undefined' ? '' : values[key];
.map(function (key) { const encodeKeyValuePair = val => (
var value = typeof values[key] === 'undefined' ? '' : values[key]; [key, val].map(encodeURIComponent).join('=')
function encodeKeyValuePair (value) { );
return [key, value]
.map(encodeURIComponent)
.join('=');
}
if (Array.isArray(value)) { if (Array.isArray(value)) {
return value.map(encodeKeyValuePair).join('&'); return value.map(encodeKeyValuePair).join('&');
} else {
return encodeKeyValuePair(value);
} }
return encodeKeyValuePair(value);
}) })
.join('&'); .join('&')
}; );
module.exports = params;

View file

@ -1,13 +1,13 @@
var keyMirror = require('keymirror'); const keyMirror = require('keymirror');
var api = require('../lib/api'); const api = require('../lib/api');
var Types = keyMirror({ const Types = keyMirror({
SET_DETAILS: null, SET_DETAILS: null,
SET_DETAILS_FETCHING: null, SET_DETAILS_FETCHING: null,
SET_DETAILS_ERROR: null SET_DETAILS_ERROR: null
}); });
module.exports.detailsReducer = function (state, action) { module.exports.detailsReducer = (state, action) => {
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = {}; state = {};
} }
@ -23,49 +23,35 @@ module.exports.detailsReducer = function (state, action) {
} }
}; };
module.exports.setDetailsError = function (error) { module.exports.setDetailsError = error => ({
return {
type: Types.SET_DETAILS_ERROR, type: Types.SET_DETAILS_ERROR,
error: error error: error
}; });
};
module.exports.setDetails = function (details) { module.exports.setDetails = details => ({
return {
type: Types.SET_DETAILS, type: Types.SET_DETAILS,
details: details details: details
}; });
};
module.exports.setDetailsFetching = function () { module.exports.setDetailsFetching = () => ({
return {
type: Types.SET_DETAILS_FETCHING, type: Types.SET_DETAILS_FETCHING,
fetching: true fetching: true
}; });
};
module.exports.startGetDetails = function (id) { module.exports.getDetails = id => (dispatch => {
return function (dispatch) {
dispatch(module.exports.setDetailsFetching());
dispatch(module.exports.getDetails(id));
};
};
module.exports.getDetails = function (id) {
return function (dispatch) {
api({ api({
uri: '/conference/' + id + '/details' uri: `/conference/${id}/details`
}, function (err, body) { }, (err, body) => {
if (err) { if (err) {
dispatch(module.exports.setDetailsError(err)); dispatch(module.exports.setDetailsError(err));
return; return;
} }
if (typeof body !== 'undefined') { if (typeof body !== 'undefined') {
var columns = body.columns; const columns = body.columns;
if (body.rows) { if (body.rows) {
var details = body.rows[0]; const details = body.rows[0];
var detailsObject = details.reduce(function (prev, cur, index) { const detailsObject = details.reduce((prev, cur, index) => {
prev[columns[index]] = cur; prev[columns[index]] = cur;
return prev; return prev;
}, {}); }, {});
@ -74,10 +60,13 @@ module.exports.getDetails = function (id) {
dispatch(module.exports.setDetailsError('Not Found')); dispatch(module.exports.setDetailsError('Not Found'));
} }
return; return;
} else { }
dispatch(module.exports.setDetailsError('An unexpected error occurred')); dispatch(module.exports.setDetailsError('An unexpected error occurred'));
return; return;
}
}); });
}; });
};
module.exports.startGetDetails = id => (dispatch => {
dispatch(module.exports.setDetailsFetching());
dispatch(module.exports.getDetails(id));
});

View file

@ -1,13 +1,13 @@
var keyMirror = require('keymirror'); const keyMirror = require('keymirror');
var api = require('../lib/api'); const api = require('../lib/api');
var Types = keyMirror({ const Types = keyMirror({
SET_SCHEDULE: null, SET_SCHEDULE: null,
SET_SCHEDULE_FETCHING: null, SET_SCHEDULE_FETCHING: null,
SET_SCHEDULE_ERROR: null SET_SCHEDULE_ERROR: null
}); });
module.exports.scheduleReducer = function (state, action) { module.exports.scheduleReducer = (state, action) => {
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = { state = {
timeSlots: [], timeSlots: [],
@ -26,40 +26,27 @@ module.exports.scheduleReducer = function (state, action) {
} }
}; };
module.exports.setSchedule = function (schedule) { module.exports.setSchedule = schedule => ({
return {
type: Types.SET_SCHEDULE, type: Types.SET_SCHEDULE,
schedule: schedule schedule: schedule
}; });
};
module.exports.setScheduleFetching = function () { module.exports.setScheduleFetching = () => ({
return {
type: Types.SET_SCHEDULE_FETCHING, type: Types.SET_SCHEDULE_FETCHING,
fetching: true fetching: true
}; });
};
module.exports.setScheduleError = function (error) { module.exports.setScheduleError = error => ({
return {
type: Types.SET_SCHEDULE_ERROR, type: Types.SET_SCHEDULE_ERROR,
error: error error: error
}; });
};
module.exports.startGetSchedule = function (day) {
return function (dispatch) {
dispatch(module.exports.setScheduleFetching());
dispatch(module.exports.getDaySchedule(day));
};
};
// group periods of time by start time // group periods of time by start time
module.exports.sortTimeSlots = function (timeSlot1, timeSlot2) { module.exports.sortTimeSlots = (timeSlot1, timeSlot2) => {
var timeSlot1Am = (timeSlot1.time.substr(timeSlot1.time.length - 1, timeSlot1.time.length) === 'a') ? true : false; const timeSlot1Am = (timeSlot1.time.substr(timeSlot1.time.length - 1, timeSlot1.time.length) === 'a');
var timeSlot2Am = (timeSlot2.time.substr(timeSlot2.time.length - 1, timeSlot2.time.length) === 'a') ? true : false; const timeSlot2Am = (timeSlot2.time.substr(timeSlot2.time.length - 1, timeSlot2.time.length) === 'a');
var timeSlot1Time = parseInt(timeSlot1.time.substr(0, timeSlot1.time.length - 1)); let timeSlot1Time = parseInt(timeSlot1.time.substr(0, timeSlot1.time.length - 1), 10);
var timeSlot2Time = parseInt(timeSlot2.time.substr(0, timeSlot2.time.length - 1)); let timeSlot2Time = parseInt(timeSlot2.time.substr(0, timeSlot2.time.length - 1), 10);
// convert to 24-hour for sorting // convert to 24-hour for sorting
if (timeSlot1Time !== 12 && !timeSlot1Am) { if (timeSlot1Time !== 12 && !timeSlot1Am) {
@ -71,40 +58,38 @@ module.exports.sortTimeSlots = function (timeSlot1, timeSlot2) {
if (timeSlot1Time < timeSlot2Time) { if (timeSlot1Time < timeSlot2Time) {
return -1; return -1;
} else {
return 1;
} }
return 1;
}; };
/** /**
* Gets the schedule for the given day from the api * Gets the schedule for the given day from the api
* @param {String} day Day of the conference (Thursday, Friday or Satrurday) * @param {string} day Day of the conference (Thursday, Friday or Satrurday)
* *
* @return {Object} Schedule for the day, broken into chunks * @return {object} Schedule for the day, broken into chunks
*/ */
module.exports.getDaySchedule = function (day) { module.exports.getDaySchedule = day => (dispatch => {
return function (dispatch) {
api({ api({
uri: '/conference/schedule/' + day uri: `/conference/schedule/${day}`
}, function (err, body) { }, (err, body) => {
if (err) { if (err) {
dispatch(module.exports.setScheduleError(err)); dispatch(module.exports.setScheduleError(err));
return; return;
} }
if (typeof body !== 'undefined') { if (typeof body !== 'undefined') {
var columns = body.columns; const columns = body.columns;
var rows = body.rows || []; const rows = body.rows || [];
// Group events by the time period in which they occur (for presentation) // Group events by the time period in which they occur (for presentation)
var scheduleByTimeSlot = rows.reduce(function (prev, cur) { const scheduleByTimeSlot = rows.reduce((prev, cur) => {
var cleanedRow = {}; const cleanedRow = {};
for (var i = 0; i < columns.length; i++) { for (let i = 0; i < columns.length; i++) {
if (cur[i].length > 0) { if (cur[i].length > 0) {
cleanedRow[columns[i]] = cur[i]; cleanedRow[columns[i]] = cur[i];
} }
} }
cleanedRow['uri'] = '/conference/2016/' + cleanedRow.rowid + '/details'; cleanedRow.uri = `/conference/2016/${cleanedRow.rowid}/details`;
var timeSlot = cleanedRow.Chunk + cleanedRow.Start; const timeSlot = cleanedRow.Chunk + cleanedRow.Start;
if (typeof prev.timeSlots[timeSlot] === 'undefined') { if (typeof prev.timeSlots[timeSlot] === 'undefined') {
prev.timeSlots[timeSlot] = [cleanedRow]; prev.timeSlots[timeSlot] = [cleanedRow];
prev.info.push({ prev.info.push({
@ -118,21 +103,22 @@ module.exports.getDaySchedule = function (day) {
}, {timeSlots: [], info: []}); }, {timeSlots: [], info: []});
scheduleByTimeSlot.info.sort(module.exports.sortTimeSlots); scheduleByTimeSlot.info.sort(module.exports.sortTimeSlots);
var schedule = scheduleByTimeSlot.info.map(function (timeSlot) { const schedule = scheduleByTimeSlot.info.map(timeSlot => ({
return {
info: timeSlot, info: timeSlot,
items: scheduleByTimeSlot.timeSlots[timeSlot.name + timeSlot.time] items: scheduleByTimeSlot.timeSlots[timeSlot.name + timeSlot.time]
}; }));
});
dispatch(module.exports.setSchedule({ dispatch(module.exports.setSchedule({
timeSlots: schedule, timeSlots: schedule,
day: day day: day
})); }));
return; return;
} else { }
dispatch(module.exports.setScheduleError('An unexpected error occurred')); dispatch(module.exports.setScheduleError('An unexpected error occurred'));
return; return;
}
}); });
}; });
};
module.exports.startGetSchedule = day => (dispatch => {
dispatch(module.exports.setScheduleFetching());
dispatch(module.exports.getDaySchedule(day));
});

View file

@ -1,22 +1,22 @@
var keyMirror = require('keymirror'); const keyMirror = require('keymirror');
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var api = require('../lib/api'); const api = require('../lib/api');
var Types = keyMirror({ const Types = keyMirror({
SET_MESSAGE_COUNT: null, SET_MESSAGE_COUNT: null,
SET_MESSAGE_COUNT_ERROR: null, SET_MESSAGE_COUNT_ERROR: null,
SET_STATUS: null SET_STATUS: null
}); });
module.exports.getInitialState = function (){ const getInitialState = () => ({
return {messageCount: 0}; messageCount: 0
}; });
module.exports.messageCountReducer = function (state, action) { module.exports.messageCountReducer = (state, action) => {
// Reducer for handling changes to session state // Reducer for handling changes to session state
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = module.exports.getInitialState(); state = getInitialState();
} }
switch (action.type) { switch (action.type) {
case Types.SET_MESSAGE_COUNT: case Types.SET_MESSAGE_COUNT:
@ -31,40 +31,32 @@ module.exports.messageCountReducer = function (state, action) {
} }
}; };
module.exports.setSessionError = function (error) { module.exports.setSessionError = error => ({
return {
type: Types.SET_MESSAGE_COUNT_ERROR, type: Types.SET_MESSAGE_COUNT_ERROR,
error: error error: error
}; });
};
module.exports.setCount = function (count) { module.exports.setCount = count => ({
return {
type: Types.SET_MESSAGE_COUNT, type: Types.SET_MESSAGE_COUNT,
count: count count: count
}; });
};
module.exports.setStatus = function (status){ module.exports.setStatus = status => ({
return {
type: Types.SET_STATUS, type: Types.SET_STATUS,
status: status status: status
}; });
};
module.exports.getCount = function (username) { module.exports.getCount = username => (dispatch => {
return function (dispatch) {
api({ api({
method: 'get', method: 'get',
uri: '/users/' + username + '/messages/count' uri: `/users/${username}/messages/count`
}, function (err, body) { }, (err, body) => {
if (err) { if (err) {
dispatch(module.exports.setCount(0)); dispatch(module.exports.setCount(0));
dispatch(module.exports.setSessionError(err)); dispatch(module.exports.setSessionError(err));
return; return;
} }
var count = parseInt(body.count, 10); const count = parseInt(body.count, 10);
dispatch(module.exports.setCount(count)); dispatch(module.exports.setCount(count));
}); });
}; });
};

View file

@ -1,10 +1,10 @@
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var defaultsDeep = require('lodash.defaultsdeep'); const defaultsDeep = require('lodash.defaultsdeep');
var keyMirror = require('keymirror'); const keyMirror = require('keymirror');
var api = require('../lib/api'); const api = require('../lib/api');
var log = require('../lib/log'); const log = require('../lib/log');
var messageCountActions = require('./message-count.js'); const messageCountActions = require('./message-count.js');
module.exports.Status = keyMirror({ module.exports.Status = keyMirror({
FETCHED: null, FETCHED: null,
@ -17,8 +17,7 @@ module.exports.Status = keyMirror({
DELETE_ERROR: null DELETE_ERROR: null
}); });
module.exports.getInitialState = function () { module.exports.getInitialState = () => ({
return {
status: { status: {
admin: module.exports.Status.NOT_FETCHED, admin: module.exports.Status.NOT_FETCHED,
message: module.exports.Status.NOT_FETCHED, message: module.exports.Status.NOT_FETCHED,
@ -30,10 +29,9 @@ module.exports.getInitialState = function () {
social: [], social: [],
invite: {} invite: {}
} }
}; });
};
module.exports.messagesReducer = function (state, action) { module.exports.messagesReducer = (state, action) => {
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = module.exports.getInitialState(); state = module.exports.getInitialState();
} }
@ -68,61 +66,48 @@ module.exports.messagesReducer = function (state, action) {
} }
}; };
module.exports.setMessagesError = function (error) { module.exports.setMessagesError = error => ({
return {
type: 'ERROR', type: 'ERROR',
error: error error: error
}; });
};
module.exports.setMessages = function (messages) { module.exports.setMessages = messages => ({
return {
type: 'SET_MESSAGES', type: 'SET_MESSAGES',
messages: messages messages: messages
}; });
};
module.exports.setMessagesOffset = function (offset) { module.exports.setMessagesOffset = offset => ({
return {
type: 'SET_MESSAGES_OFFSET', type: 'SET_MESSAGES_OFFSET',
offset: offset offset: offset
}; });
};
module.exports.setAdminMessages = function (messages) { module.exports.setAdminMessages = messages => ({
return {
type: 'SET_ADMIN_MESSAGES', type: 'SET_ADMIN_MESSAGES',
messages: messages messages: messages
}; });
};
module.exports.setScratcherInvite = function (invite) { module.exports.setScratcherInvite = invite => ({
return {
type: 'SET_SCRATCHER_INVITE', type: 'SET_SCRATCHER_INVITE',
invite: invite invite: invite
}; });
};
module.exports.setStatus = function (type, status){ module.exports.setStatus = (type, status) => ({
return {
type: type, type: type,
status: status status: status
}; });
};
/** /**
* Sends a request to mark one's unread messages count as cleared. * Sends a request to mark one's unread messages count as cleared.
* @return {null} returns nothing * @return {null} returns nothing
*/ */
module.exports.clearMessageCount = function () { module.exports.clearMessageCount = () => (dispatch => {
return function (dispatch) {
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING)); dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING));
api({ api({
host: '', host: '',
uri: '/site-api/messages/messages-clear/', uri: '/site-api/messages/messages-clear/',
method: 'POST', method: 'POST',
useCsrf: true useCsrf: true
}, function (err, body) { }, (err, body) => {
if (err) { if (err) {
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR)); dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR));
dispatch(module.exports.setMessagesError(err)); dispatch(module.exports.setMessagesError(err));
@ -135,8 +120,7 @@ module.exports.clearMessageCount = function () {
} }
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHED)); dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHED));
}); });
}; });
};
/** /**
* Marks an admin message as read, dismissing it from the page * Marks an admin message as read, dismissing it from the page
@ -146,8 +130,8 @@ module.exports.clearMessageCount = function () {
* @param {object[]} adminMessages current list of admin messages retrieved * @param {object[]} adminMessages current list of admin messages retrieved
* @return {null} returns nothing * @return {null} returns nothing
*/ */
module.exports.clearAdminMessage = function (messageType, messageId, messageCount, adminMessages) { module.exports.clearAdminMessage = (messageType, messageId, messageCount, adminMessages) => (
return function (dispatch) { dispatch => {
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING)); dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING));
api({ api({
host: '', host: '',
@ -158,7 +142,7 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
alertType: messageType, alertType: messageType,
alertId: messageId alertId: messageId
} }
}, function (err, body) { }, (err, body) => {
if (err) { if (err) {
dispatch(module.exports.setStatus('DELETE_STATUS', module.exports.Status.DELETE_ERROR)); dispatch(module.exports.setStatus('DELETE_STATUS', module.exports.Status.DELETE_ERROR));
dispatch(module.exports.setMessagesError(err)); dispatch(module.exports.setMessagesError(err));
@ -175,9 +159,9 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
dispatch(module.exports.setScratcherInvite({})); dispatch(module.exports.setScratcherInvite({}));
} else { } else {
// find the admin message and remove it // find the admin message and remove it
var toRemove = -1; let toRemove = -1;
for (var i in adminMessages) { for (const i of adminMessages) {
if (adminMessages[i].id === messageId) { if (i.id === messageId) {
toRemove = i; toRemove = i;
break; break;
} }
@ -188,8 +172,8 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
dispatch(messageCountActions.setCount(messageCount - 1)); dispatch(messageCountActions.setCount(messageCount - 1));
dispatch(module.exports.setStatus('DELETE_STATUS', module.exports.Status.FETCHED)); dispatch(module.exports.setStatus('DELETE_STATUS', module.exports.Status.FETCHED));
}); });
}; }
}; );
/** /**
* Gets a user's messages to be displayed on the /messages page * Gets a user's messages to be displayed on the /messages page
@ -201,7 +185,7 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
* @param {string} [opts.filter] type of messages to return * @param {string} [opts.filter] type of messages to return
* @return {null} returns nothing * @return {null} returns nothing
*/ */
module.exports.getMessages = function (username, token, opts) { module.exports.getMessages = (username, token, opts) => {
opts = defaults(opts, { opts = defaults(opts, {
messages: [], messages: [],
offset: 0, offset: 0,
@ -209,17 +193,17 @@ module.exports.getMessages = function (username, token, opts) {
clearCount: true clearCount: true
}); });
var filterArg = ''; let filterArg = '';
if (opts.filter.length > 0) { if (opts.filter.length > 0) {
filterArg = '&filter=' + opts.filter; filterArg = `&filter=${opts.filter}`;
} }
return function (dispatch) { return dispatch => {
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.FETCHING)); dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.FETCHING));
api({ api({
uri: '/users/' + username + '/messages?limit=40&offset=' + opts.offset + filterArg, uri: `/users/${username}/messages?limit=40&offset=${opts.offset}${filterArg}`,
authentication: token authentication: token
}, function (err, body) { }, (err, body) => {
if (err) { if (err) {
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.MESSAGES_ERROR)); dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.MESSAGES_ERROR));
dispatch(module.exports.setMessagesError(err)); dispatch(module.exports.setMessagesError(err));
@ -246,13 +230,12 @@ module.exports.getMessages = function (username, token, opts) {
* @param {string} token the user's unique token for auth * @param {string} token the user's unique token for auth
* @return {null} returns nothing * @return {null} returns nothing
*/ */
module.exports.getAdminMessages = function (username, token) { module.exports.getAdminMessages = (username, token) => (dispatch => {
return function (dispatch) {
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHING)); dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHING));
api({ api({
uri: '/users/' + username + '/messages/admin', uri: `/users/${username}/messages/admin`,
authentication: token authentication: token
}, function (err, body) { }, (err, body) => {
if (err) { if (err) {
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR)); dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR));
dispatch(module.exports.setMessagesError(err)); dispatch(module.exports.setMessagesError(err));
@ -268,8 +251,7 @@ module.exports.getAdminMessages = function (username, token) {
dispatch(module.exports.setAdminMessages(body)); dispatch(module.exports.setAdminMessages(body));
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHED)); dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHED));
}); });
}; });
};
/** /**
* Gets the invitation to become a Scratcher for a user, if one exists * Gets the invitation to become a Scratcher for a user, if one exists
@ -277,12 +259,11 @@ module.exports.getAdminMessages = function (username, token) {
* @param {string} token the user's unique token for auth * @param {string} token the user's unique token for auth
* @return {null} returns nothing * @return {null} returns nothing
*/ */
module.exports.getScratcherInvite = function (username, token) { module.exports.getScratcherInvite = (username, token) => (dispatch => {
return function (dispatch) {
api({ api({
uri: '/users/' + username + '/invites', uri: `/users/${username}/invites`,
authentication: token authentication: token
}, function (err, body) { }, (err, body) => {
if (err) { if (err) {
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.INVITE_ERROR)); dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.INVITE_ERROR));
dispatch(module.exports.setMessagesError(err)); dispatch(module.exports.setMessagesError(err));
@ -292,5 +273,4 @@ module.exports.getScratcherInvite = function (username, token) {
if (typeof body === 'undefined') return dispatch(module.exports.setMessagesError('No session content')); if (typeof body === 'undefined') return dispatch(module.exports.setMessagesError('No session content'));
dispatch(module.exports.setScratcherInvite(body)); dispatch(module.exports.setScratcherInvite(body));
}); });
}; });
};

View file

@ -1,10 +1,10 @@
var keyMirror = require('keymirror'); const keyMirror = require('keymirror');
var Types = keyMirror({ const Types = keyMirror({
SET_SEARCH_TERM: null SET_SEARCH_TERM: null
}); });
module.exports.navigationReducer = function (state, action) { module.exports.navigationReducer = (state, action) => {
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = ''; state = '';
} }
@ -16,9 +16,7 @@ module.exports.navigationReducer = function (state, action) {
} }
}; };
module.exports.setSearchTerm = function (searchTerm) { module.exports.setSearchTerm = searchTerm => ({
return {
type: Types.SET_SEARCH_TERM, type: Types.SET_SEARCH_TERM,
searchTerm: searchTerm searchTerm: searchTerm
}; });
};

View file

@ -1,14 +1,14 @@
var keyMirror = require('keymirror'); const keyMirror = require('keymirror');
var jar = require('../lib/jar.js'); const jar = require('../lib/jar.js');
var Types = keyMirror({ const Types = keyMirror({
SET_PERMISSIONS: null, SET_PERMISSIONS: null,
SET_PERMISSIONS_ERROR: null SET_PERMISSIONS_ERROR: null
}); });
module.exports.permissionsReducer = function (state, action) { module.exports.permissionsReducer = (state, action) => {
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = ''; state = {};
} }
switch (action.type) { switch (action.type) {
case Types.SET_PERMISSIONS: case Types.SET_PERMISSIONS:
@ -20,21 +20,20 @@ module.exports.permissionsReducer = function (state, action) {
} }
}; };
module.exports.storePermissions = function (permissions) { module.exports.storePermissions = permissions => {
permissions = permissions || {}; permissions = permissions || {};
return function (dispatch) { return dispatch => {
jar.set('permissions', permissions, { jar.set('permissions', permissions, {
encode: function (value) { encode: value => (
return encodeURIComponent(JSON.stringify(value)); encodeURIComponent(JSON.stringify(value))
} )
}); });
return dispatch(module.exports.setPermissions(permissions)); return dispatch(module.exports.setPermissions(permissions));
}; };
}; };
module.exports.getPermissions = function () { module.exports.getPermissions = () => (dispatch => {
return function (dispatch) { jar.get('permissions', (err, value) => {
jar.get('permissions', function (err, value) {
if (err) return dispatch(module.exports.setPermissionsError(err)); if (err) return dispatch(module.exports.setPermissionsError(err));
try { try {
@ -44,19 +43,14 @@ module.exports.getPermissions = function () {
} }
return dispatch(module.exports.setPermissions(value)); return dispatch(module.exports.setPermissions(value));
}); });
}; });
};
module.exports.setPermissions = function (permissions) { module.exports.setPermissions = permissions => ({
return {
type: Types.SET_PERMISSIONS, type: Types.SET_PERMISSIONS,
permissions: permissions permissions: permissions
}; });
};
module.exports.setPermissionsError = function (error) { module.exports.setPermissionsError = error => ({
return {
type: Types.SET_PERMISSIONS_ERROR, type: Types.SET_PERMISSIONS_ERROR,
error: error error: error
}; });
};

View file

@ -1,9 +1,9 @@
var combineReducers = require('redux').combineReducers; const combineReducers = require('redux').combineReducers;
var defaults = require('lodash.defaults'); const defaults = require('lodash.defaults');
var messageCountReducer = require('./message-count.js').messageCountReducer; const messageCountReducer = require('./message-count.js').messageCountReducer;
var permissionsReducer = require('./permissions.js').permissionsReducer; const permissionsReducer = require('./permissions.js').permissionsReducer;
var sessionReducer = require('./session.js').sessionReducer; const sessionReducer = require('./session.js').sessionReducer;
/** /**
* Returns a combined reducer to be used for a page in `render.jsx`. * Returns a combined reducer to be used for a page in `render.jsx`.
@ -11,11 +11,11 @@ var sessionReducer = require('./session.js').sessionReducer;
* - and any reducers specific to the page should be passed into * - and any reducers specific to the page should be passed into
* `render()` as an object (which will then be passed to the function * `render()` as an object (which will then be passed to the function
* below). * below).
* @param {Object} opts key/value where the key is the name of the * @param {object} opts key/value where the key is the name of the
* redux state, value is the reducer function. * redux state, value is the reducer function.
* @return {Object} combined reducer to be used in the redux store * @return {object} combined reducer to be used in the redux store
*/ */
module.exports = function (opts) { module.exports = opts => {
opts = opts || {}; opts = opts || {};
return combineReducers(defaults(opts, { return combineReducers(defaults(opts, {
session: sessionReducer, session: sessionReducer,

Some files were not shown because too many files have changed in this diff Show more