Ivar 2 лет назад
Родитель
Сommit
270ec7cdc5

+ 163 - 48
js/15_graph-ql/index.js

@@ -44,83 +44,198 @@
 
    // добавить заголовок Authorization если в localStorage есть authToken (не забудьте слово Bearer  и пробел после него. 
    // Таким образом все запросы будут уходить не анонимно если в localStorage есть токен.
-
-   const originalFetch = fetch;
-   fetch = (url, params={headers:{}}) => { 
-      if (localStorage.authToken !== 'undefined') {
-         params.headers.Authorization = "Bearer " + localStorage.authToken
-      }
-      return originalFetch(url, params)
-   }
-
-
    const getGQL = (url) => (
       (query, variables) => fetch(url, {
          method: 'POST',
          headers: {
-               "Content-Type": "application/json"
+               "Content-Type": "application/json",
+               ...(localStorage.authToken ? {Authorization: "Bearer " + localStorage.authToken} : {})
          },
          body: JSON.stringify({query, variables})
          
-      }).then(res => res.json()).then((data) => {
+      }).then(res => res.json()).then(async (data) => {
 
          if ('errors' in data) {
-            Promise.reject(new Error('ОШИБКА'))
-            // throw new Error('GFSFSDFS')
+            // return Promise.reject(new Error('ОШИБКА'))
+            throw new Error('ОШИБКА!!!!' + JSON.stringify(data.errors))
          } else {
-            return data.data
+            return data.data[Object.keys(data.data)[0]]
          }
 
       })
    );
 
 
-   (async function() {
-      const gql = getGQL('http://shop-roles.asmer.fs.a-level.com.ua/graphql')
+   const gql = getGQL('http://shop-roles.asmer.fs.a-level.com.ua/graphql');
 
-      console.log((await gql(`query cats($q:String) {
-         CategoryFind(query:$q){
-            name goods{
-               name images{
-               url
-               }
-            }
+
+   // написать функции по типу:    
+   // для логина, регистрации, а так же для остальных страниц будущего сайта, которые вам пришли на ум.
+
+   function login(log, pass) {
+      return gql(`query log($login: String, $password: String) {
+         login(login: $login, password: $password)
+       }
+       `, {login: log, password: pass})
+   }
+
+   function register(log, pass) {
+      return gql(`mutation reg2($user:UserInput) {
+         UserUpsert(user:$user) {
+           _id login
          }
-      }`, {q: "[{}]"})))
+       }
+       `, {user: {login: log, password: pass}})
+   }
+
 
+   function findUsers(_id = '') {
+      return gql(`query userFind {
+         UserFind(query: "[{}]") {
+            login
+            nick
+            _id
+         }
+      }
+       `, {query: JSON.stringify([{_id}])})
+   }
 
 
-      console.log((await gql(`
-         query NameForMe1($login:String, $password:String){
-            login(login:$login, password:$password)
+   function catById(_id) {
+      return gql(`query catById($query:String) {
+                  CategoryFindOne(query:$query) {
+                     name goods{
+                        _id name images{
+                           url
+                           }
+                     }
+                     subCategories {
+                        _id name 
+                    }
+                  }
+      }`, {query: JSON.stringify([{_id}])})
+   }
+
+   function cats() {
+      return gql(`query cats($query: String) {
+         CategoryFind(query: $query) {
+           _id name
+           goods {
+             name
+             images {
+               url
+             }
+           }
          }
-      `, {login: 'tst', password: '123'})))
+       }`, {query: JSON.stringify([{}])})
+   }
 
+   function goodById(_id) {
+      return gql(`query goodById($q: String) {
+         GoodFindOne(query: $q) {
+             _id name price description images {
+             url
+             }
+         }
+     }`, {q: JSON.stringify([{_id}])})
+   }
 
+   function orders() {
+      return gql(`query o($query: String) {
+         OrderFind(query:$query){
+            _id orderGoods{
+               price count total good{
+                 name categories{
+                   name
+                 }
+                  }
+             } 
+             owner {
+                _id login
+               }
+         }
+       }`, {query: JSON.stringify([{}])})
+   }
 
-      console.log(await gql(`query userFind {
-         UserFind(query: "[{}]") {
-             login
-             nick
+   // function ordersByUserId(_id) {
+   //    return gql(`query oUser($query: String) {
+   //       OrderFind(query:$query){
+   //          _id orderGoods{
+   //             price count total good{
+   //               name categories{
+   //                 name
+   //               }
+   //                }
+   //           } 
+   //           owner {
+   //              _id login
+   //             }
+   //       }
+   //     }`, 
+   //     {query: JSON.stringify([
+   //       {owner: {_id}}
+   //      ])})
+   // }
+
+
+   function newOrder(_id, count) {
+      return gql(`mutation newOrder($q: OrderInput){
+         OrderUpsert(order: $q) 
+         
+         {
+           _id total
          }
-     }`, {q: "[{}]"}))
+       }`, {q: {
+            orderGoods:[
+            {count, good:{_id}},
+            // {count: count, good:{_id}}
+            ]
+         }})
+   }
+
+   
 
+   ;
+   (async function() {
 
+      let allUsers = await findUsers()
+      console.log(allUsers)
 
 
-      // написать функции по типу:    
-      // для логина, регистрации, а так же для остальных страниц будущего сайта, которые вам пришли на ум.
-      function catById(_id){
-         return gql(`query catById($query:String) {
-                     CategoryFindOne(query:$query) {
-                        name goods{
-                           _id name
-                        }
-                     }
-         }`, {query: JSON.stringify([{_id}])})
-      }
-     console.log(await catById("5dc45acf5df9d670df48cc48"))
-     
+      let token1 = await login('tst', '123')
+      console.log(token1)
+      if (typeof jwtDecode(token1) === 'object') {
+         localStorage.authToken = token1
+      } 
 
-   })()
+      //   console.log(await register('zzaa', 'zzaa'))
+
+
+      let cat = await catById("5dc45acf5df9d670df48cc48")
+      console.log(cat)
+
+      let allCats = await cats()
+      console.log(allCats)
+
+      let good = await goodById('5dcad3606d09c45440d14d10')
+      console.log(good)
+
+      let ord = await orders()
+      console.log(ord)
+      
+      let newOrd = await newOrder('5dcad3606d09c45440d14d10', 5)
+      console.log(newOrd)
+
+   })();
+
+
+
+   function jwtDecode(token) {
+      try {
+          let decoded = JSON.parse(atob(token.split('.')[1])) 
+          return decoded
+      } catch (err) {
+          console.log(err)
+      }
+   }
 

+ 2 - 14
js/16_redux-thunk/index.html

@@ -12,16 +12,12 @@
       #aside {
           width: 30%;
       }
-      #aside > a {
+      #aside > a{
           display: block;
       }
-      #main > a {
+      #main > a{
           display: block;
       }
-      #loginContainer  {
-         display: flex;
-         flex-direction: column;
-      }
   </style>
 </head>
    <body>
@@ -34,14 +30,6 @@
               Контент
           </main>
       </div>
-
-        <div id="loginContainer">
-            <input id="loginInput" type="text"/>
-            <input id="passInput" type="password"/>
-            <button id="loginBtn">LOGIN</button>
-            <button id="logoutBtn">LOGOUT</button>
-        </div>
-
       <script src='index.js'></script>
   </body>
 

+ 150 - 246
js/16_redux-thunk/index.js

@@ -25,101 +25,16 @@ function createStore(reducer){
     }
 }
 
-function combineReducers(reducers) {
-    return (state={}, action) => {
-        const newState = {}
-        // перебрать все редьюсеры
-        if (reducers) {
-            for (const [reducerName, reducer] of Object.entries(reducers)) {
-                const newSubState = reducer(state[reducerName], action)
-                if (newSubState !== state[reducerName]) {
-                    newState[reducerName] = newSubState
-                }
-            }
-            // если newState не пустой, то вернуть стейт в 
-            if (Object.keys(newState).length !== 0) {
-                return {...state, ...newState}
-            } else {
-                return state
-            }
-        }
-
-    }
-}
-const combinedReducer = combineReducers({promise: promiseReducer, auth: authReducer})
-const store = createStore(combinedReducer)
-// console.log(store.getState()) // {promise: {}, auth: {}}
-
-
-
-
-function jwtDecode(token) {
-    try {
-        let decoded = JSON.parse(atob(token.split('.')[1])) 
-        return decoded
-    } catch (err) {
-        console.log(err)
-    }
-}
-
-function authReducer(state, {type, token}) {
-    if (!state) {
-        if (localStorage.authToken) {
-            token = localStorage.authToken
-            type = 'AUTH_LOGIN'
-        } else {
-            return {}
-        }
-    }
-    if (type === 'AUTH_LOGIN') {
-        let payload = jwtDecode(token)
-        if (typeof payload === 'object') {
-            localStorage.authToken = token
-            return {
-                ...state,
-                token, 
-                payload
-            }
-        } else {
-            return state
-        }
-    }
-    if (type === 'AUTH_LOGOUT') {
-        delete localStorage.authToken
-        return {}
-    }
-    return state
-}
-
-const actionAuthLogin = (token) => ({type: 'AUTH_LOGIN', token})
-const actionAuthLogout = () => ({type: 'AUTH_LOGOUT'})
-
-// const loginStore = createStore(authReducer)
-store.subscribe(() => console.log(store.getState()))
-
-
-const inputToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiaWQiOiI2MWE0ZGIyOWM3NTBjMTJiYTZiYTQwMjIiLCJsb2dpbiI6ImVxd2VxZXdldyIsImFjbCI6WyI2MWE0ZGIyOWM3NTBjMTJiYTZiYTQwMjIiLCJ1c2VyIl19LCJpYXQiOjE2MzgxOTQ1NzZ9.Pi1GO6x7wdNrIrUKCQT-32-SsqmgFY-oFDrrXmw74-8'
-
-// loginStore.dispatch(actionAuthLogin(inputToken))
-// loginStore.dispatch(actionAuthLogout())
-// console.log(store.getState())
-
-loginBtn.onclick = () => {
-    store.dispatch(actionAuthLogin(inputToken))
-}
-
-logoutBtn.onclick = () => {
-    store.dispatch(actionAuthLogout())
-}
-
-
-
 
 
 function promiseReducer(state={}, {type, status, payload, error, name}) {
-    if (!state) {
+    if (!state) { 
         return {}
+            //{ login: {status, payload, error},
+            //  catById: {status, payload, error}
+            //}
     }
+
     if (type === 'PROMISE') {         
         return {
             ...state,
@@ -137,33 +52,33 @@ 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 promiceStore = createStore(promiseReducer)
-// store.subscribe(() => console.log(store.getState()))
+const store = createStore(promiseReducer)
+store.subscribe(() => console.log(store.getState()))
 
 
-const delay = (ms) => new Promise((ok) => setTimeout(() => ok(ms), ms))
+// const delay = (ms) => new Promise((ok) => setTimeout(() => ok(ms), ms))
 
-// // store.dispatch(actionPending('delay1000'))
-// // delay(1000).then(data => store.dispatch(actionResolved('delay1000', data)),
-// //                  error => store.dispatch(actionRejected('delay1000', error)))
+// store.dispatch(actionPending('delay1000'))
+// delay(1000).then(data => store.dispatch(actionResolved('delay1000', data)),
+//                  error => store.dispatch(actionRejected('delay1000', error)))
 
-// // store.dispatch(actionPending('delay2000'))
-// // delay(2000).then(data => store.dispatch(actionResolved('delay2000', data)),
-// //                  error => store.dispatch(actionRejected('delay2000', error)))
+// store.dispatch(actionPending('delay2000'))
+// delay(2000).then(data => store.dispatch(actionResolved('delay2000', data)),
+//                  error => store.dispatch(actionRejected('delay2000', error)))
 
 
 const actionPromise = (name, promise) => 
-    async dispatch => {
-        dispatch(actionPending(name))
-        try { 
-            let data = await promise
-            dispatch(actionResolved(name, data))
-            return data
-        }
-        catch(error){
-            dispatch(actionRejected(name, error))
-        }
+async dispatch => {
+    dispatch(actionPending(name))
+    try { 
+        let data = await promise
+        dispatch(actionResolved(name, data))
+        return data
     }
+    catch(error){
+        dispatch(actionRejected(name, error))
+    }
+}
 
 const getGQL = url =>
   async (query, variables={}) => {
@@ -192,152 +107,141 @@ const getGQL = url =>
   const gql = getGQL(backURL + 'graphql');
 
 
-//   const actionRootCats = () => 
-//   actionPromise('rootCats', gql(`query {
-//       CategoryFind(query: "[{\\"parent\\":null}]"){
-//           _id name
-//       }
-//   }`))
-
-// const actionCatById = (_id) =>  //добавить подкатегории
-//   actionPromise('catById', gql(`query catById($q: String){
-//       CategoryFindOne(query: $q){
-//           _id name goods {
-//               _id name price images {
-//                   url
-//               }
-//           }
-//           subCategories {
-//               _id name 
-//           }
-//       }
-//   }`, {q: JSON.stringify([{_id}])}))
-
-// //actionGoodById по аналогии
-
-// const actionGoodById = (_id) => 
-//   actionPromise('goodById', gql(`query goodById($q: String) {
-//       GoodFindOne(query: $q) {
-//           _id name price description images {
-//             url
-//           }
-//         }
-//   }`, {q: JSON.stringify([{_id}])}))
+  const actionRootCats = () => 
+  actionPromise('rootCats', gql(`query {
+      CategoryFind(query: "[{\\"parent\\":null}]"){
+          _id name
+      }
+  }`))
+
+const actionCatById = (_id) =>  //добавить подкатегории
+  actionPromise('catById', gql(`query catById($q: String){
+      CategoryFindOne(query: $q){
+          _id name goods {
+              _id name price images {
+                  url
+              }
+          }
+          subCategories {
+              _id name 
+          }
+      }
+  }`, {q: JSON.stringify([{_id}])}))
+
+//actionGoodById по аналогии
+
+const actionGoodById = (_id) => 
+  actionPromise('goodById', gql(`query goodById($q: String) {
+      GoodFindOne(query: $q) {
+          _id name price description images {
+            url
+          }
+        }
+  }`, {q: JSON.stringify([{_id}])}))
 
 
   
-// store.dispatch(actionRootCats())
-
-// store.subscribe(() => {
-//     const {rootCats} = store.getState()
-//     if (rootCats?.payload) {
-//         aside.innerHTML = ''
-//         for (const {_id, name} of rootCats?.payload) {
-//             const link = document.createElement('a')
-//             link.href = `#/category/${_id}`
-//             link.innerText = name
-//             aside.append(link)
-//         }
-//     }
-// })
-
-
-// // location.hash - адресная строка после решетки
-// window.onhashchange = () => {
-//     const [,route, _id] = location.hash.split('/')
-
-//     const routes = {
-//         category(){
-//             store.dispatch(actionCatById(_id))
-//             console.log('страница категорий')           
-//         },
-//         good(){ //задиспатчить actionGoodById
-//             store.dispatch(actionGoodById(_id))
-//             console.log('страница товара')                
-//         },
-//     }
-//     if (route in routes)
-//         routes[route]()
-// }
-
-// window.onhashchange()
-
-// store.subscribe(() => {
-//     const {catById} = store.getState()
-//     const [,route, _id] = location.hash.split('/')
-//     if (catById?.payload && route === 'category'){
-//         const {name} = catById.payload;
-//         main.innerHTML =    `<h1>${name}</h1>`
+store.dispatch(actionRootCats())
+
+store.subscribe(() => {
+    const {rootCats} = store.getState()
+    if (rootCats?.payload) {
+        aside.innerHTML = ''
+        for (const {_id, name} of rootCats?.payload) {
+            const link = document.createElement('a')
+            link.href = `#/category/${_id}`
+            link.innerText = name
+            aside.append(link)
+        }
+    }
+})
+
+
+// location.hash - адресная строка после решетки
+window.onhashchange = () => {
+    const [,route, _id] = location.hash.split('/')
+
+    const routes = {
+        category(){
+            store.dispatch(actionCatById(_id))
+            console.log('страница категорий')           
+        },
+        good(){ //задиспатчить actionGoodById
+            store.dispatch(actionGoodById(_id))
+            console.log('страница товара')                
+        },
+    }
+    if (route in routes)
+        routes[route]()
+}
+
+window.onhashchange()
+
+store.subscribe(() => {
+    const {catById} = store.getState()
+    const [,route, _id] = location.hash.split('/')
+    if (catById?.payload && route === 'category'){
+        const {name} = catById.payload;
+        main.innerHTML =    `<h1>${name}</h1>`
       
-//         if (catById.payload.subCategories) {
-//             for(const {_id, name} of catById.payload.subCategories) {
-//                 const link      = document.createElement('a');
-//                 link.href       = `#/category/${_id}`;
-//                 link.innerText  = name;
-//                 main.append(link);
-//             }
-//         }
+        if (catById.payload.subCategories) {
+            for(const {_id, name} of catById.payload.subCategories) {
+                const link      = document.createElement('a');
+                link.href       = `#/category/${_id}`;
+                link.innerText  = name;
+                main.append(link);
+            }
+        }
         
-//         if (catById.payload.goods) {
-//             for (const {_id, name, price, images} of catById.payload.goods){
-//                 const card      = document.createElement('div')
-//                 card.innerHTML = `<h2>${name}</h2>
-//                                   <img src="${backURL}/${images[0].url}" />
-//                                   <strong>${price}</strong>
-//                                   <br>
-//                                   <a href="#/good/${_id}">Перейти на страницу товара</a>
-//                                     `
-//                 main.append(card)
-//             }
-//         }
-//     }
-// })
-
-// store.subscribe(() => {
-//     const {goodById} = store.getState();
-//     const [,route, _id] = location.hash.split('/');
-//     if (goodById?.payload && route === 'good') {
-//         main.innerHTML = '';
-//         const {_id, name, images, price, description}  = goodById.payload;
-//             const card = document.createElement('div');
-//             card.innerHTML = `<h2>${name}</h2>
-//                             <img src="${backURL}/${images[0].url}" />
-//                             <strong>${price}</strong>
-//                             <h2>${description}</h2>
-//                             `;
-//             main.append(card);
-//         }
-//     }
-//     //ТУТ ДОЛЖНА БЫТЬ ПРОВЕРКА НА НАЛИЧИЕ goodById в редакс
-//     //и проверка на то, что сейчас в адресной строке адрес ВИДА #/good/АЙДИ
-//     //в таком случае очищаем main и рисуем информацию про товар с подробностями
-// )
-
-store.dispatch(actionPromise('delay1000', delay(1000)))
-// // store.dispatch(actionPromise('delay2000', delay(2000)))     
-// // store.dispatch(actionPromise('luke', fetch('https://swapi.dev/api/people/1/').then(res => res.json())))
-
-
-
-
-
-
-
-const actionLogin = (login, password) => (
-    actionPromise('catById', gql(`query log($login: String, $password: String) {
-        login(login: $login, password: $password)
-      }`, {login, password}))
-)
-
-const actionFullLogin = (login, password) => (
-    async dispatch => {
-        let token = await dispatch(actionLogin(login, password))
-        if (token) {
-            dispatch(actionAuthLogin(token))
+        if (catById.payload.goods) {
+            for (const {_id, name, price, images} of catById.payload.goods){
+                const card      = document.createElement('div')
+                card.innerHTML = `<h2>${name}</h2>
+                                  <img src="${backURL}/${images[0].url}" />
+                                  <strong>${price}</strong>
+                                  <br>
+                                  <a href="#/good/${_id}">Перейти на страницу товара</a>
+                                    `
+                main.append(card)
+            }
         }
     }
+})
+
+store.subscribe(() => {
+    const {goodById} = store.getState();
+    const [,route, _id] = location.hash.split('/');
+    if (goodById?.payload && route === 'good') {
+        main.innerHTML = '';
+        const {_id, name, images, price, description}  = goodById.payload;
+            const card = document.createElement('div');
+            card.innerHTML = `<h2>${name}</h2>
+                            <img src="${backURL}/${images[0].url}" />
+                            <strong>${price}</strong>
+                            <h2>${description}</h2>
+                            `;
+            main.append(card);
+        }
+    }
+    //ТУТ ДОЛЖНА БЫТЬ ПРОВЕРКА НА НАЛИЧИЕ goodById в редакс
+    //и проверка на то, что сейчас в адресной строке адрес ВИДА #/good/АЙДИ
+    //в таком случае очищаем main и рисуем информацию про товар с подробностями
 )
 
+// store.dispatch(actionPromise('delay1000', delay(1000)))
+// store.dispatch(actionPromise('delay2000', delay(2000)))     
+// store.dispatch(actionPromise('luke', fetch('https://swapi.dev/api/people/1/').then(res => res.json())))
+
+
+
+
+
+
+
+
+
+
+
 
 
 

+ 56 - 0
js/17_redux-thunk-2/index.html

@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+   <meta charset="UTF-8">
+   <meta http-equiv="X-UA-Compatible" content="IE=edge">
+   <meta name="viewport" content="width=device-width, initial-scale=1.0">
+   <title>Document</title>
+   <style>
+      #mainContainer {
+          display: flex;
+      }
+      #aside {
+          width: 30%;
+      }
+      #aside > a {
+          display: block;
+      }
+      #main > a {
+          display: block;
+      }
+      #loginContainer  {
+         display: flex;
+         flex-direction: column;
+      }
+  </style>
+</head>
+   <body>
+      <header>КУДА Я ПОПАЛ?</header>
+      <div id="topContaner"></div>
+      <div id='mainContainer'>
+          <aside id='aside'>
+              Категории
+          </aside>          
+          <main id='main'>
+              Контент
+          </main>
+      </div>
+
+        <!-- <div id="regContainer"> 
+            <input id="loginReg" type="text"/>
+            <input id="passReg" type="password"/>
+            <button id="regBtn">REGISTER</button>
+        </div>
+
+        <div id="loginContainer">
+            <input id="loginInput" type="text"/>
+            <input id="passInput" type="password"/>
+            <button id="loginBtn">LOGIN</button>
+            <button id="logoutBtn">LOGOUT</button>
+        </div>
+        -->
+
+      <script src='index.js'></script>
+  </body>
+
+</html>

+ 489 - 0
js/17_redux-thunk-2/index.js

@@ -0,0 +1,489 @@
+
+function createStore(reducer){
+    let state       = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
+    let cbs         = []                     //массив подписчиков
+    
+    const getState  = () => state            //функция, возвращающая переменную из замыкания
+    const subscribe = cb => (cbs.push(cb),   //запоминаем подписчиков в массиве
+                             () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
+                             
+    const dispatch  = action => { 
+        if (typeof action === 'function'){ //если action - не объект, а функция
+            return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
+        }
+        const newState = reducer(state, action) //пробуем запустить редьюсер
+        if (newState !== state){ //проверяем, смог ли редьюсер обработать action
+            state = newState //если смог, то обновляем state 
+            for (let cb of cbs)  cb() //и запускаем подписчиков
+        }
+    }
+    
+    return {
+        getState, //добавление функции getState в результирующий объект
+        dispatch,
+        subscribe //добавление subscribe в объект
+    }
+}
+
+function combineReducers(reducers) {
+    return (state={}, action) => {
+        const newState = {}
+        // перебрать все редьюсеры
+        if (reducers) {
+            for (const [reducerName, reducer] of Object.entries(reducers)) {
+                const newSubState = reducer(state[reducerName], action)
+                if (newSubState !== state[reducerName]) {
+                    newState[reducerName] = newSubState
+                }
+            }
+            // если newState не пустой, то вернуть стейт в 
+            if (Object.keys(newState).length !== 0) {
+                return {...state, ...newState}
+            } else {
+                return state
+            }
+        }
+
+    }
+}
+const combinedReducer = combineReducers({promise: promiseReducer, auth: authReducer})
+const store = createStore(combinedReducer)
+// console.log(store.getState()) // {promise: {}, auth: {}}
+
+
+
+
+function jwtDecode(token) {
+    try {
+        let decoded = JSON.parse(atob(token.split('.')[1])) 
+        return decoded
+    } catch (err) {
+        console.log(err)
+    }
+}
+
+function authReducer(state, {type, token}) {
+    if (!state) {
+        if (localStorage.authToken) {
+            token = localStorage.authToken
+            type = 'AUTH_LOGIN'
+        } else {
+            return {}
+        }
+    }
+    if (type === 'AUTH_LOGIN') {
+        let payload = jwtDecode(token)
+        if (typeof payload === 'object') {
+            localStorage.authToken = token
+            return {
+                ...state,
+                token, 
+                payload
+            }
+        } else {
+            return state
+        }
+    }
+    if (type === 'AUTH_LOGOUT') {
+        delete localStorage.authToken
+        return {}
+    }
+    return state
+}
+
+const actionAuthLogin = (token) => ({type: 'AUTH_LOGIN', token})
+const actionAuthLogout = () => ({type: 'AUTH_LOGOUT'})
+
+// const loginStore = createStore(authReducer)
+store.subscribe(() => console.log(store.getState()))
+
+
+// const inputToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiaWQiOiI2MWE0ZGIyOWM3NTBjMTJiYTZiYTQwMjIiLCJsb2dpbiI6ImVxd2VxZXdldyIsImFjbCI6WyI2MWE0ZGIyOWM3NTBjMTJiYTZiYTQwMjIiLCJ1c2VyIl19LCJpYXQiOjE2MzgxOTQ1NzZ9.Pi1GO6x7wdNrIrUKCQT-32-SsqmgFY-oFDrrXmw74-8'
+
+// loginStore.dispatch(actionAuthLogin(inputToken))
+// loginStore.dispatch(actionAuthLogout())
+// console.log(store.getState())
+
+
+
+
+
+
+
+function promiseReducer(state={}, {type, status, payload, error, name}) {
+    if (!state) {
+        return {}
+    }
+    if (type === 'PROMISE') {         
+        return {
+            ...state,
+            [name]: {
+                status: status,
+                payload : payload,
+                error: error,
+            }
+        }
+    }
+    return state
+}
+
+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 promiceStore = createStore(promiseReducer)
+// store.subscribe(() => console.log(store.getState()))
+
+
+// const delay = (ms) => new Promise((ok) => setTimeout(() => ok(ms), ms))
+
+// // store.dispatch(actionPending('delay1000'))
+// // delay(1000).then(data => store.dispatch(actionResolved('delay1000', data)),
+// //                  error => store.dispatch(actionRejected('delay1000', error)))
+
+// // store.dispatch(actionPending('delay2000'))
+// // delay(2000).then(data => store.dispatch(actionResolved('delay2000', data)),
+// //                  error => store.dispatch(actionRejected('delay2000', error)))
+
+
+const actionPromise = (name, promise) => 
+    async (dispatch) => {
+        dispatch(actionPending(name))
+        try { 
+            let data = await promise
+            dispatch(actionResolved(name, data))
+            return data
+        }
+        catch(error){
+            dispatch(actionRejected(name, error))
+        }
+    }
+
+const getGQL = url =>
+  async (query, variables={}) => {
+    // try {
+      let obj = await fetch(url, {
+        method: 'POST',
+        headers: {
+          "Content-Type": "application/json",
+          ...(localStorage.authToken ? {Authorization: "Bearer " + localStorage.authToken} : {})
+        },
+        body: JSON.stringify({ query, variables })
+      })
+      let a = await obj.json()
+      if (!a.data && a.errors) {
+          throw new Error(JSON.stringify(a.errors))
+      } else {
+          return a.data[Object.keys(a.data)[0]]
+      }      
+    // }
+    // catch (error) {
+    //   console.log('Что-то не так, Бро ' + error);
+    // }
+  }
+
+  const backURL = 'http://shop-roles.asmer.fs.a-level.com.ua/'
+  const gql = getGQL(backURL + 'graphql');
+
+
+
+
+const actionLogin = (login, password) => (
+    actionPromise('login', gql(`query log($login: String, $password: String) {
+        login(login: $login, password: $password)
+    }`, {login, password}))
+)
+
+const actionFullLogin = (login, password) => (
+    async (dispatch) => {
+        let token = await dispatch(actionLogin(login, password))
+        if (token) {
+            // console.log(token)
+            dispatch(actionAuthLogin(token))
+        }
+    }
+)
+
+
+const actionRegister = (login, password) => (
+    actionPromise('register', gql(`mutation reg($user:UserInput) {
+        UserUpsert(user:$user) {
+        _id 
+        }
+    }
+    `, {user: {login, password}})      
+    )
+)
+
+const actionFullRegister = (login, password) => (
+    async (dispatch) => {
+        let regId = await dispatch(actionRegister(login, password))
+        if (regId) {
+            // console.log(regId)
+            dispatch(actionFullLogin(login, password))
+        }
+    }
+)
+
+
+// regBtn.onclick = () => {
+//     store.dispatch(actionFullRegister(loginReg.value, passReg.value))
+// }
+
+
+// loginBtn.onclick = () => {
+//     store.dispatch(actionFullLogin(loginInput.value, passInput.value))
+// }
+
+// logoutBtn.onclick = () => {
+//     store.dispatch(actionAuthLogout())
+// }
+
+
+
+
+const actionRootCats = () => 
+    actionPromise('rootCats', gql(`query {
+        CategoryFind(query: "[{\\"parent\\":null}]"){
+            _id name
+        }
+    }`))
+
+const actionCatById = (_id) =>  //добавить подкатегории
+    actionPromise('catById', gql(`query catById($q: String){
+        CategoryFindOne(query: $q){
+            _id name goods {
+                _id name price images {
+                    url
+                }
+            }
+            subCategories {
+                _id name 
+            }
+        }
+    }`, {q: JSON.stringify([{_id}])}))
+
+// //actionGoodById по аналогии
+
+const actionGoodById = (_id) => 
+    actionPromise('goodById', gql(`query goodById($q: String) {
+        GoodFindOne(query: $q) {
+            _id name price description images {
+            url
+            }
+        }
+    }`, {q: JSON.stringify([{_id}])}))
+
+
+const actionGoodsByUser = () => 
+actionPromise('goodByUser', gql(`query oUser($query: String) {
+    OrderFind(query:$query){
+       _id orderGoods{
+            price count total good{
+                _id name categories{
+                name
+                }
+                images {
+                    url
+                }
+            }
+        } 
+        owner {
+           _id login
+          }
+    }
+  }`, 
+   {query: JSON.stringify([{}])}))
+
+
+  
+store.dispatch(actionRootCats())
+
+store.subscribe(() => {
+    const {promise} = store.getState()
+    const {rootCats} = promise
+    if (rootCats?.payload) {
+
+
+        aside.innerHTML = ''
+        const regBtn = document.createElement('a')
+        regBtn.href = `#/register`
+        regBtn.innerText = 'Register'
+        const loginBtn = document.createElement('a')
+        loginBtn.href = `#/login`
+        loginBtn.innerText = 'Login'
+        const logoutBtn = document.createElement('button')
+        logoutBtn.innerText = 'Logout'
+        aside.append(regBtn, loginBtn, logoutBtn)
+
+
+
+        logoutBtn.onclick = () => {
+            store.dispatch(actionAuthLogout())
+        }
+        for (const {_id, name} of rootCats?.payload) {
+            const link = document.createElement('a')
+            link.href = `#/category/${_id}`
+            link.innerText = name
+            aside.append(link)
+        }
+    }
+})
+
+
+// location.hash - адресная строка после решетки
+window.onhashchange = () => {
+    const [,route, _id] = location.hash.split('/')
+
+    const routes = {
+        category(){
+            store.dispatch(actionCatById(_id))
+            console.log('страница категорий')           
+        },
+        good(){ //задиспатчить actionGoodById
+            store.dispatch(actionGoodById(_id))
+            console.log('страница товара')                
+        },
+        register(){
+            createForm(main, 'Register')
+            btnRegister.onclick = () => {
+                store.dispatch(actionFullRegister(loginRegister.value, passRegister.value))
+            }
+        },
+        login(){
+            createForm(main, 'Login')
+            btnLogin.onclick = () => {
+                store.dispatch(actionFullLogin(loginLogin.value, passLogin.value))
+            }
+        },
+        orders(){
+            store.dispatch(actionGoodsByUser())
+        }        
+    }
+    if (route in routes) {
+        routes[route]()
+    }
+}
+
+function createForm(parent, type) {
+    parent.innerHTML = ` 
+    <input id="login${type}" type="text"/>
+    <input id="pass${type}" type="password"/>
+    <button id="btn${type}">${type}</button>
+    `
+}
+
+window.onhashchange()
+
+store.subscribe(() => {
+    const {promise} = store.getState()
+    const {catById} = promise
+    const [,route, _id] = location.hash.split('/')
+    if (catById?.payload && route === 'category'){
+        const {name} = catById.payload;
+        main.innerHTML =    `<h1>${name}</h1>`
+      
+        if (catById.payload.subCategories) {
+            for(const {_id, name} of catById.payload.subCategories) {
+                const link      = document.createElement('a');
+                link.href       = `#/category/${_id}`;
+                link.innerText  = name;
+                main.append(link);
+            }
+        }
+        
+        if (catById.payload.goods) {
+            for (const {_id, name, price, images} of catById.payload.goods){
+                const card      = document.createElement('div')
+                card.innerHTML = `<h2>${name}</h2>
+                                  <img src="${backURL}/${images[0].url}" />
+                                  <strong>${price}</strong>
+                                  <br>
+                                  <a href="#/good/${_id}">Перейти на страницу товара</a>
+                                `
+                main.append(card)
+            }
+        }
+    }
+})
+
+store.subscribe(() => {
+    const {promise} = store.getState()
+    const {goodById} = promise
+    const [,route, _id] = location.hash.split('/');
+    if (goodById?.payload && route === 'good') {
+        main.innerHTML = '';
+        const {_id, name, images, price, description}  = goodById.payload;
+            const card = document.createElement('div');
+            card.innerHTML = `<h2>${name}</h2>
+                            <img src="${backURL}/${images[0].url}" />
+                            <strong>${price}</strong>
+                            <h2>${description}</h2>
+                            `;
+            main.append(card);
+        }
+    }
+    //ТУТ ДОЛЖНА БЫТЬ ПРОВЕРКА НА НАЛИЧИЕ goodById в редакс
+    //и проверка на то, что сейчас в адресной строке адрес ВИДА #/good/АЙДИ
+    //в таком случае очищаем main и рисуем информацию про товар с подробностями
+)
+
+
+store.subscribe(() => {
+    const {auth} = store.getState()
+    const {payload} = auth
+    if (payload?.sub ) {
+        topContaner.innerHTML = ''
+        const {id, login}  = payload.sub
+        const name = document.createElement('div')
+        name.innerText = `ПРИВЕТ ${login}`
+        topContaner.append(name)
+        const myOrders = document.createElement('a')
+        myOrders.innerText = 'Мои заказы'
+        myOrders.href       = `#/orders/${id}`
+        topContaner.append(myOrders)
+    } else {
+        topContaner.innerHTML = ''
+    }
+})
+
+
+
+store.subscribe(() => {
+    const {promise} = store.getState()
+    const {goodByUser} = promise
+    const [,route, _id] = location.hash.split('/')
+    if (goodByUser?.payload && route === 'orders'){
+
+        main.innerHTML = ''   
+       
+        if (goodByUser.payload.orderGoods) {
+            for (const {price, count, total, good: {name, images}} of goodByUser.payload.orderGoods){
+                const card      = document.createElement('div')
+                card.innerHTML = `<h2>${name}</h2>
+                                  <img src="${backURL}/${images[0].url}" />
+                                  <strong>Куплено ${count} по ${price} грн. Итого:${total}</strong>
+                                  <br>
+                                  <a href="#/good/${_id}">Перейти на страницу товара</a>
+                                `
+                main.append(card)
+            }
+        }
+    }
+})
+
+// store.dispatch(actionPromise('delay1000', delay(1000)))
+// // store.dispatch(actionPromise('delay2000', delay(2000)))     
+// // store.dispatch(actionPromise('luke', fetch('https://swapi.dev/api/people/1/').then(res => res.json())))
+
+
+
+
+
+
+
+
+
+
+
+

js/17/index.html → js/18/index.html


js/17/index.js → js/18/index.js


+ 13 - 0
js/19/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+   <meta charset="UTF-8">
+   <meta http-equiv="X-UA-Compatible" content="IE=edge">
+   <meta name="viewport" content="width=device-width, initial-scale=1.0">
+   <title>Document</title>
+</head>
+<body>
+   
+   <script src="./index.js"></script>
+</body>
+</html>

+ 24 - 0
js/19/index.js

@@ -0,0 +1,24 @@
+htmlTree()
+function htmlTree() {
+
+}
+
+htmlTree()
+function htmlTree() {
+
+}
+
+htmlTree()
+function htmlTree() {
+
+}
+
+htmlTree()
+function htmlTree() {
+
+}
+
+htmlTree()
+function htmlTree() {
+
+}

+ 13 - 0
js/20/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+   <meta charset="UTF-8">
+   <meta http-equiv="X-UA-Compatible" content="IE=edge">
+   <meta name="viewport" content="width=device-width, initial-scale=1.0">
+   <title>Document</title>
+</head>
+<body>
+   
+   <script src="./index.js"></script>
+</body>
+</html>

+ 24 - 0
js/20/index.js

@@ -0,0 +1,24 @@
+htmlTree()
+function htmlTree() {
+
+}
+
+htmlTree()
+function htmlTree() {
+
+}
+
+htmlTree()
+function htmlTree() {
+
+}
+
+htmlTree()
+function htmlTree() {
+
+}
+
+htmlTree()
+function htmlTree() {
+
+}