From 5df28e0e77f843f7d42b0183d1f80f5e44a13e1a Mon Sep 17 00:00:00 2001 From: Clayton Wilson Date: Wed, 2 Oct 2019 17:53:20 -0400 Subject: [PATCH] Still working on Auth State --- twistter-frontend/src/App.js | 80 +++++++---- twistter-frontend/src/index.js | 5 +- twistter-frontend/src/pages/Login.js | 52 +++---- .../src/redux/actions/dataActions.js | 0 .../src/redux/actions/userActions.js | 35 +++++ .../src/redux/reducers/dataReducer.js | 0 .../src/redux/reducers/uiReducer.js | 30 ++++ .../src/redux/reducers/userReducer.js | 28 ++++ twistter-frontend/src/redux/store.js | 27 ++++ twistter-frontend/src/redux/types.js | 12 ++ twistter-frontend/src/serviceWorker.js | 135 ++++++++++++++++++ twistter-frontend/src/util/AuthRoute.js | 19 ++- 12 files changed, 363 insertions(+), 60 deletions(-) create mode 100644 twistter-frontend/src/redux/actions/dataActions.js create mode 100644 twistter-frontend/src/redux/actions/userActions.js create mode 100644 twistter-frontend/src/redux/reducers/dataReducer.js create mode 100644 twistter-frontend/src/redux/reducers/uiReducer.js create mode 100644 twistter-frontend/src/redux/reducers/userReducer.js create mode 100644 twistter-frontend/src/redux/store.js create mode 100644 twistter-frontend/src/redux/types.js create mode 100644 twistter-frontend/src/serviceWorker.js diff --git a/twistter-frontend/src/App.js b/twistter-frontend/src/App.js index 4a13d53..df42968 100644 --- a/twistter-frontend/src/App.js +++ b/twistter-frontend/src/App.js @@ -1,30 +1,34 @@ /* eslint-disable */ -import React, { Component } from 'react'; +import React, { Component } from "react"; -import './App.css'; +import "./App.css"; -import { BrowserRouter as Router } from 'react-router-dom'; -import Route from 'react-router-dom/Route'; -import Navbar from './components/layout/NavBar'; -import jwtDecode from 'jwt-decode'; +import { BrowserRouter as Router } from "react-router-dom"; +import Route from "react-router-dom/Route"; +import Navbar from "./components/layout/NavBar"; +import jwtDecode from "jwt-decode"; + +// Redux +import { Provider } from "react-redux"; +import store from "./redux/store"; // Components -import AuthRoute from './util/AuthRoute'; +import AuthRoute from "./util/AuthRoute"; -import home from './pages/Home'; -import register from './pages/Register'; -import login from './pages/Login'; -import user from './pages/user'; -import writeMicroblog from './Writing_Microblogs.js'; -import edit from './pages/edit.js'; -import userLine from './Userline.js'; +import home from "./pages/Home"; +import register from "./pages/Register"; +import login from "./pages/Login"; +import user from "./pages/user"; +import writeMicroblog from "./Writing_Microblogs.js"; +import edit from "./pages/edit.js"; +import userLine from "./Userline.js"; let authenticated; const token = localStorage.FBIdToken; if (token) { const decodedToken = jwtDecode(token); if (decodedToken.exp * 1000 < Date.now()) { - window.location.href = '/login'; + window.location.href = "/login"; authenticated = false; } else { authenticated = true; @@ -34,21 +38,37 @@ if (token) { class App extends Component { render() { return ( - -
- -
-
- - - - - - - -
- -
+ + +
+ +
+
+ + + + + + + +
+
+
); } } diff --git a/twistter-frontend/src/index.js b/twistter-frontend/src/index.js index 5b9a1ed..f722945 100644 --- a/twistter-frontend/src/index.js +++ b/twistter-frontend/src/index.js @@ -1,8 +1,11 @@ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; +import * as serviceWorker from './serviceWorker'; ReactDOM.render( , document.getElementById('root') -); \ No newline at end of file +); + +serviceWorker.unregister(); \ No newline at end of file diff --git a/twistter-frontend/src/pages/Login.js b/twistter-frontend/src/pages/Login.js index 2686e84..23f991f 100644 --- a/twistter-frontend/src/pages/Login.js +++ b/twistter-frontend/src/pages/Login.js @@ -11,6 +11,10 @@ import TextField from "@material-ui/core/TextField"; import Typography from "@material-ui/core/Typography"; import withStyles from "@material-ui/core/styles/withStyles"; +// Redux stuff +import { connect } from 'react-redux'; +import { loginUser } from '../redux/actions/userActions'; + const styles = { form: { textAlign: "center" @@ -55,40 +59,26 @@ export class Login extends Component { this.state = { email: "", password:"", - loading: false, errors: {} }; } + componentWillReceiveProps(nextProps) { + if (nextProps.UI.errors) { + this.setState({ errors: nextProps.UI.errors }); + } + } + // Runs whenever the submit button is clicked. // Updates the database entry of the signed in user with the // data stored in the state. handleSubmit = (event) => { event.preventDefault(); - this.setState({ - loading: true - }); const loginData = { email: this.state.email, password: this.state.password, }; - axios - .post("/login", loginData) - .then((res) => { - // Save the login token - localStorage.setItem('FBIdToken', `Bearer ${res.data.token}`); - this.setState({ - loading: false - }); - // Redirects to home page - this.props.history.push('/home'); - }) - .catch((err) => { - this.setState({ - errors: err.response.data, - loading: false - }); - }); + this.props.loginUser(loginData, this.props.history); }; // Updates the state whenever one of the textboxes changes. @@ -104,8 +94,8 @@ export class Login extends Component { }; render() { - const { classes } = this.props; - const { errors, loading } = this.state; + const { classes, UI: { loading } } = this.props; + const { errors } = this.state; return ( @@ -165,7 +155,19 @@ export class Login extends Component { } Login.propTypes = { - classes: PropTypes.object.isRequired + classes: PropTypes.object.isRequired, + loginUser: PropTypes.func.isRequired, + user: PropTypes.object.isRequired, + UI: PropTypes.object.isRequired }; -export default withStyles(styles)(Login); +const mapStateToProps = (state) => ({ + user: state.user, + UI: state.UI, +}); + +const mapActionsToProps = { + loginUser +} + +export default connect(mapStateToProps, mapActionsToProps)(withStyles(styles)(Login)); diff --git a/twistter-frontend/src/redux/actions/dataActions.js b/twistter-frontend/src/redux/actions/dataActions.js new file mode 100644 index 0000000..e69de29 diff --git a/twistter-frontend/src/redux/actions/userActions.js b/twistter-frontend/src/redux/actions/userActions.js new file mode 100644 index 0000000..e7e5535 --- /dev/null +++ b/twistter-frontend/src/redux/actions/userActions.js @@ -0,0 +1,35 @@ +import {SET_USER, SET_ERRORS, CLEAR_ERRORS, LOADING_UI} from '../types'; +import axios from 'axios'; + +export const loginUser = (loginData, history) => (dispatch) => { + dispatch({ type: LOADING_UI }); + axios + .post("/login", loginData) + .then((res) => { + // Save the login token + const FBIdToken = `Bearer ${res.data.token}`; + localStorage.setItem('FBIdToken', FBIdToken); + axios.defaults.headers.common['Authorization'] = FBIdToken; + dispatch(getProfileInfo()); + dispatch({ type: CLEAR_ERRORS }) + // Redirects to home page + history.push('/home'); + }) + .catch((err) => { + dispatch ({ + type: SET_ERRORS, + payload: err.response.data, + }) + }); +} + +export const getProfileInfo = () => (dispatch) => { + axios.get('/getProfileInfo') + .then((res) => { + dispatch({ + type: SET_USER, + payload: res.data, + }) + }) + .catch((err) => console.error(err)); +} \ No newline at end of file diff --git a/twistter-frontend/src/redux/reducers/dataReducer.js b/twistter-frontend/src/redux/reducers/dataReducer.js new file mode 100644 index 0000000..e69de29 diff --git a/twistter-frontend/src/redux/reducers/uiReducer.js b/twistter-frontend/src/redux/reducers/uiReducer.js new file mode 100644 index 0000000..65e781d --- /dev/null +++ b/twistter-frontend/src/redux/reducers/uiReducer.js @@ -0,0 +1,30 @@ +import { SET_ERRORS, CLEAR_ERRORS, LOADING_UI } from '../types'; + +const initialState = { + loading: false, + errors: null +}; + +export default function(state = initialState, action) { + switch(action.type) { + case SET_ERRORS: + return { + ...state, + loading: false, + errors: action.payload + }; + case CLEAR_ERRORS: + return { + ...state, + loading: false, + errors: null + }; + case LOADING_UI: + return { + ...state, + loading: true + } + default: + return state; + } +} \ No newline at end of file diff --git a/twistter-frontend/src/redux/reducers/userReducer.js b/twistter-frontend/src/redux/reducers/userReducer.js new file mode 100644 index 0000000..7a29e90 --- /dev/null +++ b/twistter-frontend/src/redux/reducers/userReducer.js @@ -0,0 +1,28 @@ +import {SET_USER, SET_ERRORS, CLEAR_ERRORS, LOADING_UI, SET_AUTHENTICATED, SET_UNAUTHENTICATED} from '../types'; + +const initialState = { + authenticated: false, + credentials: {}, + likes: [], + notifications: [] +}; + +export default function(state = initialState, action) { + switch(action.type) { + case SET_AUTHENTICATED: + return { + ...state, + authenticated: true, + + }; + case SET_UNAUTHENTICATED: + return initialState; + case SET_USER: + return { + authenticated: true, + ...action.payload, + }; + default: + return state; + } +} \ No newline at end of file diff --git a/twistter-frontend/src/redux/store.js b/twistter-frontend/src/redux/store.js new file mode 100644 index 0000000..b79ebe6 --- /dev/null +++ b/twistter-frontend/src/redux/store.js @@ -0,0 +1,27 @@ +import { createStore, combineReducers, applyMiddleware, compose } from "redux"; +import thunk from "redux-thunk"; + +import userReducer from "./reducers/userReducer"; +import dataReducer from "./reducers/dataReducer"; +import uiReducer from "./reducers/uiReducer"; + +const initialState = {}; + +const middleWare = [thunk]; + +const reducers = combineReducers({ + user: userReducer, + data: dataReducer, + UI: uiReducer +}); + +const store = createStore( + reducers, + initialState, + compose( + applyMiddleware(...middleWare), + window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() + ) +); + +export default store; diff --git a/twistter-frontend/src/redux/types.js b/twistter-frontend/src/redux/types.js new file mode 100644 index 0000000..6f16afc --- /dev/null +++ b/twistter-frontend/src/redux/types.js @@ -0,0 +1,12 @@ +// User reducer types +export const SET_AUTHENTICATED = 'SET_AUTHENTICATED'; +export const SET_UNAUTHENTICATED = 'SET_UNAUTHENTICATED'; +export const SET_USER = 'SET_USER'; +export const LOADING_USER = 'LOADING_USER'; + +// UI reducer types +export const SET_ERRORS = 'SET_ERRORS'; +export const LOADING_UI = 'LOADING_UI'; +export const CLEAR_ERRORS = 'CLEAR_ERRORS'; + +// Data reducer types \ No newline at end of file diff --git a/twistter-frontend/src/serviceWorker.js b/twistter-frontend/src/serviceWorker.js new file mode 100644 index 0000000..b685ab1 --- /dev/null +++ b/twistter-frontend/src/serviceWorker.js @@ -0,0 +1,135 @@ +// This optional code is used to register a service worker. +// register() is not called by default. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on subsequent visits to a page, after all the +// existing tabs open on the page have been closed, since previously cached +// resources are updated in the background. + +// To learn more about the benefits of this model and instructions on how to +// opt-in, read https://bit.ly/CRA-PWA + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) + ); + + export function register(config) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit https://bit.ly/CRA-PWA' + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } + }); + } + } + + function registerValidSW(swUrl, config) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + ); + + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); + } + + function checkValidServiceWorker(swUrl, config) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get('content-type'); + if ( + response.status === 404 || + (contentType != null && contentType.indexOf('javascript') === -1) + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); + } + + export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } + } diff --git a/twistter-frontend/src/util/AuthRoute.js b/twistter-frontend/src/util/AuthRoute.js index 8cc2395..309ccf0 100644 --- a/twistter-frontend/src/util/AuthRoute.js +++ b/twistter-frontend/src/util/AuthRoute.js @@ -1,11 +1,22 @@ import React from 'react' import { Route, Redirect} from 'react-router-dom'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; -const AuthRoute = ({ component: Component, authenticated, ...rest}) => ( +const AuthRoute = ({ component: Component, authenticated, ...rest }) => ( authenticated === true ? : } + render={(props) => + authenticated === true ? : } /> -) +); -export default AuthRoute; +const mapStateToProps = (state) => ({ + authenticated: state.user.authenticated +}); + +AuthRoute.propTypes = { + user: PropTypes.object +} + +export default connect(mapStateToProps)(AuthRoute);