Revert "[Develop] Upgrade to ES6"

This commit is contained in:
Ray Schamp 2018-01-30 09:54:45 -05:00 committed by GitHub
parent a144bab0e6
commit 46511e1253
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
185 changed files with 11594 additions and 13650 deletions
.babelrc.eslintrc.eslintrc.js
.tx
Makefile
bin
dev-server
package.json
src
.eslintrc.js
components
init.jsl10n.json
lib
redux

View file

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

29
.eslintrc Normal file
View file

@ -0,0 +1,29 @@
{
"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"
}

View file

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

View file

@ -133,9 +133,3 @@ 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

@ -55,7 +55,7 @@ test:
@make tap @make tap
lint: lint:
$(ESLINT) . --ext .js,.jsx $(ESLINT) . --ext .js,.jsx,.json
$(SASSLINT) ./src/*.scss $(SASSLINT) ./src/*.scss
$(SASSLINT) ./src/**/*.scss $(SASSLINT) ./src/**/*.scss

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 routeJson = require('../src/routes.json'); var route_json = 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 = routeJson.map(function (route) { var routes = route_json.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 (e, resp) { fastly.cloneVersion(response.number, function (err, response) {
if (e) return cb('Failed to clone latest version: ' + e); if (err) return cb('Failed to clone latest version: ' + err);
cb(null, resp.number); cb(null, response.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,19 +171,20 @@ 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 (e, resp) { fastly.activateVersion(results.version, function (err, response) {
if (err) throw new Error(e); if (err) throw new Error(err);
process.stdout.write('Successfully configured and activated version ' + resp.number + '\n'); process.stdout.write('Successfully configured and activated version ' + response.number + '\n');
if (process.env.FASTLY_PURGE_ALL) { if (process.env.FASTLY_PURGE_ALL) {
fastly.purgeAll(FASTLY_SERVICE_ID, function (error) { fastly.purgeAll(FASTLY_SERVICE_ID, function (err) {
if (error) throw new Error(error); if (err) throw new Error(err);
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 p = route.routeAlias || route.pattern; var path = route.routeAlias || route.pattern;
if (paths.indexOf(p) === -1) { if (paths.indexOf(path) === -1) {
paths.push(p); paths.push(path);
} }
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 (servId, version) { fastly.getFastlyAPIPrefix = function (serviceId, version) {
return '/service/' + encodeURIComponent(servId) + '/version/' + version; return '/service/' + encodeURIComponent(serviceId) + '/version/' + version;
}; };
/* /*
@ -32,15 +32,15 @@ module.exports = function (apiKey, serviceId) {
if (!this.serviceId) { if (!this.serviceId) {
return cb('Failed to get latest version. No serviceId configured'); return cb('Failed to get latest version. No serviceId configured');
} }
var url = '/service/' + encodeURIComponent(this.serviceId) + '/version'; var url = '/service/'+ encodeURIComponent(this.serviceId) +'/version';
this.request('GET', url, function (err, versions) { this.request('GET', url, function (err, versions) {
if (err) { if (err) {
return cb('Failed to fetch versions: ' + err); return cb('Failed to fetch versions: ' + err);
} }
var latestVersion = versions.reduce(function (lateVersion, version) { var latestVersion = versions.reduce(function (latestVersion, version) {
if (!lateVersion) return version; if (!latestVersion) return version;
if (version.number > lateVersion.number) return version; if (version.number > latestVersion.number) return version;
return lateVersion; return latestVersion;
}); });
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 (e, resp) { this.request('POST', postUrl, condition, function (err, response) {
if (e) { if (err) {
return cb('Failed while inserting condition "' + condition.statement + '": ' + e); return cb('Failed while inserting condition \"' + condition.statement + '\": ' + err);
} }
return cb(null, resp); return cb(null, response);
}); });
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 (e, resp) { this.request('POST', postUrl, header, function (err, response) {
if (e) { if (err) {
return cb('Failed to insert header: ' + e); return cb('Failed to insert header: ' + err);
} }
return cb(null, resp); return cb(null, response);
}); });
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 (e, resp) { this.request('POST', postUrl, responseObj, function (err, response) {
if (e) { if (err) {
return cb('Failed to insert response object: ' + e); return cb('Failed to insert response object: ' + err);
} }
return cb(null, resp); return cb(null, response);
}); });
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 (e, resp) { this.request('POST', postUrl, content, function (err, response) {
if (e) { if (err) {
return cb('Failed while adding custom vcl "' + name + '": ' + e); return cb('Failed while adding custom vcl \"' + name + '\": ' + err);
} }
return cb(null, resp); return cb(null, response);
}); });
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,12 +1,10 @@
/* /**
* Constructor * Constructor
*/ */
const Handler = function (route) { function Handler (route) {
// Handle redirects // Handle redirects
if (route.redirect) { if (route.redirect) {
return (req, res) => { return (req, res) => { res.redirect(route.redirect); };
res.redirect(route.redirect);
};
} }
var url = '/' + route.name + '.html'; var url = '/' + route.name + '.html';
@ -14,9 +12,9 @@ const Handler = function (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

@ -33,27 +33,24 @@
"devDependencies": { "devDependencies": {
"async": "1.5.2", "async": "1.5.2",
"autoprefixer": "6.3.6", "autoprefixer": "6.3.6",
"babel-cli": "6.26.0", "babel-core": "6.10.4",
"babel-core": "6.23.1", "babel-eslint": "5.0.4",
"babel-eslint": "8.0.2", "babel-loader": "6.2.4",
"babel-loader": "7.1.0", "babel-preset-es2015": "6.9.0",
"babel-preset-es2015": "6.22.0", "babel-preset-react": "6.11.1",
"babel-preset-react": "6.22.0",
"cheerio": "1.0.0-rc.2", "cheerio": "1.0.0-rc.2",
"classnames": "2.2.5", "classnames": "2.1.3",
"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": "4.7.1", "eslint": "1.3.1",
"eslint-config-scratch": "5.0.0",
"eslint-plugin-json": "1.2.0", "eslint-plugin-json": "1.2.0",
"eslint-plugin-react": "7.4.0", "eslint-plugin-react": "3.3.1",
"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.19.5", "formsy-react": "0.18.0",
"formsy-react-components": "0.11.1", "formsy-react-components": "0.7.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",
@ -62,7 +59,6 @@
"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",
@ -74,22 +70,21 @@
"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": "2.0.10", "postcss-loader": "0.8.2",
"prop-types": "15.6.0",
"raven-js": "3.0.4", "raven-js": "3.0.4",
"react": "15.5.4", "react": "15.1.0",
"react-dom": "15.5.4", "react-dom": "15.0.1",
"react-intl": "2.1.2", "react-intl": "2.1.2",
"react-modal": "3.1.11", "react-modal": "1.5.2",
"react-onclickoutside": "6.7.1", "react-onclickoutside": "4.1.1",
"react-redux": "4.4.5", "react-redux": "4.4.5",
"react-responsive": "3.0.0", "react-responsive": "1.1.4",
"react-slick": "0.12.2", "react-slick": "0.12.2",
"react-telephone-input": "3.8.6", "react-telephone-input": "3.4.5",
"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": "6.0.6", "sass-loader": "2.0.1",
"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",
@ -97,8 +92,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": "2.7.0", "webpack": "1.12.14",
"webpack-dev-middleware": "2.0.4", "webpack-dev-middleware": "1.2.0",
"xhr": "2.2.0" "xhr": "2.2.0"
}, },
"nyc": { "nyc": {

View file

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

View file

@ -1,35 +1,33 @@
const bindAll = require('lodash.bindall'); var classNames = require('classnames');
const classNames = require('classnames'); var React = require('react');
const PropTypes = require('prop-types');
const React = require('react');
require('./accordion.scss'); require('./accordion.scss');
class Accordion extends React.Component { var Accordion = React.createClass({
constructor (props) { type: 'Accordion',
super(props); getDefaultProps: function () {
bindAll(this, [ return {
'handleClick' titleAs: 'div',
]); contentAs: 'div'
this.state = { };
},
getInitialState: function () {
return {
isOpen: false isOpen: false
}; };
} },
handleClick (e) { toggleContent: function () {
e.preventDefault();
this.setState({isOpen: !this.state.isOpen}); this.setState({isOpen: !this.state.isOpen});
} },
render () { render: function () {
const classes = classNames({ var 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 <this.props.titleAs className="title"
className="title" onClick={this.toggleContent}>
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}>
@ -38,16 +36,6 @@ class Accordion extends React.Component {
</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,39 +1,37 @@
const bindAll = require('lodash.bindall'); var React = require('react');
const connect = require('react-redux').connect; var connect = require('react-redux').connect;
const PropTypes = require('prop-types');
const React = require('react');
const Button = require('../forms/button.jsx'); var Button = require('../forms/button.jsx');
require('./adminpanel.scss'); require('./adminpanel.scss');
class AdminPanel extends React.Component { var AdminPanel = React.createClass({
constructor (props) { type: 'AdminPanel',
super(props); getInitialState: function () {
bindAll(this, [ return {
'handleToggleVisibility'
]);
this.state = {
showPanel: false showPanel: false
}; };
} },
handleToggleVisibility (e) { handleToggleVisibility: function (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 (!this.props.isAdmin) return false; if (!showAdmin) return false;
if (this.state.showPanel) { if (this.state.showPanel) {
return ( return (
<div <div id="admin-panel" className="visible">
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">
@ -46,15 +44,8 @@ class AdminPanel extends React.Component {
<dd> <dd>
<ul className="cache-list"> <ul className="cache-list">
<li> <li>
<form <form method="post" action="/scratch_admin/page/clear-anon-cache/">
action="/scratch_admin/page/clear-anon-cache/" <input type="hidden" name="path" value="/" />
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">
@ -69,32 +60,27 @@ class AdminPanel extends React.Component {
</div> </div>
</div> </div>
); );
} } else {
return ( return (
<div <div id="admin-panel" className="hidden">
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
}); });
const ConnectedAdminPanel = connect(mapStateToProps)(AdminPanel); var mapStateToProps = function (state) {
return {
session: state.session
};
};
var ConnectedAdminPanel = connect(mapStateToProps)(AdminPanel);
module.exports = ConnectedAdminPanel; module.exports = ConnectedAdminPanel;

View file

@ -1,22 +1,23 @@
const classNames = require('classnames'); var React = require('react');
const omit = require('lodash.omit'); var classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
const Avatar = props => ( var Avatar = React.createClass({
<img type: 'Avatar',
className={classNames('avatar', props.className)} propTypes: {
{...omit(props, ['className'])} src: React.PropTypes.string
/> },
); 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,38 +1,40 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./box.scss'); require('./box.scss');
const Box = props => ( var Box = React.createClass({
<div className={classNames('box', props.className)}> type: 'Box',
propTypes: {
title: React.PropTypes.string.isRequired,
subtitle: React.PropTypes.string,
moreTitle: React.PropTypes.string,
moreHref: React.PropTypes.string,
moreProps: React.PropTypes.object
},
render: function () {
var classes = classNames(
'box',
this.props.className
);
return (
<div className={classes}>
<div className="box-header"> <div className="box-header">
<h4>{props.title}</h4> <h4>{this.props.title}</h4>
<h5>{props.subtitle}</h5> <h5>{this.props.subtitle}</h5>
<p> <p>
<a <a href={this.props.moreHref} {...this.props.moreProps}>
href={props.moreHref} {this.props.moreTitle}
{...props.moreProps}
>
{props.moreTitle}
</a> </a>
</p> </p>
</div> </div>
<div className="box-content"> <div className="box-content">
{props.children} {this.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,18 +1,17 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./card.scss'); require('./card.scss');
const Card = props => ( var Card = React.createClass({
<div className={classNames(['card', props.className])}> displayName: 'Card',
{props.children} render: function () {
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,19 +1,35 @@
const classNames = require('classnames'); var classNames = require('classnames');
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react'); var Slider = require('react-slick');
const Slider = require('react-slick');
const Thumbnail = require('../thumbnail/thumbnail.jsx'); var Thumbnail = require('../thumbnail/thumbnail.jsx');
const frameless = require('../../lib/frameless.js'); var 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 => { /**
defaults(props.settings, { * Displays content in horizontal scrolling box. Example usage: splash page rows.
*/
var Carousel = React.createClass({
type: 'Carousel',
propTypes: {
items: React.PropTypes.array
},
getDefaultProps: function () {
return {
items: require('./carousel.json'),
showRemixes: false,
showLoves: false,
type: 'project'
};
},
render: function () {
var settings = this.props.settings || {};
defaults(settings, {
centerMode: false, centerMode: false,
dots: false, dots: false,
infinite: false, infinite: false,
@ -22,94 +38,58 @@ const Carousel = props => {
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
} }}
}
] ]
}); });
const arrows = props.items.length > props.settings.slidesToShow; var arrows = this.props.items.length > settings.slidesToShow;
var classes = classNames(
'carousel',
this.props.className
);
return ( return (
<Slider <Slider className={classes} arrows={arrows} {... settings}>
arrows={arrows} {this.props.items.map(function (item) {
className={classNames('carousel', props.className)} var href = '';
{... props.settings} switch (this.props.type) {
>
{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 <Thumbnail key={[this.key, item.id].join('.')}
creator={item.author.username} showLoves={this.props.showLoves}
showRemixes={this.props.showRemixes}
type={this.props.type}
href={href} href={href}
key={[props.type, item.id].join('.')}
loves={item.stats.loves}
remixes={item.stats.remixes}
showLoves={props.showLoves}
showRemixes={props.showRemixes}
src={item.image}
title={item.title} title={item.title}
type={props.type} src={item.image}
/> creator={item.author.username}
remixes={item.stats.remixes}
loves={item.stats.loves} />
); );
})} }.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,22 +1,37 @@
// 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.
const classNames = require('classnames'); var classNames = require('classnames');
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react'); var Slider = require('react-slick');
const Slider = require('react-slick');
const Thumbnail = require('../thumbnail/thumbnail.jsx'); var Thumbnail = require('../thumbnail/thumbnail.jsx');
const frameless = require('../../lib/frameless.js'); var 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 => { /**
defaults(props.settings, { * Displays content in horizontal scrolling box. Example usage: splash page rows.
*/
var LegacyCarousel = React.createClass({
type: 'LegacyCarousel',
propTypes: {
items: React.PropTypes.array
},
getDefaultProps: function () {
return {
items: require('./carousel.json'),
showRemixes: false,
showLoves: false
};
},
render: function () {
var settings = this.props.settings || {};
defaults(settings, {
centerMode: false, centerMode: false,
dots: false, dots: false,
infinite: false, infinite: false,
@ -25,94 +40,58 @@ const Carousel = props => {
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
} }}
}
] ]
}); });
const arrows = props.items.length > props.settings.slidesToShow; var arrows = this.props.items.length > settings.slidesToShow;
var classes = classNames(
'carousel',
this.props.className
);
return ( return (
<Slider <Slider className={classes} arrows={arrows} {... settings}>
arrows={arrows} {this.props.items.map(function (item) {
className={classNames('carousel', props.className)} var href = '';
{... 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 <Thumbnail key={[this.key, item.id].join('.')}
creator={item.creator} showLoves={this.props.showLoves}
href={href} showRemixes={this.props.showRemixes}
key={[props.type, item.id].join('.')}
loves={item.love_count}
remixes={item.remixers_count}
showLoves={props.showLoves}
showRemixes={props.showRemixes}
src={item.thumbnail_url}
title={item.title}
type={item.type} type={item.type}
/> href={href}
title={item.title}
src={item.thumbnail_url}
creator={item.creator}
remixes={item.remixers_count}
loves={item.love_count} />
); );
})} }.bind(this))}
</Slider> </Slider>
); );
}; }
});
Carousel.propTypes = { module.exports = LegacyCarousel;
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,33 +1,34 @@
const classNames = require('classnames'); var classNames = require('classnames');
const FormattedRelative = require('react-intl').FormattedRelative; var FormattedRelative = require('react-intl').FormattedRelative;
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const EmojiText = require('../emoji-text/emoji-text.jsx'); var EmojiText = require('../emoji-text/emoji-text.jsx');
require('./comment.scss'); require('./comment.scss');
const CommentText = props => ( var CommentText = React.createClass({
<div className={classNames('comment-text', props.className)}> type: 'CommentText',
<EmojiText propTypes: {
className="mod-comment" comment: React.PropTypes.string.isRequired,
text={props.comment} datetimeCreated: React.PropTypes.string,
/> className: React.PropTypes.string
{typeof props.datetimeCreated === 'undefined' ? [] : [ },
<p render: function () {
className="comment-text-timestamp" var classes = classNames(
key="comment-text-timestamp" 'comment-text',
> 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,29 +1,22 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./deck.scss'); require('./deck.scss');
const Deck = props => ( var Deck = React.createClass({
<div className={classNames(['deck', props.className])}> displayName: 'Deck',
render: function () {
return (
<div className={classNames(['deck', this.props.className])}>
<div className="inner"> <div className="inner">
<a <a href="/" aria-label="Scratch">
aria-label="Scratch" <img className="logo" src="/images/logo_sm.png" />
href="/"
>
<img
className="logo"
src="/images/logo_sm.png"
/>
</a> </a>
{props.children} {this.props.children}
</div> </div>
</div> </div>
); );
}
Deck.propTypes = { });
children: PropTypes.node,
className: PropTypes.string
};
module.exports = Deck; module.exports = Deck;

View file

@ -1,29 +1,33 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./banner.scss'); require('./banner.scss');
const Banner = props => ( /**
<div className={classNames('banner', props.className)}> * Container for messages displayed below the nav bar that can be dismissed
* (See: email not confirmed banner)
*/
var Banner = React.createClass({
type: 'Banner',
propTypes: {
onRequestDismiss: React.PropTypes.func
},
render: function () {
var classes = classNames(
'banner',
this.props.className
);
return (
<div className={classes}>
<div className="inner"> <div className="inner">
{props.children} {this.props.children}
{props.onRequestDismiss ? [ {this.props.onRequestDismiss ? [
<a <a className="close" key="close" href="#" onClick={this.props.onRequestDismiss}>x</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,46 +1,40 @@
const bindAll = require('lodash.bindall'); var React = require('react');
const classNames = require('classnames'); var classNames = require('classnames');
const onClickOutside = require('react-onclickoutside').default;
const PropTypes = require('prop-types');
const React = require('react');
require('./dropdown.scss'); require('./dropdown.scss');
class Dropdown extends React.Component { var Dropdown = React.createClass({
constructor (props) { type: 'Dropdown',
super(props); mixins: [
bindAll(this, [ require('react-onclickoutside')
'handleClickOutside' ],
]); propTypes: {
} onRequestClose: React.PropTypes.func,
handleClickOutside () { isOpen: React.PropTypes.bool
},
getDefaultProps: function () {
return {
as: 'div',
isOpen: false
};
},
handleClickOutside: function () {
if (this.props.isOpen) { if (this.props.isOpen) {
this.props.onRequestClose(); this.props.onRequestClose();
} }
} },
render () { render: function () {
var classes = classNames(
'dropdown',
this.props.className,
{open: this.props.isOpen}
);
return ( return (
<this.props.as <this.props.as className={classes}>
className={classNames('dropdown', this.props.className, {
open: this.props.isOpen
})}
>
{this.props.children} {this.props.children}
</this.props.as> </this.props.as>
); );
} }
} });
Dropdown.propTypes = { module.exports = Dropdown;
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,13 +24,9 @@
} }
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,25 +1,33 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./emoji-text.scss'); require('./emoji-text.scss');
const EmojiText = props => ( var EmojiText = React.createClass({
<props.as type: 'EmojiText',
className={classNames('emoji-text', props.className)} propTyes: {
dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger text: React.PropTypes.string.isRequired,
__html: props.text className: React.PropTypes.string
},
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,22 +1,26 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./flex-row.scss'); require('./flex-row.scss');
const FlexRow = props => ( var FlexRow = React.createClass({
<props.as className={classNames('flex-row', props.className)}> type: 'FlexRow',
{props.children} getDefaultProps: function () {
</props.as> return {
);
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,69 +1,58 @@
const React = require('react'); var React = require('react');
const FlexRow = require('../../../flex-row/flex-row.jsx'); var FlexRow = require('../../../flex-row/flex-row.jsx');
const FooterBox = require('../../container/footer.jsx'); var FooterBox = require('../../container/footer.jsx');
require('../footer.scss'); require('../footer.scss');
const ConferenceFooter = () => ( var ConferenceFooter = React.createClass({
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 <img src="/images/conference/footer/mit-odl.png"
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 <img src="/images/conference/footer/intel.png"
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 <img src="/images/conference/footer/lego-foundation.png"
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 <img src="/images/conference/footer/google.png"
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 <img src="/images/conference/footer/siegel-endowment.png"
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 <img src="/images/conference/footer/no-starch.png"
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 <img src="/images/conference/footer/scratch-foundation.png"
alt="Scratch Foundation" alt="Scratch Foundation" />
src="/images/conference/footer/scratch-foundation.png"
/>
</a> </a>
</li> </li>
</FlexRow> </FlexRow>
@ -72,34 +61,25 @@ const ConferenceFooter = () => (
<div className="family"> <div className="family">
<h4>Scratch Family</h4> <h4>Scratch Family</h4>
<FlexRow> <FlexRow>
<FlexRow <FlexRow as="ul" className="column">
as="ul"
className="column"
>
<li> <li>
<a href="https://scratch.mit.edu">Scratch</a> <a href="https://scratch.mit.edu" target="_blank">Scratch</a>
</li> </li>
<li> <li>
<a href="http://www.scratchjr.org/">ScratchJr</a> <a href="http://www.scratchjr.org/" target="_blank">ScratchJr</a>
</li> </li>
</FlexRow> </FlexRow>
<FlexRow <FlexRow as="ul" className="column">
as="ul"
className="column"
>
<li> <li>
<a href="http://www.scratchfoundation.org/">Scratch Foundation</a> <a href="http://www.scratchfoundation.org/" target="_blank">Scratch Foundation</a>
</li> </li>
<li> <li>
<a href="http://scratched.gse.harvard.edu/">ScratchEd</a> <a href="http://scratched.gse.harvard.edu/" target="_blank">ScratchEd</a>
</li> </li>
</FlexRow> </FlexRow>
<FlexRow <FlexRow as="ul" className="column">
as="ul"
className="column"
>
<li> <li>
<a href="http://day.scratch.mit.edu">Scratch Day</a> <a href="http://day.scratch.mit.edu" target="_blank">Scratch Day</a>
</li> </li>
</FlexRow> </FlexRow>
</FlexRow> </FlexRow>
@ -111,7 +91,7 @@ const ConferenceFooter = () => (
<div className="contact-us"> <div className="contact-us">
<h4>Contact</h4> <h4>Contact</h4>
<p> <p>
<a href="mailto:help@scratch.mit.edu"> <a href="mailto:help@scratch.mit.edu" target="_blank">
Email Us Email Us
</a> </a>
</p> </p>
@ -119,27 +99,18 @@ const ConferenceFooter = () => (
<div className="social"> <div className="social">
<FlexRow as="ul"> <FlexRow as="ul">
<li> <li>
<a href="//www.twitter.com/scratch"> <a href="//www.twitter.com/scratch" target="_blank">
<img <img src="/images/conference/footer/twitter.png" alt="scratch twitter" />
alt="scratch twitter"
src="/images/conference/footer/twitter.png"
/>
</a> </a>
</li> </li>
<li> <li>
<a href="//www.facebook.com/scratchteam"> <a href="//www.facebook.com/scratchteam" target="_blank">
<img <img src="/images/conference/footer/facebook.png" alt="scratch facebook" />
alt="scratch facebook"
src="/images/conference/footer/facebook.png"
/>
</a> </a>
</li> </li>
<li> <li>
<a href="http://medium.com/scratchfoundation-blog"> <a href="http://medium.com/scratchfoundation-blog" target="_blank">
<img <img src="/images/conference/footer/medium.png" alt="scratch foundation blog" />
alt="scratch foundation blog"
src="/images/conference/footer/medium.png"
/>
</a> </a>
</li> </li>
</FlexRow> </FlexRow>
@ -147,6 +118,8 @@ const ConferenceFooter = () => (
</div> </div>
</FlexRow> </FlexRow>
</FooterBox> </FooterBox>
); );
}
});
module.exports = ConferenceFooter; module.exports = ConferenceFooter;

View file

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

View file

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

View file

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

View file

@ -1,51 +1,54 @@
const FormattedMessage = require('react-intl').FormattedMessage; var React = require('react');
const injectIntl = require('react-intl').injectIntl; var ReactIntl = require('react-intl');
const intlShape = require('react-intl').intlShape; var FormattedMessage = ReactIntl.FormattedMessage;
const MediaQuery = require('react-responsive').default; var injectIntl = ReactIntl.injectIntl;
const React = require('react');
const FooterBox = require('../container/footer.jsx'); var FooterBox = require('../container/footer.jsx');
const LanguageChooser = require('../../languagechooser/languagechooser.jsx'); var LanguageChooser = require('../../languagechooser/languagechooser.jsx');
const frameless = require('../../../lib/frameless'); var MediaQuery = require('react-responsive');
var frameless = require('../../../lib/frameless');
require('./footer.scss'); require('./footer.scss');
const Footer = props => ( var Footer = React.createClass({
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>
@ -55,139 +58,139 @@ const Footer = props => (
<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>
@ -197,29 +200,27 @@ const Footer = props => (
</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={props.intl.locale} /> <LanguageChooser locale={this.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,26 +1,22 @@
const classNames = require('classnames'); var React = require('react');
const omit = require('lodash.omit'); var classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./button.scss'); require('./button.scss');
const Button = props => { var Button = React.createClass({
const classes = classNames('button', props.className); type: 'Button',
propTypes: {
return ( },
<button render: function () {
className={classes} var classes = classNames(
{...omit(props, ['className', 'children'])} 'button',
> this.props.className
{props.children}
</button>
); );
}; return (
<button {... this.props} className={classes} >{this.props.children}</button>
Button.propTypes = { );
children: PropTypes.node, }
className: PropTypes.string });
};
module.exports = Button; module.exports = Button;

View file

@ -1,28 +1,28 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./charcount.scss'); require('./charcount.scss');
const CharCount = props => ( var CharCount = React.createClass({
<p type: 'CharCount',
className={classNames('char-count', props.className, { getDefaultProps: function () {
overmax: (props.currentCharacters > props.maxCharacters) return {
})} 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 @@
const classNames = require('classnames'); var classNames = require('classnames');
const FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup; var FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup;
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react'); var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
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');
const CheckboxGroup = props => ( var CheckboxGroup = React.createClass({
<div className={classNames('checkbox-group', props.className)}> type: 'CheckboxGroup',
<FRCCheckboxGroup render: function () {
className={classNames('checkbox-group', props.className)} var classes = classNames(
{... props} 'checkbox-group',
/> 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 @@
const classNames = require('classnames'); var classNames = require('classnames');
const FRCCheckbox = require('formsy-react-components').Checkbox; var FRCCheckbox = require('formsy-react-components').Checkbox;
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react'); var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
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');
const Checkbox = props => ( var Checkbox = React.createClass({
<FRCCheckbox type: 'Checkbox',
rowClassName={classNames('checkbox-row', props.className)} render: function () {
{...props} var classes = classNames(
/> 'checkbox-row',
); this.props.className
);
Checkbox.propTypes = { return (
className: PropTypes.string <FRCCheckbox rowClassName={classes} {... this.props} />
}; );
}
});
module.exports = inputHOC(defaultValidationHOC(Checkbox)); module.exports = inputHOC(defaultValidationHOC(Checkbox));

View file

@ -1,61 +1,47 @@
const bindAll = require('lodash.bindall'); var classNames = require('classnames');
const classNames = require('classnames'); var Formsy = require('formsy-react');
const Formsy = require('formsy-react'); var omit = require('lodash.omit');
const omit = require('lodash.omit'); var React = require('react');
const PropTypes = require('prop-types'); var validations = require('./validations.jsx').validations;
const React = require('react');
const validations = require('./validations.jsx').validations; for (var validation in validations) {
for (const validation in validations) {
Formsy.addValidationRule(validation, validations[validation]); Formsy.addValidationRule(validation, validations[validation]);
} }
class Form extends React.Component { var Form = React.createClass({
constructor (props) { getDefaultProps: function () {
super(props); return {
bindAll(this, [ noValidate: true,
'handleChange' onChange: function () {}
]); };
this.state = { },
getInitialState: function () {
return {
allValues: {} allValues: {}
}; };
} },
handleChange (currentValues, isChanged) { onChange: function (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 () { render: function () {
var classes = classNames(
'form',
this.props.className
);
return ( return (
<Formsy.Form <Formsy.Form {... this.props} className={classes} ref="formsy" onChange={this.onChange}>
className={classNames('form', this.props.className)} {React.Children.map(this.props.children, function (child) {
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; return child;
})} }
}.bind(this))}
</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,6 +1,5 @@
const Formsy = require('formsy-react'); var Formsy = require('formsy-react');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./general-error.scss'); require('./general-error.scss');
@ -11,18 +10,13 @@ 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.
*/ */
const GeneralError = props => { module.exports = Formsy.HOC(React.createClass({
if (!props.showError()) return null; render: function () {
if (!this.props.showError()) return null;
return ( return (
<p className="general-error"> <p className="general-error">
{props.getErrorMessage()} {this.props.getErrorMessage()}
</p> </p>
); );
}; }
}));
GeneralError.propTypes = {
getErrorMessage: PropTypes.func,
showError: PropTypes.func
};
module.exports = Formsy.HOC(GeneralError);

View file

@ -1,32 +1,20 @@
const omit = require('lodash.omit'); var React = require('react');
const PropTypes = require('prop-types');
const React = require('react');
/** module.exports = function InputComponentMixin (Component) {
* Higher-order component for building an input field var InputComponent = React.createClass({
* @param {React.Component} Component an input component getDefaultProps: function () {
* @return {React.Component} a wrapped input component return {
*/
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,57 +1,46 @@
const bindAll = require('lodash.bindall'); var classNames = require('classnames');
const classNames = require('classnames'); var FRCInput = require('formsy-react-components').Input;
const FRCInput = require('formsy-react-components').Input; 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('./input.scss'); require('./input.scss');
require('./row.scss'); require('./row.scss');
class Input extends React.Component { var Input = React.createClass({
constructor (props) { type: 'Input',
super(props); getDefaultProps: function () {
bindAll(this, [ return {};
'handleInvalid', },
'handleValid' getInitialState: function () {
]); return {
this.state = {
status: '' status: ''
}; };
} },
handleValid () { onValid: function () {
this.setState({ this.setState({
status: 'pass' status: 'pass'
}); });
} },
handleInvalid () { onInvalid: function () {
this.setState({ this.setState({
status: 'fail' status: 'fail'
}); });
} },
render () { render: function () {
return ( var classes = classNames(
<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')}
)} );
onInvalid={this.handleInvalid} return (
onValid={this.handleValid} <FRCInput {... this.props}
{...omit(this.props, ['className'])} className="input"
/> 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,33 +1,23 @@
const allCountries = require('react-telephone-input/lib/country_data').allCountries; var allCountries = require('react-telephone-input/lib/country_data').allCountries;
const classNames = require('classnames'); var classNames = require('classnames');
const ComponentMixin = require('formsy-react-components').ComponentMixin; var ComponentMixin = require('formsy-react-components').ComponentMixin;
const createReactClass = require('create-react-class'); var FormsyMixin = require('formsy-react').Mixin;
const FormsyMixin = require('formsy-react').Mixin; var React = require('react');
const omit = require('lodash.omit'); var ReactPhoneInput = require('react-telephone-input/lib/withStyles');
const PropTypes = require('prop-types'); var Row = require('formsy-react-components').Row;
const React = require('react');
const ReactPhoneInput = require('react-telephone-input/lib/withStyles').default;
const Row = require('formsy-react-components').Row;
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC; var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx'); var inputHOC = require('./input-hoc.jsx');
const intl = require('../../lib/intl.jsx'); var intl = require('../../lib/intl.jsx');
const validationHOCFactory = require('./validations.jsx').validationHOCFactory; var validationHOCFactory = require('./validations.jsx').validationHOCFactory;
const allIso2 = allCountries.map(country => (country.iso2)); var allIso2 = allCountries.map(function (country) {return country.iso2;});
require('./row.scss'); require('./row.scss');
require('./phone-input.scss'); require('./phone-input.scss');
const PhoneInput = createReactClass({ // eslint-disable-line react/prefer-es6-class var PhoneInput = React.createClass({
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
@ -41,34 +31,29 @@ const PhoneInput = createReactClass({ // eslint-disable-line react/prefer-es6-cl
defaultCountry: 'us' defaultCountry: 'us'
}; };
}, },
handleChangeInput: function (number, country) { onChangeInput: function (number, country) {
const value = { var value = {national_number: number, country_code: country};
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 () {
let defaultCountry = PhoneInput.getDefaultProps().defaultCountry; var 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 <Row {... this.getRowProperties()}
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 <ReactPhoneInput className="form-control"
className="form-control" {... this.props}
defaultCountry={defaultCountry} defaultCountry={defaultCountry}
disabled={this.isFormDisabled() || this.props.disabled} onChange={this.onChangeInput}
id={this.getId()} id={this.getId()}
label={null} label={null}
onChange={this.handleChangeInput} disabled={this.isFormDisabled() || this.props.disabled}
{...omit(this.props, ['className', 'disabled', 'onChange'])}
/> />
{this.renderHelp()} {this.renderHelp()}
{this.renderErrorMessage()} {this.renderErrorMessage()}
@ -78,7 +63,7 @@ const PhoneInput = createReactClass({ // eslint-disable-line react/prefer-es6-cl
} }
}); });
const phoneValidationHOC = validationHOCFactory({ var phoneValidationHOC = validationHOCFactory({
isPhone: <intl.FormattedMessage id="teacherRegistration.validationPhoneNumber" /> isPhone: <intl.FormattedMessage id="teacherRegistration.validationPhoneNumber" />
}); });

View file

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

View file

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

View file

@ -1,46 +1,48 @@
const defaults = require('lodash.defaultsdeep'); var defaults = require('lodash.defaultsdeep');
const intl = require('../../lib/intl.jsx'); var intl = require('../../lib/intl.jsx');
const libphonenumber = require('google-libphonenumber'); var libphonenumber = require('google-libphonenumber');
const omit = require('lodash.omit'); var phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance();
const phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance(); var React = require('react');
const PropTypes = require('prop-types');
const React = require('react'); module.exports = {};
module.exports.validations = { module.exports.validations = {
notEquals: (values, value, neq) => (value !== neq), notEquals: function (values, value, neq) {
notEqualsField: (values, value, field) => (value !== values[field]), return value !== neq;
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 {
const parsed = phoneNumberUtil.parse(value.national_number, value.country_code.iso2); var 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 = defaultValidationErrors => (Component => { module.exports.validationHOCFactory = function (defaultValidationErrors) {
const ValidatedComponent = props => ( return function (Component) {
<Component var ValidatedComponent = React.createClass({
validationErrors={defaults( render: function () {
var validationErrors = defaults(
{}, {},
defaultValidationErrors, defaultValidationErrors,
props.validationErrors this.props.validationErrors
)}
{...omit(props, ['validationErrors'])}
/>
); );
return (
ValidatedComponent.propTypes = { <Component {...this.props} validationErrors={validationErrors} />
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,61 +1,15 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const Thumbnail = require('../thumbnail/thumbnail.jsx'); var Thumbnail = require('../thumbnail/thumbnail.jsx');
const FlexRow = require('../flex-row/flex-row.jsx'); var FlexRow = require('../flex-row/flex-row.jsx');
require('./grid.scss'); require('./grid.scss');
const Grid = props => ( var Grid = React.createClass({
<div className={classNames('grid', props.className)}> type: 'Grid',
<FlexRow> getDefaultProps: function () {
{props.items.map((item, key) => { return {
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,
@ -63,6 +17,63 @@ Grid.defaultProps = {
showRemixes: false, showRemixes: false,
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,34 +1,39 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react'); var TitleBanner = require('../../components/title-banner/title-banner.jsx');
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
*/ */
const InformationPage = props => ( var InformationPage = React.createClass({
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">
{props.title} {this.props.title}
</h1> </h1>
</div> </div>
</TitleBanner> </TitleBanner>
<div className={classNames('info-outer', 'inner', props.className)}> <div className={classes}>
{props.children} {this.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,180 +1,17 @@
const bindAll = require('lodash.bindall'); 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');
const sessionActions = require('../../redux/session.js'); var sessionActions = require('../../redux/session.js');
const IframeModal = require('../modal/iframe/modal.jsx'); var IframeModal = require('../modal/iframe/modal.jsx');
const Registration = require('../registration/registration.jsx'); var Registration = require('../registration/registration.jsx');
require('./intro.scss'); require('./intro.scss');
class Intro extends React.Component { var Intro = React.createClass({
constructor (props) { type: 'Intro',
super(props); getDefaultProps: function () {
bindAll(this, [ return {
'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',
@ -188,12 +25,121 @@ Intro.defaultProps = {
'over 14 million </span>projects shared' 'over 14 million </span>projects shared'
}, },
session: {} session: {}
}; };
},
const mapStateToProps = state => ({ getInitialState: function () {
session: state.session return {
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>
);
}
}); });
const ConnectedIntro = connect(mapStateToProps)(Intro); var mapStateToProps = function (state) {
return {
session: state.session
};
};
var ConnectedIntro = connect(mapStateToProps)(Intro);
module.exports = ConnectedIntro; module.exports = ConnectedIntro;

View file

@ -1,57 +1,46 @@
const bindAll = require('lodash.bindall'); var classNames = require('classnames');
const classNames = require('classnames'); var React = require('react');
const PropTypes = require('prop-types');
const React = require('react');
const jar = require('../../lib/jar.js'); var jar = require('../../lib/jar.js');
const languages = require('../../../languages.json'); var languages = require('../../../languages.json');
const Form = require('../forms/form.jsx'); var Form = require('../forms/form.jsx');
const Select = require('../forms/select.jsx'); var 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.
*/ */
class LanguageChooser extends React.Component { var LanguageChooser = React.createClass({
constructor (props) { type: 'LanguageChooser',
super(props); getDefaultProps: function () {
bindAll(this, [ return {
'handleSetLanguage' languages: languages,
]); locale: 'en'
} };
handleSetLanguage (name, value) { },
onSetLanguage: function (name, value) {
jar.set('scratchlanguage', value); jar.set('scratchlanguage', value);
window.location.reload(); window.location.reload();
} },
render () { render: function () {
const languageOptions = Object.keys(this.props.languages).map(value => ({ var classes = classNames(
value: value, 'language-chooser',
label: this.props.languages[value] this.props.className
})); );
var languageOptions = Object.keys(this.props.languages).map(function (value) {
return {value: value, label: this.props.languages[value]};
}.bind(this));
return ( return (
<Form className={classNames('language-chooser', this.props.className)}> <Form className={classes}>
<Select <Select name="language"
required
name="language"
options={languageOptions} options={languageOptions}
value={this.props.locale} value={this.props.locale}
onChange={this.handleSetLanguage} onChange={this.onSetLanguage}
/> 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,108 +1,66 @@
const bindAll = require('lodash.bindall'); 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');
const log = require('../../lib/log.js'); var log = require('../../lib/log.js');
const Form = require('../forms/form.jsx'); var Form = require('../forms/form.jsx');
const Input = require('../forms/input.jsx'); var Input = require('../forms/input.jsx');
const Button = require('../forms/button.jsx'); var Button = require('../forms/button.jsx');
const Spinner = require('../spinner/spinner.jsx'); var Spinner = require('../spinner/spinner.jsx');
require('./login.scss'); require('./login.scss');
class Login extends React.Component { var Login = React.createClass({
constructor (props) { type: 'Login',
super(props); propTypes: {
bindAll(this, [ onLogIn: React.PropTypes.func,
'handleSubmit' error: React.PropTypes.string
]); },
this.state = { getInitialState: function () {
return {
waiting: false waiting: false
}; };
} },
handleSubmit (formData) { handleSubmit: function (formData) {
this.setState({waiting: true}); this.setState({waiting: true});
this.props.onLogIn(formData, err => { this.props.onLogIn(formData, function (err) {
if (err) log.error(err); if (err) log.error(err);
this.setState({waiting: false}); this.setState({waiting: false});
}); }.bind(this));
} },
render () { render: function () {
let error; var 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 <label htmlFor="username" key="usernameLabel">
htmlFor="username" <FormattedMessage id='general.username' />
key="usernameLabel"
>
<FormattedMessage id="general.username" />
</label> </label>
<Input <Input type="text" ref="username" name="username" maxLength="30" key="usernameInput" required />
required <label htmlFor="password" key="passwordLabel">
key="usernameInput" <FormattedMessage id='general.password' />
maxLength="30"
name="username"
ref={input => {
this.username = input;
}}
type="text"
/>
<label
htmlFor="password"
key="passwordLabel"
>
<FormattedMessage id="general.password" />
</label> </label>
<Input <Input type="password" ref="password" name="password" key="passwordInput" required />
required
key="passwordInput"
name="password"
ref={input => {
this.password = input;
}}
type="password"
/>
{this.state.waiting ? [ {this.state.waiting ? [
<Button <Button className="submit-button white" type="submit" disabled="disabled" key="submitButton">
className="submit-button white"
disabled="disabled"
key="submitButton"
type="submit"
>
<Spinner /> <Spinner />
</Button> </Button>
] : [ ] : [
<Button <Button className="submit-button white" type="submit" key="submitButton">
className="submit-button white" <FormattedMessage id='general.signIn' />
key="submitButton"
type="submit"
>
<FormattedMessage id="general.signIn" />
</Button> </Button>
]} ]}
<a <a className="right" href="/accounts/password_reset/" key="passwordResetLink">
className="right" <FormattedMessage id='login.needHelp' />
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,68 +1,60 @@
const bindAll = require('lodash.bindall'); var classNames = require('classnames');
const classNames = require('classnames'); var React = require('react');
const MediaQuery = require('react-responsive').default; var MediaQuery = require('react-responsive');
const PropTypes = require('prop-types'); var frameless = require('../../lib/frameless');
const React = require('react');
const frameless = require('../../lib/frameless');
require('./masonrygrid.scss'); require('./masonrygrid.scss');
class MasonryGrid extends React.Component { var MasonryGrid = React.createClass({
constructor (props) { type: 'MasonryGrid',
super(props); getDefaultProps: function () {
bindAll(this, [ return {
'reorderColumns' as: 'div'
]); };
} },
reorderColumns (items, cols) { reorderColumns: function (items, cols) {
const a1 = []; var a1 = [];
const a2 = []; var a2 = [];
const a3 = []; var a3 = [];
let i = 0; var 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++){
const col = (i + cols) % cols; var 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={classNames('masonry', this.props.className)}> <this.props.as className={classes}>
<MediaQuery maxWidth={frameless.tablet - 1}> <MediaQuery maxWidth={frameless.tablet - 1} >
{this.props.children} {this.props.children}
</MediaQuery> </MediaQuery>
<MediaQuery <MediaQuery minWidth={frameless.tablet} maxWidth={frameless.desktop - 1} >
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} >
{this.reorderColumns(this.props.children, 3)} {this.reorderColumns(this.props.children, 3)}
</MediaQuery> </MediaQuery>
</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,53 +1,39 @@
const bindAll = require('lodash.bindall'); var React = require('react');
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');
class Microworld extends React.Component { var Box = require('../box/box.jsx');
constructor (props) { var LegacyCarousel = require('../carousel/legacy-carousel.jsx');
super(props); var IframeModal = require('../modal/iframe/modal.jsx');
bindAll(this, [ var NestedCarousel = require('../nestedcarousel/nestedcarousel.jsx');
'markVideoOpen',
'markVideoClosed', var Microworld = React.createClass({
'renderVideos', type: 'Microworld',
'renderVideo', propTypes: {
'renderEditorWindow', microworldData: React.PropTypes.node.isRequired
'renderTips', },
'renderStarterProject', markVideoOpen: function (key) {
'renderProjectIdeasBox', {/* When a video is clicked, mark it as an open video, so the video Modal will open.
'renderForum', Key is the number of the video, so distinguish between different videos on the page */}
'renderDesignStudio'
]); var videoOpenArr = this.state.videoOpen;
this.state = {
videoOpen: {}
};
}
markVideoOpen (key) {
/*
When a video is clicked, mark it as an open video, so the video Modal will open.
Key is the number of the video, so distinguish between different videos on the page
*/
const videoOpenArr = this.state.videoOpen;
videoOpenArr[key] = true; videoOpenArr[key] = true;
this.setState({videoOpen: videoOpenArr}); this.setState({videoOpen: videoOpenArr});
} },
markVideoClosed (key) { markVideoClosed: function (key) {
/* {/* When a video's x is clicked, mark it as closed, so the video Modal will disappear.
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 */}
Key is the number of the video, so distinguish between different videos on the page var videoOpenArr = this.state.videoOpen;
*/
const videoOpenArr = this.state.videoOpen;
videoOpenArr[key] = false; videoOpenArr[key] = false;
this.setState({videoOpen: videoOpenArr}); this.setState({videoOpen: videoOpenArr});
} },
renderVideos () { getInitialState: function () {
const videos = this.props.microworldData.videos; return {
videoOpen: {}
};
},
renderVideos: function () {
var videos = this.props.microworldData.videos;
if (!videos || videos.length <= 0) { if (!videos || videos.length <= 0) {
return null; return null;
} }
@ -62,32 +48,26 @@ class Microworld extends React.Component {
</div> </div>
</div> </div>
); );
} },
renderVideo (video, key) { renderVideo: function (video, key) {
return ( return (
<div> <div>
<div className="video"> <div className="video">
<div <div className="play-button" onClick={this.markVideoOpen.bind(this, key)}>
className="play-button" </div>
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 () { renderEditorWindow: function () {
const projectId = this.props.microworldData.microworld_project_id; var projectId = this.props.microworldData.microworld_project_id;
if (!projectId) { if (!projectId) {
return null; return null;
@ -95,37 +75,30 @@ class Microworld extends React.Component {
return ( return (
<div className="editor section"> <div className="editor section">
<h1 className="sectionheader">Start Creating!</h1> <h1 className="sectionheader">Start Creating!</h1>
<iframe <iframe src={'//scratch.mit.edu/projects/embed-editor/' + projectId + '/?isMicroworld=true'}
frameBorder="0" frameBorder="0"> </iframe>
src={`//scratch.mit.edu/projects/embed-editor/${projectId}/?isMicroworld=true`}
/>
{this.renderTips()} {this.renderTips()}
</div> </div>
); );
} },
renderTips () { renderTips: function () {
const tips = this.props.microworldData.tips; var 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 <NestedCarousel items={tips} settings={{slidesToShow:1,slidesToScroll:1}}/>
items={tips}
settings={{
slidesToShow: 1,
slidesToScroll: 1
}}
/>
</div> </div>
</div> </div>
); );
} },
renderStarterProject () { renderStarterProject: function () {
const starterProjects = this.props.microworldData.starter_projects; var starterProjects = this.props.microworldData.starter_projects;
if (!starterProjects || starterProjects.length <= 0){ if (!starterProjects || starterProjects.length <= 0){
return null; return null;
} }
@ -134,30 +107,28 @@ class Microworld extends React.Component {
<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 () { renderProjectIdeasBox: function () {
const communityProjects = this.props.microworldData.community_projects; var communityProjects = this.props.microworldData.community_projects;
if (!communityProjects || communityProjects.size <= 0) { if (!communityProjects || communityProjects.size <= 0) {
return null; return null;
} }
const featured = communityProjects.featured_projects; var featured = communityProjects.featured_projects;
const all = communityProjects.newest_projects; var all = communityProjects.newest_projects;
const rows = []; var 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>
); );
@ -165,9 +136,8 @@ class Microworld extends React.Component {
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>
); );
@ -181,8 +151,8 @@ class Microworld extends React.Component {
{rows} {rows}
</div> </div>
); );
} },
renderForum () { renderForum: function () {
if (!this.props.microworldData.show_forum) { if (!this.props.microworldData.show_forum) {
return null; return null;
} }
@ -190,79 +160,58 @@ class Microworld extends React.Component {
return ( return (
<div className="forum"> <div className="forum">
<h1 className="sectionheader">Chat with others!</h1> <h1 className="sectionheader">Chat with others!</h1>
<img src="/images/forum-image.png" /> <img src="/images/forum-image.png"/>
</div> </div>
); );
} },
renderDesignStudio () { renderDesignStudio: function () {
const designChallenge = this.props.microworldData.design_challenge; var designChallenge = this.props.microworldData.design_challenge;
if (!designChallenge) { if (!designChallenge) {
return null; return null;
} }
let studioHref = '';
if (designChallenge.studio_id) { if (designChallenge.studio_id) {
studioHref = `https://scratch.mit.edu//studios/${designChallenge.studio_id}/`; var 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 <iframe src={'https://scratch.mit.edu/projects/' + designChallenge.project_id +
frameBorder="0" '/#fullscreen'} frameBorder="0"> </iframe>
src={`https://scratch.mit.edu/projects/${designChallenge.project_id}/#fullscreen`}
/>
</div> </div>
<div className="design-studio-projects"> <div className="design-studio-projects">
<Box <Box title="Examples"
key="scratch_design_studio" key="scratch_design_studio"
moreHref={studioHref ? studioHref : null}
moreTitle={studioHref ? 'Visit the studio' : null} moreTitle={studioHref ? 'Visit the studio' : null}
title="Examples" moreHref={studioHref ? studioHref : null}>
>
{/* 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 <LegacyCarousel settings={{slidesToShow:2,slidesToScroll:2}}
items={this.props.microworldData.design_challenge.studio1} items={this.props.microworldData.design_challenge.studio1} />
settings={{ <LegacyCarousel settings={{slidesToShow:2,slidesToScroll:2}}
slidesToShow: 2, items={this.props.microworldData.design_challenge.studio2} />
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
key="scratch_design_studio"
moreHref={studioHref ? studioHref : null}
moreTitle={studioHref ? 'Visit the studio' : null}
title="design Challenge Projects" title="design Challenge Projects"
> key="scratch_design_studio"
<LegacyCarousel moreTitle={studioHref ? 'Visit the studio' : null}
items={ moreHref={studioHref ? studioHref : null}>
this.props.microworldData.design_challenge.studio1.concat( <LegacyCarousel items={this.props.microworldData.design_challenge.studio1.concat(
this.props.microworldData.design_challenge.studio2 this.props.microworldData.design_challenge.studio2)} />
) </Box>
}
/>
`</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">
@ -282,10 +231,6 @@ class Microworld extends React.Component {
); );
} }
} });
Microworld.propTypes = {
microworldData: PropTypes.node.isRequired
};
module.exports = Microworld; module.exports = Microworld;

View file

@ -1,9 +1,7 @@
const bindAll = require('lodash.bindall'); 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'); var ReactModal = require('react-modal');
const React = require('react');
const ReactModal = require('react-modal');
require('./modal.scss'); require('./modal.scss');
@ -12,55 +10,47 @@ ReactModal.setAppElement(document.getElementById('view'));
/** /**
* Container for pop up windows (See: registration window) * Container for pop up windows (See: registration window)
*/ */
class Modal extends React.Component { var Modal = React.createClass({
constructor (props) { type: 'Modal',
super(props); propTypes: {
bindAll(this, [ className: React.PropTypes.string,
'handleRequestClose' overlayClassName: React.PropTypes.string
]); },
} requestClose: function () {
handleRequestClose () {
return this.modal.portal.requestClose(); return this.modal.portal.requestClose();
} },
render () { render: function () {
var modalClasses = classNames(
'modal-content',
this.props.className
);
var overlayClasses = classNames(
'modal-overlay',
this.props.overlayClassName
);
return ( return (
<ReactModal <ReactModal
appElement={document.getElementById('view')} ref={
className={{ function (component) {
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 <div className="modal-content-close" onClick={this.requestClose}>
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);
} }
.modal-content:focus { .ReactModal__Content:focus {
outline: none; outline: none;
} }

View file

@ -1,28 +1,34 @@
const classNames = require('classnames'); var classNames = require('classnames');
const omit = require('lodash.omit'); var omit = require('lodash.omit');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const Modal = require('../base/modal.jsx'); var Modal = require('../base/modal.jsx');
require('./modal.scss'); require('./modal.scss');
const IframeModal = props => ( var IframeModal = React.createClass({
<Modal {...omit(props, ['src'])}> propTypes: {
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
className={classNames('modal-content-iframe', props.className)} ref={this.props.componentRef}
ref={props.componentRef} src={this.props.src}
src={props.src} className={iframeClasses}
/> />
</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,23 +1,28 @@
const FormattedMessage = require('react-intl').FormattedMessage; var FormattedMessage = require('react-intl').FormattedMessage;
const MediaQuery = require('react-responsive').default; var MediaQuery = require('react-responsive');
const omit = require('lodash.omit'); var omit = require('lodash.omit');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const FlexRow = require('../../flex-row/flex-row.jsx'); var FlexRow = require('../../flex-row/flex-row.jsx');
const frameless = require('../../../lib/frameless'); var frameless = require('../../../lib/frameless');
const Modal = require('../base/modal.jsx'); var Modal = require('../base/modal.jsx');
const TitleBanner = require('../../title-banner/title-banner.jsx'); var TitleBanner = require('../../title-banner/title-banner.jsx');
require('../../forms/button.scss'); require('../../forms/button.scss');
require('./modal.scss'); require('./modal.scss');
const TTTModal = props => ( var TTTModal = React.createClass({
<Modal propTypes: {
className="mod-ttt" title: React.PropTypes.string.isRequired,
{...omit( description: React.PropTypes.string.isRequired,
props, tutorialLoc: React.PropTypes.string.isRequired,
[ 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',
@ -25,40 +30,32 @@ const TTTModal = props => (
'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 <img className="mod-ttt-img" src={this.props.bannerUrl} alt="" />
alt=""
className="mod-ttt-img"
src={props.bannerUrl}
/>
</MediaQuery> </MediaQuery>
<MediaQuery maxWidth={frameless.mobile - 1}> <MediaQuery maxWidth={frameless.mobile - 1}>
<img <img className="mod-ttt-img" src={this.props.thumbUrl} alt="" />
alt=""
className="mod-ttt-img"
src={props.thumbUrl}
/>
</MediaQuery> </MediaQuery>
</TitleBanner> </TitleBanner>
<div className="ttt-title"> <div className="ttt-title">
<h2>{props.title}</h2> <h2>{this.props.title}</h2>
<p className="ttt-description">{props.description}</p> <p className="ttt-description">{this.props.description}</p>
</div> </div>
<ul className="modal-content-ttt"> <ul className="modal-content-ttt">
<FlexRow <FlexRow as="li" className="mod-ttt-item">
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>
@ -67,22 +64,19 @@ const TTTModal = props => (
</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 <FlexRow as="li" className="mod-ttt-item">
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>
@ -91,22 +85,19 @@ const TTTModal = props => (
</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 <FlexRow as="li" className="mod-ttt-item">
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>
@ -115,24 +106,16 @@ const TTTModal = props => (
</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>
</FlexRow> </FlexRow>
</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,18 +1,21 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./navigation.scss'); require('./navigation.scss');
const NavigationBox = props => ( var NavigationBox = React.createClass({
<div className={classNames('inner', props.className)}> type: 'NavigationBox',
{props.children} render: function () {
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 @@
const React = require('react'); var React = require('react');
const NavigationBox = require('../../base/navigation.jsx'); var NavigationBox = require('../../base/navigation.jsx');
require('./navigation.scss'); require('./navigation.scss');
const Navigation = () => ( var Navigation = React.createClass({
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 <a href="/conference/2016" className="logo-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,33 +23,20 @@ const Navigation = () => (
<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 <a href="/conference/2016/expect" className="link-a">What to Expect</a>
className="link-a"
href="/conference/2016/expect"
>
What to Expect
</a>
</li> </li>
<li className="link plan"> <li className="link plan">
<a <a href="/conference/2016/plan" className="link-a">Plan Your Visit</a>
className="link-a"
href="/conference/2016/plan"
>
Plan Your Visit
</a>
</li> </li>
<li className="link schedule"> <li className="link schedule">
<a <a href="/conference/2016/schedule" className="link-a">Schedule</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,27 +1,29 @@
const React = require('react'); var React = require('react');
const NavigationBox = require('../../base/navigation.jsx'); var NavigationBox = require('../../base/navigation.jsx');
require('./navigation.scss'); require('./navigation.scss');
const Navigation = () => ( var Navigation = React.createClass({
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 <a href="/conference" className="logo-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>
</li> </li>
</ul> </ul>
</NavigationBox> </NavigationBox>
); );
}
});
module.exports = Navigation; module.exports = Navigation;

View file

@ -1,27 +1,29 @@
const React = require('react'); var React = require('react');
const NavigationBox = require('../../base/navigation.jsx'); var NavigationBox = require('../../base/navigation.jsx');
require('./navigation.scss'); require('./navigation.scss');
const Navigation = () => ( var Navigation = React.createClass({
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 <a href="/" className="logo-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>
</li> </li>
</ul> </ul>
</NavigationBox> </NavigationBox>
); );
}
});
module.exports = Navigation; module.exports = Navigation;

View file

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

View file

@ -1,25 +1,35 @@
const classNames = require('classnames'); var classNames = require('classnames');
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react'); var Slider = require('react-slick');
const Slider = require('react-slick');
const Thumbnail = require('../thumbnail/thumbnail.jsx'); var 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.
*/ */}
const NestedCarousel = props => { var NestedCarousel = React.createClass({
defaults(props.settings, { type: 'NestedCarousel',
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,
@ -28,57 +38,38 @@ const NestedCarousel = props => {
variableWidth: false variableWidth: false
}); });
const arrows = props.items.length > props.settings.slidesToShow; var arrows = this.props.items.length > settings.slidesToShow;
const stages = [];
for (let i = 0; i < props.items.length; i++) { var classes = classNames(
const items = props.items[i].thumbnails; 'nestedcarousel',
const thumbnails = []; 'carousel',
for (let j = 0; j < items.length; j++) { this.props.className
const item = items[j];
thumbnails.push(
<Thumbnail
key={`inner_${i}_${j}`}
linkTitle={false}
src={item.thumbnailUrl}
title={item.title}
/>
); );
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(
<Thumbnail key={'inner_' + i + '_' + j}
title={item.title}
src={item.thumbnailUrl}
linkTitle = {false} />);
} }
stages.push( stages.push(
<div key={`outer_${i}`}> <div key={'outer_' + i}>
<h3>{props.items[i].title}</h3> <h3>{this.props.items[i].title}</h3>
{thumbnails} {thumbnails}
</div> </div>);
);
} }
return ( return (
<Slider <Slider className={classes} arrows={arrows} {... settings}>
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,27 +1,41 @@
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const Box = require('../box/box.jsx'); var Box = require('../box/box.jsx');
require('./news.scss'); require('./news.scss');
const News = props => ( var News = React.createClass({
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"
moreHref="/discuss/5/" title={this.props.messages['news.scratchNews']}
moreTitle={props.messages['general.viewAll']} moreTitle={this.props.messages['general.viewAll']}
title={props.messages['news.scratchNews']} moreHref="/discuss/5/">
>
<ul> <ul>
{props.items.map(item => ( {this.props.items.map(function (item) {
return (
<li key={item.id}> <li key={item.id}>
<a href={item.url}> <a href={item.url}>
<img <img src={item.image}
alt=""
className="news-image" className="news-image"
height="53"
src={item.image}
width="53" width="53"
height="53"
alt=""
/> />
<div className="news-description"> <div className="news-description">
<h4>{item.headline}</h4> <h4>{item.headline}</h4>
@ -29,25 +43,12 @@ const News = props => (
</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 @@
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const Navigation = require('../../../navigation/conference/2016/navigation.jsx'); var Navigation = require('../../../navigation/conference/2016/navigation.jsx');
const Footer = require('../../../footer/conference/2016/footer.jsx'); var Footer = require('../../../footer/conference/2016/footer.jsx');
require('../page.scss'); require('../page.scss');
const Page = props => ( var Page = React.createClass({
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">
{props.children} {this.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 @@
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const Navigation = require('../../../navigation/conference/2017/navigation.jsx'); var Navigation = require('../../../navigation/conference/2017/navigation.jsx');
const Footer = require('../../../footer/conference/2017/footer.jsx'); var Footer = require('../../../footer/conference/2017/footer.jsx');
require('../page.scss'); require('../page.scss');
const Page = props => ( var Page = React.createClass({
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">
{props.children} {this.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 @@
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const Navigation = require('../../../navigation/conference/2018/navigation.jsx'); var Navigation = require('../../../navigation/conference/2018/navigation.jsx');
const Footer = require('../../../footer/conference/2018/footer.jsx'); var Footer = require('../../../footer/conference/2018/footer.jsx');
require('../page.scss'); require('../page.scss');
const Page = props => ( var Page = React.createClass({
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">
{props.children} {this.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,31 +1,29 @@
const classNames = require('classnames'); var React = require('react');
const PropTypes = require('prop-types'); var classNames = require('classnames');
const React = require('react');
const Navigation = require('../../navigation/www/navigation.jsx'); var Navigation = require('../../navigation/www/navigation.jsx');
const Footer = require('../../footer/www/footer.jsx'); var Footer = require('../../footer/www/footer.jsx');
const Page = props => ( var Page = React.createClass({
type: 'Page',
render: function () {
var classes = classNames({
'staging': process.env.SCRATCH_ENV == 'staging'
});
return (
<div className="page"> <div className="page">
<div <div id="navigation" className={classes}>
className={classNames({
staging: process.env.SCRATCH_ENV === 'staging'
})}
id="navigation"
>
<Navigation /> <Navigation />
</div> </div>
<div id="view"> <div id="view">
{props.children} {this.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,45 +1,42 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const Progression = props => { module.exports = React.createClass({
const childProps = { displayName: 'Progression',
activeStep: props.step, propTypes: {
totalSteps: React.Children.count(props.children)
};
return (
<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) { step: function (props, propName, componentName) {
const stepValidator = (propz, name) => { var stepValidator = function (props, propName) {
if (propz[name] > -1 && propz[name] < propz.children.length) { if (props[propName] > -1 && props[propName] < props.children.length) {
return null; return null;
} } else {
return new Error('Prop `step` out of range'); return new Error('Prop `step` out of range');
}
}; };
return ( return (
(typeof props[propName] === 'number' ? null : new Error('Not a number')) || React.PropTypes.number.isRequired(props, propName, componentName) ||
stepValidator(props, propName, componentName) stepValidator(props, propName, componentName)
); );
} }
}; },
getDefaultProps: function () {
Progression.defaultProps = { return {
step: 0 step: 0
}; };
},
module.exports = Progression; render: function () {
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,62 +1,53 @@
const bindAll = require('lodash.bindall'); var React = require('react');
const PropTypes = require('prop-types'); var IframeModal = require('../modal/iframe/modal.jsx');
const React = require('react');
const IframeModal = require('../modal/iframe/modal.jsx');
require('./registration.scss'); require('./registration.scss');
class Registration extends React.Component { var Registration = React.createClass({
constructor (props) { propTypes: {
super(props); isOpen: React.PropTypes.bool,
bindAll(this, [ onRegistrationDone: React.PropTypes.func,
'handleMessage', onRequestClose: React.PropTypes.func
'toggleMessageListener' },
]); onMessage: function (e) {
} if (e.origin != window.location.origin) return;
componentDidMount () { if (e.source != this.registrationIframe.contentWindow) return;
if (this.props.isOpen) this.toggleMessageListener(true); if (e.data == 'registration-done') this.props.onRegistrationDone();
} 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 (present) { toggleMessageListener: function (present) {
if (present) { if (present) {
window.addEventListener('message', this.handleMessage); window.addEventListener('message', this.onMessage);
} else { } else {
window.removeEventListener('message', this.handleMessage); window.removeEventListener('message', this.onMessage);
} }
} },
render () { componentDidMount: function () {
if (this.props.isOpen) this.toggleMessageListener(true);
},
componentDidUpdate: function (prevProps) {
this.toggleMessageListener(this.props.isOpen && !prevProps.isOpen);
},
componentWillUnmount: function () {
this.toggleMessageListener(false);
},
render: function () {
return ( return (
<IframeModal <IframeModal
className="mod-registration"
componentRef={iframe => { // eslint-disable-line react/jsx-no-bind
this.registrationIframe = iframe;
}}
isOpen={this.props.isOpen} isOpen={this.props.isOpen}
src="/accounts/standalone-registration/"
onRequestClose={this.props.onRequestClose} onRequestClose={this.props.onRequestClose}
className="mod-registration"
componentRef={
function (iframe) {
this.registrationIframe = iframe;
}.bind(this)
}
src="/accounts/standalone-registration/"
/> />
); );
} }
} });
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,18 +1,17 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./slide.scss'); require('./slide.scss');
const Slide = props => ( var Slide = React.createClass({
<div className={classNames(['slide', props.className])}> displayName: 'Slide',
{props.children} render: function () {
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,47 +1,57 @@
const classNames = require('classnames'); var classNames = require('classnames');
const FormattedRelative = require('react-intl').FormattedRelative; var FormattedRelative = require('react-intl').FormattedRelative;
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const FlexRow = require('../flex-row/flex-row.jsx'); var FlexRow = require('../flex-row/flex-row.jsx');
require('./social-message.scss'); require('./social-message.scss');
const SocialMessage = props => ( var SocialMessage = React.createClass({
<props.as className={classNames('social-message', props.className)}> type: 'SocialMessage',
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 props.iconSrc === 'undefined' ? [] : [ {typeof this.props.iconSrc !== 'undefined' ? [
<img <img
alt={props.iconAlt}
className={classNames('social-message-icon', props.imgClassName)}
key="social-message-icon" key="social-message-icon"
src={props.iconSrc} className={imgClass}
src={this.props.iconSrc}
alt={this.props.iconAlt}
/> />
]} ] : []}
<div> <div>
{props.children} {this.props.children}
</div> </div>
</div> </div>
<span className="social-message-date"> <span className="social-message-date">
<FormattedRelative value={new Date(props.datetime)} /> <FormattedRelative value={new Date(this.props.datetime)} />
</span> </span>
</FlexRow> </FlexRow>
</props.as> </this.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,18 +1,20 @@
const range = require('lodash.range'); var range = require('lodash.range');
const React = require('react'); var React = require('react');
require('./spinner.scss'); require('./spinner.scss');
// Adapted from http://tobiasahlin.com/spinkit/ var Spinner = React.createClass({
const Spinner = () => ( // Adapted from http://tobiasahlin.com/spinkit/
type: 'Spinner',
render: function () {
return (
<div className="spinner"> <div className="spinner">
{range(1, 13).map(id => ( {range(1,13).map(function (id) {
<div return <div className={'circle' + id + ' circle'}></div>;
className={`circle${id} circle`} })}
key={`circle${id}`}
/>
))}
</div> </div>
); );
}
});
module.exports = Spinner; module.exports = Spinner;

View file

@ -1,29 +1,28 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./stepnavigation.scss'); require('./stepnavigation.scss');
const StepNavigation = props => ( var StepNavigation = React.createClass({
<ul className={classNames('step-navigation', props.className)}> type: 'Navigation',
{Array.apply(null, Array(props.steps)).map((v, step) => ( render: function () {
<li return (
<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 < props.active, active: step < this.props.active,
selected: step === props.active selected: step === this.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,38 +1,36 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
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.
*/ */
const SubNavigation = props => ( var SubNavigation = React.createClass({
<div type: 'SubNavigation',
className={classNames( getDefaultProps: function () {
return {
align: 'middle'
};
},
render: function () {
var classes = classNames(
[ [
'sub-nav', 'sub-nav',
props.className this.props.className
], ],
{ {
'sub-nav-align-left': props.align === 'left', 'sub-nav-align-left': this.props.align === 'left',
'sub-nav-align-right': props.align === 'right' 'sub-nav-align-right': this.props.align === 'right'
} }
)} );
> return (
{props.children} <div className={classes}>
{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,26 +1,28 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
const React = require('react'); var 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.
*/ */
const Tabs = props => ( var Tabs = React.createClass({
<div className="tab-background"> type: 'Tabs',
<SubNavigation className={classNames('tabs', props.className)}> render: function () {
{props.children} var classes = classNames(
'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,85 +1,19 @@
const classNames = require('classnames'); var classNames = require('classnames');
const connect = require('react-redux').connect; var connect = require('react-redux').connect;
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const sessionActions = require('../../redux/session.js'); var sessionActions = require('../../redux/session.js');
const TitleBanner = require('../title-banner/title-banner.jsx'); var TitleBanner = require('../title-banner/title-banner.jsx');
const Button = require('../forms/button.jsx'); var Button = require('../forms/button.jsx');
const FlexRow = require('../flex-row/flex-row.jsx'); var FlexRow = require('../flex-row/flex-row.jsx');
require('./teacher-banner.scss'); require('./teacher-banner.scss');
const TeacherBanner = props => ( var TeacherBanner = React.createClass({
<TitleBanner className={classNames('teacher-banner', props.className)}> type: 'TeacherBanner',
<FlexRow className="inner"> getDefaultProps: function () {
<div className="welcome"> return {
{props.sessionStatus === sessionActions.Status.FETCHED ? (
props.user ? [
<h3 key="greeting">
{props.messages['teacherbanner.greeting']},{' '}
{props.user.username}
</h3>,
<p
className="title-banner-p"
key="subgreeting"
>
{props.messages['teacherbanner.subgreeting']}
</p>
] : []
) : []}
</div>
<FlexRow className="quick-links">
{props.sessionStatus === sessionActions.Status.FETCHED ? (
props.user ? [
<a
href="/educators/classes"
key="classes-button"
>
<Button>
{props.messages['teacherbanner.classesButton']}
</Button>
</a>,
<a
href="/info/educators"
key="resources-button"
>
<Button>
{props.messages['teacherbanner.resourcesButton']}
</Button>
</a>,
<a
href="/educators/faq"
key="faq-button"
>
<Button>
{props.messages['teacherbanner.faqButton']}
</Button>
</a>
] : []
) : []}
</FlexRow>
</FlexRow>
</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: { messages: {
'teacherbanner.greeting': 'Hi', 'teacherbanner.greeting': 'Hi',
'teacherbanner.subgreeting': 'Teacher Account', 'teacherbanner.subgreeting': 'Teacher Account',
@ -87,14 +21,66 @@ TeacherBanner.defaultProps = {
'teacherbanner.resourcesButton': 'Educator Resources', 'teacherbanner.resourcesButton': 'Educator Resources',
'teacherbanner.faqButton': 'Teacher Account FAQ' 'teacherbanner.faqButton': 'Teacher Account FAQ'
}, },
user: {} session: {}
}; };
},
const mapStateToProps = state => ({ render: function () {
sessionStatus: state.session.status, var classes = classNames(
user: state.session.session.user 'teacher-banner',
this.props.className
);
return (
<TitleBanner className={classes}>
<FlexRow className="inner">
<div className="welcome">
{this.props.session.status === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [
<h3 key="greeting">
{this.props.messages['teacherbanner.greeting']},{' '}
{this.props.session.session.user.username}
</h3>,
<p
key="subgreeting"
className="title-banner-p"
>
{this.props.messages['teacherbanner.subgreeting']}
</p>
] : []
): []}
</div>
<FlexRow className="quick-links">
{this.props.session.status === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [
<a href="/educators/classes" key="classes-button">
<Button>
{this.props.messages['teacherbanner.classesButton']}
</Button>
</a>,
<a href="/info/educators" key="resources-button">
<Button>
{this.props.messages['teacherbanner.resourcesButton']}
</Button>
</a>,
<a href="/educators/faq" key="faq-button">
<Button>
{this.props.messages['teacherbanner.faqButton']}
</Button>
</a>
] : []
): []}
</FlexRow>
</FlexRow>
</TitleBanner>
);
}
}); });
const ConnectedTeacherBanner = connect(mapStateToProps)(TeacherBanner); var mapStateToProps = function (state) {
return {
session: state.session
};
};
var ConnectedTeacherBanner = connect(mapStateToProps)(TeacherBanner);
module.exports = ConnectedTeacherBanner; module.exports = ConnectedTeacherBanner;

View file

@ -1,122 +1,175 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./thumbnail.scss'); require('./thumbnail.scss');
const Thumbnail = props => { var Thumbnail = React.createClass({
const extra = []; type: 'Thumbnail',
const info = []; propTypes: {
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 (props.loves && props.showLoves) { if (this.props.loves && this.props.showLoves) {
extra.push( extra.push(
<div <div
className="thumbnail-loves"
key="loves" key="loves"
title={`${props.loves} loves`} className="thumbnail-loves"
> title={this.props.loves + ' loves'}>
{props.loves} {this.props.loves}
</div> </div>
); );
} }
if (props.favorites && props.showFavorites) { if (this.props.favorites && this.props.showFavorites) {
extra.push( extra.push(
<div <div
className="thumbnail-favorites"
key="favorites" key="favorites"
title={`${props.favorites} favorites`} className="thumbnail-favorites"
> title={this.favorites + ' favorites'}>
{props.favorites} {this.props.favorites}
</div> </div>
); );
} }
if (props.remixes && props.showRemixes) { if (this.props.remixes && this.props.showRemixes) {
extra.push( extra.push(
<div <div
className="thumbnail-remixes"
key="remixes" key="remixes"
title={`${props.remixes} remixes`} className="thumbnail-remixes"
title={this.props.remixes + ' remixes'}
> >
{props.remixes} {this.props.remixes}
</div> </div>
); );
} }
if (props.views && props.showViews) { if (this.props.views && this.props.showViews) {
extra.push( extra.push(
<div <div
className="thumbnail-views"
key="views" key="views"
title={`${props.views} views`} className="thumbnail-views"
title={this.props.views + ' views'}
> >
{props.views} {this.props.views}
</div> </div>
); );
} }
let imgElement; var imgElement, titleElement, avatarElement;
let titleElement; if (this.props.linkTitle) {
let avatarElement; if (this.state.srcFallback) {
if (props.linkTitle) {
imgElement = ( imgElement = (
<a <a
className="thumbnail-image" className="thumbnail-image"
href={props.href} href={this.props.href}
key="imgElement" key="imgElement"
> >
<img <img
alt={props.alt} alt={this.props.alt}
src={props.src} src={this.props.srcDefault}
/> />
</a> </a>
); );
titleElement = ( } else {
imgElement = (
<a <a
href={props.href} className="thumbnail-image"
key="titleElement" href={this.props.href}
key="imgElement"
> >
{props.title} <img
alt={this.props.alt}
src={this.props.src}
onError={this.handleSrcError}
/>
</a>
);
}
titleElement = (
<a href={this.props.href} key="titleElement">
{this.props.title}
</a> </a>
); );
} else { } else {
imgElement = <img src={props.src} />; imgElement = <img src={this.props.src} />;
titleElement = props.title; titleElement = this.props.title;
} }
info.push(titleElement); info.push(titleElement);
if (props.creator) { if (this.props.creator) {
info.push( info.push(
<div <div key="creator" className="thumbnail-creator">
className="thumbnail-creator" <a href={'/users/' + this.props.creator + '/'}>{this.props.creator}</a>
key="creator"
>
<a href={`/users/${props.creator}/`}>{props.creator}</a>
</div> </div>
); );
} }
if (props.avatar && props.showAvatar) { if (this.props.avatar && this.props.showAvatar) {
if (this.state.avatarFallback) {
avatarElement = ( avatarElement = (
<a <a
className="creator-image" className="creator-image"
href={`/users/${props.creator}/`} href={'/users/' + this.props.creator + '/'}
> >
<img <img
alt={props.creator} alt={this.props.creator}
src={props.avatar} src={this.props.avatarDefault}
/>
</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> </a>
); );
} }
}
return ( return (
<div <div className={classes} >
className={classNames(
'thumbnail',
props.type,
props.className
)}
>
{imgElement} {imgElement}
<div className="thumbnail-info"> <div className="thumbnail-info">
{avatarElement} {avatarElement}
@ -127,42 +180,7 @@ const Thumbnail = props => {
{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,18 +1,21 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./title-banner.scss'); require('./title-banner.scss');
const TitleBanner = props => ( var TitleBanner = React.createClass({
<div className={classNames('title-banner', props.className)}> type: 'TitleBanner',
{props.children} render: function () {
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,39 +1,33 @@
const classNames = require('classnames'); var classNames = require('classnames');
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
require('./tooltip.scss'); require('./tooltip.scss');
const Tooltip = props => ( var Tooltip = React.createClass({
<span type: 'Tooltip',
className={classNames( getDefaultProps: function () {
'tooltip', return {
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,21 +1,30 @@
const classNames = require('classnames'); var classNames = require('classnames');
const FormattedMessage = require('react-intl').FormattedMessage; var React = require('react');
const PropTypes = require('prop-types'); var FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
require('../forms/button.scss'); require('../forms/button.scss');
require('./ttt-tile.scss'); require('./ttt-tile.scss');
const TTTTile = props => ( var TTTTile = React.createClass({
<div className={classNames('ttt-tile', props.className)}> type: 'TTTTile',
<a href={props.tutorialLoc}> propTypes: {
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 <img className="ttt-tile-image-img" src={this.props.thumbUrl} alt="" />
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" />
@ -25,44 +34,25 @@ const TTTTile = props => (
<div className="ttt-tile-info"> <div className="ttt-tile-info">
<div className="ttt-tile-tag"> <div className="ttt-tile-tag">
<FormattedMessage <FormattedMessage id='ttt.tutorial' defaultMessage='Tutorial'/>
defaultMessage="Tutorial"
id="ttt.tutorial"
/>
</div> </div>
<h4 className="ttt-tile-title">{props.title}</h4> <h4 className="ttt-tile-title">{this.props.title}</h4>
<p className="ttt-tile-description"> <p className="ttt-tile-description">
{props.description} {this.props.description}
</p> </p>
</div> </div>
</div> </div>
</a> </a>
{props.onGuideClick && ( {this.props.onGuideClick && (
<div <div className="ttt-tile-guides" onClick={this.props.onGuideClick}>
className="ttt-tile-guides" <FormattedMessage id='tile.guides' defaultMessage='See Cards and Guides'/>
onClick={props.onGuideClick} <img className="ttt-tile-open-modal" src="/svgs/modal/open-blue.svg" />
>
<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,81 +1,69 @@
const PropTypes = require('prop-types'); var React = require('react');
const React = require('react');
const Box = require('../box/box.jsx'); var Box = require('../box/box.jsx');
require('./welcome.scss'); require('./welcome.scss');
const Welcome = props => ( var Welcome = React.createClass({
<Box type: 'Welcome',
className="welcome" propTypes: {
moreHref="#" onDismiss: React.PropTypes.func
moreProps={{ },
className: 'close', getDefaultProps: function () {
title: 'Dismiss', return {
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',
'welcome.tryOut': 'Try out starter projects', 'welcome.tryOut': 'Try out starter projects',
'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 @@
const jar = require('./lib/jar'); var jar = require('./lib/jar');
const Raven = require('raven-js'); var 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,22 +17,22 @@ const 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}
*/ */
const updateLocale = () => { function updateLocale () {
let obj = jar.get('scratchlanguage'); var 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) {
obj = 'pt-br'; // default Portuguese users to Brazilian Portuguese due to our user base. Added in 2.2.5. obj = 'pt-br'; // default Portuguese users to Brazilian Portuguese due to our user base. Added in 2.2.5.
} }
} }
return obj; return obj;
}; }
window._locale = updateLocale(); window._locale = updateLocale();
})(); })();

View file

@ -47,9 +47,6 @@
"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 @@
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const xhr = require('xhr'); var xhr = require('xhr');
const jar = require('./jar'); var jar = require('./jar');
const log = require('./log'); var log = require('./log');
const urlParams = require('./url-params'); var urlParams = require('./url-params');
/** /**
* Helper method that constructs requests to the scratch api. * Helper method that constructs requests to the scratch api.
@ -12,11 +12,9 @@ const 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: {},
@ -42,29 +40,26 @@ module.exports = (opts, callback) => {
opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'; opts.headers['Content-Type'] = 'application/x-www-form-urlencoded';
} }
const apiRequest = options => { var apiRequest = function (opts) {
if (options.host !== '') { if (opts.host !== '') {
if ('withCredentials' in new XMLHttpRequest()) { if ('withCredentials' in new XMLHttpRequest()) {
options.useXDR = false; opts.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.
options.useXDR = true; opts.useXDR = true;
delete options.headers; delete opts.headers;
if (options.authentication) { if (opts.authentication) {
const authenticationParams = [`x-token=${options.authentication}`]; var authenticationParams = ['x-token=' + opts.authentication];
const parts = options.uri.split('?'); var parts = opts.uri.split('?');
const qs = (parts[1] || '') var qs = (parts[1] || '').split('&').concat(authenticationParams).join('&');
.split('&') opts.uri = parts[0] + '?' + qs;
.concat(authenticationParams)
.join('&');
options.uri = `${parts[0]}?${qs}`;
} }
} }
} }
xhr(options, (err, res, body) => { xhr(opts, function (err, res, body) {
if (err) log.error(err); if (err) log.error(err);
if (options.responseType === 'json' && typeof body === 'string') { if (opts.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
@ -78,25 +73,25 @@ module.exports = (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 (e) { } catch (err) {
// 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/', (err, csrftoken) => { jar.use('scratchcsrftoken', '/csrf_token/', function (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,28 +1,22 @@
module.exports = {}; module.exports = {};
const countries = module.exports.data = require('iso-3166-2').data; var countries = module.exports.data = require('iso-3166-2').data;
module.exports.countryOptions = Object.keys(countries).map(code => ({ module.exports.countryOptions = Object.keys(countries).map(function (code) {
value: code.toLowerCase(), return {value: code.toLowerCase(), label: countries[code].name};
label: countries[code].name }).sort(function (a, b) {
})) return a.label < b.label ? -1 : 1;
.sort((a, b) => { });
if (a.label < b.label) {
return -1;
}
return 1;
});
module.exports.subdivisionOptions = Object.keys(countries).reduce((subByCountry, code) => { module.exports.subdivisionOptions =
subByCountry[code.toLowerCase()] = Object.keys(countries[code].sub).map(subCode => ({ Object.keys(countries).reduce(function (subByCountry, code) {
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((a, b) => { }).sort(function (a, b) {
if (a.label < b.label) { return a.label < b.label ? -1 : 1;
return -1;
}
return 1;
}); });
return subByCountry; return subByCountry;
}, {}); }, {});

34
src/lib/format.js Normal file
View file

@ -0,0 +1,34 @@
/**
* 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,10 +1,9 @@
/* 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 @@
const requireAll = require('./require-all'); var requireAll = require('./require-all');
const ReactIntl = require('react-intl'); var ReactIntl = require('react-intl');
const allLocaleData = requireAll(require.context('react-intl/locale-data', true, /^\.\/.*\.js$/)); var allLocaleData = requireAll(require.context('react-intl/locale-data', true, /^\.\/.*\.js$/));
const customLocaleData = require('../../custom-locales.json'); var customLocaleData = require('../../custom-locales.json');
/** /**
* Add all locales * Add all locales
*/ */
for (const locale in allLocaleData) { for (var 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 (const customLocale in customLocaleData) { for (var customLocale in customLocaleData) {
ReactIntl.addLocaleData(customLocaleData[customLocale]); ReactIntl.addLocaleData(customLocaleData[customLocale]);
} }

View file

@ -1,7 +1,7 @@
const cookie = require('cookie'); var cookie = require('cookie');
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const xhr = require('xhr'); var xhr = require('xhr');
const pako = require('pako'); var pako = require('pako');
/** /**
* Module that handles coookie interactions. * Module that handles coookie interactions.
@ -11,16 +11,16 @@ const 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.
*/ */
const Jar = { var Jar = {
unsign: (value, callback) => { unsign: function (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 {
let b64Data = value.split(':')[0]; var b64Data = value.split(':')[0];
let decompress = false; var decompress = false;
if (b64Data[0] === '.') { if (b64Data[0] === '.') {
decompress = true; decompress = true;
b64Data = b64Data.substring(1); b64Data = b64Data.substring(1);
@ -29,29 +29,24 @@ const 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( b64Data = b64Data.replace(/[-_]/g, function (c) {return {'-':'+', '_':'/'}[c]; });
/[-_]/g, var strData = atob(b64Data);
c => ({
'-': '+',
'_': '/'
}[c])
);
let strData = atob(b64Data);
if (decompress) { if (decompress) {
const charData = strData.split('').map(c => (c.charCodeAt(0))); var charData = strData.split('').map(function (c) { return c.charCodeAt(0); });
const binData = new Uint8Array(charData); var binData = new Uint8Array(charData);
const data = pako.inflate(binData); var 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: (name, callback) => { get: function (name, callback) {
// Get cookie by name // Get cookie by name
const obj = cookie.parse(document.cookie) || {}; var obj = cookie.parse(document.cookie) || {};
// Handle optional callback // Handle optional callback
if (typeof callback === 'function') { if (typeof callback === 'function') {
@ -61,44 +56,44 @@ const Jar = {
return obj[name]; return obj[name];
}, },
use: (name, uri, callback) => { use: function (name, uri, callback) {
// Attempt to get cookie // Attempt to get cookie
Jar.get(name, (err, obj) => { Jar.get(name, function (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
}, e => { }, function (err) {
if (e) return callback(e); if (err) return callback(err);
Jar.get(name, callback); Jar.get(name, callback);
}); });
}); });
}, },
set: (name, value, opts) => { set: function (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 = '/';
const obj = cookie.serialize(name, value, opts); var obj = cookie.serialize(name, value, opts);
document.cookie = obj; document.cookie = obj;
}, },
getUnsignedValue: (cookieName, signedValue, callback) => { getUnsignedValue: function (cookieName, signedValue, callback) {
// Get a value from a signed object // Get a value from a signed object
Jar.get(cookieName, (err, value) => { Jar.get(cookieName, function (err, value) {
if (err) return callback(err); if (err) return callback(err);
if (typeof value === 'undefined') return callback(null, value); if (typeof value === 'undefined') return callback(null, value);
Jar.unsign(value, (e, contents) => { Jar.unsign(value, function (err, contents) {
if (e) return callback(e); if (err) return callback(err);
try { try {
const data = JSON.parse(contents); var data = JSON.parse(contents);
return callback(null, data[signedValue]); } catch (err) {
} catch (error) { return callback(err);
return callback(error);
} }
return callback(null, data[signedValue]);
}); });
}); });
} }

View file

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

View file

@ -1,27 +1,21 @@
const redux = require('redux'); var redux = require('redux');
const thunk = require('redux-thunk').default; var thunk = require('redux-thunk').default;
// JSX syntax transforms to React.createElement // JSX syntax transforms to React.createElement
const React = require('react'); // eslint-disable-line var React = require('react'); // eslint-disable-line
const ReactDOM = require('react-dom'); var ReactDOM = require('react-dom');
const StoreProvider = require('react-redux').Provider; var StoreProvider = require('react-redux').Provider;
const IntlProvider = require('./intl.jsx').IntlProvider; var IntlProvider = require('./intl.jsx').IntlProvider;
const permissionsActions = require('../redux/permissions.js'); var permissionsActions = require('../redux/permissions.js');
const sessionActions = require('../redux/session.js'); var sessionActions = require('../redux/session.js');
const reducer = require('../redux/reducer.js'); var 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")
let locale = window._locale || 'en'; var locale = window._locale || 'en';
let messages = {}; var 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
@ -34,8 +28,8 @@ const render = (jsx, element, reducers) => {
messages = window._messages[locale]; messages = window._messages[locale];
} }
const allReducers = reducer(reducers); var allReducers = reducer(reducers);
const store = redux.createStore( var store = redux.createStore(
allReducers, allReducers,
redux.applyMiddleware(thunk) redux.applyMiddleware(thunk)
); );
@ -43,10 +37,7 @@ const render = (jsx, element, reducers) => {
// Render view component // Render view component
ReactDOM.render( ReactDOM.render(
<StoreProvider store={store}> <StoreProvider store={store}>
<IntlProvider <IntlProvider locale={locale} messages={messages}>
locale={locale}
messages={messages}
>
{jsx} {jsx}
</IntlProvider> </IntlProvider>
</StoreProvider>, </StoreProvider>,

View file

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

View file

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

View file

@ -2,18 +2,21 @@
* 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
*/ */
const params = values => ( module.exports = function urlParams (values) {
Object.keys(values).map(key => { return Object
const value = typeof values[key] === 'undefined' ? '' : values[key]; .keys(values)
const encodeKeyValuePair = val => ( .map(function (key) {
[key, val].map(encodeURIComponent).join('=') var value = typeof values[key] === 'undefined' ? '' : values[key];
); 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 @@
const keyMirror = require('keymirror'); var keyMirror = require('keymirror');
const api = require('../lib/api'); var api = require('../lib/api');
const Types = keyMirror({ var 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 = (state, action) => { module.exports.detailsReducer = function (state, action) {
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = {}; state = {};
} }
@ -23,35 +23,49 @@ module.exports.detailsReducer = (state, action) => {
} }
}; };
module.exports.setDetailsError = error => ({ module.exports.setDetailsError = function (error) {
return {
type: Types.SET_DETAILS_ERROR, type: Types.SET_DETAILS_ERROR,
error: error error: error
}); };
};
module.exports.setDetails = details => ({ module.exports.setDetails = function (details) {
return {
type: Types.SET_DETAILS, type: Types.SET_DETAILS,
details: details details: details
}); };
};
module.exports.setDetailsFetching = () => ({ module.exports.setDetailsFetching = function () {
return {
type: Types.SET_DETAILS_FETCHING, type: Types.SET_DETAILS_FETCHING,
fetching: true fetching: true
}); };
};
module.exports.getDetails = id => (dispatch => { module.exports.startGetDetails = function (id) {
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'
}, (err, body) => { }, function (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') {
const columns = body.columns; var columns = body.columns;
if (body.rows) { if (body.rows) {
const details = body.rows[0]; var details = body.rows[0];
const detailsObject = details.reduce((prev, cur, index) => { var detailsObject = details.reduce(function (prev, cur, index) {
prev[columns[index]] = cur; prev[columns[index]] = cur;
return prev; return prev;
}, {}); }, {});
@ -60,13 +74,10 @@ module.exports.getDetails = id => (dispatch => {
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 @@
const keyMirror = require('keymirror'); var keyMirror = require('keymirror');
const api = require('../lib/api'); var api = require('../lib/api');
const Types = keyMirror({ var 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 = (state, action) => { module.exports.scheduleReducer = function (state, action) {
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = { state = {
timeSlots: [], timeSlots: [],
@ -26,27 +26,40 @@ module.exports.scheduleReducer = (state, action) => {
} }
}; };
module.exports.setSchedule = schedule => ({ module.exports.setSchedule = function (schedule) {
return {
type: Types.SET_SCHEDULE, type: Types.SET_SCHEDULE,
schedule: schedule schedule: schedule
}); };
};
module.exports.setScheduleFetching = () => ({ module.exports.setScheduleFetching = function () {
return {
type: Types.SET_SCHEDULE_FETCHING, type: Types.SET_SCHEDULE_FETCHING,
fetching: true fetching: true
}); };
};
module.exports.setScheduleError = error => ({ module.exports.setScheduleError = function (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 = (timeSlot1, timeSlot2) => { module.exports.sortTimeSlots = function (timeSlot1, timeSlot2) {
const timeSlot1Am = (timeSlot1.time.substr(timeSlot1.time.length - 1, timeSlot1.time.length) === 'a'); var timeSlot1Am = (timeSlot1.time.substr(timeSlot1.time.length - 1, timeSlot1.time.length) === 'a') ? true : false;
const timeSlot2Am = (timeSlot2.time.substr(timeSlot2.time.length - 1, timeSlot2.time.length) === 'a'); var timeSlot2Am = (timeSlot2.time.substr(timeSlot2.time.length - 1, timeSlot2.time.length) === 'a') ? true : false;
let timeSlot1Time = parseInt(timeSlot1.time.substr(0, timeSlot1.time.length - 1), 10); var timeSlot1Time = parseInt(timeSlot1.time.substr(0, timeSlot1.time.length - 1));
let timeSlot2Time = parseInt(timeSlot2.time.substr(0, timeSlot2.time.length - 1), 10); var timeSlot2Time = parseInt(timeSlot2.time.substr(0, timeSlot2.time.length - 1));
// convert to 24-hour for sorting // convert to 24-hour for sorting
if (timeSlot1Time !== 12 && !timeSlot1Am) { if (timeSlot1Time !== 12 && !timeSlot1Am) {
@ -58,38 +71,40 @@ module.exports.sortTimeSlots = (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 = day => (dispatch => { module.exports.getDaySchedule = function (day) {
return function (dispatch) {
api({ api({
uri: `/conference/schedule/${day}` uri: '/conference/schedule/' + day
}, (err, body) => { }, function (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') {
const columns = body.columns; var columns = body.columns;
const rows = body.rows || []; var 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)
const scheduleByTimeSlot = rows.reduce((prev, cur) => { var scheduleByTimeSlot = rows.reduce(function (prev, cur) {
const cleanedRow = {}; var cleanedRow = {};
for (let i = 0; i < columns.length; i++) { for (var 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';
const timeSlot = cleanedRow.Chunk + cleanedRow.Start; var 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({
@ -103,22 +118,21 @@ module.exports.getDaySchedule = day => (dispatch => {
}, {timeSlots: [], info: []}); }, {timeSlots: [], info: []});
scheduleByTimeSlot.info.sort(module.exports.sortTimeSlots); scheduleByTimeSlot.info.sort(module.exports.sortTimeSlots);
const schedule = scheduleByTimeSlot.info.map(timeSlot => ({ var schedule = scheduleByTimeSlot.info.map(function (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 @@
const keyMirror = require('keymirror'); var keyMirror = require('keymirror');
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const api = require('../lib/api'); var api = require('../lib/api');
const Types = keyMirror({ var 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
}); });
const getInitialState = () => ({ module.exports.getInitialState = function (){
messageCount: 0 return {messageCount: 0};
}); };
module.exports.messageCountReducer = (state, action) => { module.exports.messageCountReducer = function (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 = getInitialState(); state = module.exports.getInitialState();
} }
switch (action.type) { switch (action.type) {
case Types.SET_MESSAGE_COUNT: case Types.SET_MESSAGE_COUNT:
@ -31,32 +31,40 @@ module.exports.messageCountReducer = (state, action) => {
} }
}; };
module.exports.setSessionError = error => ({ module.exports.setSessionError = function (error) {
return {
type: Types.SET_MESSAGE_COUNT_ERROR, type: Types.SET_MESSAGE_COUNT_ERROR,
error: error error: error
}); };
};
module.exports.setCount = count => ({ module.exports.setCount = function (count) {
return {
type: Types.SET_MESSAGE_COUNT, type: Types.SET_MESSAGE_COUNT,
count: count count: count
}); };
};
module.exports.setStatus = status => ({ module.exports.setStatus = function (status){
return {
type: Types.SET_STATUS, type: Types.SET_STATUS,
status: status status: status
}); };
};
module.exports.getCount = username => (dispatch => { module.exports.getCount = function (username) {
return function (dispatch) {
api({ api({
method: 'get', method: 'get',
uri: `/users/${username}/messages/count` uri: '/users/' + username + '/messages/count'
}, (err, body) => { }, function (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;
} }
const count = parseInt(body.count, 10); var count = parseInt(body.count, 10);
dispatch(module.exports.setCount(count)); dispatch(module.exports.setCount(count));
}); });
}); };
};

View file

@ -1,10 +1,10 @@
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const defaultsDeep = require('lodash.defaultsdeep'); var defaultsDeep = require('lodash.defaultsdeep');
const keyMirror = require('keymirror'); var keyMirror = require('keymirror');
const api = require('../lib/api'); var api = require('../lib/api');
const log = require('../lib/log'); var log = require('../lib/log');
const messageCountActions = require('./message-count.js'); var messageCountActions = require('./message-count.js');
module.exports.Status = keyMirror({ module.exports.Status = keyMirror({
FETCHED: null, FETCHED: null,
@ -17,7 +17,8 @@ module.exports.Status = keyMirror({
DELETE_ERROR: null DELETE_ERROR: null
}); });
module.exports.getInitialState = () => ({ module.exports.getInitialState = function () {
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,
@ -29,9 +30,10 @@ module.exports.getInitialState = () => ({
social: [], social: [],
invite: {} invite: {}
} }
}); };
};
module.exports.messagesReducer = (state, action) => { module.exports.messagesReducer = function (state, action) {
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = module.exports.getInitialState(); state = module.exports.getInitialState();
} }
@ -66,48 +68,61 @@ module.exports.messagesReducer = (state, action) => {
} }
}; };
module.exports.setMessagesError = error => ({ module.exports.setMessagesError = function (error) {
return {
type: 'ERROR', type: 'ERROR',
error: error error: error
}); };
};
module.exports.setMessages = messages => ({ module.exports.setMessages = function (messages) {
return {
type: 'SET_MESSAGES', type: 'SET_MESSAGES',
messages: messages messages: messages
}); };
};
module.exports.setMessagesOffset = offset => ({ module.exports.setMessagesOffset = function (offset) {
return {
type: 'SET_MESSAGES_OFFSET', type: 'SET_MESSAGES_OFFSET',
offset: offset offset: offset
}); };
};
module.exports.setAdminMessages = messages => ({ module.exports.setAdminMessages = function (messages) {
return {
type: 'SET_ADMIN_MESSAGES', type: 'SET_ADMIN_MESSAGES',
messages: messages messages: messages
}); };
};
module.exports.setScratcherInvite = invite => ({ module.exports.setScratcherInvite = function (invite) {
return {
type: 'SET_SCRATCHER_INVITE', type: 'SET_SCRATCHER_INVITE',
invite: invite invite: invite
}); };
};
module.exports.setStatus = (type, status) => ({ module.exports.setStatus = function (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 = () => (dispatch => { module.exports.clearMessageCount = function () {
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
}, (err, body) => { }, function (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));
@ -120,7 +135,8 @@ module.exports.clearMessageCount = () => (dispatch => {
} }
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
@ -130,8 +146,8 @@ module.exports.clearMessageCount = () => (dispatch => {
* @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 = (messageType, messageId, messageCount, adminMessages) => ( module.exports.clearAdminMessage = function (messageType, messageId, messageCount, adminMessages) {
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: '',
@ -142,7 +158,7 @@ module.exports.clearAdminMessage = (messageType, messageId, messageCount, adminM
alertType: messageType, alertType: messageType,
alertId: messageId alertId: messageId
} }
}, (err, body) => { }, function (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));
@ -159,9 +175,9 @@ module.exports.clearAdminMessage = (messageType, messageId, messageCount, adminM
dispatch(module.exports.setScratcherInvite({})); dispatch(module.exports.setScratcherInvite({}));
} else { } else {
// find the admin message and remove it // find the admin message and remove it
let toRemove = -1; var toRemove = -1;
for (const i of adminMessages) { for (var i in adminMessages) {
if (i.id === messageId) { if (adminMessages[i].id === messageId) {
toRemove = i; toRemove = i;
break; break;
} }
@ -172,8 +188,8 @@ module.exports.clearAdminMessage = (messageType, messageId, messageCount, adminM
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
@ -185,7 +201,7 @@ module.exports.clearAdminMessage = (messageType, messageId, messageCount, adminM
* @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 = (username, token, opts) => { module.exports.getMessages = function (username, token, opts) {
opts = defaults(opts, { opts = defaults(opts, {
messages: [], messages: [],
offset: 0, offset: 0,
@ -193,17 +209,17 @@ module.exports.getMessages = (username, token, opts) => {
clearCount: true clearCount: true
}); });
let filterArg = ''; var filterArg = '';
if (opts.filter.length > 0) { if (opts.filter.length > 0) {
filterArg = `&filter=${opts.filter}`; filterArg = '&filter=' + opts.filter;
} }
return dispatch => { return function (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
}, (err, body) => { }, function (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));
@ -230,12 +246,13 @@ module.exports.getMessages = (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 = (username, token) => (dispatch => { module.exports.getAdminMessages = function (username, token) {
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
}, (err, body) => { }, function (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));
@ -251,7 +268,8 @@ module.exports.getAdminMessages = (username, token) => (dispatch => {
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
@ -259,11 +277,12 @@ module.exports.getAdminMessages = (username, token) => (dispatch => {
* @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 = (username, token) => (dispatch => { module.exports.getScratcherInvite = function (username, token) {
return function (dispatch) {
api({ api({
uri: `/users/${username}/invites`, uri: '/users/' + username + '/invites',
authentication: token authentication: token
}, (err, body) => { }, function (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));
@ -273,4 +292,5 @@ module.exports.getScratcherInvite = (username, token) => (dispatch => {
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,11 +1,11 @@
const keyMirror = require('keymirror'); var keyMirror = require('keymirror');
const Types = keyMirror({ var Types = keyMirror({
SET_SEARCH_TERM: null SET_SEARCH_TERM: null
}); });
module.exports.navigationReducer = (state, action) => { module.exports.navigationReducer = function (state, action) {
if (typeof state === 'undefined') { if(typeof state === 'undefined') {
state = ''; state = '';
} }
switch (action.type) { switch (action.type) {
@ -16,7 +16,9 @@ module.exports.navigationReducer = (state, action) => {
} }
}; };
module.exports.setSearchTerm = searchTerm => ({ module.exports.setSearchTerm = function (searchTerm) {
return {
type: Types.SET_SEARCH_TERM, type: Types.SET_SEARCH_TERM,
searchTerm: searchTerm searchTerm: searchTerm
}); };
};

View file

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

View file

@ -1,9 +1,9 @@
const combineReducers = require('redux').combineReducers; var combineReducers = require('redux').combineReducers;
const defaults = require('lodash.defaults'); var defaults = require('lodash.defaults');
const messageCountReducer = require('./message-count.js').messageCountReducer; var messageCountReducer = require('./message-count.js').messageCountReducer;
const permissionsReducer = require('./permissions.js').permissionsReducer; var permissionsReducer = require('./permissions.js').permissionsReducer;
const sessionReducer = require('./session.js').sessionReducer; var 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 @@ const 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 = opts => { module.exports = function (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