Maxim 5 years ago
parent
commit
293bffe5b2

+ 10 - 0
src/actions/auth/tokenCheckout.js

@@ -0,0 +1,10 @@
+import * as types from './../../constants/auth';
+
+export const activeToken = payload => ({
+    type: types.ACTIVE_TOKEN,
+    payload
+})
+
+export const inactiveToken = () => ({
+    type: types.INACTIVE_TOKEN
+})

+ 28 - 21
src/components/common/protectedRoute.js

@@ -1,43 +1,50 @@
 import React from 'react';
 import { Route, Redirect } from 'react-router-dom';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import { inactiveToken, activeToken } from './../../actions/auth/tokenCheckout'
 import decode from 'jwt-decode';
 
 import * as routes from './../../constants/routes';
 import storageKey from './../../utils/storageKey';
 
-export default ({ component: Component, tokenAuth, access, ...rest }) => (
+const protectedRoute = ({ component: Component, activeToken, inactiveToken, access, ...rest }) => (
     <Route
         {...rest}
         render={props => {
-            // test
             try {
-                const storagedData = localStorage.getItem(storageKey);
-
-                if (access === 'public') {
-                    return <Component {...props} />
-                }
-                
-                if (!storagedData || storagedData.exp < Date.now()) {
-                    throw new Error(1);
+                const user = decode(localStorage.getItem(storageKey));
+                activeToken(user.role);
+                if (user.exp < Date.now()*1000) {
+                    throw new Error('Date expired');
                 }
 
-                const user = decode(storagedData);
-                const { role } = user; 
-
-                if (access === 'admin-only' && !role) {
-                    throw new Error(2)                   
+                if (access === 'admin-only' && !user.role) {
+                    throw new Error('Permission Denied')
                 }
 
                 return <Component {...props} />
             }
-            catch ({ message: errorCode }) {
-                if (errorCode === '1') {
-                    return <Redirect to={routes.SIGN_IN} />
-                }
-                if (errorCode === '2') {
+            catch ({ message }) {
+                if (message === 'Permission Denied') {
                     return <div>Permission denied</div>
                 }
+
+                inactiveToken();
+
+                if (access === 'public') {
+                    return <Component {...props} />
+                }
+
+                return <Redirect to={routes.SIGN_IN} />
             }
         }}
     />
-)
+)
+
+const mapDispatchToProps = dispatch => bindActionCreators({
+    activeToken,
+    inactiveToken
+}, dispatch);
+
+export default connect(null, mapDispatchToProps)(protectedRoute);

+ 62 - 48
src/components/public/Header.js

@@ -1,67 +1,82 @@
-import React from 'react';
-import { Link } from "react-router-dom";
+import React, { PureComponent } from 'react';
+import { Link, withRouter, Redirect } from "react-router-dom";
 import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
+import storageKey from './../../utils/storageKey';
 
-import * as routes from '../../constants/routes'
-import logOut from './../../actions/auth/logOut';
+import * as routes from '../../constants/routes';
+import logOut from '../../actions/auth/logOut';
 
-class Header extends React.Component {
+//pure component + make storage check an action -> connect router
+class Header extends PureComponent {
     handleLogOut = () => {
-        const { logOut } = this.props;
-        logOut()
-        //TODO: REDIRECT
+        const { history: { push } } = this.props;
     }
 
     render() {
+        const { role, logOutStatus } = this.props;
+
         return (
-            <nav class="navbar navbar-expand-lg navbar-dark bg-dark p-3">
-                <Link to={routes.LANDING} class="navbar-brand" href="#">Test.io</Link>
-                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
-                    <span class="navbar-toggler-icon" />
+            <nav className="navbar navbar-expand-lg navbar-dark bg-dark p-3">
+                <Link to={routes.LANDING} className="navbar-brand" href="#">Test.io</Link>
+                <button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
+                    <span className="navbar-toggler-icon" />
                 </button>
 
-                <div class="collapse navbar-collapse" id="navbarSupportedContent">
-                    <ul class="navbar-nav mr-auto">
-                        <li class="nav-item active">
-                            <Link onClick={this.handleClick} class="nav-link" to={routes.HOME}>Home <span class="sr-only">(current)</span></Link>
+                <div className="collapse navbar-collapse" id="navbarSupportedContent">
+                    <ul className="navbar-nav mr-auto">
+                        <li className="nav-item active">
+                            <Link onClick={this.handleClick} className="nav-link" to={routes.HOME}>Home <span className="sr-only">(current)</span></Link>
                         </li>
-                        <li class="nav-item">
-                            <Link onClick={this.handleClick} class="nav-link" to={routes.PROFILE}>Profile</Link>
+                        <li className="nav-item">
+                            <Link onClick={this.handleClick} className="nav-link" to={routes.PROFILE}>Profile</Link>
                         </li>
-                        <li class="nav-item">
-                            <Link onClick={this.handleClick} class="nav-link" to={routes.TESTS}>Tests</Link>
+                        <li className="nav-item">
+                            <Link onClick={this.handleClick} className="nav-link" to={routes.TESTS}>Tests</Link>
                         </li>
-                        <li class="nav-item">
-                            <Link onClick={this.handleClick} class="nav-link" to={routes.CATEGORIES}>Catigories</Link>
+                        <li className="nav-item">
+                            <Link onClick={this.handleClick} className="nav-link" to={routes.CATEGORIES}>Catigories</Link>
                         </li>
                         {/* TODO: admin-only ! */}
-                        <li class="nav-item dropdown">
-                            <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                                Dropdown
+                        {
+                            role
+                                ?
+                                <li className="nav-item dropdown">
+                                    {/* eslint-disable-next-line */}
+                                    <a className="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                                        Dropdown
                                 </a>
-                            <div class="dropdown-menu" aria-labelledby="navbarDropdown">
-                                <Link to={routes.CREATE_TEST} className="nav-link text-secondary">Create Test</Link>
-                                <Link to={routes.CREATE_CATEGORY} className="nav-link text-secondary">Create Category</Link>
-
-                                <div class="dropdown-divider"></div>
-                                <a class="nav-link text-secondary" href="#">Something else here</a>
-                            </div>
-                        </li>
+                                    <div className="dropdown-menu" aria-labelledby="navbarDropdown">
+                                        <Link to={routes.CREATE_TEST} className="nav-link text-secondary">Create Test</Link>
+                                        <Link to={routes.CREATE_CATEGORY} className="nav-link text-secondary">Create Category</Link>
 
+                                        <div className="dropdown-divider"></div>
+                                        {/* eslint-disable-next-line */}
+                                        <a className="nav-link text-secondary" href="#">Something else here</a>
+                                    </div>
+                                </li>
+                                :
+                                null
+                        }
                     </ul>
-                    <form class="form-inline my-lg-0">
-                        <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search" />
-                        <button class="btn btn-outline-success my-2 my-sm-0 mr-2" type="submit">Search</button>
+                    <form className="form-inline my-lg-0">
+                        <input className="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search" />
+                        <button className="btn btn-outline-success my-2 my-sm-0 mr-2" type="submit">Search</button>
                     </form>
-                    <div className="">
-                        <Link to={routes.SIGN_IN}>
-                            <button className="btn btn-outline-primary">Sign In</button>
-                        </Link>
-                        <Link to={routes.SIGN_UP}>
-                            <button className="btn btn-outline-primary mx-2">Sign Up</button>
-                        </Link>
-                        <button onClick={this.handleLogOut} className="btn btn-outline-primary">Log Out</button>
+                    <div className="controls">
+                        {
+                            role !== null
+                                ?
+                                <button onClick={this.handleLogOut} className="btn btn-outline-primary">Log Out</button>
+                                :
+                                <React.Fragment>
+                                    <Link to={routes.SIGN_IN}>
+                                        <button className="btn btn-outline-primary">Sign In</button>
+                                    </Link>
+                                    <Link to={routes.SIGN_UP}>
+                                        <button className="btn btn-outline-primary mx-2">Sign Up</button>
+                                    </Link>
+                                </React.Fragment>
+                        }
                     </div>
                 </div>
             </nav>
@@ -70,8 +85,7 @@ class Header extends React.Component {
 }
 
 const mapStateToProps = state => ({
-    state: state.logOut
+    role: state.tokenCheckout.role,
 });
-const mapDispatchToProps = dispatch => bindActionCreators({ logOut }, dispatch)
 
-export default connect(mapStateToProps, mapDispatchToProps)(Header);
+export default withRouter(connect(mapStateToProps, null)(Header));

+ 0 - 2
src/components/user/ProfilePage/ChangeLoginForm/index.js

@@ -1,5 +1,4 @@
 import React from 'react';
-import { connect } from 'react-redux';
 import { Field, reduxForm } from 'redux-form';
 
 import formInput from '../../../common/formInput';
@@ -7,7 +6,6 @@ import formInput from '../../../common/formInput';
 class ChangeLoginForm extends React.Component {
 
     render() {
-        // const { children, handleSubmit, handlers: { handleClick, submit } } = this.props;
         return (
             <form  >
                 <Field name="login" type="email" placeholder="Enter new login" component={formInput} />

+ 5 - 6
src/components/user/ProfilePage/index.js

@@ -25,7 +25,6 @@ class ProfilePage extends React.Component {
     }
 
     render() {
-        const { users, userChangeRequest, passwordChangeRequest } = this.props;
         const { data } = user;
         const {clicked} =this.state;
 
@@ -33,30 +32,30 @@ class ProfilePage extends React.Component {
             <div className="page page--bottom-only profile-page">
                 <section className="border bg-autumn-foliage m-5 rounded d-flex">
                     <div>
-                        <img className="m-4 " src=" https://cdn.iconscout.com/icon/free/png-256/avatar-373-456325.png" ></img>
+                        <img className="m-4" src="https://cdn.iconscout.com/icon/free/png-256/avatar-373-456325.png" alt="banner" ></img>
                     </div>
                     <div className="border-left bg-autumn-foliage m-5 rounded w-75 ">
                             {!clicked
                             ?
                         <div  className ="d-flex mb-3" >
-                            <h3 className = " p-2 flex-fill bd-highlight">Name</h3>
+                            <h3 className = "p-2 flex-fill bd-highlight">Name</h3>
                             <h5 className="p-2 font-po-bold flex-fill bd-highlight align-self-end">{data.name}</h5>
                             <button type="button" onClick ={this.handleClick} className=" flex-fill bg-shadow btn btn-outline-light col-3">Change</button>
                         </div>
                         :
                         <div>
-                        <h3 className = " p-2 flex-fill bd-highlight">Name</h3>
+                        <h3 className="p-2 flex-fill bd-highlight">Name</h3>
                         <h5 className="p-2 font-po-bold flex-fill bd-highlight align-self-end">{data.name}</h5>
                         <ChangeLoginForm> </ChangeLoginForm>
                         </div>
                             }
                         <div  className ="d-flex mb-3" >
-                            <h3 className = "p-2 flex-fill bd-highlight">Email</h3>
+                            <h3 className="p-2 flex-fill bd-highlight">Email</h3>
                             <h5 className="p-2 flex-fill font-po-bold bd-highlight align-self-end">{data.email}</h5>
                             <button type="button" className=" flex-fill bg-shadow btn btn-outline-light col-3">Change</button>
                         </div>
                         <div  className ="d-flex mb-3" >
-                            <h3 className = "p-2 flex-fill bd-highlight">Password</h3>
+                            <h3 className="p-2 flex-fill bd-highlight">Password</h3>
                             <button type="button" className=" flex-fill bg-shadow btn btn-outline-light col-3">Change</button>
                         </div>
                  

+ 4 - 1
src/constants/auth.js

@@ -11,4 +11,7 @@ export const SIGN_UP_REQUEST_FAILURE = 'SIGN_UP_REQUEST_FAILURE';
 export const LOG_OUT_URL = 'https://test-app-a-level.herokuapp.com/auth/logout';
 export const LOG_OUT_REQUEST = 'LOG_OUT_REQUEST';
 export const LOG_OUT_REQUEST_SUCCESS = 'LOG_OUT_REQUEST_SUCCESS';
-export const LOG_OUT_REQUEST_FAILURE = 'LOG_OUT_REQUEST_FAILURE';
+export const LOG_OUT_REQUEST_FAILURE = 'LOG_OUT_REQUEST_FAILURE';
+
+export const ACTIVE_TOKEN = 'ACTIVE_TOKEN';
+export const INACTIVE_TOKEN = 'INACTIVE_TOKEN'; 

+ 1 - 1
src/reducers/auth/logOut.js

@@ -1,7 +1,7 @@
 import * as actionTypes from './../../constants/auth';
 import initialState from './../initialState';
 
-export default function signInReducer(state = initialState.signIn, {type, error}) {
+export default function signInReducer(state = initialState.logOut, {type, error}) {
 
     switch (type) {
         case actionTypes.LOG_OUT_REQUEST: {

+ 22 - 0
src/reducers/auth/tokenCheckout.js

@@ -0,0 +1,22 @@
+import * as actionTypes from './../../constants/auth';
+import initialState from './../initialState';
+
+export default function signInReducer(state = initialState.tokenCheckout, {type, payload: role, error}) {
+    switch(type) {
+        case actionTypes.ACTIVE_TOKEN: {
+            return {
+                ...state,
+                role
+            }
+        }
+        case actionTypes.INACTIVE_TOKEN: {
+            return {
+                ...state,
+                role: null
+            }
+        }
+        default: {  
+            return state;
+        }
+    }
+}

+ 4 - 0
src/reducers/index.js

@@ -3,8 +3,12 @@ import { reducer as form } from 'redux-form';
 
 import signIn from './auth/signIn';
 import signUp from './auth/signUp';
+import logOut from './auth/logOut';
+import tokenCheckout from './auth/tokenCheckout';
 
 export default combineReducers({
+    tokenCheckout,
+    logOut,
     signIn,
     signUp,
     form

+ 3 - 0
src/reducers/initialState.js

@@ -14,5 +14,8 @@ export default {
         isFetching: false,
         isSuccessful: null,
         error: null
+    },
+    tokenCheckout: {
+        role: null
     }
 }

+ 4 - 6
src/router.js

@@ -1,5 +1,5 @@
-import React, { Suspense, lazy } from "react";
-import { Switch, Route, withRouter } from "react-router-dom";
+import React, { Suspense } from "react";
+import { Switch, withRouter } from "react-router-dom";
 import { connect } from 'react-redux';
 
 import ProtectedRoute from './components/common/protectedRoute';
@@ -8,11 +8,9 @@ import config from './configs/routerConfig';
 import Header from './components/public/Header';
 import Spinner from './components/common/spinner';
 
-import { bindActionCreators } from "redux";
-
 class Router extends React.Component {
     render() {
-        const { user, tokenAuth } = this.props;
+        const { user } = this.props;
         // TODO: add footer
         return (
             <div className="app">
@@ -25,7 +23,7 @@ class Router extends React.Component {
                                 component={route.component}
                                 access={route.access}
                                 user={user}
-                                key={route}
+                                key={route.path}
                                 exact
                             />
                         )}

+ 2 - 0
src/state/index.js

@@ -17,6 +17,8 @@ const store = createStore(
     applyMiddleware(sagaMiddleware, logger)
 );
 
+window.state = () => store.getState()
+
 sagaMiddleware.run(sagas);
 
 export default store;