Browse Source

Module done

ostapenkonataliia 1 year ago
parent
commit
9988d113db

+ 37 - 29
.idea/workspace.xml

@@ -2,24 +2,18 @@
 <project version="4">
   <component name="ChangeListManager">
     <list default="true" id="c45bf7d2-992f-400a-8194-6f236ee5f805" name="Changes" comment="">
-      <change afterPath="$PROJECT_DIR$/JS_17_rest, graphQL/index.html" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/JS_17_rest, graphQL/js.js" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/JS_17_rest, graphQL/style.css" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/Js_13_foop/Index.html" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/Js_13_foop/css.css" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/Js_13_foop/js.js" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/Js_15/index.html" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/Js_15/js.js" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/Js_16_async_chat/index.html" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/Js_16_async_chat/js.js" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/Js_16_async_chat/style.css" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/MODULE/index.html" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/MODULE/js.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/Js_18_oop/index.html" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/Js_18_oop/js.js" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/MODULE/cart.png" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/MODULE/logo.png" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/MODULE/style.css" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/MODULE/thumbnail_add6f9d1c30d_1x.webp" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/Js_12/js.js" beforeDir="false" afterPath="$PROJECT_DIR$/Js_12/js.js" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/Js_12/style.css" beforeDir="false" afterPath="$PROJECT_DIR$/Js_12/style.css" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/js.js" beforeDir="false" />
-      <change beforePath="$PROJECT_DIR$/rgb.js" beforeDir="false" afterPath="$PROJECT_DIR$/rgb.js" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/Js_12/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/Js_12_redux/index.html" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/Js_12/js.js" beforeDir="false" afterPath="$PROJECT_DIR$/Js_12_redux/js.js" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/Js_12/style.css" beforeDir="false" afterPath="$PROJECT_DIR$/Js_12_redux/style.css" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/MODULE/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/MODULE/index.html" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/MODULE/js.js" beforeDir="false" afterPath="$PROJECT_DIR$/MODULE/js.js" afterDir="false" />
     </list>
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -47,19 +41,19 @@
     <option name="hideEmptyMiddlePackages" value="true" />
     <option name="showLibraryContents" value="true" />
   </component>
-  <component name="PropertiesComponent"><![CDATA[{
-  "keyToString": {
-    "DefaultHtmlFileTemplate": "HTML File",
-    "RunOnceActivity.OpenProjectViewOnStart": "true",
-    "RunOnceActivity.ShowReadmeOnStart": "true",
-    "WebServerToolWindowFactoryState": "false",
-    "last_opened_file_path": "C:/A-Level/JS",
-    "list.type.of.created.stylesheet": "CSS",
-    "nodejs_package_manager_path": "npm",
-    "settings.editor.selected.configurable": "project.propVCSSupport.DirectoryMappings",
-    "vue.rearranger.settings.migration": "true"
+  <component name="PropertiesComponent">{
+  &quot;keyToString&quot;: {
+    &quot;DefaultHtmlFileTemplate&quot;: &quot;HTML File&quot;,
+    &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
+    &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
+    &quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
+    &quot;last_opened_file_path&quot;: &quot;C:/A-Level/Modul/index.html&quot;,
+    &quot;list.type.of.created.stylesheet&quot;: &quot;CSS&quot;,
+    &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
+    &quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,
+    &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
   }
-}]]></component>
+}</component>
   <component name="RecentsManager">
     <key name="MoveFile.RECENT_KEYS">
       <recent name="C:\A-Level\JS" />
@@ -114,6 +108,20 @@
       <workItem from="1673512789885" duration="17176000" />
       <workItem from="1673679696247" duration="643000" />
       <workItem from="1673856577763" duration="1291000" />
+      <workItem from="1673857985833" duration="2260000" />
+      <workItem from="1673866221595" duration="50000" />
+      <workItem from="1673872780739" duration="114000" />
+      <workItem from="1673872907705" duration="50000" />
+      <workItem from="1673873068792" duration="436000" />
+      <workItem from="1673881143203" duration="1053000" />
+      <workItem from="1673900471122" duration="7540000" />
+      <workItem from="1673947281698" duration="5265000" />
+      <workItem from="1673959657006" duration="61406000" />
+      <workItem from="1674315750693" duration="18431000" />
+      <workItem from="1674413962218" duration="30270000" />
+      <workItem from="1674670731917" duration="32222000" />
+      <workItem from="1674856401303" duration="1332000" />
+      <workItem from="1674903743476" duration="36957000" />
     </task>
     <servers />
   </component>

Js_12/index.html → Js_12_redux/index.html


Js_12/js.js → Js_12_redux/js.js


Js_12/style.css → Js_12_redux/style.css


+ 13 - 0
Js_18_oop/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Title</title>
+</head>
+<body>
+
+
+
+<script src="js.js"></script>
+</body>
+</html>

+ 188 - 0
Js_18_oop/js.js

@@ -0,0 +1,188 @@
+//1. Store Class
+// Переделайте задание Store на синтаксис ES6-классов:
+// {
+//
+// }class Store {
+//     #reducer;
+//     #state;
+//     #cbs = []
+//
+//     constructor(reducer) {
+//         this.#reducer = reducer
+//         this.#state = (undefined, {} )
+//     }
+//     getState () {
+//         return this.#state
+//     }
+//
+//     subscribe (cb) {
+//         this.#cbs.push(cb)
+//         return (cb) => {
+//             this.#cbs = this.#cbs.filter(c => c !== cb)
+//         }
+//     }
+//
+//     dispatch (action) {
+//         const newState = this.#reducer(this.#state, action)
+//         if (newState !== this.#state) {
+//             this.#state = newState
+//             for (let cb of this.#cbs)
+//                 cb()
+//         }
+//     }
+//
+//     get state () {
+//         return this.#state
+//     }
+// }
+
+
+//2. Password Class
+// По аналогии, переделайте код задания Password в синтаксис классов ES6. Спрячьте все что можно в #приватные свойства
+// объектов класса. Проверьте на форме логина - ведь она использует Password
+// {
+//     class Password {
+//         #parent
+//         #open
+//         #inputPassword = document.createElement('input')
+//         #btnSee = document.createElement('input')
+//         #onChange
+//         #onOpenChange
+//
+//         onchange = () => this.setOpen(this.#btnSee.checked)
+//
+//         constructor(parent, open) {
+//             this.#parent = parent
+//             this.#open = open
+//
+//
+//             this.#parent.append(this.#inputPassword)
+//             this.#btnSee.type = 'checkbox'
+//             this.#parent.append(this.#btnSee)
+//             this.#btnSee.onchange = this.onchange
+//         }
+//
+//         set onChange(value) {
+//             this.#onChange = value
+//         }
+//
+//         set onOpenChange(value) {
+//             this.#onOpenChange = value
+//         }
+//
+//         setValue(value) {
+//             this.#inputPassword.value = value
+//         }
+//
+//         setOpen(open) {
+//             this.#open = open
+//             this.#inputPassword.type = this.#open ? 'text' : 'password'
+//         }
+//
+//         getValue() {
+//             return this.#inputPassword.value
+//         }
+//
+//         getOpen() {
+//             return this.#inputPassword.type
+//         }
+//     }
+//
+//     const p = new Password(document.body, true)
+//     p.onChange = data => console.log(data)
+//     p.onOpenChange = open => console.log(open)
+//
+//     p.setValue('qwerty')
+//     console.log(p.getValue())
+//
+//     p.setOpen(false)
+//     console.log(p.getOpen())
+// }
+
+// StoreThunk Class
+// Унаследуйте класс Store в новом классе StoreThunk. Новый класс должен перекрывать метод dispatch, проверять тип
+// переданного экшона и если это функция, запускать её, передав у неё this.dispatch и this.getState. Данное условие
+// написано тут. Учтите, что в thunk передаются функции dispatch и getState без объекта до точечки, а эти методы в
+// классе Store являются обычными функциями, склонными к потере this. Для прибития this намертво к функции используйте
+// метод bind. Посмотреть можно тут и тут Проверьте на модульном проекте
+{
+    class Store {
+        #reducer;
+        #state;
+        #cbs = []
+
+        constructor(reducer){
+            this.#reducer = reducer
+            this.#state = reducer(undefined, {})
+        }
+
+        getState(){
+            return this.#state
+        }
+
+        subscribe(cb){
+            (this.#cbs.push(cb), () => this.#cbs = this.#cbs.filter(c => c !== cb))
+        }
+
+        dispatch(action){
+            let newState = this.#reducer (this.#state,action)
+            if (newState !== this.#state) {
+                this.#state = newState
+                for ( let cb of this.#cbs) {
+                    cb ()
+                }
+            }
+        }
+
+        get state () {
+            return this.#state
+        }
+    }
+
+    class StoreThunk  extends Store {
+        dispatch(action) {
+            //если это функция,
+            if (typeof action === 'function') {
+                //запускать её, передав у неё this.dispatch и this.getState; используйте метод bind
+                return action(this.dispatch.bind(this), this.getState.bind(this))
+            } else {
+                super.dispatch(action)
+            }
+        }
+    }
+}
+
+//RGB Class
+// Напишите класс RGB, приватными свойствами которого являются три числа #r, #g, #b. Класс должен обладать следующими
+// геттерами и сеттерами:
+
+class RGB {
+    #r;
+    #g;
+    #b;
+
+    set r (newR) {
+        if (typeof newR !="number" || !(newR>= 0 && newR<=255)) {
+            throw new RangeError('Неправильный формат или диапазон')
+        } else {
+            this.#r = newR
+        }
+    }
+
+    set g (newG) {
+        if (typeof newG !="number" || !(newG>= 0 && newG<=255)) {
+            throw new RangeError('Ошибка в типе значений')
+        } else {
+            this.#g = newG
+        }
+    }
+
+    set b (newB) {
+        if (typeof newB != 'number' || !(newB>= 0 && newB<=255)) {
+            throw new RangeError('Ошибка в типе значений')
+        } else {
+            this.#b = newB
+        }
+    }
+
+}

BIN
MODULE/cart.png


+ 22 - 2
MODULE/index.html

@@ -1,12 +1,32 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
-    <meta charset="UTF-8">
-    <title>Title</title>
+  <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>Shop</title>
+  <link rel="stylesheet" href="style.css">
+
 </head>
 <body>
 
+<header>
+  <img src="thumbnail_add6f9d1c30d_1x.webp" id="logo">
+  <div id="container">
+    <div id="loginForm">
+      <button id="login">Войти</button>
+      <button id="registration">Зарегистрироваться</button>
+    </div>
+  </div>
+  <div id='cartIcon'><b></b></div>
+</header>
+
+<div id='mainContainer'>
+  <aside id='aside'></aside>
+  <main id='main'></main>
+</div>
 
 <script src="js.js"></script>
+
 </body>
 </html>

+ 609 - 53
MODULE/js.js

@@ -13,9 +13,10 @@ function createStore(reducer){
         const newState = reducer(state, action) //пробуем запустить редьюсер
         if (newState !== state){ //проверяем, смог ли редьюсер обработать action
             state = newState //если смог, то обновляем state
-            for (let cb of cbs)  cb() //и запускаем подписчиков
+            for (let cb of cbs)  cb(state) //и запускаем подписчиков
         }
     }
+
     return {
         getState, //добавление функции getState в результирующий объект
         dispatch,
@@ -23,82 +24,136 @@ function createStore(reducer){
     }
 }
 
-    function promiseReducer(state = {}, {promiseName, type, status, payload, error}) {
-        if (type === 'PROMISE') {
-            return {...state, [promiseName]: {status, payload, error}}
-        }
-        return state
+const jwtDecode = (token) => {
+    try {
+        let payload = JSON.parse(atob(token.split('.')[1]))
+        console.log(payload)
+        return payload
+    } catch (e) {
+        return undefined
     }
+}
 
-    const delay = ms => new Promise(ok => setTimeout(() => ok(ms), ms));
+//---------------------------------------getGql---------------------------------------------
+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.data) {
+                return Object.values(data.data)[0]
+            }
+            else throw new Error(JSON.stringify(data.errors))
+        })
 
-    const actionPending = promiseName => ({promiseName, type: 'PROMISE', status: 'PENDING'})
-    const actionFulfilled = (promiseName, payload) => ({promiseName, type: 'PROMISE', status: 'FULFILLED', payload})
-    const actionRejected = (promiseName, error) => ({promiseName, type: 'PROMISE', status: 'REJECTED', error})
 
-    const actionPromise = (promiseName, promise) =>
-        async dispatch => {
-            dispatch(actionPending(promiseName)) //сигнализируем redux, что промис начался
-            try {
-                const payload = await promise //ожидаем промиса
-                dispatch(actionFulfilled(promiseName, payload)) //сигнализируем redux, что промис успешно выполнен
-                return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
-            } catch (error) {
-                dispatch(actionRejected(promiseName, error)) //в случае ошибки - сигнализируем redux, что промис несложился
-            }
-        }
+const url = 'http://shop-roles.node.ed.asmer.org.ua/'
+const gql = getGql(url + 'graphql')
+
 
-    const store = createStore(promiseReducer)
+//------------------------------------------------PromiseReducer---------------------------------
+function promiseReducer(state={}, {type, status, payload, error, name}){
+    if (type === 'PROMISE'){
+        return {
+            ...state,
+            [name] : {status, payload, error}
+        }
+    }
+    return state
+}
 
-    store.subscribe(() => console.log(store.getState())) //должен запускаться 6 раз
+const actionPending   = (name) => ({type: 'PROMISE', status: 'PENDING', name})
+const actionFulfilled = (name, payload) => ({type: 'PROMISE', status: 'FULFILLED', name, payload})
+const actionRejected  = (name, error)   => ({type: 'PROMISE', status: 'REJECTED',  name, error})
 
-    store.dispatch(actionPromise('delay', delay(1000)))
-    store.dispatch(actionPromise('luke', fetch("https://swapi.dev/api/people/1").then(res => res.json())))
-    store.dispatch(actionPromise('tatooine', fetch("https://swapi.dev/api/planets/1").then(res => res.json())))
 
-/////////////////////////////////////////////////////////////////////
-function authReducer (state={}, {token, type}){
-    if (type === 'AUTH_LOGIN') {
-        try {
-            let str = token.split('.')[1];
-            let result = JSON.parse(atob(str))
-            return {...state, 'token': token, 'playload':result}
-        } catch (e) {
-            return {}
+//-----------------------------------------------------actionPromise-------------------------------------------
+const actionPromise = (name, promise) =>
+    async dispatch => {
+        dispatch(actionPending(name)) //сигнализируем redux, что промис начался
+        try{
+            const payload = await promise //ожидаем промиса
+            dispatch(actionFulfilled(name, payload)) //сигнализируем redux, что промис успешно выполнен
+            return payload //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
+        }
+        catch (error){
+            dispatch(actionRejected(name, error)) //в случае ошибки - сигнализируем redux, что промис несложился
         }
     }
-    else if (type === 'AUTH_LOGOUT') {
+
+
+//----------------------------------------------------authReducer---------------------------------------
+function authReducer(state={}, {type, token}) {
+    if (type === 'AUTH_LOGOUT'){
+        window.localStorage.removeItem('authToken');
         return {}
     }
+    if(type === "AUTH_LOGIN"){
+        try{
+            window.localStorage.setItem('authToken',token);
+            return {
+                token: token,
+                payload: jwtDecode(token)
+            }
+        }catch (e) {
+        }
+    }
     return state
 }
 
 const actionAuthLogin  = token => ({type: 'AUTH_LOGIN', token})
 const actionAuthLogout = ()    => ({type: 'AUTH_LOGOUT'})
 
-const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiaWQiOiI2Mzc3ZTEzM2I3NGUxZjVmMmVjMWMxMjUiLCJsb2dpbiI6InRlc3Q1IiwiYWNsIjpbIjYzNzdlMTMzYjc0ZTFmNWYyZWMxYzEyNSIsInVzZXIiXX0sImlhdCI6MTY2ODgxMjQ1OH0.t1eQlRwkcP7v9JxUPMo3dcGKprH-uy8ujukNI7xE3A0"
 
-const storeAuth = createStore(authReducer)
-storeAuth.subscribe(() => console.log(storeAuth.getState()))
+//--------------------------------------------------cartReducer------------------------------------------
+function cartReducer  (state = {}, {type, good, count=1}) {
 
-storeAuth.dispatch(actionAuthLogin(token))
+    if (type === 'CART_ADD') {
+        return {
+            ...state,
+            [good._id]: {
+                good,
+                count: +count}
+        }
+    }
+
+    if (type === 'CART_SUB') {
+        if (state([good._id].count - count) <= 0) {
+            delete state[good._id]
+        } else {
+            return {
+                ...state,
+                [good._id]: {
+                    good,
+                    count: state[good._id].count - count}
+            }
+        }
+    }
+
+    if (type === 'CART_DEL') {
+        delete state[good._id]
+        return {...state}
 
-// ///////////////////////////////////////////////////////////////////////
-function cartReducer (state = {}, {type, good, count}) {
-    let goodKey, oldCount, goodValue;
-    if (good) {
-        goodKey = good['_id'];
-        oldCount = state[goodKey]?.count || 0;
-        goodValue = {good, count: oldCount}
     }
-    if (type === 'CARD_ADD') {
-        goodValue.count += count;
-        return {...state, [goodKey]: goodValue}
+
+    if (type === 'CART_SET') {
+        return {
+            ...state,
+            [good._id]: {
+                good,
+                count}
+        }
     }
-    else if (type === 'CART_DEL') {
-        delete state[goodKey];
 
+    if (type === 'CART_CLEAR') {
+        state = {}
     }
+    return state
 }
 
 const actionCartAdd = (good, count=1) => ({type: 'CART_ADD', count, good})
@@ -107,5 +162,506 @@ const actionCartDel = (good) => ({type: 'CART_DEL', good})
 const actionCartSet = (good, count=1) => ({type: 'CART_SET', count, good})
 const actionCartClear = () => ({type: 'CART_CLEAR'})
 
-/////////////////////////////////////////////////////////////////
 
+//---------------------------------------------localStoredReducer---------------------------------
+function localStoredReducer(originalReducer, localStorageKey) {
+    function wrapper(state, action) {
+        if (!state) {
+            try {
+                return JSON.parse(localStorage[localStorageKey])
+            }
+            catch { }
+        }
+        let res = originalReducer(state, action)
+        localStorage[localStorageKey] = JSON.stringify(res)
+        return res;
+    }
+    return wrapper
+}
+
+// -------------------------------------------CombineReducers---------------------------------------
+function combineReducers(reducers){
+    function totalReducer(state={}, action){
+        const newTotalState = {}
+        for (const [reducerName, reducer] of Object.entries(reducers)){
+            const newSubState = reducer(state[reducerName], action)
+            if (newSubState !== state[reducerName]){
+                newTotalState[reducerName] = newSubState
+            }
+        }
+        if (Object.keys(newTotalState).length){
+            return {...state, ...newTotalState}
+        }
+        return state
+    }
+    return totalReducer
+}
+
+
+const totalReducer = combineReducers({
+    promise: promiseReducer,
+    auth: localStoredReducer(authReducer,'auth'),
+    cart: localStoredReducer(cartReducer,'cart')
+})
+
+const store = createStore(totalReducer)
+store.subscribe(() => console.log(store.getState()))
+
+
+//Запрос на список корневых категорий
+const actionRootCats = () =>
+    actionPromise('rootCats', gql(`query rootCats2{
+    CategoryFind(query: "[{\\"parent\\": null}]"){
+            _id 
+            name
+        }   
+    }`))
+
+store.dispatch(actionRootCats())
+
+//Запрос для получения одной категории с товарами и картинками
+const oneCatWithGoods = (_id) =>
+    actionPromise('oneCatWithGoods', gql(`query oneCatWithGoods ($q:String) {
+      CategoryFindOne (query: $q){
+          _id 
+          name 
+          parent{
+            _id 
+            name} 
+          subCategories {
+          _id 
+          name
+        },
+        goods {
+          _id 
+          name 
+          price 
+          description
+          images {
+            url
+          }
+        }
+      }}`,
+        {q: JSON.stringify([{_id}])}
+    ))
+
+
+//Запрос на получение товара с описанием и картинками
+const goodWithDescAndImg = (_id) =>
+    actionPromise('goodWithDescAndImg', gql(`query goodWithDescAndImg ($q:String) {
+      GoodFindOne (query: $q){
+          _id 
+          name
+          price
+          description 
+          images {
+            url
+          }
+    }}`,
+        {q: JSON.stringify([{_id}])}
+    ))
+
+
+// Запрос на регистрацию
+const registration = (login, password) =>
+    actionPromise ('registration', gql(`mutation registration ($login:String, $password: String) {
+    UserUpsert (user: {login: $login, password: $password}) {
+      _id createdAt
+    }
+  }`,
+        {"login" : login, "password": password}
+    ))
+
+
+// Запрос на логин
+const loginUser = (login, password) =>
+    actionPromise(
+        'login',
+        gql(
+            `query log($login: String, $password: String) {
+      login(login: $login, password: $password)
+      }`,
+            {login, password}
+        )
+    )
+
+
+// Запрос истории заказов
+const historyOfOrders = () =>
+    actionPromise('historyOfOrders', gql(`query historyOfOrders ($q: String) {
+      OrderFind(query: $q) {
+        _id
+        total
+        createdAt
+        orderGoods {
+          good {
+            name
+          }
+          price
+          count
+          total
+        }
+        total
+      }
+    }`,
+        {q: JSON.stringify([{}])}
+    ))
+
+store.dispatch(actionRootCats())
+
+
+// Запрос оформления заказа
+const NewOrder = (orderGoods) =>
+    actionPromise('NewOrder', gql(`mutation NewOrder($order: OrderInput) {
+        OrderUpsert(order: $order) {
+          _id
+          orderGoods {
+            _id
+            price
+            count
+            total
+            good {
+              name
+              _id
+              price
+              images {
+                url
+              }
+            }
+          }
+        }
+      }`,
+        {order: {orderGoods}}
+    ))
+
+
+//-----------------------------------Отрисовка категорий-------------------------------------
+store.subscribe(() => {
+    const {status, payload, error} = store.getState().promise.rootCats
+    if (status === 'FULFILLED'){
+        aside.innerHTML = ''
+        for (const {_id, name} of payload){
+            aside.innerHTML += `<a href= "#/category/${_id}">${name}</a>`
+        }
+    }
+})
+
+
+//--------------------------------------отрисовка товаров в категории-----------------------
+store.subscribe(() => {
+    const {status, payload, error} = store.getState().promise?.oneCatWithGoods || {}
+    const [,route] = location.hash.split('/')
+    if(route !== 'category') {
+        return
+    }
+    if (status === 'FULFILLED'){
+        main.innerHTML = ''
+
+        const {name, goods, subCategories} = payload
+        main.innerHTML = `<h1>${name}</h1>`
+
+        if (subCategories !== null) {
+            for (const {_id, name} of subCategories) {
+                main.innerHTML += `<a href= "#/category/${_id}">${name}</a>`
+                console.log(name)
+            }
+        }
+
+        for (const {_id, name, price, images} of goods){
+
+            for (const img of images) {
+                main.innerHTML += `<img src= "${url+ img.url}"> </br>`
+            }
+            main.innerHTML += `<a href= "#/good/${_id}">${name} </br> ${price} грн</a>`
+        }
+    }}
+)
+
+
+//-------------------------------------Отрисовка товара------------------------------------------
+store.subscribe(() => {
+        const {status, payload, error} = store.getState().promise?.goodWithDescAndImg || { }
+        const [,route] = location.hash.split('/')
+        if(route !== 'good') {
+            return
+        }
+
+        if (status === 'FULFILLED'){
+            main.innerHTML = ''
+            const {name, description, images, price} = payload
+
+            main.innerHTML = `<h1>${name}</h1>`
+            for (const img of images) {
+                main.innerHTML += `<img src= "${url+ img.url}">`
+            }
+
+            main.innerHTML += `<p>${description}</p>
+            <p>${price} грн. </p>
+            <button id="buy"> В корзину </button>`
+
+            const buyButton = document.getElementById('buy')
+            cartIcon.innerHTML = ''
+            buyButton.onclick = function () {
+                store.dispatch(actionCartAdd({_id: name, price: price, img: images}))
+            }
+        }
+    }
+)
+
+//----------------------------------Отрисовка цифры в корзине-------------------------------
+store.subscribe(() => {
+    const {cart} = store.getState()
+    let summ = 0
+    for(const {count} of Object.values(cart)) {
+        summ += +count
+    }
+    cartIcon.innerHTML = `<b>${summ}</b>`
+})
+
+
+//-----------------------------------------Логин----------------------------------------
+const loginButton = document.getElementById('login')
+loginForm.append(loginButton)
+loginButton.onclick = () => location.href = `#/login`
+
+const actionFullLogin = (login, password) =>
+    async (dispatch) => {
+        const token = await dispatch(loginUser(login, password))
+
+        if(typeof token === "string"){
+            dispatch(actionAuthLogin(token))
+            main.innerHTML = `<h1>Вы вошли на сайт</h1>`
+
+        } else {
+            main.innerHTML =
+                `<p>Вы ввели неправильные логин или пароль. Повторите попытку </p>
+                <button id="buttonRepeat">Повторить попытку</button>`
+
+            const loginRepeat = document.getElementById('buttonRepeat')
+            loginRepeat.onclick = () => {
+                location.reload()
+                location.href = `#/login`
+            }
+        }
+    }
+
+//-----------------------------------------Авторизация-------------------------------------
+store.subscribe(() => {
+    if(!store.getState().auth) return;
+    const {payload} = store.getState().auth;
+    if(payload){
+        loginForm.innerHTML =
+        `<button id="history"> История заказов </button>
+        <button id="logOut"> Выйти с сайта </button>`
+
+        loginButton.hidden = true
+        registration.hidden = true
+
+        const historyButton = document.getElementById('history')
+        historyButton.onclick = function () {
+            location.href = `#/history`
+        }
+
+        const logOutButton = document.getElementById('logOut')
+        logOutButton.onclick = function () {
+            store.dispatch(actionAuthLogout())
+            main.innerHTML = ` `
+            loginForm.innerHTML = ` `
+            loginButton.hidden = false
+            registration.hidden = false
+        }
+    }
+})
+
+//------------------------------------Регистрация--------------------------------------------
+const registrationButton = document.getElementById('registration')
+loginForm.append(registrationButton)
+registrationButton.onclick = () => location.href = `#/register`
+
+const actionFullRegister = (login, password) =>
+    async (dispatch) => {
+        let userReg = await dispatch(registration(login, password))
+
+        if(userReg){
+            dispatch(actionFullLogin(login,password))
+        } else {
+            main.innerHTML = `Регистрация не удалась. Повторите попытку ещё раз.
+            <button id="buttonRepeatReg">Повторить попытку</button>`
+
+            const buttonRepeatReg = document.getElementById('buttonRepeatReg')
+            buttonRepeatReg.onclick = () => {
+                location.reload()
+                location.href = `#/register`
+            }
+        }
+    }
+
+//-------------------------------------------Заказ-------------------------------------
+const newOrder = () => async (dispatch, getState) => {
+    let { cart } = getState();
+    const orderGoods = Object.entries(cart).map(([_id, { count }]) => ({ good: { _id }, count }));
+
+    let result = await dispatch(NewOrder(orderGoods))
+    if (result?._id) {
+        dispatch(actionCartClear())
+    }
+}
+
+
+//--------------------------------------Корзина------------------------------------------
+store.subscribe ( () => {
+    let cartIcon = document.getElementById('cartIcon')
+    cartIcon.onclick = function myCart() {
+        location.href = `#/cartIcon`
+        console.log(store.getState().cart)
+
+        let storeCart = store.getState().cart
+        main.innerHTML = `<h1>Корзина</h1>`
+
+
+        for (let i=0; i<(Object.keys(storeCart).length); i++){
+
+            let div = document.createElement('div')
+            div.id = i
+            main.append(div)
+            let order = document.getElementById(i)
+
+            let name = Object.keys(storeCart)[i]
+            order.innerHTML += `<p>${store.getState().cart[name].good._id}</p>`
+
+            for (const img of store.getState().cart[name].good.img) {
+                order.innerHTML += `<p><img src= "${url+ img.url}"></p>`
+            }
+
+            order.innerHTML +=
+                `<p>${store.getState().cart[name].count} шт</p>
+                <p>Итого: ${store.getState().cart[name].count * store.getState().cart[name].good.price}  </p>`
+
+            let input = document.createElement('input')
+            input.type = 'number'
+            input.value = store.getState().cart[name].count
+            order.append(input)
+
+            let divForBtn = document.createElement('div')
+            order.append(divForBtn)
+            let button = document.createElement('button')
+            button.id = 'delCartBtn'
+            button.innerText = 'Удалить товар'
+            divForBtn.append(button)
+
+            input.oninput = function () {
+                if (input.value <= 0){
+                    store.dispatch(actionCartDel({_id: name}))
+                    myCart()
+                }
+                console.log(input.value, name)
+
+                store.dispatch(actionCartSet({_id: name, price: store.getState().cart[name].good.price, img: store.getState().cart[name].good.img}, input.value))
+                myCart()
+            }
+
+            button.onclick = function () {
+                store.dispatch(actionCartDel({_id: name}))
+                myCart()
+            }
+        }
+
+        let btnCreateOrder = document.createElement('button')
+        btnCreateOrder.id = 'createOrder'
+        btnCreateOrder.innerText = 'Оформить заказ'
+        main.append(btnCreateOrder)
+
+        const idCreateOrderBtn = document.getElementById('createOrder')
+        if (Object.keys(store.getState().auth).length === 0) {
+            idCreateOrderBtn.disabled = true
+        }
+
+        if (Object.keys(store.getState().auth).length !== 0) {
+            idCreateOrderBtn.disabled = false
+
+            idCreateOrderBtn.onclick = function () {
+                store.dispatch(newOrder())
+                store.dispatch(actionCartClear())
+                myCart()
+            }
+        }
+    }
+})
+
+
+//--------------------------------------------История заказов--------------------------------------
+store.subscribe ( () => {
+    const {status, payload, error} = store.getState().promise?.historyOfOrders || { }
+    const [,route] = location.hash.split('/')
+    if(route !== 'history') {
+        return
+    }
+
+    if (status === 'FULFILLED'){
+        main.innerHTML = `<h1> История заказов </h1>`
+        const {_id, total} = payload
+        console.log(payload)
+
+        for(const order of payload){
+            const {_id, total} = order
+            main.innerHTML +=
+                `<div style="width: 300px; border:  solid skyblue;">
+            <p>Номер заказа: ${_id}</p>
+                <p>Всего: ${total} денег </p>
+            </div>  
+            `
+        }
+    }
+})
+
+window.onhashchange = () => {
+    const [,route, _id] = location.hash.split('/')
+
+    const routes = {
+        category() {
+            store.dispatch(oneCatWithGoods(_id))
+        },
+
+        good(){
+            store.dispatch(goodWithDescAndImg(_id))
+        },
+
+        login(){
+            main.innerHTML =
+                `<h2 id="inputTitle">Вход на сайт:</h2>
+        <input id="loginInput" type="text" name="login" placeholder="Введите логин">
+        <input id="passwordInput" type="password" name="password" placeholder="Введите пароль">
+        <button id="sign_in">Войти</button>`
+
+            const sign_inBtn = document.getElementById('sign_in')
+            sign_inBtn.onclick = function () {
+                store.dispatch(actionFullLogin(loginInput.value, passwordInput.value))
+            }
+        },
+
+        register(){
+            main.innerHTML =
+                `<h2>Регистрация:</h2>
+        <input id="loginReg" type="text" name="login" placeholder="Введите логин">
+        <input id="passwordReg" type="password" name="password" placeholder="Введите пароль">
+        <button id="reg">Зарегистрироваться</button>`
+            const regBtn = document.getElementById('reg')
+
+            regBtn.onclick = function () {
+                store.dispatch(actionFullRegister(loginReg.value, passwordReg.value))
+            }
+        },
+
+        cart(){},
+
+        history(){
+            store.dispatch(historyOfOrders())
+        }
+    }
+
+    if (route in routes){ //если route есть в routes
+        routes[route]() //то запустить функцию, которая там лежит
+    }
+}
+
+window.onhashchange()

BIN
MODULE/logo.png


+ 119 - 0
MODULE/style.css

@@ -0,0 +1,119 @@
+body {
+    padding: 0px;
+    margin: 0px;
+}
+
+#mainContainer {
+    display: flex;
+    background-color: #f1eee7;
+}
+
+main {
+    padding: 20px;
+}
+
+#aside {
+    width: 20%;
+}
+
+#aside > a{
+    display: block;
+    text-decoration: none;
+    color: rgb(88, 88, 88);
+    padding: 7px 15px;
+    border: 1px solid lightgray;
+}
+
+#aside > a:hover {
+    background-color: #e0a4b6;
+}
+
+header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    min-height: 120px;
+    background-color: #e0a4b6;
+}
+
+#logo {
+    padding-left: 30px;
+    width: 170px;
+    height: 75px;
+}
+
+main a {
+    display: block;
+    text-decoration: none;
+    color: rgb(88, 88, 88);
+
+}
+
+#cartIcon {
+    min-width: 60px;
+    min-height: 50px;
+    background-image: url(cart.png);
+    background-size: 70px;
+    margin-right: 20px;
+    border-radius: 4px;
+
+}
+
+#cartIcon b {
+    border-radius: 5px;
+    background-color: #f6d1f1;
+    padding: 6px;
+    font-size: 11px;
+}
+
+img {
+    max-width: 150px;
+    max-height: 150px;
+}
+
+#greeting {
+    border: 1px solid #fff;
+    height: 26px;
+    font-size: 14px;
+}
+
+#login,
+#registration,
+#logOut,
+#history{
+    margin: 10px;
+    padding: 5px 15px;
+    font-size: 14px;
+    box-shadow: 0 5px 6px rgb(65 132 144 / 10%), 0 2px 4px rgb(0 0 0 / 8%);
+    background-color: #efd6d6;
+
+}
+
+#loginInput,
+#passwordInput,
+#sign_in,
+#buy,
+#delCartBtn,
+#loginReg,
+#passwordReg,
+#reg{
+    box-sizing: border-box;
+    text-decoration: none;
+    font-size: 14px;
+    padding: 5px;
+    margin: 15px;
+    border: 1px solid dimgrey;
+}
+
+#ordersLink {
+    border: 1px solid #fff;
+    height: 26px;
+    font-size: 14px;
+}
+
+#createOrder {
+    font-size: 18px;
+    padding: 10px 15px;
+}
+
+

BIN
MODULE/thumbnail_add6f9d1c30d_1x.webp