Bläddra i källkod

login add login logic

Artem 3 år sedan
förälder
incheckning
fcb94d016b

+ 11 - 0
package-lock.json

@@ -12,6 +12,7 @@
         "@testing-library/user-event": "^12.8.3",
         "classnames": "^2.3.1",
         "hook-easy-form": "^2.7.2",
+        "jwt-decode": "^3.1.2",
         "node-sass": "^6.0.1",
         "react": "^17.0.2",
         "react-dom": "^17.0.2",
@@ -13563,6 +13564,11 @@
         "node": ">=4.0"
       }
     },
+    "node_modules/jwt-decode": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
+      "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
+    },
     "node_modules/killable": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
@@ -33371,6 +33377,11 @@
         "object.assign": "^4.1.2"
       }
     },
+    "jwt-decode": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
+      "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
+    },
     "killable": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",

+ 1 - 0
package.json

@@ -8,6 +8,7 @@
     "@testing-library/user-event": "^12.8.3",
     "classnames": "^2.3.1",
     "hook-easy-form": "^2.7.2",
+    "jwt-decode": "^3.1.2",
     "node-sass": "^6.0.1",
     "react": "^17.0.2",
     "react-dom": "^17.0.2",

+ 11 - 11
src/components/inputs/text-input/text-input.module.scss

@@ -1,14 +1,14 @@
 $input-text-color: rgb(74, 86, 96);
 
 .container {
-  height: 3.6rem;
+  height: 36px;
   width: 100%;
   position: relative;
   display: flex;
   background: var(--base-white);
   border-radius: 4px;
   border: 1px solid var(--primary);
-  padding: 0 1.2rem;
+  padding: 0 12px;
   box-sizing: border-box;
   align-items: center;
 
@@ -17,9 +17,9 @@ $input-text-color: rgb(74, 86, 96);
     background: transparent;
     font-style: normal;
     font-weight: 500;
-    font-size: 1.4rem;
+    font-size: 14px;
     font-family: inherit;
-    line-height: 1.6rem;
+    line-height: 16px;
     border: none;
     box-shadow: none;
     width: 100%;
@@ -30,11 +30,11 @@ $input-text-color: rgb(74, 86, 96);
   &__label {
     color: var(--text-color);
     font-family: inherit;
-    font-size: 1.4rem;
+    font-size: 14px;
     font-weight: 400;
     position: absolute;
     left: 0;
-    top: -2rem;
+    top: -20px;
 
     text-transform: none;
     white-space: nowrap;
@@ -51,12 +51,12 @@ $input-text-color: rgb(74, 86, 96);
   &__error-span {
     color: var(--button-red-hover);
     font-weight: 100;
-    font-size: 1.2rem;
-    line-height: 1.2rem;
+    font-size: 12px;
+    line-height: 12px;
 
     position: absolute;
     left: 0;
-    bottom: -1.6rem;
+    bottom: -16px;
   }
 
   input:disabled,
@@ -67,9 +67,9 @@ $input-text-color: rgb(74, 86, 96);
 }
 
 .margin-bottom {
-  margin-bottom: 2rem;
+  margin-bottom: 20px;
 }
 
 .margin-top {
-  margin-top: 2rem;
+  margin-top: 20px;
 }

+ 23 - 3
src/routes/app/index.js

@@ -1,9 +1,24 @@
-import { Suspense } from 'react';
-import { Switch, Route } from 'react-router-dom';
+import { Suspense, useEffect } from 'react';
+import { useDispatch } from 'react-redux';
+// import jwtDecode from 'jwt-decode';
+import { Switch, Route, Redirect } from 'react-router-dom';
 
+import { getMe } from 'store/auth/actions';
+import { getToken } from 'utils/local-storage-actions';
 import { ROUTES } from '../index';
 
+// const decode = (token) => {
+//   return jwtDecode(token);
+// }
+
 const App = () => {
+  const dispatch = useDispatch();
+
+  useEffect(() => {
+    const token = getToken();
+    if (token) dispatch(getMe())
+  }, [dispatch])
+
   return (
     <Switch>
       <Suspense fallback={<div>Loading...</div>}>
@@ -13,7 +28,12 @@ const App = () => {
           exact={el.exact}
           path={el.path}
           // component={el.component}
-          render={(props) => <el.component {...props} /> }
+          render={(props) => {
+            const token = getToken();
+            if (!token && el.private) return <Redirect to="/auth" />
+
+            return <el.component {...props} />
+          }}
         />
       ))}
       </Suspense>

+ 7 - 0
src/routes/auth/auth.module.scss

@@ -2,8 +2,15 @@
   display: flex;
   justify-content: center;
   align-items: center;
+
+  height: 100vh;
 }
 
 .form {
   width: 300px;
+
+  display: grid; 
+  grid-template-columns: 1fr;
+
+  grid-gap: 20px;
 }

+ 24 - 15
src/routes/auth/index.jsx

@@ -1,5 +1,7 @@
+import { useEffect } from 'react';
 import hookForm from 'hook-easy-form';
-import { useDispatch } from 'react-redux';
+import { useHistory } from 'react-router-dom';
+import { useDispatch, useSelector } from 'react-redux';
 
 import { runAuth } from 'store/auth/actions';
 import Input from 'components/inputs/text-input/text-input';
@@ -32,26 +34,33 @@ const form = [
 
 const Auth = () => {
   const dispatch = useDispatch();
+  const history = useHistory();
+
+  const user = useSelector((s) => s.auth.user)
+  console.log('user', user);
+  useEffect(() => {
+    if (user) history.push('/');
+  }, [user, history]);
 
-  // const [values, setValues] = useState({ email: '', password: '' })
   const { formArray, updateEvent, valid, disabled, submitEvent } = hookForm({ initialForm: form })
 
   const submit = submitEvent((v) => dispatch(runAuth(v)));
 
-  // console.log('s', formArray);  
   return (
-    <form onSubmit={submit} className={classes.container}>
-      {formArray.map(el => (
-        <div className={classes.form} key={el.name}>
-          <Input
-            name={el.name}
-            label={el.options.label}
-            type={el.options.type}
-            onChange={updateEvent} />
-        </div>
-      ))}
-      <button type="submit" disabled={disabled || !valid}>Submit</button>
-    </form>
+    <div className={classes.container}>
+      <form onSubmit={submit} className={classes.form}>
+        {formArray.map(el => (
+          <div className={classes.form} key={el.name}>
+            <Input
+              name={el.name}
+              label={el.options.label}
+              type={el.options.type}
+              onChange={updateEvent} />
+          </div>
+        ))}
+        <button type="submit" disabled={disabled || !valid}>Submit</button>
+      </form>
+    </div>
   )
 }
 

+ 7 - 1
src/routes/main/index.js

@@ -1,5 +1,11 @@
+import { useSelector } from 'react-redux';
+
 const Main = () => {
-  return (<div>MAIN PAGE</div>)
+  const user = useSelector((s) => s.auth.user)
+  return (<div>
+    <h1>MAIN PAGE</h1>
+    <pre>{JSON.stringify(user, null, 2)}</pre>
+  </div>)
 }
 
 export default Main;

+ 15 - 0
src/store/auth/actions.js

@@ -13,4 +13,19 @@ export const runAuthSubmit = (payload) => ({
 export const runAuthFail = (payload) => ({
   type: types.RUN_AUTH_FAIL,
   payload
+})
+
+export const getMe = (payload) => ({
+  type: types.GET_ME,
+  payload
+})
+
+export const getMeSuccess = (payload) => ({
+  type: types.GET_ME_SUCCESS,
+  payload
+})
+
+export const getMeFail = (payload) => ({
+  type: types.GET_ME_FAIL,
+  payload
 })

+ 11 - 7
src/store/auth/reducer.js

@@ -1,19 +1,23 @@
 import * as types from './types';
 
 const initialState = {
-  auth: null
+  user: null,
 }
 
 export const authReducer = (state = initialState, action) => {
   switch(action.type) {
-    case types.RUN_AUTH: {
-      return { ...state }
-    }
     case types.RUN_AUTH_SUCCESS: {
-      return { ...state }
+      return { ...state, user: action.payload }
+    }
+    case types.RUN_AUTH: 
+    case types.RUN_AUTH_FAIL:
+    case types.GET_ME: 
+    case types.GET_ME_FAIL: {
+      return state;
     }
-    case types.RUN_AUTH_FAIL: {
-      return { ...state }
+
+    case types.GET_ME_SUCCESS:  {
+      return { ...state, user: action.payload }
     }
 
     default: {

+ 40 - 3
src/store/auth/saga.js

@@ -1,15 +1,52 @@
-import { call, put, takeLatest } from 'redux-saga/effects';
+import { takeLatest, call, put } from 'redux-saga/effects';
 
 import * as types from './types';
 import * as actions from './actions'
 
+import { getToken, setToLocalStorage } from 'utils/local-storage-actions';
+
+const authRequest = (cred) => fetch('http://localhost:8000/auth/login', {
+  method: 'POST',
+  headers: {
+    Authorization: `Basic ${cred}`
+  }
+}).then(r => r.json())
+
+const meRequest = (token) => fetch('http://localhost:8000/users/me', {
+  headers: {
+    Authorization: `Bearer ${token}`
+  }
+}).then(r => r.json())
+
+
+// const auth = (cred) => fetch('http://localhost:8000/auth/register')
+
 function* runAuth({ payload }) {
   const credentials = yield window.btoa(`${payload.email}:${payload.password}`)
 
-  console.log('credentials', credentials);
+  try {
+    const result = yield call(authRequest, credentials);
+
+    yield setToLocalStorage(result.token)
+
+    yield put(actions.runAuthSubmit(result.user))
+  } catch (error) {
+    yield put(actions.runAuthFail(error))
+  }
+}
+
+function* getMe({ payload }) {
+  try {
+    const token = getToken();
+    const result = yield call(meRequest, token);
+
+    yield put(actions.getMeSuccess(result))
+  } catch (error) {
+    yield put(actions.getMeFail(error))
+  }
 }
 
 export default function* auth() {
-  // yield takeEvery(types.GET_FAKE_DATA, getFilms);
   yield takeLatest(types.RUN_AUTH, runAuth);
+  yield takeLatest(types.GET_ME, getMe);
 }

+ 5 - 1
src/store/auth/types.js

@@ -1,3 +1,7 @@
 export const RUN_AUTH = 'RUN_AUTH';
 export const RUN_AUTH_SUCCESS = 'RUN_AUTH_SUCCESS';
-export const RUN_AUTH_FAIL = 'RUN_AUTH_FAIL';
+export const RUN_AUTH_FAIL = 'RUN_AUTH_FAIL';
+
+export const GET_ME = 'GET_ME';
+export const GET_ME_SUCCESS = 'GET_ME_SUCCESS';
+export const GET_ME_FAIL = 'GET_ME_FAIL';

+ 9 - 0
src/utils/local-storage-actions.js

@@ -0,0 +1,9 @@
+const TOKEN = 'token';
+
+export const getToken = () => {
+  return window.localStorage.getItem(TOKEN);
+}
+
+export const setToLocalStorage = (token) => {
+  window.localStorage.setItem(TOKEN, token)
+}