Bladeren bron

HW 21 done

Illia Kozyr 1 jaar geleden
bovenliggende
commit
313f742223
5 gewijzigde bestanden met toevoegingen van 768 en 110 verwijderingen
  1. 1 1
      Market GraphQL + Redux/script.js
  2. 166 0
      REACT/HW 20/package-lock.json
  3. 3 0
      REACT/HW 20/package.json
  4. 46 10
      REACT/HW 20/src/App.css
  5. 552 99
      REACT/HW 20/src/App.js

+ 1 - 1
Market GraphQL + Redux/script.js

@@ -764,7 +764,7 @@ store.subscribe(() => {
             const card      = document.createElement('div')
             card.style     = 'width: 100%;border-style: groove;border-color: #black;padding: 10px;border-radius: 10px;margin: 5px;' 
             card.innerHTML = `<h3>Order: ${createdAt}</h3>`
-            for (const {count, good} of orderGoods){2
+            for (const {count, good} of orderGoods){{ _id, name, price, images, }
                 const divGood      = document.createElement('div')
                 divGood.style= "display:flex;margin-bottom: 20px;"
                 

+ 166 - 0
REACT/HW 20/package-lock.json

@@ -13,7 +13,10 @@
         "@testing-library/user-event": "^13.5.0",
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
+        "react-redux": "^8.0.2",
         "react-scripts": "5.0.1",
+        "redux": "^4.2.0",
+        "redux-thunk": "^2.4.1",
         "web-vitals": "^2.1.4"
       }
     },
@@ -3747,6 +3750,15 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/hoist-non-react-statics": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+      "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+      "dependencies": {
+        "@types/react": "*",
+        "hoist-non-react-statics": "^3.3.0"
+      }
+    },
     "node_modules/@types/html-minifier-terser": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -4131,6 +4143,11 @@
       "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
       "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
     },
+    "node_modules/@types/use-sync-external-store": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+      "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+    },
     "node_modules/@types/ws": {
       "version": "8.5.3",
       "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@@ -8484,6 +8501,19 @@
         "he": "bin/he"
       }
     },
+    "node_modules/hoist-non-react-statics": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+      "dependencies": {
+        "react-is": "^16.7.0"
+      }
+    },
+    "node_modules/hoist-non-react-statics/node_modules/react-is": {
+      "version": "16.13.1",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+    },
     "node_modules/hoopy": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -13971,6 +14001,49 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
     },
+    "node_modules/react-redux": {
+      "version": "8.0.2",
+      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.2.tgz",
+      "integrity": "sha512-nBwiscMw3NoP59NFCXFf02f8xdo+vSHT/uZ1ldDwF7XaTpzm+Phk97VT4urYBl5TYAPNVaFm12UHAEyzkpNzRA==",
+      "dependencies": {
+        "@babel/runtime": "^7.12.1",
+        "@types/hoist-non-react-statics": "^3.3.1",
+        "@types/use-sync-external-store": "^0.0.3",
+        "hoist-non-react-statics": "^3.3.2",
+        "react-is": "^18.0.0",
+        "use-sync-external-store": "^1.0.0"
+      },
+      "peerDependencies": {
+        "@types/react": "^16.8 || ^17.0 || ^18.0",
+        "@types/react-dom": "^16.8 || ^17.0 || ^18.0",
+        "react": "^16.8 || ^17.0 || ^18.0",
+        "react-dom": "^16.8 || ^17.0 || ^18.0",
+        "react-native": ">=0.59",
+        "redux": "^4"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "@types/react-dom": {
+          "optional": true
+        },
+        "react-dom": {
+          "optional": true
+        },
+        "react-native": {
+          "optional": true
+        },
+        "redux": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/react-redux/node_modules/react-is": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+      "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+    },
     "node_modules/react-refresh": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@@ -14117,6 +14190,22 @@
         "node": ">=8"
       }
     },
+    "node_modules/redux": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz",
+      "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
+      "dependencies": {
+        "@babel/runtime": "^7.9.2"
+      }
+    },
+    "node_modules/redux-thunk": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
+      "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
+      "peerDependencies": {
+        "redux": "^4"
+      }
+    },
     "node_modules/regenerate": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -15784,6 +15873,14 @@
         "punycode": "^2.1.0"
       }
     },
+    "node_modules/use-sync-external-store": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+      "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+      }
+    },
     "node_modules/util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -19299,6 +19396,15 @@
         "@types/node": "*"
       }
     },
+    "@types/hoist-non-react-statics": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+      "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+      "requires": {
+        "@types/react": "*",
+        "hoist-non-react-statics": "^3.3.0"
+      }
+    },
     "@types/html-minifier-terser": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -19633,6 +19739,11 @@
       "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
       "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
     },
+    "@types/use-sync-external-store": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+      "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+    },
     "@types/ws": {
       "version": "8.5.3",
       "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@@ -22781,6 +22892,21 @@
       "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
       "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
     },
+    "hoist-non-react-statics": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+      "requires": {
+        "react-is": "^16.7.0"
+      },
+      "dependencies": {
+        "react-is": {
+          "version": "16.13.1",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+          "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+        }
+      }
+    },
     "hoopy": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -26565,6 +26691,26 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
     },
+    "react-redux": {
+      "version": "8.0.2",
+      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.2.tgz",
+      "integrity": "sha512-nBwiscMw3NoP59NFCXFf02f8xdo+vSHT/uZ1ldDwF7XaTpzm+Phk97VT4urYBl5TYAPNVaFm12UHAEyzkpNzRA==",
+      "requires": {
+        "@babel/runtime": "^7.12.1",
+        "@types/hoist-non-react-statics": "^3.3.1",
+        "@types/use-sync-external-store": "^0.0.3",
+        "hoist-non-react-statics": "^3.3.2",
+        "react-is": "^18.0.0",
+        "use-sync-external-store": "^1.0.0"
+      },
+      "dependencies": {
+        "react-is": {
+          "version": "18.2.0",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+          "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+        }
+      }
+    },
     "react-refresh": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@@ -26678,6 +26824,20 @@
         "strip-indent": "^3.0.0"
       }
     },
+    "redux": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz",
+      "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
+      "requires": {
+        "@babel/runtime": "^7.9.2"
+      }
+    },
+    "redux-thunk": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
+      "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
+      "requires": {}
+    },
     "regenerate": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -27897,6 +28057,12 @@
         "punycode": "^2.1.0"
       }
     },
+    "use-sync-external-store": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+      "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+      "requires": {}
+    },
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

+ 3 - 0
REACT/HW 20/package.json

@@ -8,7 +8,10 @@
     "@testing-library/user-event": "^13.5.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
+    "react-redux": "^8.0.2",
     "react-scripts": "5.0.1",
+    "redux": "^4.2.0",
+    "redux-thunk": "^2.4.1",
     "web-vitals": "^2.1.4"
   },
   "scripts": {

+ 46 - 10
REACT/HW 20/src/App.css

@@ -1,9 +1,8 @@
-body {
-  background-color: black;
-}
-
 .App {
   display: flex;
+  flex-wrap: wrap;
+  max-width: 1250px;
+  margin: 0 auto;
 }
 
 .formBlock {
@@ -37,11 +36,7 @@ button  {
     cursor: pointer;
 }
 
-input:hover {
-  color: orange;
-  background-color: black;
-  transition: 800ms;
-}
+
 
 h2 {
   color: orange;
@@ -50,4 +45,45 @@ h2 {
 .card h3,
 .card h4 {
   color: olivedrab;
-}
+}
+
+.Spoiler  {
+  text-align: center;
+}
+
+
+.Spoiler {
+  background-color: #282c34;
+  min-height: 100vh;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  font-size: calc(10px + 2vmin);
+  color: white;
+}
+
+.Spoiler {
+  color: #61dafb;
+}
+
+
+.error{
+    background-color: red;
+}
+
+input{
+  outline: none;
+}
+
+.PasswordConfirmWrapper{
+  display: flex;
+  flex-direction: column;
+  width: 25%;
+}
+
+
+
+input{
+  outline: none;
+}

+ 552 - 99
REACT/HW 20/src/App.js

@@ -2,17 +2,336 @@ import logo from "./logo.svg";
 import React, { useState } from "react";
 import "./App.css";
 
-const LoginForm = () => {
+import thunk from "redux-thunk";
+import { createStore, combineReducers, applyMiddleware } from "redux";
+import { Provider, connect } from "react-redux";
+
+const store = createStore(
+    combineReducers({
+        auth: authReducer,
+        promise: promiseReducer,
+        cart: localStoreReducer(cartReducer, "cart"),
+    }),
+    applyMiddleware(thunk)
+);
+
+function jwtDecode(token) {
+    try {
+        return JSON.parse(atob(token.split(".")[1]));
+    } catch (e) {}
+}
+
+const getGQL = (url) => (query, variables) =>
+    fetch(url, {
+        method: "POST",
+        headers: {
+            "Content-Type": "application/json",
+            // 'Accept' : '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 backendURL = "http://shop-roles.node.ed.asmer.org.ua";
+const gql = getGQL(backendURL + "/graphql");
+
+function authReducer(state, { type, token }) {
+    if (state === undefined) {
+        if (localStorage.authToken) {
+            type = "AUTH_LOGIN";
+            token = localStorage.authToken;
+        }
+    }
+    if (type === "AUTH_LOGIN") {
+        let payload = jwtDecode(token);
+        if (payload) {
+            localStorage.authToken = token;
+            return { token, payload };
+        }
+    }
+    if (type === "AUTH_LOGOUT") {
+        localStorage.removeItem("authToken");
+        return {};
+    }
+    return state || {};
+}
+
+const actionAuthLogin = (token) => ({ type: "AUTH_LOGIN", token });
+
+const actionAuthLogout = () => (dispatch) => {
+    dispatch({ type: "AUTH_LOGOUT" });
+    localStorage.removeItem("authToken");
+};
+
+function promiseReducer(state = {}, { type, name, status, payload, error }) {
+    if (type === "PROMISE") {
+        return {
+            ...state,
+            [name]: { status, payload, error },
+        };
+    }
+    return state;
+}
+
+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,
+});
+
+const actionPromise = (name, promise) => async (dispatch) => {
+    try {
+        dispatch(actionPending(name));
+        let payload = await promise;
+        dispatch(actionFulfilled(name, payload));
+        return payload;
+    } catch (e) {
+        dispatch(actionRejected(name, e));
+    }
+};
+
+function cartReducer(state = {}, { type, count = 1, good }) {
+    if (type === "CART_ADD") {
+        return {
+            ...state,
+            [good._id]: { count: count + (state[good._id]?.count || 0), good },
+        };
+    }
+
+    if (type === "CART_DELETE") {
+        if (state[good._id].count > 1) {
+            return {
+                ...state,
+                [good._id]: {
+                    count: -count + (state[good._id]?.count || 0),
+                    good,
+                },
+            };
+        }
+
+        if (state[good._id].count === 1) {
+            let { [good._id]: id1, ...newState } = state; //o4en strashnoe koldunstvo
+            //delete newState[good._id]
+            return newState;
+        }
+    }
+
+    if (type === "CART_CLEAR") {
+        return {};
+    }
+    if (type === "CART_REMOVE") {
+        // let newState = {...state}
+        let { [good._id]: id1, ...newState } = state; //o4en strashnoe koldunstvo
+        //delete newState[good._id]
+        return newState;
+    }
+
+    return state;
+}
+
+const actionCartAdd = (good, count = 1) => ({ type: "CART_ADD", good, count });
+const actionCartDelete = (good) => ({ type: "CART_DELETE", good });
+const actionCartClear = () => ({ type: "CART_CLEAR" });
+const actionCartRemove = (good) => ({ type: "CART_REMOVE", good });
+
+function localStoreReducer(reducer, localStorageKey) {
+    function localStoredReducer(state, action) {
+        // Если state === undefined, то достать старый state из local storage
+        if (state === undefined) {
+            try {
+                return JSON.parse(localStorage[localStorageKey]);
+            } catch (e) {}
+        }
+        const newState = reducer(state, action);
+        // Сохранить newState в local storage
+        localStorage[localStorageKey] = JSON.stringify(newState);
+        return newState;
+    }
+    return localStoredReducer;
+}
+
+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 subCategories {
+    name _id
+}
+goods {
+    _id name price images {
+        url
+    }
+}
+}
+}`,
+            { q: JSON.stringify([{ _id }]) }
+        )
+    );
+
+const actionLogin = (login, password) =>
+    actionPromise(
+        "actionLogin",
+        gql(
+            `query log($login:String, $password:String){
+                                  login(login:$login, password:$password)
+                                }`,
+            { login, password }
+        )
+    );
+
+const actionGoodById = (_id) =>
+    actionPromise(
+        "GoodFineOne",
+        gql(
+            `query goodByid($goodId: String) {
+        GoodFindOne(query: $goodId) {
+            _id
+          name
+          price
+          description
+          images {
+            url
+          }
+        }
+      }`,
+            { goodId: JSON.stringify([{ _id }]) }
+        )
+    );
+
+store.dispatch(actionRootCats());
+store.dispatch(actionCatById("6262ca7dbf8b206433f5b3d1"));
+
+const actionFullLogin = (log, pass) => async (dispatch) => {
+    let token = await dispatch(
+        actionPromise(
+            "login",
+            gql(
+                `query login($login: String, $password: String) {
+            login(login: $login, password: $password)
+            }`,
+                { login: log, password: pass }
+            )
+        )
+    );
+    if (token) {
+        dispatch(actionAuthLogin(token));
+    }
+};
+
+const actionFullRegister = (login, password) => async (dispatch) => {
+    let user = await dispatch(
+        actionPromise(
+            "register",
+            gql(
+                `mutation register($login: String, $password: String) {
+                UserUpsert(user: {login: $login, password: $password}) {
+                   _id
+                   login
+                 }
+               }`,
+                { login: login, password: password }
+            )
+        )
+    );
+    if (user) {
+        dispatch(actionFullLogin(login, password));
+    }
+};
+
+const actionOrder = () => async (dispatch, getState) => {
+    let { cart } = getState();
+    const orderGoods = Object.entries(cart).map(([_id, { count }]) => ({
+        good: { _id },
+        count,
+    }));
+
+    let result = await dispatch(
+        actionPromise(
+            "order",
+            gql(
+                `
+                    mutation newOrder($order:OrderInput){
+                    OrderUpsert(order:$order)
+                        { _id total 	}
+                    }
+            `,
+                { order: { orderGoods } }
+            )
+        )
+    );
+    if (result?._id) {
+        dispatch(actionCartClear());
+        document.location.hash = "#/cart/";
+        alert("Purchase completed");
+    }
+};
+
+const orderHistory = () =>
+    actionPromise(
+        "history",
+        gql(` query OrderFind{
+        OrderFind(query:"[{}]"){
+            _id total createdAt orderGoods{
+                count good{
+                    _id name price images{
+                        url
+                    }
+                }
+                owner{
+                    _id login 
+                }
+            }
+        }
+    }
+    `)
+    );
+
+const LoginForm = ({ onLogin }) => {
     const [login, setLogin] = useState("");
     const [password, setPassword] = useState("");
 
-    const onLogin = () => {
-        return !(login !== "" && password.match(/\w{8,30}$/))
+    const checkDisable = () => {
+        return !(login !== "" && password.match(/\w{8,30}$/));
     };
 
     const Authorization = () => {
-        return alert("Welcome, authorization was successful")
-    }
+        onLogin(login, password);
+    };
 
     return (
         <div className="formBlock">
@@ -29,132 +348,266 @@ const LoginForm = () => {
                 value={password}
                 onChange={(e) => setPassword(e.target.value)}
             />
-            <button disabled={onLogin()} onClick = {Authorization}>Log In</button>
+            <button disabled={checkDisable()} onClick={Authorization}>
+                Log In
+            </button>
         </div>
     );
 };
 
+if (localStorage.authToken) {
+    store.dispatch(actionAuthLogin(localStorage.authToken));
+}
+
+store.subscribe(() => console.log(store.getState()));
+
 const cat = [
     {
-        "_id": "62d57ab8b74e1f5f2ec1a148",
-        "name": "Motorola Razr 5G 8/256GB Graphite",
-        "price": 3500,
+        _id: "62d57ab8b74e1f5f2ec1a148",
+        name: "Motorola Razr 5G 8/256GB Graphite",
+        price: 3500,
     },
     {
-        "_id": "62d57c4db74e1f5f2ec1a14a",
-        "name": "Смартфон Google Pixel 6 Pro 12/128GB Stormy Black",
-        "price": 2800,
+        _id: "62d57c4db74e1f5f2ec1a14a",
+        name: "Смартфон Google Pixel 6 Pro 12/128GB Stormy Black",
+        price: 2800,
     },
     {
-        "_id": "62d58318b74e1f5f2ec1a14e",
-        "name": "Microsoft Surface Duo 2 8GB/256GB",
-        "price": 4500,
+        _id: "62d58318b74e1f5f2ec1a14e",
+        name: "Microsoft Surface Duo 2 8GB/256GB",
+        price: 4500,
     },
     {
-        "_id": "62d5869bb74e1f5f2ec1a150",
-        "name": "Смартфон Poco F3 6/128GB EU Arctic White",
-        "price": 1800,
+        _id: "62d5869bb74e1f5f2ec1a150",
+        name: "Смартфон Poco F3 6/128GB EU Arctic White",
+        price: 1800,
     },
     {
-        "_id": "62d58810b74e1f5f2ec1a152",
-        "name": "Мобильный телефон Xiaomi Redmi Note 9 4G (Redmi 9t EU)",
-        "price": 800,
+        _id: "62d58810b74e1f5f2ec1a152",
+        name: "Мобильный телефон Xiaomi Redmi Note 9 4G (Redmi 9t EU)",
+        price: 800,
     },
     {
-        "_id": "62d5a7deb74e1f5f2ec1a154",
-        "name": "LG V50 black REF",
-        "price": 900,
-      
-    }
-]
+        _id: "62d5a7deb74e1f5f2ec1a154",
+        name: "LG V50 black REF",
+        price: 900,
+    },
+];
 
 const rootCats = [
-  {
-      "name": "test3"
-  },
-  {
-      "name": "Tools"
-  },
-  {
-      "name": "Tomatoes"
-  },
-  {
-      "name": "123"
-  },
-  {
-      "name": "iPhone"
-  },
-  {
-      "name": "Samsung"
-  },
-  {
-      "name": "Smartphone"
-  },
-  {
-      "name": "Large home appliances"
-  },
-  {
-      "name": "Garden"
-  },
-  {
-      "name": "Children's products"
-  },
-  {
-      "name": " Hobbies and sports"
-  },
-  {
-      "name": "Sale"
-  }
-]
-
-const CategoryMenu = () => {
-  return (
-      <>
-          <aside id='aside'>
-              <ul>
-                  {rootCats.map(cat => <CategoryMenuItem name={cat.name}/>)}
-              </ul>
-          </aside>
-      </>
-  );
+    {
+        name: "test3",
+    },
+    {
+        name: "Tools",
+    },
+    {
+        name: "Tomatoes",
+    },
+    {
+        name: "123",
+    },
+    {
+        name: "iPhone",
+    },
+    {
+        name: "Samsung",
+    },
+    {
+        name: "Smartphone",
+    },
+    {
+        name: "Large home appliances",
+    },
+    {
+        name: "Garden",
+    },
+    {
+        name: "Children's products",
+    },
+    {
+        name: " Hobbies and sports",
+    },
+    {
+        name: "Sale",
+    },
+];
+
+const CategoryMenu = ({rootCats = []}) => {
+    return (
+        <>
+            <aside id="aside">
+                <ul>
+                    {rootCats.map((cat) => (
+                        <CategoryMenuItem name={cat.name} />
+                    ))}
+                </ul>
+            </aside>
+        </>
+    );
 };
 
-const CategoryMenuItem = ({name}) => {
-  return (
-      <>
-          <h2>{name}</h2>
-      </>
-  );
+const CCategoryMenu = connect((state) => ({rootCats: state.promise?.rootCats.payload}))(CategoryMenu)
+
+const CategoryMenuItem = ({ name }) => {
+    return (
+        <>
+            <h2>{name}</h2>
+        </>
+    );
 };
 
+const Category = ({cat = []}) => {
+    return (
+        <div className="cardBlock">
+            {cat.map((item) => (
+                <GoodCard name={item.name} price={item.price} />
+            ))}
+        </div>
+    );
+};
 
+const CCategory = connect((state) => ({cat: state.promise?.catById.payload?.goods}))(Category)
 
-const Category = () => {
-  return (
-      <div className="cardBlock">
-          {cat.map(item => <GoodCard name={item.name} price={item.price}/>)}
-      </div>
-  );
+const GoodCard = ({ name, price }) => {
+    return (
+        <div className="card">
+            <h3>{name}</h3>
+            <h4>{price}</h4>
+        </div>
+    );
 };
 
+const CLoginForm = connect(null, { onLogin: actionFullLogin })(LoginForm);
 
+const Spoiler = ({header = '+', open, children}) => {
+    const [isOpen, setIsOpen] = useState(open);
 
-const GoodCard = ({name, price}) => {
-  return (
-      <div className='card'>
-          <h3>{name}</h3>
-          <h4>{price}</h4>
-      </div>
-  );
+    return (
+        <div>
+            <div onClick={() => {
+                setIsOpen(!isOpen);
+            }}>
+                <span>{header}</span>
+                {isOpen && (
+                    <div>
+                        {children}
+                    </div>
+                )}
+            </div>
+        </div>
+    );
 };
 
-function App() {
+const SpoilerParent = () => {
+
     return (
-        <div className="App">
-            <LoginForm />
-            <CategoryMenu />
-            <Category />
+        <div>
+            <Spoiler header={<h1>Заголовок</h1>} open>
+                Контент 1
+                <p>
+                лорем ипсум траливали и тп.лорем ипсум траливали и тп.лорем ипсум траливали и тп.лорем ипсум траливали и тп.лорем ипсум траливали и тп.
+                </p>
+            </Spoiler>
+
+            <Spoiler>
+                <h2>Контент 2</h2>
+                <p>
+                лорем ипсум траливали и тп.лорем ипсум траливали и тп.лорем ипсум траливали и тп.лорем ипсум траливали и тп.лорем ипсум траливали и тп.
+                </p>
+            </Spoiler>
         </div>
     );
+};
+
+class RangeInput extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            value: '',
+        };
+    }
+
+    inputChange = (e) => {
+        this.setState({value: e.target.value});
+    };
+
+    render() {
+        const {min, max} = this.props;
+        const hasError = this.state.value.length > max || this.state.value.length < min;
+        return (
+            <div>
+                <input onChange={this.inputChange} className={hasError ? 'error' : ''}/>
+            </div>
+        );
+    }
+}
+
+const RangeInputParent = () => {
+    return(
+        <RangeInput min={2} max={10} />
+    )
+ }
+
+ class PasswordConfirm extends React.Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            password: '',
+            passwordConfirm: '',
+        };
+    }
+
+    inputChange = (e) => {
+        this.setState({[e.target.name]: e.target.value})
+    };
+
+    render() {
+        const {min} = this.props;
+        const arePasswordsEqual = this.state.password === this.state.passwordConfirm;
+        const hasErrorPsw = this.state.password.length < min || !arePasswordsEqual;
+        const hasErrorPswConfirm = this.state.passwordConfirm.length < min || !arePasswordsEqual;
+
+        return (
+            <div className="PasswordConfirmWrapper">
+                <input placeholder={'password'}
+                       name={'password'}
+                       type={'password'}
+                       className={hasErrorPsw ? 'error' : ''}
+                       onChange={this.inputChange}
+                />
+                <input placeholder={'confirm password'}
+                       name={'passwordConfirm'}
+                       type={'password'}
+                       className={hasErrorPswConfirm ? 'error' : ''}
+                       onChange={this.inputChange}/>
+            </div>
+        );
+    }
+}
+
+class PasswordConfirmParent extends React.Component{
+    render() {
+        return(
+            <div>
+                <PasswordConfirm min={2} />
+            </div>
+        )
+    }
+}
+
+function App() {
+    return (
+        <Provider store={store}>
+            <div className="App">
+                <SpoilerParent></SpoilerParent>
+                <RangeInputParent></RangeInputParent>
+                <PasswordConfirmParent></PasswordConfirmParent>
+                <CLoginForm />
+                <CCategoryMenu />
+                <CCategory />
+            </div>
+        </Provider>
+    );
 }
 export default App;