|
@ -46,6 +46,7 @@
|
|||
"lodash.clone": "3.0.3",
|
||||
"lodash.defaultsdeep": "3.10.0",
|
||||
"lodash.omit": "3.1.0",
|
||||
"lodash.range": "3.0.1",
|
||||
"minilog": "2.0.8",
|
||||
"node-sass": "3.3.3",
|
||||
"po2icu": "git://github.com/LLK/po2icu.git#develop",
|
||||
|
|
|
@ -18,6 +18,9 @@ module.exports = {
|
|||
// Analytics & Monitoring
|
||||
ga_tracker: process.env.GA_TRACKER || '',
|
||||
|
||||
// Error handling
|
||||
sentry_dsn: process.env.SENTRY_DSN || '',
|
||||
|
||||
// Use minified JS libraries
|
||||
min: (process.env.NODE_ENV === 'production') ? '.min' : ''
|
||||
};
|
||||
|
|
|
@ -48,10 +48,16 @@
|
|||
<!-- Scripts -->
|
||||
<script src="/js/lib/react{{min}}.js"></script>
|
||||
<script src="/js/lib/react-dom{{min}}.js"></script>
|
||||
<script src="/js/lib/raven.min.js"></script>
|
||||
|
||||
<script src="/js/main.bundle.js"></script>
|
||||
<script src="/js/{{view}}.bundle.js"></script>
|
||||
|
||||
<!-- Error logging (Sentry) -->
|
||||
<script>
|
||||
Raven.config('{{&sentry_dsn}}').install()
|
||||
</script>
|
||||
|
||||
<!-- Analytics (GA) -->
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
|
|
|
@ -9,6 +9,7 @@ $background-color: hsla(0, 0, 99, 1); //#FDFDFD
|
|||
|
||||
/* UI Secondary Colors */
|
||||
$ui-aqua: hsla(170, 70, 50, 1); //#26D9BB
|
||||
$ui-purple: hsla(265, 55, 55, 1); //#824DCB
|
||||
$ui-white: #fff;
|
||||
|
||||
$ui-border: hsla(0, 0, 85, 1); //#D9D9D9
|
||||
|
|
|
@ -34,7 +34,7 @@ var Activity = React.createClass({
|
|||
title={formatMessage(defaultMessages.whatsHappening)}>
|
||||
|
||||
{this.props.items && this.props.items.length > 0 ? [
|
||||
<ul>
|
||||
<ul key="activity-ul">
|
||||
{this.props.items.map(function (item) {
|
||||
if (item.message.replace(/\s/g, '')) {
|
||||
var actorProfileUrl = '/users/' + item.actor.username + '/';
|
||||
|
@ -60,7 +60,7 @@ var Activity = React.createClass({
|
|||
})}
|
||||
</ul>
|
||||
] : [
|
||||
<div className="empty">
|
||||
<div className="empty" key="activity-empty">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="activity.seeUpdates"
|
||||
|
|
|
@ -2,8 +2,11 @@ var React = require('react');
|
|||
var ReactDOM = require('react-dom');
|
||||
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||
|
||||
var log = require('../../lib/log.js');
|
||||
|
||||
var Input = require('../forms/input.jsx');
|
||||
var Button = require('../forms/button.jsx');
|
||||
var Spinner = require('../spinner/spinner.jsx');
|
||||
|
||||
require('./login.scss');
|
||||
|
||||
|
@ -13,12 +16,21 @@ var Login = React.createClass({
|
|||
onLogIn: React.PropTypes.func,
|
||||
error: React.PropTypes.string
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
waiting: false
|
||||
};
|
||||
},
|
||||
handleSubmit: function (event) {
|
||||
event.preventDefault();
|
||||
this.setState({waiting: true});
|
||||
this.props.onLogIn({
|
||||
'username': ReactDOM.findDOMNode(this.refs.username).value,
|
||||
'password': ReactDOM.findDOMNode(this.refs.password).value
|
||||
});
|
||||
}, function (err) {
|
||||
if (err) log.error(err);
|
||||
this.setState({waiting: false});
|
||||
}.bind(this));
|
||||
},
|
||||
render: function () {
|
||||
var error;
|
||||
|
@ -40,11 +52,17 @@ var Login = React.createClass({
|
|||
defaultMessage={'Password'} />
|
||||
</label>
|
||||
<Input type="password" ref="password" name="password" />
|
||||
<Button className="submit-button white" type="submit">
|
||||
<FormattedMessage
|
||||
id='general.signIn'
|
||||
defaultMessage={'Sign in'} />
|
||||
</Button>
|
||||
{this.state.waiting ? [
|
||||
<Button className="submit-button white" type="submit" disabled="disabled">
|
||||
<Spinner />
|
||||
</Button>
|
||||
] : [
|
||||
<Button className="submit-button white" type="submit">
|
||||
<FormattedMessage
|
||||
id='general.signIn'
|
||||
defaultMessage={'Sign in'} />
|
||||
</Button>
|
||||
]}
|
||||
<a className="right" href="/accounts/password_reset/">
|
||||
<FormattedMessage
|
||||
id='login.forgotPassword'
|
||||
|
|
|
@ -8,6 +8,12 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
margin: 0 .8rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ var Navigation = React.createClass({
|
|||
closeLogin: function () {
|
||||
this.setState({'loginOpen': false});
|
||||
},
|
||||
handleLogIn: function (formData) {
|
||||
handleLogIn: function (formData, callback) {
|
||||
this.setState({'loginError': null});
|
||||
formData['useMessages'] = true;
|
||||
this.api({
|
||||
|
@ -126,12 +126,14 @@ var Navigation = React.createClass({
|
|||
json: formData,
|
||||
useCsrf: true
|
||||
}, function (err, body) {
|
||||
if (err) this.setState({'loginError': err.message});
|
||||
if (body) {
|
||||
body = body[0];
|
||||
if (!body.success) {
|
||||
if (body.redirect) {
|
||||
window.location = body.redirect;
|
||||
}
|
||||
// Update login error message to a friendlier one if it exists
|
||||
this.setState({'loginError': body.msg});
|
||||
} else {
|
||||
this.closeLogin();
|
||||
|
@ -143,6 +145,8 @@ var Navigation = React.createClass({
|
|||
window.refreshSession();
|
||||
}
|
||||
}
|
||||
// JS error already logged by api mixin
|
||||
callback();
|
||||
}.bind(this));
|
||||
},
|
||||
handleLogOut: function (e) {
|
||||
|
|
20
src/components/spinner/spinner.jsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
var range = require('lodash.range');
|
||||
var React = require('react');
|
||||
|
||||
require('./spinner.scss');
|
||||
|
||||
var Spinner = React.createClass({
|
||||
// Adapted from http://tobiasahlin.com/spinkit/
|
||||
type: 'Spinner',
|
||||
render: function () {
|
||||
return (
|
||||
<div className="spinner">
|
||||
{range(1,13).map(function (id) {
|
||||
return <div className={'circle' + id + ' circle'}></div>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Spinner;
|
47
src/components/spinner/spinner.scss
Normal file
|
@ -0,0 +1,47 @@
|
|||
@import "../../colors";
|
||||
|
||||
.spinner {
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
.circle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&:before {
|
||||
display: block;
|
||||
animation: circleFadeDelay 1.2s infinite ease-in-out both;
|
||||
margin: 0 auto;
|
||||
border-radius: 100%;
|
||||
background-color: darken($ui-blue, 8%);
|
||||
width: 15%;
|
||||
height: 15%;
|
||||
content: "";
|
||||
-webkit-animation: circleFadeDelay 1.2s infinite ease-in-out both;
|
||||
}
|
||||
}
|
||||
|
||||
@for $i from 1 through 12 {
|
||||
$rotation: 30deg * ($i - 1);
|
||||
$delay: -1.3s + $i * .1;
|
||||
.circle#{$i} {
|
||||
transform: rotate($rotation);
|
||||
-ms-transform: rotate($rotation);
|
||||
-webkit-transform: rotate($rotation);
|
||||
}
|
||||
.circle#{$i}:before {
|
||||
animation-delay: $delay;
|
||||
-webkit-animation-delay: $delay;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@keyframes circleFadeDelay {
|
||||
0%, 39%, 100% { opacity: 0; }
|
||||
40% { opacity: 1; }
|
||||
}
|
|
@ -26,6 +26,12 @@ var Api = {
|
|||
opts.uri = opts.host + opts.uri;
|
||||
|
||||
var apiRequest = function (opts) {
|
||||
if (opts.host !== '') {
|
||||
// For IE < 10, we must use XDR for cross-domain requests. XDR does not support
|
||||
// custom headers.
|
||||
defaults(opts, {useXDR: true});
|
||||
delete opts.headers;
|
||||
}
|
||||
xhr(opts, function (err, res, body) {
|
||||
if (err) log.error(err);
|
||||
callback(err, body);
|
||||
|
|
|
@ -6,6 +6,7 @@ var Box = require('../../components/box/box.jsx');
|
|||
var Button = require('../../components/forms/button.jsx');
|
||||
var Carousel = require('../../components/carousel/carousel.jsx');
|
||||
var Input = require('../../components/forms/input.jsx');
|
||||
var Spinner = require('../../components/spinner/spinner.jsx');
|
||||
|
||||
|
||||
require('./components.scss');
|
||||
|
@ -37,6 +38,8 @@ var Components = React.createClass({
|
|||
<Activity />
|
||||
<h1>{'Nothing!!!'}</h1>
|
||||
<Activity items={[]} />
|
||||
<h1>This is a Spinner</h1>
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@ var Hoc = React.createClass({
|
|||
|
||||
<div className="inner">
|
||||
<Box title={''}>
|
||||
<section className="one-up">
|
||||
<section id="teacher" className="one-up">
|
||||
<div className="column">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
|
@ -272,13 +272,13 @@ var Hoc = React.createClass({
|
|||
</div>
|
||||
|
||||
<div className="card">
|
||||
<a href="/projects/editor/?tip_bar=pong">
|
||||
<a href="/hoops">
|
||||
<div className="card-info">
|
||||
<img src="/images/hoc2015/pong-tutorial.jpg" />
|
||||
<img src="/images/hoc2015/bball-tutorial.jpg" />
|
||||
<Button>
|
||||
<FormattedMessage
|
||||
id='general.tipsPongGame'
|
||||
defaultMessage={'Create a Pong Game'} />
|
||||
id='general.tipsBBallHoops'
|
||||
defaultMessage={'B-Ball Hoops'} />
|
||||
</Button>
|
||||
</div>
|
||||
</a>
|
||||
|
|
BIN
static/images/hoc2015/bball-tutorial.jpg
Normal file
After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 94 KiB |