4 Komitmen dc79b4e3e8 ... f952c35f77

Pembuat SHA1 Pesan Tanggal
  Maxim f952c35f77 user storaging via redux is done -> router works correctly 5 tahun lalu
  Svetlana 939956eb6e marge 5 tahun lalu
  Svetlana e26c783cb0 Merge remote-tracking branch 'origin/new_Sveta' into new_dev 5 tahun lalu
  Maxim ba885f86b9 added reworked sign in 5 tahun lalu

+ 16 - 21
package-lock.json

@@ -1803,6 +1803,15 @@
       "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
       "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
     },
+    "axios": {
+      "version": "0.18.0",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz",
+      "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=",
+      "requires": {
+        "follow-redirects": "^1.3.0",
+        "is-buffer": "^1.1.5"
+      }
+    },
     "axobject-query": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz",
@@ -5741,13 +5750,11 @@
         },
         "balanced-match": {
           "version": "1.0.0",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
-          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -5760,18 +5767,15 @@
         },
         "code-point-at": {
           "version": "1.1.0",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "concat-map": {
           "version": "0.0.1",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "console-control-strings": {
           "version": "1.1.0",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -5874,8 +5878,7 @@
         },
         "inherits": {
           "version": "2.0.3",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "ini": {
           "version": "1.3.5",
@@ -5885,7 +5888,6 @@
         "is-fullwidth-code-point": {
           "version": "1.0.0",
           "bundled": true,
-          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -5898,20 +5900,17 @@
         "minimatch": {
           "version": "3.0.4",
           "bundled": true,
-          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
         },
         "minimist": {
           "version": "0.0.8",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "minipass": {
           "version": "2.2.4",
           "bundled": true,
-          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.1",
             "yallist": "^3.0.0"
@@ -5928,7 +5927,6 @@
         "mkdirp": {
           "version": "0.5.1",
           "bundled": true,
-          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -6001,8 +5999,7 @@
         },
         "number-is-nan": {
           "version": "1.0.1",
-          "bundled": true,
-          "optional": true
+          "bundled": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -6012,7 +6009,6 @@
         "once": {
           "version": "1.4.0",
           "bundled": true,
-          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -6119,7 +6115,6 @@
         "string-width": {
           "version": "1.0.2",
           "bundled": true,
-          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",

+ 1 - 0
package.json

@@ -3,6 +3,7 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "axios": "^0.18.0",
     "bootstrap": "^4.2.1",
     "jquery": "^3.3.1",
     "node-sass": "^4.11.0",

+ 17 - 0
src/actions/auth/signIn.js

@@ -0,0 +1,17 @@
+import * as actionTypes from './../../constants/auth';
+
+export const signIn = payload => {
+    console.log('Inside the signIn action', payload);
+    return {
+        type: actionTypes.SIGN_IN_REQUEST,
+        payload
+    };
+}
+export const signInSuccess = payload => ({
+    type: actionTypes.SIGN_IN_REQUEST_SUCCESS,
+    payload
+});
+export const signInFailure = error => ({
+    type: actionTypes.SIGN_IN_REQUEST_FAILURE,
+    error
+});

+ 14 - 0
src/actions/auth/signUp.js

@@ -0,0 +1,14 @@
+import * as actionTypes from './../../constants/auth';
+
+export const signUp = payload => ({
+    type: actionTypes.SIGN_UP_REQUEST,
+    payload
+});
+export const signUpSuccess = payload => ({
+    type: actionTypes.SIGN_UP_REQUEST_SUCCESS,
+    payload
+});
+export const signUpFailure = error => ({
+    type: actionTypes.SIGN_UP_REQUEST_FAILURE,
+    error
+});

+ 0 - 0
src/actions/user/changelogin.js


+ 1 - 1
src/components/common/formInput.js

@@ -3,6 +3,6 @@ import React, { Fragment } from 'react';
 export default ({ input, className, placeholder, type, meta: { touched, error } }) => (
     <Fragment>
         <input className={className} placeholder={placeholder} type={type} {...input} />
-        {touched && (error && <p style={{"color": "red", "textTransform": "uppercase"}}>⚠ {error}</p>)}
+        {touched && (error && <p className="text-af pt-2 text-uppercase">⚠ {error}</p>)}
     </Fragment>
 )

+ 3 - 2
src/components/common/protectedRoute.js

@@ -2,7 +2,7 @@ import React from 'react';
 import { Route, Redirect } from 'react-router-dom';
 import * as routes from './../../constants/routes';
 
-export default ({ component: Component, user: { data : checkedData }, tokenAuth, access, ...rest }) => (
+export default ({ component: Component, user, tokenAuth, access, ...rest }) => (
     <Route
         {...rest}
         render={props => {
@@ -21,10 +21,11 @@ export default ({ component: Component, user: { data : checkedData }, tokenAuth,
                     checkedData = null;
                 }
             } */}
+            const checkedData = user && user.data ? user.data : null;
 
             console.log('Component', Component);
             console.log('Access', access);
-            console.log('CheckedData', checkedData);
+            console.log('User Data', user);
 
             if (access === 'public') {
                 return <Component {...props} />

+ 6 - 3
src/components/common/spinner.js

@@ -1,6 +1,9 @@
 import React from 'react';
 
-export default props => (
-    // add props optionally
-    null
+export default () => (
+    <div className="container d-flex align-items-center justify-content-center">
+        <div class="spinner-border" role="status">
+            <span class="sr-only">Loading...</span>
+        </div>
+    </div>
 )

+ 45 - 41
src/components/public/Header.js

@@ -5,50 +5,54 @@ import * as routes from '../../constants/routes'
 export default class Header extends React.Component {
     render() {
         return (
-            <div className="row">
-                <div className="container-fluid">
-                    <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-2">
-                        <a class="navbar-brand" href="#">Test.io</a>
-                        <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>
-                        </button>
+            <nav class="navbar navbar-expand-lg navbar-dark bg-dark p-3">
+                <a class="navbar-brand" href="#">Test.io</a>
+                <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>
+                </button>
 
-                        <div class="collapse navbar-collapse" id="navbarSupportedContent">
-                            <ul class="navbar-nav mr-auto p-3">
-                                <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>
-                                </li>
-                                <li class="nav-item">
-                                <Link onClick={this.handleClick}  class="nav-link font-ci font-ci-bold"  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>
-                                </li>
-                                <li class="nav-item">
-                                <Link onClick={this.handleClick}  class="nav-link font-ci font-ci-bold "  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
+                <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>
+                        </li>
+                        <li class="nav-item">
+                            <Link onClick={this.handleClick} class="nav-link font-ci font-ci-bold" 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>
+                        </li>
+                        <li class="nav-item">
+                            <Link onClick={this.handleClick} class="nav-link font-ci font-ci-bold " 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>
-                                    <div class="dropdown-menu" aria-labelledby="navbarDropdown">
-                                        <a class="dropdown-item" href="#">Action</a>
-                                        <a class="dropdown-item" href="#">Another action</a>
-                                        <div class="dropdown-divider"></div>
-                                        <a class="dropdown-item" href="#">Something else here</a>
-                                    </div>
-                                </li>
-                               
-                            </ul>
-                            <form class="form-inline my-2 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>
-                            </form>
-                        </div>
-                    </nav>
+                            <div class="dropdown-menu" aria-labelledby="navbarDropdown">
+                                <a class="dropdown-item" href="#">Action</a>
+                                <a class="dropdown-item" href="#">Another action</a>
+                                <div class="dropdown-divider"></div>
+                                <a class="dropdown-item" href="#">Something else here</a>
+                            </div>
+                        </li>
+
+                    </ul>
+                    <form class="form-inline my-2 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>
+                    </form>
+                    <div className="pl-2">
+                        <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>
+                        </Link>
+                    </div>
                 </div>
-            </div>
+            </nav>
         )
     }
 }

+ 40 - 24
src/components/public/SignIn/Form/index.js

@@ -1,43 +1,59 @@
 import React from 'react';
 import { reduxForm, Field } from 'redux-form';
-import { connect } from 'react-redux'
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
 import validate from './validate';
 import formInput from './../../../common/formInput';
 
 class Form extends React.Component {
 
-    submit(values) {
-        const { } = this.props;
-        console.log('Submit', values);
-        // TODO: request
+    submit = ({ email, password }) => {
+        const { signIn } = this.props;
+        signIn({
+            email,
+            password
+        })
     }
 
     render() {
-        const { handleSubmit } = this.props;
+        const { handleSubmit, error } = this.props;
+        console.log('Auth error', error);
 
         return (
-            <form className="text-center">
-                <div className="form-group">
-                    <label>Email address</label>
-                    <Field type="email" name='email' className="form-control" placeholder="name@example.com" component={formInput} />
-                </div>
-
-                <div className="form-group">
-                    <label>Password</label>
-                    <Field type="password" name='password' className="form-control" placeholder="example123" component={formInput} />
+            <div className="container">
+                <div className="row">
+                    <div className="col-md-8 col-10 col-lg-6 m-auto p-4 border rounded">
+
+                        <form className="text-center" onSubmit={handleSubmit(this.submit)}>
+                            {
+                                error
+                                    ?
+                                    <p className="text-af text-uppercase">{error}</p>
+                                    :
+                                    null
+                            }
+
+                            <div className="form-group">
+                                <label className="text-uppercase">Email address</label>
+                                <Field type="email" name='email' className="form-control" placeholder="name@example.com" component={formInput} />
+                            </div>
+
+                            <div className="form-group">
+                                <label className="text-uppercase">Password</label>
+                                <Field type="password" name='password' className="form-control" placeholder="example123" component={formInput} />
+                            </div>
+
+                            <button type="submit" className="btn btn-primary">Sign In!</button>
+                        </form>
+                    </div>
                 </div>
-
-                <button type="submit" className="btn btn-primary">Sign In!</button>
-            </form>
+            </div>
         )
     }
 }
 
-const mapStateToProps = state => ({
-    user: state.user
-})
-
-export default connect(mapStateToProps, null)(reduxForm({
+export default reduxForm({
     form: "SignIn",
     validate
-})(Form))
+})(Form)

+ 27 - 9
src/components/public/SignIn/index.js

@@ -1,14 +1,32 @@
 import React from 'react';
 import Form from './Form';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import { Redirect } from 'react-router-dom'
 
-const SignIn = props => (
-    <div className="row">
-        <div className="container">
-            <div className="col-4 m-auto p-4 border rounded">
-                <Form />
-            </div>
-        </div>
-    </div>
+import Spinner from './../../common/spinner';
+import { signIn } from './../../../actions/auth/signIn';
+
+const SignIn = ({ signIn, user }) => (
+    user.isFetching
+        ?
+        <Spinner />
+        : user.data
+            ?
+            <Redirect to='/' />
+            : user.error
+                ?
+                <Form error={user.error} signIn={signIn} />
+                :
+                <Form signIn={signIn} />
 )
 
-export default SignIn;
+const mapStateToProps = state => ({
+    user: state.signIn
+});
+const mapDispatchToProps = dispatch => bindActionCreators({
+    signIn
+}, dispatch);
+
+
+export default connect(mapStateToProps, mapDispatchToProps)(SignIn);

+ 2 - 2
src/constants/auth.js

@@ -1,9 +1,9 @@
-export const SIGN_IN_URL = 'https://quiz.maxcrc.de/api/v1/user?';
+export const SIGN_IN_URL = 'https://test-app-a-level.herokuapp.com/auth/login'; 
 export const SIGN_IN_REQUEST = 'SIGN_IN_REQUEST';
 export const SIGN_IN_REQUEST_SUCCESS = 'SIGN_IN_REQUEST_SUCCESS';
 export const SIGN_IN_REQUEST_FAILURE = 'SIGN_IN_REQUEST_FAILURE';
 
-export const SIGN_UP_URL = 'https://quiz.maxcrc.de/api/v1/user';
+export const SIGN_UP_URL = '';
 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';

+ 0 - 2
src/index.js

@@ -7,8 +7,6 @@ import Router from './router'
 import reduxStore from './state'
 
 import 'bootstrap/dist/css/bootstrap.min.css';
-import $ from 'jquery';
-import Popper from 'popper.js';
 import 'bootstrap/dist/js/bootstrap.bundle.min';
 
 import './styles/_index.scss';

+ 24 - 6
src/reducers/auth/signIn.js

@@ -1,13 +1,31 @@
-// import * as types from '';
-import intialState from './../initialState'
+import * as actionTypes from './../../constants/auth';
 import initialState from './../initialState';
 
-export default function signInReducer(state = initialState.signIn, {type, payload, error}) {
-    switch(type) {
-        case 1: {
+export default function signInReducer(state = initialState.signIn, { payload, token, type }) {
+    switch (type) {
+        case actionTypes.SIGN_IN_REQUEST: {
             return {
                 ...state,
-                test: 'test'
+                isFetching: true
+            }
+        }
+        case actionTypes.SIGN_IN_REQUEST_SUCCESS: {
+            const { user, token } = payload;
+
+            return {
+                ...state,
+                isFetching: false,
+                data: user,
+                token
+            }
+        }
+        case actionTypes.SIGN_IN_REQUEST_FAILURE: {
+            const { error } = payload;
+
+            return {
+                ...state,
+                isFetching: false,
+                error
             }
         }
         default: {

+ 30 - 0
src/reducers/auth/signUp.js

@@ -0,0 +1,30 @@
+import * as actionTypes from './../../constants/auth';
+import initialState from './../initialState';
+
+export default function signInReducer(state = initialState.signUp, {type, payload, error}) {
+    switch(type) {
+        case actionTypes.SIGN_IN_REQUEST: {
+            return {
+                ...state,
+                isFetching: true
+            }
+        }
+        case actionTypes.SIGN_IN_REQUEST_SUCCESS: {
+            return {
+                ...state,
+                isFetching: false,
+                payload
+            }
+        }
+        case actionTypes.SIGN_IN_REQUEST_FAILURE: {
+            return {
+                ...state,
+                isFetching: false,
+                error
+            }
+        }
+        default: {
+            return state;
+        }
+    }
+}

+ 11 - 1
src/reducers/initialState.js

@@ -1,3 +1,13 @@
 export default {
-    signIn: {}
+    signIn: {
+        isFetching: false,
+        data: null,
+        error: null,
+        token: null
+    },
+    signUp: {
+        isFetching: false,
+        data: null,
+        error: null
+    }
 }

+ 9 - 18
src/router.js

@@ -2,38 +2,31 @@ import React, { Suspense, lazy } from "react";
 import { Switch, Route, withRouter } from "react-router-dom";
 import { connect } from 'react-redux';
 
-// import Header from "./containers/header";
-// import Footer from './components/public-components/footer';
-
 import ProtectedRoute from './components/common/protectedRoute';
 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;
+        console.log(user);
 
-        // TODO: add spinner, user
+        // TODO: add footer
         return (
             <div className="app">
                 <Header />
-                <Suspense fallback={
-                    <div class="text-center">
-                        <div class="spinner-border" role="status">
-                            <span class="sr-only">Loading...</span>
-                        </div>
-                    </div>
-                }>
+                <Suspense fallback={<Spinner />}>
                     <Switch>
                         {config.map(route =>
                             <ProtectedRoute
                                 path={route.path}
                                 component={route.component}
                                 access={route.access}
-                                user={{data: {role: 1}}}
+                                user={user}
                                 key={route}
                                 exact
                             />
@@ -45,11 +38,9 @@ class Router extends React.Component {
     }
 }
 
-// const mapStateToProps = state => ({
-//     user: state.user
-// })
+const mapStateToProps = state => ({
+    user: state.signIn
+})
 // const mapDispatchToProps = dispatch => bindActionCreators({ tokenAuth }, dispatch);
 
-// export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Router));
-
-export default Router;
+export default withRouter(connect(mapStateToProps, null)(Router));

+ 2 - 2
src/sagas/auth/index.js

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

+ 25 - 2
src/sagas/auth/signIn.js

@@ -1,3 +1,26 @@
-export default function* () {
-    
+import { put, call } from 'redux-saga/effects';
+import axios from 'axios';
+
+import { SIGN_IN_URL } from './../../constants/auth';
+import { signInSuccess, signInFailure } from './../../actions/auth/signIn'
+
+export default function* ({payload}) {
+    console.log('User inside the worker-saga', payload);
+    try {
+        const config = {
+            headers: {
+                "Content-Type": "application/json"
+            }
+        }
+
+        const user = yield call(() =>
+            axios.post(SIGN_IN_URL, payload, config)
+                .then(({ data }) => data)
+        )
+
+        yield put(signInSuccess(user));
+    }
+    catch ({ message }) {
+        yield put(signInFailure(message));
+    }
 }

+ 1 - 3
src/sagas/index.js

@@ -2,7 +2,5 @@ import { fork } from "redux-saga/effects";
 import auth from './auth'
 
 export default function*() {
-    yield [
-        fork(auth)
-    ]
+    yield fork(auth)
 }

+ 3 - 2
src/state/index.js

@@ -16,6 +16,7 @@ const store = createStore(
     initialState,
     applyMiddleware(sagaMiddleware, logger)
 );
-export default store;
 
-sagaMiddleware.run(sagas);
+sagaMiddleware.run(sagas);
+
+export default store;

+ 2 - 1
src/styles/_index.scss

@@ -1,3 +1,4 @@
 @import 'abstracts/variables';
+@import 'abstracts/theme';
+
 @import 'base/typography';
-@import 'base/theme';

+ 2 - 2
src/styles/base/_theme.scss

@@ -8,7 +8,7 @@
     &-mist {
         background: $color-mist !important;
     }
-    &-autumn-foliage {
+    &-af {
         background: $color-autumn-foliage !important;
     }
 }
@@ -22,7 +22,7 @@
     &-mist {
         color: $color-mist !important;
     }
-    &-autumn-foliage {
+    &-af {
         color: $color-autumn-foliage !important;
     }
 }

+ 0 - 2
src/styles/base/_typography.scss

@@ -1,7 +1,5 @@
 @import url('https://fonts.googleapis.com/css?family=Cormorant+Infant:400,700|Open+Sans+Condensed:300,700|Poiret+One');
 
-
-
 .font {
     &-ci {
         font-family: 'Cormorant Infant', serif !important;