Explorar el Código

saga implementation to hw18 attempt

miskson hace 3 años
padre
commit
8485a7e0b2
Se han modificado 3 ficheros con 1228 adiciones y 852 borrados
  1. 1080 722
      hw18-react-store/package-lock.json
  2. 1 0
      hw18-react-store/package.json
  3. 147 130
      hw18-react-store/src/App.js

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1080 - 722
hw18-react-store/package-lock.json


+ 1 - 0
hw18-react-store/package.json

@@ -13,6 +13,7 @@
     "react-router-dom": "^5.3.0",
     "react-scripts": "5.0.0",
     "redux": "^4.1.2",
+    "redux-saga": "^1.1.3",
     "redux-thunk": "^2.4.1",
     "web-vitals": "^2.1.2"
   },

+ 147 - 130
hw18-react-store/src/App.js

@@ -1,49 +1,40 @@
 import { useEffect, useState } from 'react';
 import logo from './logo.svg';
 import './App.scss';
-import thunk from 'redux-thunk';
-import {createStore, combineReducers, applyMiddleware} from 'redux';
-import {Provider, connect} from 'react-redux';
+//import thunk from 'redux-thunk';
+import { createStore, combineReducers, applyMiddleware } from 'redux';
+import { Provider, connect } from 'react-redux';
 import { Link, Route, Router, Switch, Redirect } from 'react-router-dom';
 import createHistory from 'history/createBrowserHistory'
+import createSagaMiddleware from 'redux-saga'
+import { all, put, takeEvery } from 'redux-saga/effects'
 
-const actionPending  = name => ({type: 'PROMISE', status: 'PENDING', name})
-const actionResolved = (name, payload) => ({type: 'PROMISE', status: 'RESOLVED', name, payload})
-const actionRejected = (name, error) => ({type: 'PROMISE', status: 'REJECTED', name,  error})
+const actionPending = name => ({ type: 'PROMISE', status: 'PENDING', name })
+const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED', name, payload })
+const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error })
 
-const actionPromise = (name, promise) =>
-    async dispatch => {
-        dispatch(actionPending(name)) // 1. {delay1000: {status: 'PENDING'}}
-        try{
-            let payload = await promise
-            dispatch(actionResolved(name, payload))
-            return payload
-        }
-        catch(error){
-            dispatch(actionRejected(name, error))
-        }
-    }
+const actionPromise = (name, promise) => ({type: 'PROMISE_START', name, promise})
 
 const getGQL = url =>
-   (query, variables = {}) =>
-      fetch(url, {
-         method: 'POST',
-         headers: {
-            "Content-Type": "application/json",
-            ...(localStorage.authToken ? { "Authorization": "Bearer " + localStorage.authToken } :
-               {})
-         },
-         body: JSON.stringify({ query, variables })
-      })
-         .then(res => res.json())
-         .then(data => {
-            if (data.errors && !data.data)
-               throw new Error(JSON.stringify(data.errors))
-            return data.data[Object.keys(data.data)[0]]
-         })
-
-const backendURL = "http://shop-roles.asmer.fs.a-level.com.ua" 
-const gql        = getGQL(backendURL + '/graphql')
+    (query, variables = {}) =>
+        fetch(url, {
+            method: 'POST',
+            headers: {
+                "Content-Type": "application/json",
+                ...(localStorage.authToken ? { "Authorization": "Bearer " + localStorage.authToken } :
+                    {})
+            },
+            body: JSON.stringify({ query, variables })
+        })
+            .then(res => res.json())
+            .then(data => {
+                if (data.errors && !data.data)
+                    throw new Error(JSON.stringify(data.errors))
+                return data.data[Object.keys(data.data)[0]]
+            })
+
+const backendURL = "http://shop-roles.asmer.fs.a-level.com.ua"
+const gql = getGQL(backendURL + '/graphql')
 
 function jwtDecode(token) {
     try {
@@ -71,29 +62,29 @@ const actionCartRemove = (good, count = 1) => ({ type: 'CART_REMOVE', good, coun
 const actionCartChange = (good, count = 1) => ({ type: 'CART_CHANGE', good, count })
 const actionCartClear = (good, count = 1) => ({ type: 'CART_CLEAR', good, count })
 
-function cartReducer(state = {}, { type, good={}, count=1}){
-    const {_id } = good
+function cartReducer(state = {}, { type, good = {}, count = 1 }) {
+    const { _id } = good
     const types = {
         CART_ADD() {
             return {
                 ...state,
-                [_id]: { good, count: count + (state[_id]?.count || 0)}
+                [_id]: { good, count: count + (state[_id]?.count || 0) }
             }
         },
-        CART_REMOVE(){ 
+        CART_REMOVE() {
             let newState = { ...state }
             delete newState[_id]
             return {
                 ...newState
-            }            
+            }
         },
-        CART_CHANGE(){
+        CART_CHANGE() {
             return {
-                ...state, 
-                    [_id]:{good, count}
+                ...state,
+                [_id]: { good, count }
             }
         },
-        CART_CLEAR(){
+        CART_CLEAR() {
             return {}
         },
     }
@@ -105,7 +96,7 @@ function cartReducer(state = {}, { type, good={}, count=1}){
     return state
 }
 
-function authReducer(state, {type, token}){
+function authReducer(state, { type, token }) {
     if (!state) {
         if (localStorage.authToken) {
             type = 'AUTH_LOGIN'
@@ -129,11 +120,11 @@ function authReducer(state, {type, token}){
     return state
 }
 
-function promiseReducer(state={}, {type, name, status, payload, error}){
-    if (type === 'PROMISE'){
+function promiseReducer(state = {}, { type, name, status, payload, error }) {
+    if (type === 'PROMISE') {
         return {
             ...state,
-            [name]:{status, payload, error}
+            [name]: { status, payload, error }
         }
     }
     return state
@@ -206,13 +197,39 @@ const actionGoodById = (_id) =>
         }`, { good: JSON.stringify([{ _id }]) }))
 
 
-const store = createStore(combineReducers({promise: promiseReducer, 
-                                            auth: authReducer, 
-                                            cart: cartReducer}), applyMiddleware(thunk)) 
+
+const sagaMiddleWare = createSagaMiddleware()
+const store = createStore(combineReducers({ promise: promiseReducer, auth: authReducer, cart: cartReducer }), applyMiddleware(sagaMiddleWare)) //вторым параметром идет миддлварь
 store.subscribe(() => console.log(store.getState()))
+sagaMiddleWare.run(rootSaga)
 store.dispatch(actionRootCats())
 
 
+function* promiseWorker(action) {
+    const { type, name, promise } = action
+    console.log('promiseWorker')
+    yield put(actionPending(name))
+    try {
+        let payload = yield promise
+        yield put(actionResolved(name, payload))
+        return payload
+    }
+    catch (error) {
+        yield put(actionRejected(name, error))
+    }
+}
+
+function* promiseWatcher() {
+    console.log('promiseWatcher')
+    yield takeEvery('PROMISE_START', promiseWorker)
+}
+
+function* rootSaga() {
+    yield all([
+        promiseWatcher()
+    ])
+}
+
 const Logo = () =>
     <Link to="/">
         <img src={logo} className="Logo" alt="logo" />
@@ -225,85 +242,85 @@ const Header = () =>
 
 const Navbar = () =>
     <nav className='Navbar'>
-        <CKoshik/>
+        <CKoshik />
         <LogBtn />
         <RegBtn />
     </nav>
 //--------------------------------------------------------------------------------------------------------------------------------
-const CategoryListItem = ({_id, name}) =>
+const CategoryListItem = ({ _id, name }) =>
     <Link to={`/category/:${_id}`}>
-        <li className='CatLink' style={{color:'dodgerblue'}}>{name}</li>
+        <li className='CatLink' style={{ color: 'dodgerblue' }}>{name}</li>
     </Link>
 
-const CategoryList = ({cats}) =>
-    <ul>{cats.map((item) => <CategoryListItem {...item}/>)}</ul>
+const CategoryList = ({ cats }) =>
+    <ul>{cats.map((item) => <CategoryListItem {...item} />)}</ul>
 
-const CCategoryList = connect(state => ({cats:state.promise.rootCats?.payload || []}))(CategoryList)
+const CCategoryList = connect(state => ({ cats: state.promise.rootCats?.payload || [] }))(CategoryList)
 
 const Aside = () =>
     <aside><CCategoryList /></aside>
 //--------------------------------------------------------------------------------------------------------------------------------
-const GoodCard = ({good:{_id, name, price, images}, onAdd}) =>
-    <li className='GoodCard'> 
+const GoodCard = ({ good: { _id, name, price, images }, onAdd }) =>
+    <li className='GoodCard'>
         <h2>{name}</h2>
         <Link to={`/good/:${_id}`}>На страницу товара</Link>
-        <br/>
+        <br />
         {images && images[0] && images[0].url && <img className='GoodImg' alt='img' src={backendURL + '/' + images[0].url} />}
-        <br/>
+        <br />
         <strong>Цена: {price}</strong>
-        <br/>
-        <button onClick={() => onAdd({_id, name, price, images})}>Добавить в корзину</button>
+        <br />
+        <button onClick={() => onAdd({ _id, name, price, images })}>Добавить в корзину</button>
     </li>
 
-const CGoodCard = connect(null, {onAdd: actionCartAdd})(GoodCard)
+const CGoodCard = connect(null, { onAdd: actionCartAdd })(GoodCard)
 //--------------------------------------------------------------------------------------------------------------------------------
-const GoodPg = ({good:{_id, name, price, images, description}, onAdd}) =>
-    <div className='GoodCard'> 
+const GoodPg = ({ good: { _id, name, price, images, description }, onAdd }) =>
+    <div className='GoodCard'>
         <h2>{name}</h2>
-        <br/>
+        <br />
         {images && images[0] && images[0].url && <img className='GoodImg' alt='img' src={backendURL + '/' + images[0].url} />}
-        <br/>
+        <br />
         <strong>Цена: {price}</strong>
-        <br/>
+        <br />
         <p>{description}</p>
         <br />
-        <button onClick={() => onAdd({_id, name, price, images})}>Добавить в корзину</button>
+        <button onClick={() => onAdd({ _id, name, price, images })}>Добавить в корзину</button>
     </div>
 
-const CGoodPg = connect(state => ({good:state.promise.goodById?.payload || {}}),{onAdd: actionCartAdd})(GoodPg)
+const CGoodPg = connect(state => ({ good: state.promise.goodById?.payload || {} }), { onAdd: actionCartAdd })(GoodPg)
 
-const PageGood = ({match: {params: {_id}}, getData}) => {
+const PageGood = ({ match: { params: { _id } }, getData }) => {
     useEffect(() => {
         getData(_id.substring(1))
-        console.log('get', _id,typeof _id)
-    },[_id])
+        console.log('get', _id, typeof _id)
+    }, [_id])
     return (
         <CGoodPg />
     )
 
 }
 
-const CPageGood = connect(null, {getData: actionGoodById})(PageGood)
+const CPageGood = connect(null, { getData: actionGoodById })(PageGood)
 //--------------------------------------------------------------------------------------------------------------------------------
 const LogBtn = () =>
-    <Link to="/login" style={{color:'white', textDecoration:'none'}}>
+    <Link to="/login" style={{ color: 'white', textDecoration: 'none' }}>
         <div className='KoshikCnt item'>Войти / Выйти</div>
     </Link>
 
 const RegBtn = () =>
-    <Link to="/registration" style={{color:'white', textDecoration:'none'}}>
+    <Link to="/registration" style={{ color: 'white', textDecoration: 'none' }}>
         <div className='KoshikCnt item'>Зарегистрироваться</div>
     </Link>
 
 
-const Koshik = ({cart}) => {
+const Koshik = ({ cart }) => {
     let goodsInCart = cart
     let allGoodsInCart = 0
     for (let key in goodsInCart) {
         allGoodsInCart += goodsInCart[key].count
     }
     return (
-        <Link to="/cart" style={{color:'white', textDecoration:'none'}}>
+        <Link to="/cart" style={{ color: 'white', textDecoration: 'none' }}>
             <div className='KoshikCnt item'>
                 Корзина: {allGoodsInCart}
             </div>
@@ -311,9 +328,9 @@ const Koshik = ({cart}) => {
     )
 }
 
-const CKoshik = connect(({cart}) => ({cart}))(Koshik)
+const CKoshik = connect(({ cart }) => ({ cart }))(Koshik)
 //--------------------------------------------------------------------------------------------------------------------------------
-const Category = ({cat:{name, goods=[]}={}}) => 
+const Category = ({ cat: { name, goods = [] } = {} }) =>
     <div className='Category'>
         <h1>{name}</h1>
         <ul>
@@ -321,94 +338,94 @@ const Category = ({cat:{name, goods=[]}={}}) =>
         </ul>
     </div>
 
-const CCategory = connect(state => ({cat:state.promise.catById?.payload || {}}))(Category)
+const CCategory = connect(state => ({ cat: state.promise.catById?.payload || {} }))(Category)
 
-const PageCategory = ({match: {params: {_id}}, getData}) => {
+const PageCategory = ({ match: { params: { _id } }, getData }) => {
     useEffect(() => {
         getData(_id.substring(1))
-        console.log('get', _id,typeof _id)
-    },[_id])
-    return ( <CCategory /> )
+        console.log('get', _id, typeof _id)
+    }, [_id])
+    return (<CCategory />)
 }
 
-const CPageCategory = connect(null, {getData: actionCatById})(PageCategory)
+const CPageCategory = connect(null, { getData: actionCatById })(PageCategory)
 //--------------------------------------------------------------------------------------------------------------------------------
-const CartItem = ({cart:{_id, name, price, images}, count: {count}, onChange, onRemove}) => {
+const CartItem = ({ cart: { _id, name, price, images }, count: { count }, onChange, onRemove }) => {
     console.log('good', _id)
-    return(
-        <li className='GoodCard'> 
+    return (
+        <li className='GoodCard'>
             <h2>{name}</h2>
             <Link to={`/good/:${_id}`}>На страницу товара</Link>
-            <br/>
+            <br />
             {images && images[0] && images[0].url && <img className='GoodImg' alt='img' src={backendURL + '/' + images[0].url} />}
-            <br/>
+            <br />
             <strong>Цена: {price * count}</strong>
-            <br/>
-            <label>Кол-во покупки: <input type="number" value={count} min="1" onInput={(e) => onChange({_id, name, price, images}, e.target.value)}/></label>
-            <br/>
-            <button disabled={!localStorage.authToken}>{localStorage.authToken? 'Заказать' : 'Авторизуйтесть чтобы заказать'}</button>
-            <button onClick={() => onRemove({_id, name, price, images})}>Удалить заказ[X]</button>
+            <br />
+            <label>Кол-во покупки: <input type="number" value={count} min="1" onInput={(e) => onChange({ _id, name, price, images }, e.target.value)} /></label>
+            <br />
+            <button disabled={!localStorage.authToken}>{localStorage.authToken ? 'Заказать' : 'Авторизуйтесть чтобы заказать'}</button>
+            <button onClick={() => onRemove({ _id, name, price, images })}>Удалить заказ[X]</button>
         </li>
     )
 }
 
-const CCartItem = connect(null, {onChange: actionCartChange, onRemove: actionCartRemove})(CartItem)
+const CCartItem = connect(null, { onChange: actionCartChange, onRemove: actionCartRemove })(CartItem)
 
-const Cart = ({cart}) => {
+const Cart = ({ cart }) => {
     let cartArr = []
-    for(let item in cart) {
+    for (let item in cart) {
         cartArr.push(cart[item])
     }
-    return(
+    return (
         <div>
-            <h1 style={{marginLeft:'30px'}}>Корзина</h1>
+            <h1 style={{ marginLeft: '30px' }}>Корзина</h1>
             <ul>{cartArr.map(item => <CCartItem cart={item.good} count={item} />)}</ul>
         </div>
     )
 }
 
-const CCart = connect(state => ({cart:state.cart}))(Cart)
+const CCart = connect(state => ({ cart: state.cart }))(Cart)
 //--------------------------------------------------------------------------------------------------------------------------------
-const LoginForm = ({log:{sub}, onLogin, onLogout}) => {
+const LoginForm = ({ log: { sub }, onLogin, onLogout }) => {
     let [pass, setPass] = useState()
     let [login, setLogin] = useState()
     return (
-        <div className='form' style={{margin: '0 auto'}}>
+        <div className='form' style={{ margin: '0 auto' }}>
             <h3>Войти</h3>
-            <input placeholder='Логин' style={{outlineColor: login? 'black' : 'firebrick'}} onChange={(e) => setLogin(e.target.value)}/>
-            <br/>
-            <input placeholder='Пароль' type="password" style={{outlineColor: pass? 'black' : 'firebrick'}} onChange={(e) => setPass(e.target.value)}/>
-            <br/>
-            <button disabled={!pass || !login || localStorage.authToken} onClick={() => onLogin(login, pass)}>{localStorage.authToken? 'Авторизация была выполнена.' : 'Войти'}</button>
+            <input placeholder='Логин' style={{ outlineColor: login ? 'black' : 'firebrick' }} onChange={(e) => setLogin(e.target.value)} />
+            <br />
+            <input placeholder='Пароль' type="password" style={{ outlineColor: pass ? 'black' : 'firebrick' }} onChange={(e) => setPass(e.target.value)} />
+            <br />
+            <button disabled={!pass || !login || localStorage.authToken} onClick={() => onLogin(login, pass)}>{localStorage.authToken ? 'Авторизация была выполнена.' : 'Войти'}</button>
             {localStorage.authToken && <button onClick={() => onLogout()}>Выйти</button>}
-            <br/>
+            <br />
             {sub && <small>Пользователь {sub.login} авторизован</small>}
         </div>
     )
 }
 
-const CLoginForm = connect(state => ({log: state.auth?.payload || {}}), {onLogin: actionFullLogin, onLogout: actionAuthLogout})(LoginForm)
+const CLoginForm = connect(state => ({ log: state.auth?.payload || {} }), { onLogin: actionFullLogin, onLogout: actionAuthLogout })(LoginForm)
 //--------------------------------------------------------------------------------------------------------------------------------
-const RegForm = ({reg:{login}, loged: {sub}, onRegister, onFullRegister}) => {
+const RegForm = ({ reg: { login }, loged: { sub }, onRegister, onFullRegister }) => {
     let [pass, setPass] = useState()
     let [log, setLog] = useState()
     return (
-        <div className='form' style={{margin: '0 auto'}}>
+        <div className='form' style={{ margin: '0 auto' }}>
             <h3>Зарегистрироваться</h3>
-            <input placeholder='Логин' style={{outlineColor: log? 'black' : 'firebrick'}} onChange={(e) => setLog(e.target.value)}/>
-            <br/>
-            <input placeholder='Пароль' type="password" style={{outlineColor: pass? 'black' : 'firebrick'}} onChange={(e) => setPass(e.target.value)}/>
-            <br/>
-            <button disabled={!pass || !log || localStorage.authToken} onClick={() => onRegister(log, pass)}>{localStorage.authToken? 'Авторизация была выполнена' : 'Зарегистрироваться'}</button>
-            <br/>
-            {!localStorage.authToken && <button disabled={!pass || !log} onClick={() => {onFullRegister(log, pass); }}>Зарегистрироваться и Войти</button>}
-            <br/>
+            <input placeholder='Логин' style={{ outlineColor: log ? 'black' : 'firebrick' }} onChange={(e) => setLog(e.target.value)} />
+            <br />
+            <input placeholder='Пароль' type="password" style={{ outlineColor: pass ? 'black' : 'firebrick' }} onChange={(e) => setPass(e.target.value)} />
+            <br />
+            <button disabled={!pass || !log || localStorage.authToken} onClick={() => onRegister(log, pass)}>{localStorage.authToken ? 'Авторизация была выполнена' : 'Зарегистрироваться'}</button>
+            <br />
+            {!localStorage.authToken && <button disabled={!pass || !log} onClick={() => { onFullRegister(log, pass); }}>Зарегистрироваться и Войти</button>}
+            <br />
             {login && `Пользователь ${login} зарегистрирован ${sub && 'и авторизован' || ''}!`}
         </div>
     )
 }
 
-const CRegForm = connect(state => ({reg: state.promise.registration?.payload || {}, loged: state.auth?.payload || {}}), {onRegister: actionRegister, onFullRegister: actionFullRegister})(RegForm)
+const CRegForm = connect(state => ({ reg: state.promise.registration?.payload || {}, loged: state.auth?.payload || {} }), { onRegister: actionRegister, onFullRegister: actionFullRegister })(RegForm)
 //--------------------------------------------------------------------------------------------------------------------------------
 
 
@@ -419,12 +436,12 @@ const Page404 = () => <h1> 404 </h1>
 const Main = () =>
     <main>
         <Aside />
-        <Content style={{border:'1px solid black'}}>
+        <Content style={{ border: '1px solid black' }}>
             <Switch>
                 <Redirect from='/main' to='/' />
-                <Route path="/" component={PageMain} exact/>
-                <Route path="/category/:_id" component={CPageCategory}/>
-                <Route path="/good/:_id" component={CPageGood}/>
+                <Route path="/" component={PageMain} exact />
+                <Route path="/category/:_id" component={CPageCategory} />
+                <Route path="/good/:_id" component={CPageGood} />
                 <Route path="/cart" component={CCart} />
                 <Route path="/login" component={CLoginForm} />
                 <Route path="/registration" component={CRegForm} />
@@ -433,7 +450,7 @@ const Main = () =>
         </Content>
     </main>
 
-const Content = ({children}) =>
+const Content = ({ children }) =>
     <div className="Content">{children}</div>