summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package.json6
-rw-r--r--src/actions/auth.js15
-rw-r--r--src/api/index.js0
-rw-r--r--src/auth/index.js55
-rw-r--r--src/components/navigation/LogoutButton.js16
-rw-r--r--src/components/navigation/LogoutButton.sass17
-rw-r--r--src/components/navigation/Navbar.js6
-rw-r--r--src/components/navigation/Navbar.sass16
-rw-r--r--src/containers/auth/Login.js56
-rw-r--r--src/containers/auth/Logout.js20
-rw-r--r--src/pages/Home.js8
-rw-r--r--src/pages/Projects.js2
-rw-r--r--src/reducers/auth.js12
-rw-r--r--src/reducers/index.js2
-rw-r--r--src/routes/index.js11
-rw-r--r--src/store/configureStore.js14
16 files changed, 226 insertions, 30 deletions
diff --git a/package.json b/package.json
index 03fbeb84..33138c33 100644
--- a/package.json
+++ b/package.json
@@ -14,14 +14,14 @@
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-fontawesome": "^1.6.1",
- "react-google-login": "^2.9.2",
+ "react-google-login": "^2.9.3",
"react-mailto": "^0.4.0",
"react-redux": "^5.0.5",
"react-router-dom": "^4.1.1",
"react-scripts": "1.0.10",
"redux": "^3.7.2",
- "redux-logger": "^3.0.6",
- "redux-thunk": "^2.2.0"
+ "redux-localstorage": "^0.4.1",
+ "redux-logger": "^3.0.6"
},
"scripts": {
"build-css": "node-sass-chokidar src/ -o src/",
diff --git a/src/actions/auth.js b/src/actions/auth.js
new file mode 100644
index 00000000..f54563ae
--- /dev/null
+++ b/src/actions/auth.js
@@ -0,0 +1,15 @@
+export const COMPLETE_LOGIN = "COMPLETE_LOGIN";
+export const LOG_OUT = "LOG_OUT";
+
+export const completeLogin = (payload) => {
+ return {
+ type: COMPLETE_LOGIN,
+ payload
+ };
+};
+
+export const logOut = () => {
+ return {
+ type: LOG_OUT
+ };
+};
diff --git a/src/api/index.js b/src/api/index.js
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/api/index.js
diff --git a/src/auth/index.js b/src/auth/index.js
new file mode 100644
index 00000000..5ea24ff9
--- /dev/null
+++ b/src/auth/index.js
@@ -0,0 +1,55 @@
+import {COMPLETE_LOGIN, LOG_OUT} from "../actions/auth";
+
+const getAuthObject = () => {
+ const authItem = localStorage.getItem("auth");
+ if (!authItem) {
+ return undefined;
+ }
+ return JSON.parse(authItem);
+};
+
+export const userIsLoggedIn = () => {
+ const authObj = getAuthObject();
+
+ if (!authObj || !authObj.googleId) {
+ return false;
+ }
+
+ const currentTime = (new Date()).getTime();
+ return parseInt(authObj.expiresAt, 10) - currentTime > 0;
+};
+
+export const getAuthToken = () => {
+ const authObj = getAuthObject();
+ if (!authObj) {
+ return undefined;
+ }
+
+ return authObj.authToken;
+};
+
+export const saveAuthLocalStorage = (payload) => {
+ localStorage.setItem("auth", JSON.stringify(payload));
+};
+
+export const clearAuthLocalStorage = () => {
+ localStorage.setItem("auth", "{}");
+};
+
+export const authRedirectMiddleware = store => next => action => {
+ switch (action.type) {
+ case COMPLETE_LOGIN:
+ saveAuthLocalStorage(action.payload);
+ window.location.href = "/projects";
+ break;
+ case LOG_OUT:
+ clearAuthLocalStorage();
+ window.location.href = "/";
+ break;
+ default:
+ next(action);
+ return;
+ }
+
+ next(action);
+};
diff --git a/src/components/navigation/LogoutButton.js b/src/components/navigation/LogoutButton.js
new file mode 100644
index 00000000..e2da7751
--- /dev/null
+++ b/src/components/navigation/LogoutButton.js
@@ -0,0 +1,16 @@
+import PropTypes from "prop-types";
+import React from "react";
+import {Link} from "react-router-dom";
+import "./LogoutButton.css";
+
+const LogoutButton = ({onLogout}) => (
+ <Link className="logout" title="Sign out" to="#" onClick={onLogout}>
+ <span className="fa fa-lg fa-power-off"/>
+ </Link>
+);
+
+LogoutButton.propTypes = {
+ onLogout: PropTypes.func.isRequired,
+};
+
+export default LogoutButton;
diff --git a/src/components/navigation/LogoutButton.sass b/src/components/navigation/LogoutButton.sass
new file mode 100644
index 00000000..b63494ab
--- /dev/null
+++ b/src/components/navigation/LogoutButton.sass
@@ -0,0 +1,17 @@
+@import ../../style-globals/_mixins.sass
+@import ../../style-globals/_variables.sass
+
+.logout
+ float: right
+ margin-top: -1px
+ width: $navbar-height
+
+ font-size: 14pt
+
+ +clickable
+
+ &:hover
+ background: #e3474d
+
+ &:active
+ background: #a73438
diff --git a/src/components/navigation/Navbar.js b/src/components/navigation/Navbar.js
index bd6fd750..bbd08591 100644
--- a/src/components/navigation/Navbar.js
+++ b/src/components/navigation/Navbar.js
@@ -2,6 +2,7 @@ import React, {Component} from 'react';
import FontAwesome from "react-fontawesome";
import Mailto from "react-mailto";
import {Link} from "react-router-dom";
+import Logout from "../../containers/auth/Logout";
import "./Navbar.css";
class Navbar extends Component {
@@ -23,11 +24,8 @@ class Navbar extends Component {
<FontAwesome name="question-circle" size="lg"/>
</Mailto>
<Link className="username" title="My Profile" to="/profile">Profile</Link>
- <Link className="sign-out" title="Sign out" to="#">
- <FontAwesome name="power-off" size="lg"/>
- </Link>
+ <Logout/>
</div>
- <div id="google-signin" className="navbar-right"/>
</div>
);
}
diff --git a/src/components/navigation/Navbar.sass b/src/components/navigation/Navbar.sass
index a592eab0..8be622db 100644
--- a/src/components/navigation/Navbar.sass
+++ b/src/components/navigation/Navbar.sass
@@ -114,19 +114,3 @@
.username:active
background: #2d6527
-
- .sign-out
- float: right
- margin-top: -1px
- width: $navbar-height
-
- font-size: 14pt
-
- .sign-out:hover
- background: #e3474d
-
- .sign-out:active
- background: #a73438
-
-#google-signin
- display: none
diff --git a/src/containers/auth/Login.js b/src/containers/auth/Login.js
new file mode 100644
index 00000000..358ea7e9
--- /dev/null
+++ b/src/containers/auth/Login.js
@@ -0,0 +1,56 @@
+import PropTypes from "prop-types";
+import React from "react";
+import GoogleLogin from "react-google-login";
+import {connect} from "react-redux";
+import {completeLogin} from "../../actions/auth";
+
+class LoginContainer extends React.Component {
+ static propTypes = {
+ visible: PropTypes.bool.isRequired,
+ onLogin: PropTypes.func.isRequired,
+ };
+
+ onAuthResponse(response) {
+ this.props.onLogin({
+ googleId: response.googleId,
+ authToken: response.accessToken,
+ expiresAt: response.getAuthResponse().expires_at
+ });
+ }
+
+ render() {
+ if (!this.props.visible) {
+ return <span/>;
+ }
+
+ return (
+ <GoogleLogin
+ clientId="311799954046-jv2inpg9nu7m0avcg6gulvkuvfgbtgb4.apps.googleusercontent.com"
+ onSuccess={this.onAuthResponse.bind(this)}
+ onFailure={this.onAuthResponse.bind(this)}>
+ <span className='fa fa-google'/>
+ {' '}
+ <span>Login with Google</span>
+ </GoogleLogin>
+ );
+ }
+}
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ visible: ownProps.visible,
+ };
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onLogin: (payload) => dispatch(completeLogin(payload)),
+ };
+};
+
+const Login = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(LoginContainer);
+
+export default Login;
diff --git a/src/containers/auth/Logout.js b/src/containers/auth/Logout.js
new file mode 100644
index 00000000..8d329c1f
--- /dev/null
+++ b/src/containers/auth/Logout.js
@@ -0,0 +1,20 @@
+import {connect} from "react-redux";
+import {logOut} from "../../actions/auth";
+import LogoutButton from "../../components/navigation/LogoutButton";
+
+const mapStateToProps = state => {
+ return {};
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onLogout: () => dispatch(logOut()),
+ };
+};
+
+const Logout = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(LogoutButton);
+
+export default Logout;
diff --git a/src/pages/Home.js b/src/pages/Home.js
new file mode 100644
index 00000000..bd32d0d9
--- /dev/null
+++ b/src/pages/Home.js
@@ -0,0 +1,8 @@
+import React from 'react';
+import Login from "../containers/auth/Login";
+
+const Index = () => (
+ <Login visible={true}/>
+);
+
+export default Index;
diff --git a/src/pages/Projects.js b/src/pages/Projects.js
index 40902d97..69a28f9d 100644
--- a/src/pages/Projects.js
+++ b/src/pages/Projects.js
@@ -4,6 +4,7 @@ import {addProject, openNewProjectModal} from "../actions/projects";
import Navbar from "../components/navigation/Navbar";
import ProjectFilterPanel from "../components/projects/FilterPanel";
import NewProjectButton from "../components/projects/NewProjectButton";
+import Login from "../containers/auth/Login";
import NewProjectModal from "../containers/projects/NewProjectModal";
import VisibleProjectList from "../containers/projects/VisibleProjectAuthList";
import "./Projects.css";
@@ -27,6 +28,7 @@ class Projects extends React.Component {
<NewProjectButton onClick={() => {this.props.dispatch(openNewProjectModal())}}/>
</div>
<NewProjectModal/>
+ <Login visible={false}/>
</div>
);
}
diff --git a/src/reducers/auth.js b/src/reducers/auth.js
new file mode 100644
index 00000000..0d01b300
--- /dev/null
+++ b/src/reducers/auth.js
@@ -0,0 +1,12 @@
+import {COMPLETE_LOGIN, LOG_OUT} from "../actions/auth";
+
+export const auth = (state = {}, action) => {
+ switch (action.type) {
+ case COMPLETE_LOGIN:
+ return action.payload;
+ case LOG_OUT:
+ return {};
+ default:
+ return state;
+ }
+};
diff --git a/src/reducers/index.js b/src/reducers/index.js
index 3974ce4a..4e35f6e8 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -1,7 +1,9 @@
import {combineReducers} from "redux";
+import {auth} from "./auth";
import {authorizations, authVisibilityFilter, newProjectModalVisible} from "./projects";
const rootReducer = combineReducers({
+ auth,
authorizations,
newProjectModalVisible,
authVisibilityFilter,
diff --git a/src/routes/index.js b/src/routes/index.js
index 54dc0703..6b3a454d 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -1,5 +1,6 @@
import React from 'react';
-import {BrowserRouter, Route, Switch} from "react-router-dom";
+import {BrowserRouter, Redirect, Route, Switch} from "react-router-dom";
+import {userIsLoggedIn} from "../auth/index";
import Home from "../pages/Home";
import NotFound from "../pages/NotFound";
import Projects from "../pages/Projects";
@@ -8,7 +9,13 @@ const Routes = () => (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Home}/>
- <Route exact path="/projects" component={Projects}/>
+ <Route exact path="/projects" render={() => (
+ userIsLoggedIn() ? (
+ <Projects/>
+ ) : (
+ <Redirect to="/"/>
+ )
+ )}/>
<Route path="/*" component={NotFound}/>
</Switch>
</BrowserRouter>
diff --git a/src/store/configureStore.js b/src/store/configureStore.js
index 9584f591..ec932cf7 100644
--- a/src/store/configureStore.js
+++ b/src/store/configureStore.js
@@ -1,15 +1,19 @@
-import {applyMiddleware, createStore} from "redux";
+import {applyMiddleware, compose, createStore} from "redux";
+import persistState from "redux-localstorage";
import {createLogger} from "redux-logger";
-import thunkMiddleware from "redux-thunk";
+import {authRedirectMiddleware} from "../auth/index";
import rootReducer from "../reducers/index";
const logger = createLogger();
const configureStore = () => createStore(
rootReducer,
- applyMiddleware(
- thunkMiddleware,
- logger,
+ compose(
+ persistState("auth"),
+ applyMiddleware(
+ logger,
+ authRedirectMiddleware,
+ )
)
);