Skip to content

Commit 7c0363e

Browse files
committed
Merge pull request #211 from rschamp/bugfix/GH-195
Fix GH-195: Use a spinner component to convey activity while logging in
2 parents e25eb1f + 0420457 commit 7c0363e

File tree

7 files changed

+103
-7
lines changed

7 files changed

+103
-7
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"lodash.clone": "3.0.3",
4747
"lodash.defaultsdeep": "3.10.0",
4848
"lodash.omit": "3.1.0",
49+
"lodash.range": "3.0.1",
4950
"minilog": "2.0.8",
5051
"node-sass": "3.3.3",
5152
"po2icu": "git://github.com/LLK/po2icu.git#develop",

src/components/login/login.jsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ var React = require('react');
22
var ReactDOM = require('react-dom');
33
var FormattedMessage = require('react-intl').FormattedMessage;
44

5+
var log = require('../../lib/log.js');
6+
57
var Input = require('../forms/input.jsx');
68
var Button = require('../forms/button.jsx');
9+
var Spinner = require('../spinner/spinner.jsx');
710

811
require('./login.scss');
912

@@ -13,12 +16,21 @@ var Login = React.createClass({
1316
onLogIn: React.PropTypes.func,
1417
error: React.PropTypes.string
1518
},
19+
getInitialState: function () {
20+
return {
21+
waiting: false
22+
};
23+
},
1624
handleSubmit: function (event) {
1725
event.preventDefault();
26+
this.setState({waiting: true});
1827
this.props.onLogIn({
1928
'username': ReactDOM.findDOMNode(this.refs.username).value,
2029
'password': ReactDOM.findDOMNode(this.refs.password).value
21-
});
30+
}, function (err) {
31+
if (err) log.error(err);
32+
this.setState({waiting: false});
33+
}.bind(this));
2234
},
2335
render: function () {
2436
var error;
@@ -40,11 +52,17 @@ var Login = React.createClass({
4052
defaultMessage={'Password'} />
4153
</label>
4254
<Input type="password" ref="password" name="password" />
43-
<Button className="submit-button white" type="submit">
44-
<FormattedMessage
45-
id='general.signIn'
46-
defaultMessage={'Sign in'} />
47-
</Button>
55+
{this.state.waiting ? [
56+
<Button className="submit-button white" type="submit" disabled="disabled">
57+
<Spinner />
58+
</Button>
59+
] : [
60+
<Button className="submit-button white" type="submit">
61+
<FormattedMessage
62+
id='general.signIn'
63+
defaultMessage={'Sign in'} />
64+
</Button>
65+
]}
4866
<a className="right" href="/accounts/password_reset/">
4967
<FormattedMessage
5068
id='login.forgotPassword'

src/components/login/login.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
font-weight: bold;
99
}
1010

11+
.spinner {
12+
margin: 0 .8rem;
13+
width: 1rem;
14+
height: 1rem;
15+
}
16+
1117
.submit-button {
1218
margin-top: 5px;
1319
}

src/components/navigation/navigation.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ var Navigation = React.createClass({
116116
closeLogin: function () {
117117
this.setState({'loginOpen': false});
118118
},
119-
handleLogIn: function (formData) {
119+
handleLogIn: function (formData, callback) {
120120
this.setState({'loginError': null});
121121
formData['useMessages'] = true;
122122
this.api({
@@ -142,6 +142,7 @@ var Navigation = React.createClass({
142142
}.bind(this));
143143
window.refreshSession();
144144
}
145+
callback();
145146
}
146147
}.bind(this));
147148
},

src/components/spinner/spinner.jsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
var range = require('lodash.range');
2+
var React = require('react');
3+
4+
require('./spinner.scss');
5+
6+
var Spinner = React.createClass({
7+
// Adapted from http://tobiasahlin.com/spinkit/
8+
type: 'Spinner',
9+
render: function () {
10+
return (
11+
<div className="spinner">
12+
{range(1,13).map(function (id) {
13+
return <div className={'circle' + id + ' circle'}></div>;
14+
})}
15+
</div>
16+
);
17+
}
18+
});
19+
20+
module.exports = Spinner;

src/components/spinner/spinner.scss

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
@import "../../colors";
2+
3+
.spinner {
4+
position: relative;
5+
width: 20px;
6+
height: 20px;
7+
8+
.circle {
9+
position: absolute;
10+
top: 0;
11+
left: 0;
12+
width: 100%;
13+
height: 100%;
14+
15+
&:before {
16+
display: block;
17+
animation: circleFadeDelay 1.2s infinite ease-in-out both;
18+
margin: 0 auto;
19+
border-radius: 100%;
20+
background-color: darken($ui-blue, 8%);
21+
width: 15%;
22+
height: 15%;
23+
content: "";
24+
-webkit-animation: circleFadeDelay 1.2s infinite ease-in-out both;
25+
}
26+
}
27+
28+
@for $i from 1 through 12 {
29+
$rotation: 30deg * ($i - 1);
30+
$delay: -1.3s + $i * .1;
31+
.circle#{$i} {
32+
transform: rotate($rotation);
33+
-ms-transform: rotate($rotation);
34+
-webkit-transform: rotate($rotation);
35+
}
36+
.circle#{$i}:before {
37+
animation-delay: $delay;
38+
-webkit-animation-delay: $delay;
39+
}
40+
}
41+
42+
}
43+
44+
@keyframes circleFadeDelay {
45+
0%, 39%, 100% { opacity: 0; }
46+
40% { opacity: 1; }
47+
}

src/views/components/components.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var Box = require('../../components/box/box.jsx');
66
var Button = require('../../components/forms/button.jsx');
77
var Carousel = require('../../components/carousel/carousel.jsx');
88
var Input = require('../../components/forms/input.jsx');
9+
var Spinner = require('../../components/spinner/spinner.jsx');
910

1011

1112
require('./components.scss');
@@ -37,6 +38,8 @@ var Components = React.createClass({
3738
<Activity />
3839
<h1>{'Nothing!!!'}</h1>
3940
<Activity items={[]} />
41+
<h1>This is a Spinner</h1>
42+
<Spinner />
4043
</div>
4144
);
4245
}

0 commit comments

Comments
 (0)