Prechádzať zdrojové kódy

router logic rewrote: static -> using token

Maxim 5 rokov pred
rodič
commit
063a04fffd

+ 9 - 9
package-lock.json

@@ -5731,8 +5731,7 @@
         },
         "ansi-regex": {
           "version": "2.1.1",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "aproba": {
           "version": "1.2.0",
@@ -6084,8 +6083,7 @@
         },
         "safe-buffer": {
           "version": "5.1.1",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -6132,7 +6130,6 @@
         "strip-ansi": {
           "version": "3.0.1",
           "bundled": true,
-          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -6171,13 +6168,11 @@
         },
         "wrappy": {
           "version": "1.0.2",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "yallist": {
           "version": "3.0.2",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         }
       }
     },
@@ -8479,6 +8474,11 @@
         "array-includes": "^3.0.3"
       }
     },
+    "jwt-decode": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz",
+      "integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk="
+    },
     "killable": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",

+ 1 - 0
package.json

@@ -6,6 +6,7 @@
     "axios": "^0.18.0",
     "bootstrap": "^4.2.1",
     "jquery": "^3.3.1",
+    "jwt-decode": "^2.2.0",
     "node-sass": "^4.11.0",
     "popper.js": "^1.14.6",
     "react": "^16.6.3",

+ 17 - 3
src/actions/auth/logOut.js

@@ -1,3 +1,17 @@
-export const logOut = () => ({
-    
-})
+import * as actionTypes from './../../constants/auth';
+
+const logOut = () => ({
+    type: actionTypes.LOG_OUT_REQUEST
+})
+
+export const logOutSuccess = payload => ({
+    type: actionTypes.LOG_OUT_REQUEST_SUCCESS,
+    payload
+})
+
+export const logOutFailure = ({ message: error }) => ({
+    type: actionTypes.LOG_OUT_REQUEST_FAILURE,
+    error
+})
+
+export default logOut;

+ 0 - 0
src/actions/auth/saveToken.js


+ 3 - 1
src/actions/auth/signIn.js

@@ -1,6 +1,6 @@
 import * as actionTypes from './../../constants/auth';
 
-export const signIn = payload => ({
+const signIn = payload => ({
     type: actionTypes.SIGN_IN_REQUEST,
     payload
 });
@@ -12,3 +12,5 @@ export const signInFailure = error => ({
     type: actionTypes.SIGN_IN_REQUEST_FAILURE,
     error
 });
+
+export default signIn;

+ 3 - 1
src/actions/auth/signUp.js

@@ -1,6 +1,6 @@
 import * as actionTypes from './../../constants/auth';
 
-export const signUp = payload => ({
+const signUp = payload => ({
     type: actionTypes.SIGN_UP_REQUEST,
     payload
 });
@@ -12,3 +12,5 @@ export const signUpFailure = error => ({
     type: actionTypes.SIGN_UP_REQUEST_FAILURE,
     error
 });
+
+export default signUp;

+ 35 - 0
src/components/admin/CardsCreator/index.js

@@ -0,0 +1,35 @@
+import React from 'react';
+import { Field, reduxForm } from 'redux-form';
+import { connect } from 'react-redux';
+
+class CardCreator extends React.Component {
+    state = {
+        type: null
+    }
+
+    render() {
+        const { formProps } = this.props;
+        console.log('------------ PROPS --------------');
+        console.log(formProps);
+
+        return (
+            <div>
+                <h1>Create Test</h1>
+                <Field name='some' component='select'>
+                    <option value="some value 1">Optional</option>
+                    <option value="some value 2">Multioptional</option>
+                    <option value="some value 3">Open Answer</option>
+                    <option value="some value 4">Conformity</option>
+                </Field>
+            </div>
+        )
+    }
+}
+
+const mapStateToProps = state => ({
+    formProps: state.form
+})
+
+export default connect(mapStateToProps, null)(reduxForm({
+    form: "CardCreator"
+})(CardCreator))

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

@@ -1,47 +1,43 @@
 import React from 'react';
 import { Route, Redirect } from 'react-router-dom';
+import decode from 'jwt-decode';
+
 import * as routes from './../../constants/routes';
+import storageKey from './../../utils/storageKey';
 
-export default ({ component: Component, user, tokenAuth, access, ...rest }) => (
+export default ({ component: Component, tokenAuth, access, ...rest }) => (
     <Route
         {...rest}
         render={props => {
-            {/* let checkedData;
+            // test
+            try {
+                const storagedData = localStorage.getItem(storageKey);
 
-            if (data) {
-                checkedData = data;
-            }
-            else {
-                const storagedUser = JSON.parse(localStorage.getItem(token));
-                if (storagedUser) {
-                    checkedData = storagedUser;
-                    tokenAuth(storagedUser);
+                if (access === 'public') {
+                    return <Component {...props} />
                 }
-                else {
-                    checkedData = null;
+                
+                if (!storagedData || storagedData.exp < Date.now()) {
+                    throw new Error(1);
                 }
-            } */}
-            const checkedData = user && user.data ? user.data : null;
-
-            console.log('\n\nHere goes the route:', '\nUser', user, '\n\n\n')
 
-            if (access === 'public') {
-                return <Component {...props} />
-            }
+                const user = decode(storagedData);
+                const { role } = user; 
 
-            if ((access === 'user-only' || access === 'admin-only') && !checkedData) {
-                return <Redirect to={routes.SIGN_IN} />
-            }
+                if (access === 'admin-only' && !role) {
+                    throw new Error(2)                   
+                }
 
-            if (access === 'user-only' && checkedData) {
                 return <Component {...props} />
             }
-
-            if (access === 'admin-only' && checkedData.role) {
-                return <Component {...props} />
+            catch ({ message: errorCode }) {
+                if (errorCode === '1') {
+                    return <Redirect to={routes.SIGN_IN} />
+                }
+                if (errorCode === '2') {
+                    return <div>Permission denied</div>
+                }
             }
-
-            return null // permission denied
         }}
     />
 )

+ 34 - 15
src/components/public/Header.js

@@ -1,58 +1,77 @@
 import React from 'react';
 import { Link } from "react-router-dom";
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
 import * as routes from '../../constants/routes'
+import logOut from './../../actions/auth/logOut';
+
+class Header extends React.Component {
+    handleLogOut = () => {
+        const { logOut } = this.props;
+        logOut()
+        //TODO: REDIRECT
+    }
 
-export default class Header extends React.Component {
     render() {
         return (
             <nav class="navbar navbar-expand-lg navbar-dark bg-dark p-3">
-                <a class="navbar-brand" href="#">Test.io</a>
+                <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"></span>
+                    <span class="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 font-ci font-ci-bold" to={routes.HOME}>Home <span class="sr-only">(current)</span></Link>
+                            <Link onClick={this.handleClick} class="nav-link" to={routes.HOME}>Home <span class="sr-only">(current)</span></Link>
                         </li>
                         <li class="nav-item">
-                            <Link onClick={this.handleClick} class="nav-link font-ci font-ci-bold" to={routes.PROFILE}>Profile</Link>
+                            <Link onClick={this.handleClick} class="nav-link" to={routes.PROFILE}>Profile</Link>
                         </li>
                         <li class="nav-item">
-                            <Link onClick={this.handleClick} class="nav-link font-ci font-ci-bold " to={routes.TESTS}>Tests</Link>
+                            <Link onClick={this.handleClick} class="nav-link" to={routes.TESTS}>Tests</Link>
                         </li>
                         <li class="nav-item">
-                            <Link onClick={this.handleClick} class="nav-link font-ci font-ci-bold " to={routes.CATEGORIES}>Catigories</Link>
+                            <Link onClick={this.handleClick} class="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
-                                    </a>
+                                </a>
                             <div class="dropdown-menu" aria-labelledby="navbarDropdown">
-                                <a class="dropdown-item" href="#">Action</a>
-                                <a class="dropdown-item" href="#">Another action</a>
+                                <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="dropdown-item" href="#">Something else here</a>
+                                <a class="nav-link text-secondary" href="#">Something else here</a>
                             </div>
                         </li>
 
                     </ul>
-                    <form class="form-inline my-2 my-lg-0">
+                    <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" type="submit">Search</button>
+                        <button class="btn btn-outline-success my-2 my-sm-0 mr-2" type="submit">Search</button>
                     </form>
-                    <div className="pl-2">
+                    <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 ml-2">Sign Up</button>
+                            <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>
                 </div>
             </nav>
         )
     }
 }
+
+const mapStateToProps = state => ({
+    state: state.logOut
+});
+const mapDispatchToProps = dispatch => bindActionCreators({ logOut }, dispatch)
+
+export default connect(mapStateToProps, mapDispatchToProps)(Header);

+ 32 - 0
src/components/public/landing.js

@@ -0,0 +1,32 @@
+import React from 'react';
+
+const landing = () => (
+    <div id="carouselExampleIndicators" className="carousel slide" data-ride="carousel">
+        <ol className="carousel-indicators">
+            <li data-target="#carouselExampleIndicators" data-slide-to="0" className="active"></li>
+            <li data-target="#carouselExampleIndicators" data-slide-to="1"></li>
+            <li data-target="#carouselExampleIndicators" data-slide-to="2"></li>
+        </ol>
+        <div className="carousel-inner">
+            <div className="carousel-item active">
+                <img src="https://images.theconversation.com/files/17962/original/jt558trs-1353642967.jpg?ixlib=rb-1.1.0&rect=23%2C5%2C3831%2C2573&q=45&auto=format&w=926&fit=clip" className="d-block w-100 slider-image" alt="..." />
+            </div>
+            <div className="carousel-item">
+                <img src="http://dyslexiahelp.umich.edu/sites/default/files/upload/testing%20instuments%20dreamstimesmall_5908127.jpg" className="d-block w-100 slider-image" alt="..." />
+            </div>
+            <div className="carousel-item">
+                <img src="https://amp.businessinsider.com/images/55a7e74f2acae716008b7469-750-562.jpg" className="d-block w-100 slider-image" alt="..." />
+            </div>
+        </div>
+        <a className="carousel-control-prev" href="#carouselExampleIndicators" role="button" data-slide="prev">
+            <span className="carousel-control-prev-icon" aria-hidden="true"></span>
+            <span className="sr-only">Previous</span>
+        </a>
+        <a className="carousel-control-next" href="#carouselExampleIndicators" role="button" data-slide="next">
+            <span className="carousel-control-next-icon" aria-hidden="true"></span>
+            <span className="sr-only">Next</span>
+        </a>
+    </div>
+)
+
+export default landing;

src/components/public/SignIn/Form/index.js → src/components/public/signIn/Form/index.js


src/components/public/SignIn/Form/validate.js → src/components/public/signIn/Form/validate.js


+ 1 - 1
src/components/public/SignIn/index.js

@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
 import { Redirect } from 'react-router-dom'
 
 import Spinner from './../../common/spinner';
-import { signIn } from './../../../actions/auth/signIn';
+import signIn from './../../../actions/auth/signIn';
 
 const SignIn = ({ signIn, user }) => {
     const someShit = user.isFetching

src/components/public/SignUp/Form/index.js → src/components/public/signUp/Form/index.js


src/components/public/SignUp/Form/validate.js → src/components/public/signUp/Form/validate.js


+ 1 - 1
src/components/public/SignUp/index.js

@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
 import { Redirect } from 'react-router-dom';
 
 import Spinner from './../../common/spinner';
-import { signUp } from './../../../actions/auth/signUp';
+import signUp from './../../../actions/auth/signUp';
 
 const SignUp = ({ status, signUp }) => (
     status.isFetching

+ 12 - 0
src/configs/requestsConfigs/index.js

@@ -0,0 +1,12 @@
+export const defaultConfig = {
+    headers: {
+        "Content-Type": "application/json"
+    }
+}
+
+export const makeConfigWithJWT = jwt => ({
+    headers: {
+        "Content-Type": "application/json",
+        "Authorization": `Bearer ${jwt}`
+    }
+})

+ 17 - 4
src/configs/routerConfig.js

@@ -1,17 +1,20 @@
 import React, { lazy } from 'react';
+
+import Landing from './../components/public/landing';
 import * as routes from './../constants/routes';
 
+const SignIn = lazy(() => import('./../components/public/signIn'));
+const SignUp = lazy(() => import('./../components/public/signUp'));
+
 const ProfilePage = lazy(() => import('../components/user/ProfilePage'))
 
-// import SignIn from '../components/public/SignIn';
-const SignIn = lazy(() => import('./../components/public/SignIn'));
-const SignUp = lazy(() => import('./../components/public/SignUp'));
+const CardsCreator = lazy(() => import('./../components/admin/CardsCreator'))
 
 export default [
     {
         path: routes.LANDING,
         access: 'public',
-        component: () => <div>landing</div>
+        component: Landing
     },
     {
         path: routes.SIGN_IN,
@@ -43,11 +46,21 @@ export default [
         access: 'user-only',
         component: () => <div>test</div>
     },
+    {
+        path: '/admin',
+        access: 'admin-only',
+        component: () => <div>Some admin-only component</div>
+    },
     {
         path: routes.DELETE_USER,
         access: 'admin-only',
         component: () => <div>delete user</div>
     },
+    {
+        path: routes.CREATE_TEST,
+        access: 'public',
+        component: CardsCreator
+    },
     {
         access: 'public',
         component: () => <div>404</div>

+ 6 - 1
src/constants/auth.js

@@ -6,4 +6,9 @@ export const SIGN_IN_REQUEST_FAILURE = 'SIGN_IN_REQUEST_FAILURE';
 export const SIGN_UP_URL = 'https://test-app-a-level.herokuapp.com/auth/register';
 export const SIGN_UP_REQUEST = 'SIGN_UP_REQUEST';
 export const SIGN_UP_REQUEST_SUCCESS = 'SIGN_UP_REQUEST_SUCCESS';
-export const SIGN_UP_REQUEST_FAILURE = 'SIGN_UP_REQUEST_FAILURE';
+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';

+ 31 - 0
src/reducers/auth/logOut.js

@@ -0,0 +1,31 @@
+import * as actionTypes from './../../constants/auth';
+import initialState from './../initialState';
+
+export default function signInReducer(state = initialState.signIn, {type, error}) {
+
+    switch (type) {
+        case actionTypes.LOG_OUT_REQUEST: {
+            return {
+                ...state,
+                isFetching: true
+            }
+        }
+        case actionTypes.LOG_OUT_REQUEST_SUCCESS: {
+            return {
+                ...state,
+                isFetching: false,
+                isSuccessful: true
+            }
+        }
+        case actionTypes.LOG_OUT_REQUEST_FAILURE: {
+            return {
+                ...state,
+                isFetching: false,
+                error
+            }
+        }
+        default: {
+            return state;
+        }
+    }
+}

+ 5 - 0
src/reducers/initialState.js

@@ -9,5 +9,10 @@ export default {
         isFetching: false,
         isSuccessful: null,
         error: null
+    },
+    logOut: {
+        isFetching: false,
+        isSuccessful: null,
+        error: null
     }
 }

+ 5 - 3
src/sagas/auth/index.js

@@ -1,10 +1,12 @@
+import { takeEvery } from 'redux-saga/effects';
+
+import * as actionTypes from './../../constants/auth'
 import signIn from './signIn';
 import signUp from './signUp';
-import * as actionTypes from './../../constants/auth'
-
-import { takeEvery } from 'redux-saga/effects';
+import logOut from './logOut';
 
 export default function* () {
     yield takeEvery(actionTypes.SIGN_IN_REQUEST, signIn)
     yield takeEvery(actionTypes.SIGN_UP_REQUEST, signUp)
+    yield takeEvery(actionTypes.LOG_OUT_REQUEST, logOut)
 }

+ 25 - 0
src/sagas/auth/logOut.js

@@ -0,0 +1,25 @@
+import { put, call } from 'redux-saga/effects';
+import axios from 'axios';
+
+import { LOG_OUT_URL } from './../../constants/auth';
+import storageKey from './../../utils/storageKey';
+import { logOutFailure, logOutSuccess } from './../../actions/auth/logOut';
+
+const removeItem = () => {
+    localStorage.removeItem(storageKey);
+}
+
+export default function* ({ payload }) {
+    try {
+        const user = yield call(() =>
+            axios.get(LOG_OUT_URL, payload)
+                .then(({ data }) => data)
+        )
+
+        yield call(removeItem);
+        yield put(logOutSuccess(user));
+    }
+    catch ({ message }) {
+        yield put(logOutFailure(message));
+    }
+}

+ 7 - 9
src/sagas/auth/signIn.js

@@ -3,23 +3,21 @@ import axios from 'axios';
 
 import { SIGN_IN_URL } from './../../constants/auth';
 import storageKey from './../../utils/storageKey';
-import { signInSuccess, signInFailure } from './../../actions/auth/signIn'
+import { signInSuccess, signInFailure } from './../../actions/auth/signIn';
+import { defaultConfig as config } from '../../configs/requestsConfigs';
+
+const setItem = (value) => {
+    localStorage.setItem(storageKey, value);
+}
 
 export default function* ({ payload }) {
     try {
-        const config = {
-            headers: {
-                "Content-Type": "application/json"
-            }
-        }
-
         const user = yield call(() =>
             axios.post(SIGN_IN_URL, payload, config)
                 .then(({ data }) => data)
         )
 
-        //TODO: TOKEN
-        // yield call(localStorage.setItem, storageKey, user.token);
+        yield call(setItem, user.token);
         yield put(signInSuccess(user));
     }
     catch ({ message }) {

+ 2 - 0
src/styles/_index.scss

@@ -3,3 +3,5 @@
 @import 'extended/vh';
 
 @import 'base/typography';
+
+@import 'components/landing';

+ 15 - 0
src/styles/components/_landing.scss

@@ -0,0 +1,15 @@
+img.slider-image {
+    height: calc(100vh - 72px);
+    @media screen and (max-width: 1200px) {
+        // border: 10px solid red;
+        height: 500px;
+    }    
+    @media screen and (max-width: 600px) {
+        // border: 10px solid green;
+        height: 400px;
+    }
+    @media screen and (max-width: 450px) {
+        // border: 10px solid yellow;        
+        height: 300px;
+    }
+}