diff --git a/client/less/main.less b/client/less/main.less index 0a54914552..e1400a0ad3 100644 --- a/client/less/main.less +++ b/client/less/main.less @@ -1151,3 +1151,4 @@ and (max-width : 400px) { @import "toastr.less"; @import "map.less"; @import "drawers.less"; +@import "sk-wave.less"; diff --git a/client/less/sk-wave.less b/client/less/sk-wave.less new file mode 100644 index 0000000000..2427c92cb8 --- /dev/null +++ b/client/less/sk-wave.less @@ -0,0 +1,36 @@ +// original source: +// https://github.com/tobiasahlin/SpinKit +@duration: 3s; +@delayRange: 1s; + +.create-wave-child(@numOfCol, @iter: 2) when (@iter <= @numOfCol) { + div:nth-child(@{iter}) { + animation-delay: -(@duration - (@delayRange / (@numOfCol - 1)) * (@iter - 1)); + } + .create-wave-child(@numOfCol, (@iter + 1)); +} + +.sk-wave { + height: 100px; + margin: 100px auto; + text-align: center; + width: 50px; + > div { + animation: sk-stretchdelay @duration infinite ease-in-out; + background-color: @brand-primary; + display: inline-block; + height: 100%; + margin-right: 2px; + width: 6px; + } + .create-wave-child(5) +} + +@keyframes sk-stretchdelay { + 0%, 40%, 100% { + transform: scaleY(0.4); + } + 20% { + transform: scaleY(1.0); + } +} diff --git a/common/app/App.jsx b/common/app/App.jsx index de4fb7722d..1f566ea8e7 100644 --- a/common/app/App.jsx +++ b/common/app/App.jsx @@ -21,7 +21,7 @@ import Nav from './components/Nav'; import Toasts from './toasts/Toasts.jsx'; import { userSelector } from './redux/selectors'; -const bindableActions = { +const mapDispatchToProps = { initWindowHeight, updateNavHeight, fetchUser, @@ -35,14 +35,14 @@ const bindableActions = { const mapStateToProps = createSelector( userSelector, - state => state.app.shouldShowSignIn, + state => state.app.isSignInAttempted, state => state.app.toast, state => state.app.isMapDrawerOpen, state => state.app.isMapAlreadyLoaded, state => state.challengesApp.toast, ( { user: { username, points, picture } }, - shouldShowSignIn, + isSignInAttempted, toast, isMapDrawerOpen, isMapAlreadyLoaded, @@ -51,41 +51,38 @@ const mapStateToProps = createSelector( points, picture, toast, - shouldShowSignIn, + showLoading: !isSignInAttempted, isMapDrawerOpen, isMapAlreadyLoaded, isSignedIn: !!username }) ); +const propTypes = { + children: PropTypes.node, + username: PropTypes.string, + isSignedIn: PropTypes.bool, + points: PropTypes.number, + picture: PropTypes.string, + toast: PropTypes.object, + updateNavHeight: PropTypes.func, + initWindowHeight: PropTypes.func, + submitChallenge: PropTypes.func, + isMapDrawerOpen: PropTypes.bool, + isMapAlreadyLoaded: PropTypes.bool, + toggleMapDrawer: PropTypes.func, + toggleMainChat: PropTypes.func, + fetchUser: PropTypes.func, + showLoading: PropTypes.bool, + params: PropTypes.object, + updateAppLang: PropTypes.func.isRequired, + trackEvent: PropTypes.func.isRequired, + loadCurrentChallenge: PropTypes.func.isRequired +}; +const contextTypes = { router: PropTypes.object }; + // export plain class for testing export class FreeCodeCamp extends React.Component { - static displayName = 'FreeCodeCamp'; - static contextTypes = { - router: PropTypes.object - }; - static propTypes = { - children: PropTypes.node, - username: PropTypes.string, - isSignedIn: PropTypes.bool, - points: PropTypes.number, - picture: PropTypes.string, - toast: PropTypes.object, - updateNavHeight: PropTypes.func, - initWindowHeight: PropTypes.func, - submitChallenge: PropTypes.func, - isMapDrawerOpen: PropTypes.bool, - isMapAlreadyLoaded: PropTypes.bool, - toggleMapDrawer: PropTypes.func, - toggleMainChat: PropTypes.func, - fetchUser: PropTypes.func, - shouldShowSignIn: PropTypes.bool, - params: PropTypes.object, - updateAppLang: PropTypes.func.isRequired, - trackEvent: PropTypes.func.isRequired, - loadCurrentChallenge: PropTypes.func.isRequired - }; - componentWillReceiveProps(nextProps) { if (this.props.params.lang !== nextProps.params.lang) { this.props.updateAppLang(nextProps.params.lang); @@ -125,7 +122,7 @@ export class FreeCodeCamp extends React.Component { isMapAlreadyLoaded, toggleMapDrawer, toggleMainChat, - shouldShowSignIn, + showLoading, params: { lang }, trackEvent, loadCurrentChallenge @@ -138,7 +135,7 @@ export class FreeCodeCamp extends React.Component { updateNavHeight, toggleMapDrawer, toggleMainChat, - shouldShowSignIn, + showLoading, trackEvent, loadCurrentChallenge }; @@ -160,7 +157,11 @@ export class FreeCodeCamp extends React.Component { } } +FreeCodeCamp.displayName = 'FreeCodeCamp'; +FreeCodeCamp.contextTypes = contextTypes; +FreeCodeCamp.propTypes = propTypes; + export default connect( mapStateToProps, - bindableActions + mapDispatchToProps )(FreeCodeCamp); diff --git a/common/app/components/Nav/Nav.jsx b/common/app/components/Nav/Nav.jsx index feac803b52..666a1c912d 100644 --- a/common/app/components/Nav/Nav.jsx +++ b/common/app/components/Nav/Nav.jsx @@ -29,7 +29,21 @@ function handleNavLinkEvent(content) { }); } -export default class extends React.Component { +const propTypes = { + points: PropTypes.number, + picture: PropTypes.string, + signedIn: PropTypes.bool, + username: PropTypes.string, + isOnMap: PropTypes.bool, + updateNavHeight: PropTypes.func, + toggleMapDrawer: PropTypes.func, + toggleMainChat: PropTypes.func, + showLoading: PropTypes.bool, + trackEvent: PropTypes.func.isRequired, + loadCurrentChallenge: PropTypes.func.isRequired +}; + +export default class FCCNav extends React.Component { constructor(...props) { super(...props); this.handleMapClickOnMap = this.handleMapClickOnMap.bind(this); @@ -38,20 +52,6 @@ export default class extends React.Component { this[`handle${content}Click`] = handleNavLinkEvent.bind(this, content); }); } - static displayName = 'Nav'; - static propTypes = { - points: PropTypes.number, - picture: PropTypes.string, - signedIn: PropTypes.bool, - username: PropTypes.string, - isOnMap: PropTypes.bool, - updateNavHeight: PropTypes.func, - toggleMapDrawer: PropTypes.func, - toggleMainChat: PropTypes.func, - shouldShowSignIn: PropTypes.bool, - trackEvent: PropTypes.func.isRequired, - loadCurrentChallenge: PropTypes.func.isRequired - }; componentDidMount() { const navBar = ReactDOM.findDOMNode(this); @@ -165,8 +165,8 @@ export default class extends React.Component { }); } - renderSignIn(username, points, picture, shouldShowSignIn) { - if (!shouldShowSignIn) { + renderSignIn(username, points, picture, showLoading) { + if (showLoading) { return null; } if (username) { @@ -198,7 +198,7 @@ export default class extends React.Component { isOnMap, toggleMapDrawer, toggleMainChat, - shouldShowSignIn + showLoading } = this.props; return ( @@ -230,10 +230,13 @@ export default class extends React.Component { { this.renderMapLink(isOnMap, toggleMapDrawer) } { this.renderChat(toggleMainChat) } { this.renderLinks() } - { this.renderSignIn(username, points, picture, shouldShowSignIn) } + { this.renderSignIn(username, points, picture, showLoading) } ); } } + +FCCNav.displayName = 'Nav'; +FCCNav.propTypes = propTypes; diff --git a/common/app/components/SK-Wave.jsx b/common/app/components/SK-Wave.jsx new file mode 100644 index 0000000000..8f8080a0fb --- /dev/null +++ b/common/app/components/SK-Wave.jsx @@ -0,0 +1,19 @@ +import React, { PureComponent } from 'react'; + +const propTypes = { +}; +export default class SKWave extends PureComponent { + render() { + return ( +