Jelajahi Sumber

+CartPage | +GoodPage

ilya_shyian 2 tahun lalu
induk
melakukan
babf911916

+ 166 - 1
package-lock.json

@@ -13,6 +13,7 @@
         "@testing-library/jest-dom": "^5.16.4",
         "@testing-library/react": "^13.2.0",
         "@testing-library/user-event": "^13.5.0",
+        "formik": "^2.2.9",
         "node-sass": "^7.0.1",
         "react": "^18.1.0",
         "react-dom": "^18.1.0",
@@ -24,7 +25,8 @@
         "redux": "^4.2.0",
         "redux-devtools-extension": "^2.13.9",
         "redux-thunk": "^2.4.1",
-        "web-vitals": "^2.1.4"
+        "web-vitals": "^2.1.4",
+        "yup": "^0.32.11"
       }
     },
     "node_modules/@ampproject/remapping": {
@@ -3770,6 +3772,11 @@
       "version": "0.0.29",
       "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4="
     },
+    "node_modules/@types/lodash": {
+      "version": "4.14.182",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
+      "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q=="
+    },
     "node_modules/@types/mime": {
       "version": "1.3.2",
       "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
@@ -7839,6 +7846,42 @@
         "node": ">= 6"
       }
     },
+    "node_modules/formik": {
+      "version": "2.2.9",
+      "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.9.tgz",
+      "integrity": "sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://opencollective.com/formik"
+        }
+      ],
+      "dependencies": {
+        "deepmerge": "^2.1.1",
+        "hoist-non-react-statics": "^3.3.0",
+        "lodash": "^4.17.21",
+        "lodash-es": "^4.17.21",
+        "react-fast-compare": "^2.0.1",
+        "tiny-warning": "^1.0.2",
+        "tslib": "^1.10.0"
+      },
+      "peerDependencies": {
+        "react": ">=16.8.0"
+      }
+    },
+    "node_modules/formik/node_modules/deepmerge": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
+      "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/formik/node_modules/tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+    },
     "node_modules/forwarded": {
       "version": "0.2.0",
       "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
@@ -11116,6 +11159,11 @@
       "version": "4.17.21",
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
     },
+    "node_modules/lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+    },
     "node_modules/lodash.debounce": {
       "version": "4.0.8",
       "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
@@ -11586,6 +11634,11 @@
       "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
       "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ=="
     },
+    "node_modules/nanoclone": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz",
+      "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA=="
+    },
     "node_modules/nanoid": {
       "version": "3.3.4",
       "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
@@ -13469,6 +13522,11 @@
       "version": "16.13.1",
       "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
     },
+    "node_modules/property-expr": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz",
+      "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA=="
+    },
     "node_modules/proxy-addr": {
       "version": "2.0.7",
       "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
@@ -13757,6 +13815,11 @@
       "version": "6.0.11",
       "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
     },
+    "node_modules/react-fast-compare": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
+      "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
+    },
     "node_modules/react-icons": {
       "version": "4.3.1",
       "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz",
@@ -15690,6 +15753,11 @@
       "version": "1.1.0",
       "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="
     },
+    "node_modules/tiny-warning": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+      "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+    },
     "node_modules/tmpl": {
       "version": "1.0.5",
       "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="
@@ -15718,6 +15786,11 @@
         "node": ">=0.6"
       }
     },
+    "node_modules/toposort": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+      "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA="
+    },
     "node_modules/tough-cookie": {
       "version": "4.0.0",
       "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==",
@@ -16922,6 +16995,23 @@
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
+    },
+    "node_modules/yup": {
+      "version": "0.32.11",
+      "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz",
+      "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==",
+      "dependencies": {
+        "@babel/runtime": "^7.15.4",
+        "@types/lodash": "^4.14.175",
+        "lodash": "^4.17.21",
+        "lodash-es": "^4.17.21",
+        "nanoclone": "^0.2.1",
+        "property-expr": "^2.0.4",
+        "toposort": "^2.0.2"
+      },
+      "engines": {
+        "node": ">=10"
+      }
     }
   },
   "dependencies": {
@@ -19390,6 +19480,11 @@
       "version": "0.0.29",
       "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4="
     },
+    "@types/lodash": {
+      "version": "4.14.182",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
+      "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q=="
+    },
     "@types/mime": {
       "version": "1.3.2",
       "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
@@ -22309,6 +22404,32 @@
         "mime-types": "^2.1.12"
       }
     },
+    "formik": {
+      "version": "2.2.9",
+      "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.9.tgz",
+      "integrity": "sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA==",
+      "requires": {
+        "deepmerge": "^2.1.1",
+        "hoist-non-react-statics": "^3.3.0",
+        "lodash": "^4.17.21",
+        "lodash-es": "^4.17.21",
+        "react-fast-compare": "^2.0.1",
+        "tiny-warning": "^1.0.2",
+        "tslib": "^1.10.0"
+      },
+      "dependencies": {
+        "deepmerge": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
+          "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA=="
+        },
+        "tslib": {
+          "version": "1.14.1",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+          "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+        }
+      }
+    },
     "forwarded": {
       "version": "0.2.0",
       "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
@@ -24613,6 +24734,11 @@
       "version": "4.17.21",
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
     },
+    "lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+    },
     "lodash.debounce": {
       "version": "4.0.8",
       "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
@@ -24957,6 +25083,11 @@
       "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
       "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ=="
     },
+    "nanoclone": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz",
+      "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA=="
+    },
     "nanoid": {
       "version": "3.3.4",
       "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
@@ -26138,6 +26269,11 @@
         }
       }
     },
+    "property-expr": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz",
+      "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA=="
+    },
     "proxy-addr": {
       "version": "2.0.7",
       "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
@@ -26336,6 +26472,11 @@
       "version": "6.0.11",
       "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
     },
+    "react-fast-compare": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
+      "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
+    },
     "react-icons": {
       "version": "4.3.1",
       "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz",
@@ -27752,6 +27893,11 @@
       "version": "1.1.0",
       "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="
     },
+    "tiny-warning": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+      "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+    },
     "tmpl": {
       "version": "1.0.5",
       "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="
@@ -27771,6 +27917,11 @@
       "version": "1.0.1",
       "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
     },
+    "toposort": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+      "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA="
+    },
     "tough-cookie": {
       "version": "4.0.0",
       "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==",
@@ -28689,6 +28840,20 @@
     "yocto-queue": {
       "version": "0.1.0",
       "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
+    },
+    "yup": {
+      "version": "0.32.11",
+      "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz",
+      "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==",
+      "requires": {
+        "@babel/runtime": "^7.15.4",
+        "@types/lodash": "^4.14.175",
+        "lodash": "^4.17.21",
+        "lodash-es": "^4.17.21",
+        "nanoclone": "^0.2.1",
+        "property-expr": "^2.0.4",
+        "toposort": "^2.0.2"
+      }
     }
   }
 }

+ 3 - 1
package.json

@@ -9,6 +9,7 @@
     "@testing-library/jest-dom": "^5.16.4",
     "@testing-library/react": "^13.2.0",
     "@testing-library/user-event": "^13.5.0",
+    "formik": "^2.2.9",
     "node-sass": "^7.0.1",
     "react": "^18.1.0",
     "react-dom": "^18.1.0",
@@ -20,7 +21,8 @@
     "redux": "^4.2.0",
     "redux-devtools-extension": "^2.13.9",
     "redux-thunk": "^2.4.1",
-    "web-vitals": "^2.1.4"
+    "web-vitals": "^2.1.4",
+    "yup": "^0.32.11"
   },
   "scripts": {
     "start": "react-scripts start",

+ 116 - 0
src/actions/actionCatById.js

@@ -0,0 +1,116 @@
+import { mock, query } from '../helpers';
+
+import { actionPromise } from '../reducers';
+
+export const actionCatById = (_id) => async (dispatch, getState) => {
+    dispatch(
+        actionPromise(
+            'catById',
+            new Promise((resolve) => {
+                setTimeout(
+                    Math.random() > 0.01
+                        ? resolve({
+                              data: {
+                                  _id: 1,
+                                  name: 'Category 1',
+                                  subcategories: [
+                                      { _id: 11, name: 'Category 1' },
+                                      { _id: 12, name: 'Category 1' },
+                                  ],
+                                  goods: [
+                                      {
+                                          _id: 1,
+                                          name: 'Good 1',
+                                          description: 'adaadasda asasd asd asd asd asd ',
+                                          price: '999',
+                                          images: [
+                                              {
+                                                  url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                              },
+                                              {
+                                                  url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                              },
+                                          ],
+                                      },
+                                      {
+                                          _id: 2,
+                                          name: 'Good 2',
+                                          description: 'adaadasda asasd asd asd asd asd ',
+                                          price: '999',
+                                          images: [
+                                              {
+                                                  url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                              },
+                                              {
+                                                  url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                              },
+                                          ],
+                                      },
+                                      {
+                                          _id: 3,
+                                          name: 'Good 3',
+                                          description: 'adaadasda asasd asd asd asd asd ',
+                                          price: '999',
+                                          images: null,
+                                      },
+                                      {
+                                          _id: 4,
+                                          name: 'Good 4',
+                                          description: 'adaadasda asasd asd asd asd asd ',
+                                          price: '999',
+                                          images: [
+                                              {
+                                                  url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                              },
+                                              {
+                                                  url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                              },
+                                          ],
+                                      },
+                                      {
+                                          _id: 6,
+                                          name: 'Good 5',
+                                          description: 'adaadasda asasd asd asd asd asd ',
+                                          price: '999',
+                                          images: [
+                                              {
+                                                  url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                              },
+                                              {
+                                                  url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                              },
+                                          ],
+                                      },
+                                      {
+                                          id: 6,
+                                          name: 'Good 6',
+                                          description: 'adaadasda asasd asd asd asd asd ',
+                                          price: '999',
+                                          images: [
+                                              {
+                                                  url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                              },
+                                              {
+                                                  url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                              },
+                                          ],
+                                      },
+                                  ],
+                              },
+                          })
+                        : resolve({
+                              errors: [{ message: 'Error adsasdadas' }],
+                          }),
+                    400
+                );
+            })
+                // .then((res) => res.json())
+                .then((data) => {
+                    console.log(data);
+                    if (data.errors) {
+                        throw new Error(JSON.stringify(data.errors));
+                    } else return data.data;
+                })
+        )
+    );
+};

+ 43 - 0
src/actions/actionGoodById.js

@@ -0,0 +1,43 @@
+import { mock, query } from '../helpers';
+
+import { actionPromise } from '../reducers';
+
+export const actionGoodById = (_id) => async (dispatch, getState) => {
+    dispatch(
+        actionPromise(
+            'goodById',
+            new Promise((resolve) => {
+                setTimeout(
+                    Math.random() > 0.01
+                        ? resolve({
+                              data: {
+                                  id: 6,
+                                  name: 'Good 6',
+                                  description: 'adaadasda asasd asd asd asd asd ',
+                                  price: '999',
+                                  images: [
+                                      {
+                                          url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                      },
+                                      {
+                                          url: 'https://content2.rozetka.com.ua/goods/images/big/183546719.jpg',
+                                      },
+                                  ],
+                              },
+                          })
+                        : resolve({
+                              errors: [{ message: 'Error adsasdadas' }],
+                          }),
+                    400
+                );
+            })
+                // .then((res) => res.json())
+                .then((data) => {
+                    console.log(data);
+                    if (data.errors) {
+                        throw new Error(JSON.stringify(data.errors));
+                    } else return data.data;
+                })
+        )
+    );
+};

+ 29 - 0
src/actions/actionNewOrder.js

@@ -0,0 +1,29 @@
+import { mock, query } from '../helpers';
+
+import { actionPromise } from '../reducers';
+
+export const actionNewOrder = () => async (dispatch, getState) => {
+    dispatch(
+        actionPromise(
+            'newOrder',
+            new Promise((resolve) => {
+                setTimeout(
+                    Math.random() > 0.01
+                        ? resolve({
+                              data: { _id: '12313' },
+                          })
+                        : resolve({
+                              errors: [{ message: 'Error adsasdadas' }],
+                          }),
+                    400
+                );
+            })
+                // .then((res) => res.json())
+                .then((data) => {
+                    if (data.errors) {
+                        throw new Error(JSON.stringify(data.errors));
+                    } else return Object.values(data.data);
+                })
+        )
+    );
+};

+ 84 - 0
src/components/CartPage/CartItem.js

@@ -0,0 +1,84 @@
+import { Box, width } from '@mui/system';
+import defaultGoodImage from '../../images/default-good-image.png';
+import { IoCloseOutline } from 'react-icons/io5';
+import { AiOutlinePlus, AiOutlineMinus } from 'react-icons/ai';
+import { actionCartChange } from '../../reducers';
+import { useEffect, useState } from 'react';
+import { useDispatch } from 'react-redux';
+
+const {
+    Typography,
+    Stack,
+    IconButton,
+    TextField,
+    ButtonGroup,
+    Button,
+    TableCell,
+    TableRow,
+    Input,
+} = require('@mui/material');
+
+export const CartItem = ({ order, onDeleteClick }) => {
+    const {
+        good: { _id, images = [], name = '', price = 0 },
+        count = 1,
+    } = order || {};
+
+    const dispatch = useDispatch();
+    const [countInput, setCountInput] = useState(count || 1);
+
+    useEffect(() => {
+        setCountInput(+count);
+    }, [count]);
+
+    useEffect(() => {
+        dispatch(actionCartChange({ _id, name, images, price }, +countInput));
+    }, [countInput]);
+
+    const handleChange = (count) => {
+        if (count >= 0 && count <= 99) {
+            setCountInput(+count);
+        }
+    };
+
+    return (
+        <TableRow className="CartItem">
+            <TableCell>
+                <Box
+                    component="img"
+                    src={images && images[0]?.url ? `${images ? images[0]?.url : ''}` : defaultGoodImage}
+                    sx={{ width: 50 }}
+                />
+            </TableCell>
+            <TableCell>
+                <Box sx={{ flexGrow: 1 }}>
+                    <Typography variant="h5">{name}</Typography>
+                    <Typography variant="body1">{price}</Typography>
+                </Box>
+            </TableCell>
+            <TableCell>
+                <Stack justifyContent="center" direction="row" alignItems="center">
+                    <IconButton onClick={() => handleChange(countInput - 1)}>
+                        <AiOutlineMinus />
+                    </IconButton>
+                    <Typography>{countInput}</Typography>
+                    <IconButton onClick={() => handleChange(countInput + 1)}>
+                        <AiOutlinePlus />
+                    </IconButton>
+                </Stack>
+            </TableCell>
+            <TableCell>
+                <Stack justifyContent="center">
+                    <Typography variant="body1" textAlign="center">
+                        {price * count}
+                    </Typography>
+                </Stack>
+            </TableCell>
+            <TableCell>
+                <IconButton onClick={() => onDeleteClick({ _id, images, name, price })}>
+                    <IoCloseOutline />
+                </IconButton>
+            </TableCell>
+        </TableRow>
+    );
+};

+ 151 - 0
src/components/CartPage/OrderForm/index.js

@@ -0,0 +1,151 @@
+import { Box, Grid, TextField, MenuItem, Button } from '@mui/material';
+import * as Yup from 'yup';
+import { useFormik } from 'formik';
+
+const deliveryOptions = [
+    { label: 'Нова пошта', value: 'nova-poshta' },
+    { label: 'Justin', value: 'justin' },
+];
+
+const phoneRegExp =
+    /^((\\+[1-9]{1,4}[ \\-]*)|(\\([0-9]{2,3}\\)[ \\-]*)|([0-9]{2,4})[ \\-]*)*?[0-9]{3,4}?[ \\-]*[0-9]{3,4}?$/;
+const orderSchema = Yup.object().shape({
+    name: Yup.string().min(3, 'не меньше 3 символів').max(22, 'не більше 22 символів').required("обов'язкове"),
+    surname: Yup.string().min(3, 'не меньше 3 символів').max(22, 'не більше 22 символів').required("обов'язкове"),
+    email: Yup.string().email('не вірний формат').required("обов'язкове"),
+    address: Yup.string().required("обов'язкове"),
+    phoneNumber: Yup.string().matches(phoneRegExp, 'не вірний формат').required("обов'язкове"),
+    delivery: Yup.string()
+        .required("обов'язкове")
+        .oneOf(
+            deliveryOptions.map((option) => option.value),
+            'не знайдено'
+        ),
+});
+
+export const OrderForm = ({ onSubmit = null }) => {
+    const formik = useFormik({
+        initialValues: {
+            name: '',
+            surname: '',
+            email: '',
+            address: '',
+            phoneNumber: '',
+            delivery: '',
+        },
+        validationSchema: orderSchema,
+        validateOnChange: true,
+        onSubmit: () => {
+            onSubmit(formik.values);
+        },
+    });
+
+    return (
+        <Box className="OrderForm" component="form" onSubmit={formik.handleSubmit}>
+            <Grid container spacing={2} rowSpacing={1}>
+                <Grid item xs={6}>
+                    <TextField
+                        id="name"
+                        name="name"
+                        variant="outlined"
+                        label="Ім'я"
+                        size="small"
+                        error={formik.touched.name && Boolean(formik.errors.name)}
+                        value={formik.values.name}
+                        onBlur={formik.handleBlur}
+                        onChange={formik.handleChange}
+                        helperText={formik.touched.name && formik.errors.name}
+                        fullWidth
+                    />
+                </Grid>
+                <Grid item xs={6}>
+                    <TextField
+                        id="surname"
+                        name="surname"
+                        variant="outlined"
+                        label="Прізвище"
+                        size="small"
+                        error={formik.touched.surname && Boolean(formik.errors.surname)}
+                        value={formik.values.surname}
+                        onBlur={formik.handleBlur}
+                        onChange={formik.handleChange}
+                        helperText={formik.touched.surname && formik.errors.surname}
+                        fullWidth
+                    />
+                </Grid>
+                <Grid item xs={6}>
+                    <TextField
+                        id="email"
+                        name="email"
+                        variant="outlined"
+                        label="Email"
+                        size="small"
+                        error={formik.touched.email && Boolean(formik.errors.email)}
+                        value={formik.values.email}
+                        onBlur={formik.handleBlur}
+                        onChange={formik.handleChange}
+                        helperText={formik.touched.email && formik.errors.email}
+                        fullWidth
+                    />
+                </Grid>
+                <Grid item xs={6}>
+                    <TextField
+                        id="phoneNumber"
+                        name="phoneNumber"
+                        variant="outlined"
+                        label="Номер телефону"
+                        size="small"
+                        error={formik.touched.phoneNumber && Boolean(formik.errors.phoneNumber)}
+                        value={formik.values.phoneNumber}
+                        onBlur={formik.handleBlur}
+                        onChange={formik.handleChange}
+                        helperText={formik.touched.phoneNumber && formik.errors.phoneNumber}
+                        fullWidth
+                    />
+                </Grid>
+                <Grid item xs={6}>
+                    <TextField
+                        id="address"
+                        name="address"
+                        variant="outlined"
+                        label="Адреса доставки"
+                        size="small"
+                        error={formik.touched.address && Boolean(formik.errors.address)}
+                        value={formik.values.address}
+                        onBlur={formik.handleBlur}
+                        onChange={formik.handleChange}
+                        helperText={formik.touched.address && formik.errors.address}
+                        fullWidth
+                    />
+                </Grid>
+                <Grid item xs={6}>
+                    <TextField
+                        id="delivery"
+                        name="delivery"
+                        variant="outlined"
+                        label="Тип доставкі"
+                        size="small"
+                        extAlign="left"
+                        select
+                        value={formik.values.delivery}
+                        onBlur={formik.handleBlur}
+                        onChange={formik.handleChange}
+                        helperText={formik.touched.delivery && formik.errors.delivery}
+                        fullWidth
+                    >
+                        {deliveryOptions.map((option) => (
+                            <MenuItem key={option.value} value={option.value} t>
+                                {option.label}
+                            </MenuItem>
+                        ))}
+                    </TextField>
+                </Grid>
+                <Grid item xs={12} display="flex" justifyContent="flex-end">
+                    <Button variant="contained" type="submit" disabled={formik.isSubmitting || !formik.isValid}>
+                        Підтвердити
+                    </Button>
+                </Grid>
+            </Grid>
+        </Box>
+    );
+};

+ 50 - 0
src/components/CartPage/index.js

@@ -0,0 +1,50 @@
+import { Box, Button, Stack, Table, TableBody, TableCell, TableRow, Typography } from '@mui/material';
+import { useEffect } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
+import { actionNewOrder } from '../../actions/actionNewOrder';
+import { actionCartDelete } from '../../reducers';
+import { CartItem } from './CartItem';
+import { OrderForm } from './OrderForm';
+
+export const CartPage = () => {
+    const cart = useSelector((state) => state.cart || {});
+    const sum = Object.entries(cart).reduce((prev, [_id, order]) => prev + order.count * order.good.price, 0);
+    const dispatch = useDispatch();
+    const navigate = useNavigate();
+
+    useEffect(() => {
+        if (!Object.entries(cart).length) {
+            navigate('/');
+        }
+    }, []);
+
+    return (
+        <Box className="CartPage">
+            <Stack spacing={2}>
+                <Typography>Оформлення замовлення</Typography>
+                <Table className="table">
+                    <TableBody>
+                        {Object.entries(cart).map(([_id, order]) => (
+                            <CartItem order={order} onDeleteClick={(good) => dispatch(actionCartDelete(good))} />
+                        ))}
+
+                        <TableRow>
+                            <TableCell colSpan={3}>
+                                <Typography variant="body1" bold>
+                                    Всього:
+                                </Typography>
+                            </TableCell>
+                            <TableCell>
+                                <Typography textAlign="center">{sum}</Typography>
+                            </TableCell>
+                            <TableCell></TableCell>
+                        </TableRow>
+                    </TableBody>
+                </Table>
+
+                <OrderForm />
+            </Stack>
+        </Box>
+    );
+};

+ 44 - 0
src/components/GoodPage/index.js

@@ -0,0 +1,44 @@
+import { useEffect, useState } from 'react';
+import { CBuyButton } from '../common/BuyButton';
+import { useSelector } from 'react-redux';
+import defaultGoodImage from '../../images/default-good-image.png';
+import { Divider, Grid, Paper, Stack, Table, TableBody, TableCell, TableRow, Typography } from '@mui/material';
+import { Box } from '@mui/system';
+import { Carousel } from 'react-responsive-carousel';
+
+export const GoodPage = () => {
+    const good = useSelector((state) => state.promise?.goodById?.payload || {});
+    const { _id = '', name = '', price = '', description = '', images = [] } = good || {};
+    return (
+        <Box className="GoodPage">
+            <Grid container spacing={2} className="images">
+                <Grid item xs={12} md={4}>
+                    <Carousel showIndicators={false} showStatus={false}>
+                        {(good.images || [{ url: defaultGoodImage }]).map((image) => (
+                            <img src={image?.url ? `${image?.url}` : defaultGoodImage} />
+                        ))}
+                    </Carousel>
+                </Grid>
+                <Grid item xs={12} md={8} className="content">
+                    <Stack spacing={2}>
+                        <Stack direction="row" alignItems="center" spacing={2}>
+                            <Typography variant="body1" color="#1C1B1F">
+                                <b>Ціна:</b> {price}
+                            </Typography>
+                            <CBuyButton good={{ name, images, price, _id }} />
+                        </Stack>
+                        <Divider />
+                        <Typography variant="h5">
+                            <b>Назва: {name}</b>
+                        </Typography>
+                        <Typography variant="body1">
+                            Опис:
+                            <br />
+                            {description}
+                        </Typography>
+                    </Stack>
+                </Grid>
+            </Grid>
+        </Box>
+    );
+};

+ 24 - 0
src/components/GoodsPage/index.js

@@ -0,0 +1,24 @@
+import { Grid } from '@mui/material';
+import { Box } from '@mui/system';
+import { connect } from 'react-redux';
+import { useParams } from 'react-router-dom';
+import { GoodCard } from '../common/GoodCard';
+
+const GoodsPage = ({ category = {} }) => {
+    const { goods = [], name = '' } = category || {};
+
+    return (
+        <Box className="MainPage">
+            <Grid container spacing={2}>
+                {(goods || []).map((good) => (
+                    <Grid item xs={3}>
+                        <GoodCard good={good} />
+                    </Grid>
+                ))}
+            </Grid>
+        </Box>
+    );
+};
+
+const CGoodsPage = connect((state) => ({ category: state?.promise?.catById?.payload || [] }))(GoodsPage);
+export { GoodsPage, CGoodsPage };

+ 30 - 6
src/components/Root/index.js

@@ -1,4 +1,4 @@
-import { Route, Router, Routes } from 'react-router-dom';
+import { Route, Router, Routes, useNavigate, useParams } from 'react-router-dom';
 
 import { Container, Box, Stack, Grid } from '@mui/material';
 
@@ -11,10 +11,32 @@ import { Header } from '../layout/Header';
 import { Footer } from '../layout/Footer';
 import { MainPage } from '../MainPage';
 import { actionPageStart } from '../../actions/actionPageStart';
+import { CGoodsPage } from '../GoodsPage';
+import { useDispatch, useSelector } from 'react-redux';
+import { actionCatById } from '../../actions/actionCatById';
+import { CartPage } from '../CartPage';
+import { actionGoodById } from '../../actions/actionGoodById';
+import { GoodPage } from '../GoodPage';
+
+const GoodsPageContainer = () => {
+    const params = useParams();
+    const dispatch = useDispatch();
+    dispatch(actionCatById(params._id));
+
+    return <CGoodsPage />;
+};
+
+const GoodPageContainer = () => {
+    const params = useParams();
+    const dispatch = useDispatch();
+    dispatch(actionGoodById(params._id));
+    return <GoodPage />;
+};
 
 const Root = ({ user = {} }) => {
     const isSignIn = true;
-    store.dispatch(actionPageStart());
+    const dispatch = useDispatch();
+    dispatch(actionPageStart());
 
     return (
         <Box className="Root">
@@ -27,10 +49,12 @@ const Root = ({ user = {} }) => {
                     <Content>
                         <Routes>
                             <Route path="/" exact element={<MainPage />} />
-                            {/* <Route path="/good/:id" />
-                        <Route path="/category/:id" />
-                        <Route path="/category/" />
-                        <Route path="/good/" />
+                            <Route path="/cart" exact element={<CartPage />} />
+                            <Route path="/category/:_id" element={<GoodsPageContainer />} />
+                            <Route path="/category/" element={<GoodsPageContainer />} />
+                            <Route path="/good/:id" element={<GoodPageContainer />} />
+                            {/*
+
 
                         <CProtectedRoute path="/admin" component={AdminLayoutPage} roles={['admin']} /> */}
                         </Routes>

+ 64 - 0
src/components/common/DrawerCart/DrawerCart.js

@@ -0,0 +1,64 @@
+import { actionNewOrder } from '../../../actions/actionNewOrder';
+import { Link, Navigate, useNavigate } from 'react-router-dom';
+import { actionCartDelete } from '../../../reducers';
+import { IoMdClose } from 'react-icons/io';
+
+import {
+    List,
+    Divider,
+    ListItem,
+    Typography,
+    Button,
+    TableRow,
+    TableBody,
+    Table,
+    TableCell,
+    Stack,
+    ListItemButton,
+    IconButton,
+} from '@mui/material';
+import { useSelector, useDispatch } from 'react-redux';
+import { DrawerCartItem } from './DrawerCartItem';
+import { DrawerRight } from '../DrawerRight';
+import { Box } from '@mui/system';
+
+export const DrawerCart = ({ isOpen = false, onClose = null } = {}) => {
+    const cart = useSelector((state) => state.cart || {});
+    const dispatch = useDispatch();
+    const navigate = useNavigate();
+
+    return (
+        <DrawerRight open={isOpen} onClose={() => onClose()}>
+            <Box className="DrawerCart">
+                <Stack className="list " spacing={2} px={1}>
+                    <Stack spacing={2} direction="row" alignItems="center" justifyContent="space-between" p={1}>
+                        <Typography variant="h5" flexGrow="1">
+                            Кошик
+                        </Typography>
+                        <IconButton onClick={onClose}>
+                            <IoMdClose />
+                        </IconButton>
+                    </Stack>
+
+                    <Divider />
+                    {Object.entries(cart).map(([_id, order]) => (
+                        <DrawerCartItem order={order} onDeleteClick={(good) => dispatch(actionCartDelete(good))} />
+                    ))}
+
+                    {!!Object.keys(cart).length && (
+                        <Button
+                            variant="text"
+                            onClick={() => {
+                                dispatch(actionNewOrder([...Object.values(cart)]));
+                                onClose();
+                                navigate('/cart');
+                            }}
+                        >
+                            Підтвердити
+                        </Button>
+                    )}
+                </Stack>
+            </Box>
+        </DrawerRight>
+    );
+};

+ 111 - 0
src/components/common/DrawerCart/DrawerCartItem.js

@@ -0,0 +1,111 @@
+import { Box, width } from '@mui/system';
+import { backendURL } from '../../../helpers';
+import defaultGoodImage from '../../../images/default-good-image.png';
+import { IoCloseOutline } from 'react-icons/io5';
+import { AiOutlinePlus, AiOutlineMinus } from 'react-icons/ai';
+import { actionCartChange } from '../../../reducers';
+import { useEffect, useState } from 'react';
+import { useDispatch } from 'react-redux';
+
+import {
+    ListItem,
+    Grid,
+    Typography,
+    Stack,
+    Container,
+    IconButton,
+    TextField,
+    ButtonGroup,
+    Button,
+    Input,
+    TableCell,
+    TableRow,
+    Card,
+    CardMedia,
+    CardContent,
+} from '@mui/material';
+
+const DrawerCartItem = ({ order, onDeleteClick }) => {
+    const {
+        good: { _id, images = [], name = '', price = 0 },
+    } = order || {};
+
+    return (
+        <Card className="DrawerCartItem">
+            <CardMedia
+                component="img"
+                sx={{ width: 90 }}
+                src={images && images[0]?.url ? `${images ? images[0]?.url : ''}` : defaultGoodImage}
+            />
+            <Box sx={{ display: 'flex', width: '100%' }}>
+                <CardContent className="content">
+                    <Typography component="div" variant="h5">
+                        {name}
+                    </Typography>
+                    <Typography variant="subtitle1" color="text.secondary" component="div">
+                        {price}
+                    </Typography>
+                </CardContent>
+                <Box className="buttons">
+                    <IconButton onClick={() => onDeleteClick({ _id, images, name, price })}>
+                        <IoCloseOutline />
+                    </IconButton>
+                </Box>
+            </Box>
+        </Card>
+    );
+};
+
+export { DrawerCartItem };
+
+{
+    /* <TableRow className="DrawerCartItem">
+<TableCell>
+    <Box
+        component="img"
+        src={images && images[0]?.url ? `/${images ? images[0]?.url : ''}` : defaultGoodImage}
+        sx={{ width: 50 }}
+    />
+</TableCell>
+<TableCell>
+    <Box sx={{ flexGrow: 1 }}>
+        <Typography variant="h5">{name}</Typography>
+        <Typography variant="body1">{price}</Typography>
+    </Box>
+</TableCell>
+<TableCell>
+    <Stack justifyContent="center" direction="row" alignItems="center">
+        <IconButton onClick={() => handleChange(countInput - 1)}>
+            <AiOutlineMinus />
+        </IconButton>
+        <Input
+            value={countInput}
+            onChange={(e) => handleChange(+e.target.value)}
+            size="small"
+            sx={{
+                width: 70,
+                resize: {
+                    fontSize: 10,
+                },
+                px: 3,
+            }}
+        />
+        <IconButton onClick={() => handleChange(countInput + 1)}>
+            <AiOutlinePlus />
+        </IconButton>
+    </Stack>
+</TableCell>
+<TableCell>
+    <Stack justifyContent="center">
+        <Typography variant="body1" textAlign="center">
+            x{count}
+        </Typography>
+    </Stack>
+</TableCell>
+<TableCell>
+    <IconButton onClick={() => onDeleteClick({ _id, images, name, price })}>
+        <IoCloseOutline />
+    </IconButton>
+</TableCell>
+</TableRow> */
+}

+ 11 - 0
src/components/common/DrawerRight/index.js

@@ -0,0 +1,11 @@
+import { Drawer } from '@mui/material';
+import ReactDOM from 'react-dom';
+
+export const DrawerRight = ({ children, onClose = null, open } = {}) => {
+    return ReactDOM.createPortal(
+        <Drawer anchor="right" className="DrawerRight" open={open} onClose={onClose}>
+            {children}
+        </Drawer>,
+        document.body
+    );
+};

+ 3 - 2
src/components/common/GoodCard/index.js

@@ -1,5 +1,6 @@
 import { Button, Card, CardActionArea, CardActions, CardContent, CardMedia, Typography } from '@mui/material';
 import { connect } from 'react-redux';
+import { Link } from 'react-router-dom';
 import defaultGoodImage from '../../../images/default-good-image.png';
 import { actionCartAdd } from '../../../reducers';
 import { CBuyButton } from '../BuyButton';
@@ -7,10 +8,10 @@ import { CBuyButton } from '../BuyButton';
 const GoodCard = ({ good = {} }) => {
     return (
         <Card className="GoodCard">
-            <CardActionArea>
+            <CardActionArea component={Link} to={`/good/${good._id}`}>
                 <CardMedia
                     component="img"
-                    height="140"
+                    height="200"
                     image={`${good.images ? good.images[0]?.url : defaultGoodImage}`}
                 />
                 <CardContent>

+ 26 - 0
src/components/layout/Header/CartIcon/index.js

@@ -0,0 +1,26 @@
+import { Badge, Box, IconButton } from '@mui/material';
+import { useEffect, useState } from 'react';
+import { connect } from 'react-redux';
+import { MdOutlineShoppingCart } from 'react-icons/md';
+
+export const CartIcon = ({ cart }) => {
+    const [count, setCount] = useState(0);
+    useEffect(() => {
+        let count = 0;
+        for (let order of Object.values(cart)) {
+            count += +order.count;
+        }
+        setCount(count);
+    }, [cart]);
+    return (
+        <Box className="CartIcon">
+            <Badge badgeContent={count} color="primary">
+                <IconButton size="medium">
+                    <MdOutlineShoppingCart className="CartLogo" />
+                </IconButton>
+            </Badge>
+        </Box>
+    );
+};
+
+export const CCartIcon = connect((state) => ({ cart: state.cart || {} }))(CartIcon);

+ 52 - 35
src/components/layout/Header/index.js

@@ -1,42 +1,59 @@
 import { AppBar, Box, Button, IconButton, Stack, TextField, Toolbar, Typography } from '@mui/material';
-import { MdOutlineShoppingCart } from 'react-icons/md';
+import { useState } from 'react';
+
+import { useSelector } from 'react-redux';
+import { Link } from 'react-router-dom';
 import { ReactComponent as ShoppingLogo } from '../../../images/shopping-logo.svg';
+import { DrawerCart } from '../../common/DrawerCart/DrawerCart';
 import { SearchBar } from '../../common/SearchBar';
+import { CCartIcon } from './CartIcon';
 
-const Header = () => (
-    <Box className="Header">
-        <AppBar position="static" className="AppBar">
-            <Toolbar variant="dense" className="ToolBar">
-                <IconButton>
-                    <ShoppingLogo className="Logo" />
-                </IconButton>
-                <Stack direction="row" spacing={2}>
-                    <Button variant="text" color="inherit">
-                        <Typography variant="body1" component="div">
-                            Головна
-                        </Typography>
-                    </Button>
-                    <Button variant="text" color="inherit">
-                        <Typography variant="body1" component="div">
-                            Товари
-                        </Typography>
-                    </Button>
-                    <Button variant="text" color="inherit">
-                        <Typography variant="body1" component="div">
-                            Зворотній зв'язок
-                        </Typography>
-                    </Button>
-                </Stack>
-                <Box className="SearchBarWrapper">
-                    <SearchBar />
-                </Box>
+const Header = () => {
+    const rootCats = useSelector((state) => state?.promise?.rootCats?.payload || []);
+    const [isCartDrawerOpen, setIsCartDrawerOpen] = useState(false);
+    return (
+        <Box className="Header">
+            <AppBar position="static" className="AppBar">
+                <Toolbar variant="dense" className="ToolBar">
+                    <IconButton component={Link} to="/">
+                        <ShoppingLogo className="Logo" />
+                    </IconButton>
+                    <Stack direction="row" spacing={2}>
+                        <Button variant="text" color="inherit" component={Link} to="/">
+                            <Typography variant="body1" component="div">
+                                Головна
+                            </Typography>
+                        </Button>
+                        <Button
+                            variant="text"
+                            color="inherit"
+                            component={Link}
+                            to={rootCats[0] ? `/category/${rootCats[0]._id}` : '/'}
+                        >
+                            <Typography variant="body1" component="div">
+                                Товари
+                            </Typography>
+                        </Button>
+                        <Button variant="text" color="inherit">
+                            <Typography variant="body1" component="div">
+                                Зворотній зв'язок
+                            </Typography>
+                        </Button>
+                    </Stack>
+                    <Box className="SearchBarWrapper">
+                        <SearchBar />
+                    </Box>
 
-                <IconButton color="inherit" className="CartLogoButton">
-                    <MdOutlineShoppingCart className="CartLogo" />
-                </IconButton>
-            </Toolbar>
-        </AppBar>
-    </Box>
-);
+                    <IconButton color="inherit" className="CartLogoButton">
+                        <Box onClick={() => setIsCartDrawerOpen(true)}>
+                            <CCartIcon />
+                        </Box>
+                    </IconButton>
+                </Toolbar>
+            </AppBar>
+            <DrawerCart isOpen={isCartDrawerOpen} onClose={() => setIsCartDrawerOpen(false)} />
+        </Box>
+    );
+};
 
 export { Header };

+ 70 - 1
src/index.scss

@@ -3,6 +3,18 @@
   margin:0;
 }
 
+
+.carousel {
+  &   .thumb {
+
+    border: 1px solid rgba(0,0,0,0)!important;
+    cursor:pointer;
+    &:hover{
+      border:1px solid #C9C5CA!important;
+    }
+  }
+}
+
 .GoodCard{
 
   & .BuyButton{
@@ -10,7 +22,42 @@
   }
 }
 
+
+.DrawerCart{
+  width:350px;
+  & .header{
+    justify-content: space-between;
+    width: 100%;
+  }
+  & .header{
+    padding: 10px;
+  }
+
+
+  & .DrawerCartItem{
+    width:100%;
+    display: flex;
+
+
+    & .content{
+      flex:1;
+    }
+
+    & .buttons{
+      padding: 10px;
+      display: flex;
+      align-items: center;
+    }
+  }
+  & .list{
+
+  }
+}
+
+
+
 .App{
+
   & .Header{
     margin-bottom: 30px;
     & .AppBar{
@@ -35,8 +82,15 @@
             }
           }
         }
-        & .CartLogoButton{
+        & .CartIcon{
+          & .MuiBadge-badge{
+            right: 4px;
+            top: 35px;
+            padding: 0 4px;
+          }
+
           & .CartLogo{
+            color:#6750A4;
             width:30px;
             height:30px;
           }
@@ -77,6 +131,21 @@
       }
 
     }
+
+    & .CartPage{
+      & .OrderForm{
+        padding: 10px 15px;
+        text-align: left;
+      }
+    }
+
+    & .GoodPage{
+      padding:10px;
+      & .content{
+        text-align: left;
+      }
+
+    }
   }
 
   & .Footer{