Преглед на файлове

material ui refactoring

viktoriia.kapran преди 2 години
родител
ревизия
db20c714ab
променени са 35 файла, в които са добавени 483 реда и са изтрити 448 реда
  1. 166 0
      js21 react/my-react-app/package-lock.json
  2. 1 0
      js21 react/my-react-app/package.json
  3. BIN
      js21 react/my-react-app/public/images/logo.jpg
  4. BIN
      js21 react/my-react-app/public/images/logo.png
  5. 6 6
      js21 react/my-react-app/src/App.js
  6. 0 178
      js21 react/my-react-app/src/App.scss
  7. 19 19
      js21 react/my-react-app/src/api/gql.js
  8. 0 8
      js21 react/my-react-app/src/components/Button/Button.js
  9. 12 16
      js21 react/my-react-app/src/components/Categories/CategoryMenu.js
  10. 4 3
      js21 react/my-react-app/src/components/CategoriesSection/CategoriesSection.js
  11. 8 12
      js21 react/my-react-app/src/components/CategoriesSection/CategoriesSection.scss
  12. 5 4
      js21 react/my-react-app/src/components/GoodCart/GoodCart.js
  13. 58 0
      js21 react/my-react-app/src/components/Header.js
  14. 0 49
      js21 react/my-react-app/src/components/Header/Header.js
  15. 0 63
      js21 react/my-react-app/src/components/Header/Header.scss
  16. 3 2
      js21 react/my-react-app/src/components/Image/Image.js
  17. 20 0
      js21 react/my-react-app/src/components/ImageSlider.js
  18. 28 0
      js21 react/my-react-app/src/components/Layout.js
  19. 0 24
      js21 react/my-react-app/src/components/Layout/Layout.js
  20. 0 7
      js21 react/my-react-app/src/components/Layout/Layout.scss
  21. 11 0
      js21 react/my-react-app/src/components/Price.js
  22. 0 10
      js21 react/my-react-app/src/components/Price/Price.js
  23. 0 5
      js21 react/my-react-app/src/components/Price/Price.scss
  24. 12 0
      js21 react/my-react-app/src/components/Title.js
  25. 0 11
      js21 react/my-react-app/src/components/Title/Title.js
  26. 0 5
      js21 react/my-react-app/src/components/Title/Title.scss
  27. 9 9
      js21 react/my-react-app/src/pages/Category/Category.js
  28. 1 2
      js21 react/my-react-app/src/pages/Category/Category.scss
  29. 0 10
      js21 react/my-react-app/src/pages/Good.js
  30. 33 0
      js21 react/my-react-app/src/pages/Good/Good.js
  31. 7 0
      js21 react/my-react-app/src/pages/Good/Good.scss
  32. 9 2
      js21 react/my-react-app/src/redux/reducers/index.js
  33. 30 0
      js21 react/my-react-app/src/redux/slices/authSlice.js
  34. 3 3
      js21 react/my-react-app/src/redux/slices/categoriesSlice.js
  35. 38 0
      js21 react/my-react-app/src/redux/slices/goodSlice.js

+ 166 - 0
js21 react/my-react-app/package-lock.json

@@ -18,6 +18,7 @@
         "@testing-library/user-event": "^13.5.0",
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
+        "react-material-ui-carousel": "^3.4.2",
         "react-redux": "^8.0.5",
         "react-router-dom": "^6.8.2",
         "react-scripts": "5.0.1",
@@ -8820,6 +8821,48 @@
         "url": "https://www.patreon.com/infusion"
       }
     },
+    "node_modules/framer-motion": {
+      "version": "4.1.17",
+      "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-4.1.17.tgz",
+      "integrity": "sha512-thx1wvKzblzbs0XaK2X0G1JuwIdARcoNOW7VVwjO8BUltzXPyONGAElLu6CiCScsOQRI7FIk/45YTFtJw5Yozw==",
+      "dependencies": {
+        "framesync": "5.3.0",
+        "hey-listen": "^1.0.8",
+        "popmotion": "9.3.6",
+        "style-value-types": "4.1.4",
+        "tslib": "^2.1.0"
+      },
+      "optionalDependencies": {
+        "@emotion/is-prop-valid": "^0.8.2"
+      },
+      "peerDependencies": {
+        "react": ">=16.8 || ^17.0.0",
+        "react-dom": ">=16.8 || ^17.0.0"
+      }
+    },
+    "node_modules/framer-motion/node_modules/@emotion/is-prop-valid": {
+      "version": "0.8.8",
+      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
+      "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
+      "optional": true,
+      "dependencies": {
+        "@emotion/memoize": "0.7.4"
+      }
+    },
+    "node_modules/framer-motion/node_modules/@emotion/memoize": {
+      "version": "0.7.4",
+      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
+      "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==",
+      "optional": true
+    },
+    "node_modules/framesync": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/framesync/-/framesync-5.3.0.tgz",
+      "integrity": "sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
     "node_modules/fresh": {
       "version": "0.5.2",
       "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -9200,6 +9243,11 @@
         "he": "bin/he"
       }
     },
+    "node_modules/hey-listen": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz",
+      "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="
+    },
     "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",
@@ -13269,6 +13317,17 @@
         "node": ">=4"
       }
     },
+    "node_modules/popmotion": {
+      "version": "9.3.6",
+      "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-9.3.6.tgz",
+      "integrity": "sha512-ZTbXiu6zIggXzIliMi8LGxXBF5ST+wkpXGEjeTUDUOCdSQ356hij/xjeUdv0F8zCQNeqB1+PR5/BB+gC+QLAPw==",
+      "dependencies": {
+        "framesync": "5.3.0",
+        "hey-listen": "^1.0.8",
+        "style-value-types": "4.1.4",
+        "tslib": "^2.1.0"
+      }
+    },
     "node_modules/postcss": {
       "version": "8.4.21",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
@@ -14842,6 +14901,28 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
     },
+    "node_modules/react-material-ui-carousel": {
+      "version": "3.4.2",
+      "resolved": "https://registry.npmjs.org/react-material-ui-carousel/-/react-material-ui-carousel-3.4.2.tgz",
+      "integrity": "sha512-jUbC5aBWqbbbUOOdUe3zTVf4kMiZFwKJqwhxzHgBfklaXQbSopis4iWAHvEOLcZtSIJk4JAGxKE0CmxDoxvUuw==",
+      "dependencies": {
+        "@emotion/react": "^11.7.1",
+        "@emotion/styled": "^11.6.0",
+        "@mui/icons-material": "^5.4.1",
+        "@mui/material": "^5.4.1",
+        "@mui/system": "^5.4.1",
+        "framer-motion": "^4.1.17"
+      },
+      "peerDependencies": {
+        "@emotion/react": "^11.4.1",
+        "@emotion/styled": "^11.3.0",
+        "@mui/icons-material": "^5.0.0",
+        "@mui/material": "^5.0.0",
+        "@mui/system": "^5.0.0",
+        "react": "^17.0.1 || ^18.0.0",
+        "react-dom": "^17.0.2 || ^18.0.0"
+      }
+    },
     "node_modules/react-redux": {
       "version": "8.0.5",
       "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz",
@@ -16150,6 +16231,15 @@
         "webpack": "^5.0.0"
       }
     },
+    "node_modules/style-value-types": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-4.1.4.tgz",
+      "integrity": "sha512-LCJL6tB+vPSUoxgUBt9juXIlNJHtBMy8jkXzUJSBzeHWdBu6lhzHqCvLVkXFGsFIlNa2ln1sQHya/gzaFmB2Lg==",
+      "dependencies": {
+        "hey-listen": "^1.0.8",
+        "tslib": "^2.1.0"
+      }
+    },
     "node_modules/stylehacks": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz",
@@ -24095,6 +24185,44 @@
       "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
       "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA=="
     },
+    "framer-motion": {
+      "version": "4.1.17",
+      "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-4.1.17.tgz",
+      "integrity": "sha512-thx1wvKzblzbs0XaK2X0G1JuwIdARcoNOW7VVwjO8BUltzXPyONGAElLu6CiCScsOQRI7FIk/45YTFtJw5Yozw==",
+      "requires": {
+        "@emotion/is-prop-valid": "^0.8.2",
+        "framesync": "5.3.0",
+        "hey-listen": "^1.0.8",
+        "popmotion": "9.3.6",
+        "style-value-types": "4.1.4",
+        "tslib": "^2.1.0"
+      },
+      "dependencies": {
+        "@emotion/is-prop-valid": {
+          "version": "0.8.8",
+          "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
+          "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
+          "optional": true,
+          "requires": {
+            "@emotion/memoize": "0.7.4"
+          }
+        },
+        "@emotion/memoize": {
+          "version": "0.7.4",
+          "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
+          "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==",
+          "optional": true
+        }
+      }
+    },
+    "framesync": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/framesync/-/framesync-5.3.0.tgz",
+      "integrity": "sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==",
+      "requires": {
+        "tslib": "^2.1.0"
+      }
+    },
     "fresh": {
       "version": "0.5.2",
       "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -24356,6 +24484,11 @@
       "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
       "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
     },
+    "hey-listen": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz",
+      "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="
+    },
     "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",
@@ -27299,6 +27432,17 @@
         }
       }
     },
+    "popmotion": {
+      "version": "9.3.6",
+      "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-9.3.6.tgz",
+      "integrity": "sha512-ZTbXiu6zIggXzIliMi8LGxXBF5ST+wkpXGEjeTUDUOCdSQ356hij/xjeUdv0F8zCQNeqB1+PR5/BB+gC+QLAPw==",
+      "requires": {
+        "framesync": "5.3.0",
+        "hey-listen": "^1.0.8",
+        "style-value-types": "4.1.4",
+        "tslib": "^2.1.0"
+      }
+    },
     "postcss": {
       "version": "8.4.21",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
@@ -28255,6 +28399,19 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
     },
+    "react-material-ui-carousel": {
+      "version": "3.4.2",
+      "resolved": "https://registry.npmjs.org/react-material-ui-carousel/-/react-material-ui-carousel-3.4.2.tgz",
+      "integrity": "sha512-jUbC5aBWqbbbUOOdUe3zTVf4kMiZFwKJqwhxzHgBfklaXQbSopis4iWAHvEOLcZtSIJk4JAGxKE0CmxDoxvUuw==",
+      "requires": {
+        "@emotion/react": "^11.7.1",
+        "@emotion/styled": "^11.6.0",
+        "@mui/icons-material": "^5.4.1",
+        "@mui/material": "^5.4.1",
+        "@mui/system": "^5.4.1",
+        "framer-motion": "^4.1.17"
+      }
+    },
     "react-redux": {
       "version": "8.0.5",
       "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz",
@@ -29200,6 +29357,15 @@
       "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==",
       "requires": {}
     },
+    "style-value-types": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-4.1.4.tgz",
+      "integrity": "sha512-LCJL6tB+vPSUoxgUBt9juXIlNJHtBMy8jkXzUJSBzeHWdBu6lhzHqCvLVkXFGsFIlNa2ln1sQHya/gzaFmB2Lg==",
+      "requires": {
+        "hey-listen": "^1.0.8",
+        "tslib": "^2.1.0"
+      }
+    },
     "stylehacks": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz",

+ 1 - 0
js21 react/my-react-app/package.json

@@ -13,6 +13,7 @@
     "@testing-library/user-event": "^13.5.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
+    "react-material-ui-carousel": "^3.4.2",
     "react-redux": "^8.0.5",
     "react-router-dom": "^6.8.2",
     "react-scripts": "5.0.1",

BIN
js21 react/my-react-app/public/images/logo.jpg


BIN
js21 react/my-react-app/public/images/logo.png


+ 6 - 6
js21 react/my-react-app/src/App.js

@@ -9,16 +9,16 @@ import Register from './pages/Register';
 import Login from './pages/Login';
 import Admin from './pages/Admin';
 import Cart from './pages/Cart';
-import {Category} from './pages/Category/Category';
-import Good from './pages/Good';
-import Layout from './components/Layout/Layout';
+import Category from './pages/Category/Category';
+import Good from './pages/Good/Good';
+import Layout from './components/Layout';
 import store from './redux/reducers';
-import './App.scss'
+import { Box } from '@mui/material';
 
 
 function App() {
   return (
-    <div className='app'>
+    <Box sx={{ height: '100vh', display: 'flex', flexDirection: 'column'}}>
       <Provider store={store}>
         <Routes>
           <Route path='/' element={<Layout />}>
@@ -34,7 +34,7 @@ function App() {
           </Route>
         </Routes>
       </Provider>
-    </div>
+    </Box>
   );
 }
 

+ 0 - 178
js21 react/my-react-app/src/App.scss

@@ -1,178 +0,0 @@
-.app {
-  height: 100vh;
-  display: flex;
-  flex-direction: column;
-}
-
-.active {
-  color: rgb(18, 139, 199);
-}
-
-// #main {
-//   max-width: 1000px;
-//   width: 100%;
-// }
-
-// .good-cart .image-container {
-//   cursor: pointer;
-// }
-
-// .icon-container {
-//   width: 20px;
-//   text-align: center;
-//   height: 20px;
-//   padding: 0;
-//   border: none;
-//   background: none;
-//   cursor: pointer;
-// }
-
-// .count-container {
-//   display: flex;
-//   justify-content: flex-end;
-// }
-
-// .count-container > * {
-//   margin: 8px;
-// }
-
-// .count-container input,
-// .good-cart input,
-// .good-section input {
-//   max-width: 50px;
-//   width: 100%;
-//   text-align: center;
-//   padding: 4px 0;
-// }
-
-// input[type="number"]::-webkit-outer-spin-button,
-// input[type="number"]::-webkit-inner-spin-button {
-//   -webkit-appearance: none;
-// }
-
-// .icon-container img {
-//   width: 100%;
-//   height: 100%;
-// }
-// .goods-container {
-//   display: flex;
-//   flex-wrap: wrap;
-//   justify-content: center;
-//   gap: 24px 12px;
-//   margin-top: 20px;
-// }
-
-// .good-cart {
-//   flex-basis: 270px;
-//   border-radius: 16px;
-//   padding: 16px;
-//   position: relative;
-//   box-shadow: 0px 0px 13px -8px rgba(66, 68, 90, 1);
-//   display: flex;
-//   flex-direction: column;
-//   align-items: center;
-// }
-
-// .good-cart > * {
-//   margin: 6px;
-// }
-
-// .good-cart > a {
-//   text-decoration: none;
-//   text-align: center;
-//   color: rgba(66, 68, 90, 1);
-//   font-weight: 300;
-//   line-height: 20px;
-//   height: 40px;
-// }
-
-
-// .goods-out-stock,
-// .cart-is-empty {
-//   font-size: 24px;
-//   text-align: center;
-//   font-weight: 300;
-//   margin: 20px 0;
-// }
-
-// .good-section {
-//   display: flex;
-//   margin: 20px 0;
-// }
-
-// .good-section > * {
-//   width: 50%;
-// }
-
-// .good-section .image-container {
-//   width: 100%;
-//   max-height: 400px;
-//   height: 100%;
-// }
-
-// .good-info {
-//   padding: 20px;
-//   display: flex;
-//   flex-direction: column;
-//   align-items: center;
-// }
-
-// .good-info input {
-//   margin: 20px 0;
-// }
-
-// .good-info .buttons-container {
-//   text-align: center;
-// }
-
-// .good-info .buttons-container :first-child {
-//   margin-bottom: 20px;
-// }
-
-// .description {
-//   font-weight: 300;
-//   margin-bottom: 20px;
-// }
-
-// .order-container {
-//   border-radius: 10px;
-//   box-shadow: 0px 0px 23px -8px rgb(155, 157, 176);
-//   padding: 20px;
-//   margin: 20px 0;
-//   font-weight: 300;
-// }
-
-// .order-container > * {
-//   margin: 4px 0;
-// }
-
-// .right-aligment {
-//   text-align: end;
-// }
-
-// .order-button {
-//   margin-top: 20px;
-//   float: right;
-// }
-
-// .form {
-//   border: 1px solid gray;
-//   display: flex;
-//   flex-direction: column;
-//   align-items: center;
-//   border-radius: 10px;
-//   max-width: 300px;
-//   width: 100%;
-//   margin: 100px auto 0;
-//   padding: 20px;
-// }
-
-// .form > * {
-//   margin: 4px;
-// }
-
-// .warning-message {
-//   margin-top: 70px;
-//   display: none;
-//   font-style: italic;
-// }

+ 19 - 19
js21 react/my-react-app/src/api/gql.js

@@ -85,25 +85,25 @@ export const gqlGetCategory = (id) => {
   return gql(categoryQuery, { q: `[{"_id": "${id}"}]` });
 }
 
-// const gqlGetGood = (id) => {
-//   const goodQuery = `query good($q: String) {
-//     GoodFindOne(query: $q) {
-//       _id,
-//       name,
-//       categories{
-//         _id,
-//         name
-//       },
-//       description,
-//       price,
-//       images{
-//         _id,
-//         url
-//       }
-//     }
-// }`;
-//   return gql(goodQuery, { q: `[{"_id": "${id}"}]` });
-// }
+export const gqlGetGood = (id) => {
+  const goodQuery = `query good($q: String) {
+    GoodFindOne(query: $q) {
+      _id,
+      name,
+      categories{
+        _id,
+        name
+      },
+      description,
+      price,
+      images{
+        _id,
+        url
+      }
+    }
+}`;
+  return gql(goodQuery, { q: `[{"_id": "${id}"}]` });
+}
 
 // const gqlLogin = (login, password) => {
 //   const loginQuery = `query login($login:String, $password:String){

+ 0 - 8
js21 react/my-react-app/src/components/Button/Button.js

@@ -1,8 +0,0 @@
-import React from "react";
-import Button from '@mui/material/Button';
-
-export default function ButtonEl({children}) {
-  return (
-      <Button variant="contained">{children}</Button>
-  );
-}

+ 12 - 16
js21 react/my-react-app/src/components/Categories/CategoryMenu.js

@@ -2,26 +2,22 @@ import React, { useEffect } from 'react';
 import { Link } from 'react-router-dom';
 import { useDispatch, useSelector } from 'react-redux';
 import './CategoryMenu.scss'
-import { fetchCategories, selectCategories, selectStatus } from '../../redux/slices/categoriesSlice';
+import { fetchCategories, selectCategories } from '../../redux/slices/categoriesSlice';
 import Skeleton from '@mui/material/Skeleton';
+import { Box } from '@mui/material';
 
 
 export const CategoryMenu = () => {
-const dispatch = useDispatch();
-useEffect(()=> {
-  dispatch(fetchCategories());
-}, [dispatch])
-
-
-
-const categories = useSelector(selectCategories);
-const status = useSelector(selectStatus);
-console.log('categories', categories);
-
+  const categories = useSelector(selectCategories);
+  const dispatch = useDispatch();
+  useEffect(() => {
+    dispatch(fetchCategories());
+  }, [dispatch]);
+  console.log('categories', categories)
   return (
-    <aside className='aside'>
-       {status === 'loading' && Array(10).fill(1).map(item => <Skeleton className='skeleton'/> )}
-       {status === 'succeeded' && categories?.map(category => <Link key={category._id} to={`/${category.name}/${category._id}`}>{category.name}</Link>)}
-    </aside>
+    <Box className='aside' component='aside'>
+      {categories?.length == 0 && Array(10).fill(1).map((item, index) => <Skeleton key={index} className='skeleton' />)}
+      {categories?.map(category => <Link key={category._id} to={`/${category.name}/${category._id}`}>{category.name}</Link>)}
+    </Box>
   )
 }

+ 4 - 3
js21 react/my-react-app/src/components/CategoriesSection/CategoriesSection.js

@@ -1,13 +1,14 @@
+import { Box } from "@mui/material";
 import React from "react";
 import { Link } from "react-router-dom";
 import './CategoriesSection.scss';
 
 export function CategoriesSection({categories, categoryEl}) {
   return (
-    <section className="info-about-category">
+    <Box sx={{ flexGrow: 1}}>
       {categoryEl}
-      {categories?.map(category => <Link key={category.id} to={`/${category.name}/${category._id}`} className="link-style">
+      {categories?.map(category => <Link key={category._id} to={`/${category.name}/${category._id}`} className="link-style">
                                         {category.name}</Link>)}
-    </section>
+    </Box>
   )
 }

+ 8 - 12
js21 react/my-react-app/src/components/CategoriesSection/CategoriesSection.scss

@@ -1,13 +1,9 @@
-.info-about-category {
-  text-align: left;
+.link-style {
+  color: rgb(18, 139, 199);
+  margin: 0 6px;
+}
 
-  .link-style {
-    color: rgb(18, 139, 199);
-    margin: 0 6px;
-  }
-
-  .link-style:hover {
-    color: rgb(255, 42, 42);
-    transition: color 0.4s;
-  }
-}
+.link-style:hover {
+  color: rgb(255, 42, 42);
+  transition: color 0.4s;
+}

+ 5 - 4
js21 react/my-react-app/src/components/GoodCart/GoodCart.js

@@ -1,24 +1,25 @@
+import React from 'react';
 import Card from '@mui/material/Card';
 import CardActions from '@mui/material/CardActions';
 import CardContent from '@mui/material/CardContent';
 import { Link } from 'react-router-dom';
 import './GoodCart.scss'
 import { Image } from '../Image/Image';
-import { Price } from '../Price/Price';
+import { Price } from '../Price';
 import { Counter } from '../Counter/Counter.js';
-import ButtonEl from '../Button/Button';
+import { Button } from '@mui/material';
 
 
 
 export default function GoodCart({ good }) {
   return (
-    <Card sx={{ width: 270 }}>
+    <Card sx={{ width: 275 }}>
       <CardContent className='good-cart'>
         <Link to={`/good/${good?._id}`} className='good-name'>{good?.name}</Link>
         <Link to={`/good/${good?._id}`}><Image url={`${good?.images[0]?.url}`} /></Link>
         <Price>{good?.price}</Price>
         <Counter value={1} />
-        <ButtonEl>Add to cart</ButtonEl>
+        <Button variant="contained">Add to cart</Button>
       </CardContent>
     </Card>
   )

+ 58 - 0
js21 react/my-react-app/src/components/Header.js

@@ -0,0 +1,58 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import Badge from '@mui/material/Badge';
+import { styled } from '@mui/material/styles';
+import IconButton from '@mui/material/IconButton';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import AppBar from '@mui/material/AppBar';
+import Box from '@mui/material/Box';
+import Toolbar from '@mui/material/Toolbar';
+import Typography from '@mui/material/Typography';
+import { Button } from '@mui/material';
+
+// const UserName = ({userName}) => <div>{userName || 'anon'}</div>
+
+// const ConnectUserName = connect(state => ({userName: state.auth.payload?.sub?.login}))(UserName);
+
+const StyledBadge = styled(Badge)(({ theme }) => ({
+  '& .MuiBadge-badge': {
+
+    top: 24,
+    border: `2px solid ${theme.palette.background.paper}`,
+    padding: '0 4px',
+  },
+}));
+export default function Header() {
+  return (
+    <AppBar position="static">
+      <Toolbar>
+        <Box sx={{ flexGrow: 1 }}>
+          <Link to="/"><img src="/images/logo.jpg" /></Link>
+        </Box>
+        <Box sx={{ display: 'flex', alignItems: 'center' }}>
+          <Box sx={{ mx: 1, my: 2 }}> {/* отображать, когда залогинен */}
+            <Button to="history" component={Link} color="inherit">My orders</Button>
+          </Box>
+          <Box sx={{ mx: 1, my: 2 }}>
+            <Link to="cart">
+              <IconButton aria-label="cart">
+                <StyledBadge badgeContent={1} color="primary">
+                  <ShoppingCartIcon sx={{ color: 'white', fontSize: '30px' }} />
+                </StyledBadge>
+              </IconButton>
+            </Link>
+          </Box>
+          {/* <ConnectUserName /> */}
+          <Box sx={{ mx: 1, my: 2 }}>
+            <Button to="login" component={Link} color="inherit">Login</Button>
+          </Box>
+          <Box sx={{ mx: 1, my: 2 }}>
+            <Button to="register" component={Link} color="inherit">Registration</Button>
+          </Box>
+          {/* <button id="logout" className="button">Logout</button> */}
+        </Box>
+      </Toolbar>
+    </AppBar>
+
+  )
+}

+ 0 - 49
js21 react/my-react-app/src/components/Header/Header.js

@@ -1,49 +0,0 @@
-import React from 'react';
-import { NavLink } from 'react-router-dom';
-import { authReducer } from '../../redux/reducers/authReducer';
-import './Header.scss';
-import DiscountIcon from '@mui/icons-material/Discount';
-import Badge from '@mui/material/Badge';
-import { styled } from '@mui/material/styles';
-import IconButton from '@mui/material/IconButton';
-import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
-
-// const UserName = ({userName}) => <div>{userName || 'anon'}</div>
-
-// const ConnectUserName = connect(state => ({userName: state.auth.payload?.sub?.login}))(UserName);
-
-const StyledBadge = styled(Badge)(({ theme }) => ({
-  '& .MuiBadge-badge': {
-
-    top: 24,
-    border: `2px solid ${theme.palette.background.paper}`,
-    padding: '0 4px',
-  },
-}));
-export default function Header() {
-  return (
-    <header>
-      <NavLink to='/' className='logo'><DiscountIcon sx={{ fontSize: 40 }} /><span>KAPRAN Co.</span></NavLink>
-      <div className="header-info">
-        <div id="historyPage">
-          <NavLink to="history">My orders</NavLink>
-        </div>
-        <div className="cart-section">
-          <NavLink to="cart">
-            <IconButton aria-label="cart">
-              <StyledBadge badgeContent={1} color="primary">
-                <ShoppingCartIcon sx={{color: 'white', fontSize: '30px'}}/>
-              </StyledBadge>
-            </IconButton>
-          </NavLink>
-        </div>
-        {/* <ConnectUserName /> */}
-        <div className="login-block">
-          <NavLink to="login" id="login" className="button">Login</NavLink>
-          <NavLink to="register" id="registration" className="button">Registration</NavLink>
-          {/* <button id="logout" className="button">Logout</button> */}
-        </div>
-      </div>
-    </header>
-  )
-}

+ 0 - 63
js21 react/my-react-app/src/components/Header/Header.scss

@@ -1,63 +0,0 @@
-header {
-  min-height: 100px;
-  background-color: #1976d2;
-  color: #fff;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 0 20px;
-
-  .button {
-    border: none;
-    background-color: #fff;
-    border-radius: 10px;
-    padding: 6px 12px;
-    cursor: pointer;
-    color: #1976d2;
-    font-size: 16px;
-    transition: color 0.3s;
-  }
-  .logo {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-  }
-  a {
-    color: #fff;
-    text-decoration: none;
-    transition: color 0.3s;
-  }
-
-  .button:hover,
-  a:hover {
-    color: rgb(165, 189, 251);
-  }
-  .header-info {
-    display: flex;
-    align-items: center;
-    & > div {
-      margin: 0 14px;
-    }
-  }
-  .cart-section {
-    position: relative;
-    .cart-icon {
-      width: 34px;
-      cursor: pointer;
-    }
-    #cartIconEl {
-      border-radius: 50%;
-      background-color: rgb(158, 205, 255);
-      padding: 1px 6px;
-      font-size: 14px;
-      position: absolute;
-      transform: translate(28px, -16px);
-    }
-  }
-
-  .login-block {
-    .button:first-child {
-      margin-right: 8px;
-    }
-  }
-}

+ 3 - 2
js21 react/my-react-app/src/components/Image/Image.js

@@ -1,11 +1,12 @@
+import { Box } from "@mui/material";
 import React from "react";
 import './Image.scss';
 
 export function Image({url}) {
 
   return (
-    <div className="image-container">
+    <Box className="image-container">
       <img className="good-image" src={`http://shop-roles.node.ed.asmer.org.ua/${url}`}/>
-    </div>
+    </Box>
   )
 }

+ 20 - 0
js21 react/my-react-app/src/components/ImageSlider.js

@@ -0,0 +1,20 @@
+import { Box } from "@mui/material";
+import {React, useState} from "react";
+import { Image } from "./Image/Image";
+
+export default function ImageSlider({images}) {
+  const [selectedImage, setSelectedImage] = useState(images[0]?.url);
+  const [allImages, setAllImages] = useState(images);
+  console.log(images);
+  return (
+    <Box>
+      <Image url={selectedImage}/>
+      <Box sx={{display: 'flex'}}>
+        {allImages?.map(image => <Image key={image?._id}
+                    onClick={() => {console.log('click');setSelectedImage(`${image?.url}`)}}
+                    url={`${image?.url}`}
+                    />)}
+      </Box>
+    </Box>
+  )
+}

+ 28 - 0
js21 react/my-react-app/src/components/Layout.js

@@ -0,0 +1,28 @@
+import React from 'react';
+import { Link, Outlet } from 'react-router-dom';
+import Footer from './Footer';
+import Header from './Header';
+import { CategoryMenu, ReduxCategoryMenu } from './Categories/CategoryMenu';
+import Box from '@mui/material/Box';
+
+
+export default function Layout() {
+  return (
+    <>
+      <Header />
+      <Box sx={{ display: 'flex',
+                mx: 'auto',
+                position: 'relative',
+                width: '100%',
+                p: '20px',
+                flexGrow: 1,
+                maxWidth: '1180px'}}>
+        <CategoryMenu />
+        <Box sx={{ margin: '0 auto', width: '100%' }}>
+          <Outlet />
+        </Box>
+      </Box>
+      <Footer />
+    </>
+  )
+}

+ 0 - 24
js21 react/my-react-app/src/components/Layout/Layout.js

@@ -1,24 +0,0 @@
-import React from 'react';
-import { Link, Outlet } from 'react-router-dom';
-import Footer from '../Footer';
-import Header from '../Header/Header';
-import {CategoryMenu, ReduxCategoryMenu} from '../Categories/CategoryMenu';
-import Box from '@mui/material/Box';
-
-
-import './Layout.scss'
-
-export default function Layout() {
-  return (
-    <>
-    <Header />
-    <main className='main-container'>
-    <CategoryMenu />
-    <Box sx={{ margin: '0 auto', width: '100%'}}>
-    <Outlet />
-    </Box>
-    </main>
-    <Footer />
-    </>
-  )
-}

+ 0 - 7
js21 react/my-react-app/src/components/Layout/Layout.scss

@@ -1,7 +0,0 @@
-.main-container {
-  display: flex;
-  position: relative;
-  width: 100%;
-  padding: 20px;
-  flex-grow: 1;
-}

+ 11 - 0
js21 react/my-react-app/src/components/Price.js

@@ -0,0 +1,11 @@
+import { Box } from "@mui/material";
+import React from "react";
+
+
+export function Price( {children}) {
+  return (
+    <Box sx={{ fontSize: '18px', fontWeight: '400', color: 'rgb(15, 29, 67)'}}>
+      {children} UAH
+    </Box>
+  )
+}

+ 0 - 10
js21 react/my-react-app/src/components/Price/Price.js

@@ -1,10 +0,0 @@
-import React from "react";
-import './Price.scss';
-
-export function Price( {children}) {
-  return (
-    <div className="price">
-      {children} UAH
-    </div>
-  )
-}

+ 0 - 5
js21 react/my-react-app/src/components/Price/Price.scss

@@ -1,5 +0,0 @@
-.price {
-  font-size: 18px;
-  font-weight: 400;
-  color: rgb(15, 29, 67);
-}

+ 12 - 0
js21 react/my-react-app/src/components/Title.js

@@ -0,0 +1,12 @@
+import { Typography } from "@mui/material";
+import React from "react";
+
+
+export function Title({children}) {
+  return (
+    <Typography variant="h4" component="h1" sx={{ textAlign: 'center', fontWeight: '300', my: '20px', mx: '0'}}>
+      {children}
+    </Typography>
+  )
+
+}

+ 0 - 11
js21 react/my-react-app/src/components/Title/Title.js

@@ -1,11 +0,0 @@
-import React from "react";
-import './Title.scss';
-
-export function Title({children}) {
-  return (
-    <h1 className="title">
-      {children}
-    </h1>
-  )
-
-}

+ 0 - 5
js21 react/my-react-app/src/components/Title/Title.scss

@@ -1,5 +0,0 @@
-.title {
-  text-align: center;
-  font-weight: 300;
-  margin: 20px 0;
-}

+ 9 - 9
js21 react/my-react-app/src/pages/Category/Category.js

@@ -2,14 +2,14 @@ import React, { useEffect } from 'react'
 import { useParams } from 'react-router-dom';
 import { useDispatch, useSelector } from 'react-redux';
 import { fetchCategoryById, selectSelectedCategory, selectStatus } from '../../redux/slices/categoriesSlice';
-import { Title } from '../../components/Title/Title';
+import { Title } from '../../components/Title';
 import { CategoriesSection } from '../../components/CategoriesSection/CategoriesSection';
 import GoodCart from '../../components/GoodCart/GoodCart';
 import './Category.scss';
 import Loader from '../../components/Loader';
+import { Box } from "@mui/material";
 
-
-export const Category = () => {
+const Category = () => {
   const { categoryId } = useParams();
   const dispatch = useDispatch();
   const status = useSelector(selectStatus);
@@ -17,19 +17,19 @@ export const Category = () => {
     dispatch(fetchCategoryById(categoryId));
   }, [dispatch, categoryId]);
   const category = useSelector(selectSelectedCategory);
-  console.log('category', category);
   return (
     <>
       {status === 'loading' && <Loader />}
       {status === 'succeeded' && <>
         <Title>{category?.name}</Title>
-        {category?.subCategories?.length > 0 && <CategoriesSection categories={category?.subCategories} categoryEl='Subcategories:' />}
+        {category?.subCategories?.length > 0 && <CategoriesSection key={category._id} categories={category?.subCategories} categoryEl='Subcategories:' />}
         {/* {category?.parent?.length > 0 && <CategoriesSection categories={category?.parent} categoryEl='Parent Category:'/>} */}
         {/* спросить Андрея про parent */}
-        <div className='good-container'>
-          {category?.goods?.map(good => <GoodCart good={good} />)}
-        </div>
+        <Box className='goods-container'>
+          {category?.goods?.map(good => <GoodCart key={good._id} good={good} />)}
+        </Box>
       </>}
     </>
   )
-}
+}
+export default Category;

+ 1 - 2
js21 react/my-react-app/src/pages/Category/Category.scss

@@ -1,7 +1,6 @@
-.good-container {
+.goods-container {
   display: flex;
   flex-wrap: wrap;
-  justify-content: center;
   gap: 24px 12px;
   margin-top: 20px;
 }

+ 0 - 10
js21 react/my-react-app/src/pages/Good.js

@@ -1,10 +0,0 @@
-import React from 'react'
-import { useParams } from 'react-router-dom';
-
-const Good = () => {
-  const { category, catId, goodId } = useParams();
-  return (
-    <div>Good</div>
-  )
-}
-export default Good;

+ 33 - 0
js21 react/my-react-app/src/pages/Good/Good.js

@@ -0,0 +1,33 @@
+
+import React, { useEffect } from 'react'
+import { useDispatch, useSelector } from 'react-redux';
+import { useParams } from 'react-router-dom';
+import { CategoriesSection } from '../../components/CategoriesSection/CategoriesSection';
+import Loader from '../../components/Loader';
+import { Title } from '../../components/Title';
+import { fetchGoodById, selectGood, selectGoodStatus } from '../../redux/slices/goodSlice';
+import './Good.scss';
+import ImageSlider from '../../components/ImageSlider';
+
+const Good = () => {
+  const { goodId } = useParams();
+  const dispatch = useDispatch();
+  const status = useSelector(selectGoodStatus);
+  useEffect(() => {
+    dispatch(fetchGoodById(goodId));
+  }, [dispatch, goodId]);
+  const good = useSelector(selectGood);
+  return (
+    <>
+      {status === 'loading' && <Loader />}
+      {status === 'succeeded' && <>
+      <Title>{good?.name}</Title>
+      {good?.categories?.length > 0 && <CategoriesSection key={good?.categories?._id} categories={good?.categories} categoryEl='Category:' />}
+      <section className='good-section'>
+        <ImageSlider images={good?.images}/>
+      </section>
+      </>}
+    </>
+  )
+}
+export default Good;

+ 7 - 0
js21 react/my-react-app/src/pages/Good/Good.scss

@@ -0,0 +1,7 @@
+.good-section {
+  display: flex;
+  margin: 20px 0;
+  * {
+    width: 50%;
+  }
+}

+ 9 - 2
js21 react/my-react-app/src/redux/reducers/index.js

@@ -1,15 +1,18 @@
 import { createStore, combineReducers, applyMiddleware } from 'redux';
 import thunk from 'redux-thunk';
-import { authReducer } from "./authReducer";
 import { cartReducer } from "./cartReducer";
 import { promiseReducer } from "./promiseReducer";
 import { actionCategories, actionFullLogin} from '../../redux/actions/actions';
 import { configureStore } from '@reduxjs/toolkit';
 import categoriesReducer from '../slices/categoriesSlice';
+import goodReducer from '../slices/goodSlice';
+import authReducer, { login, logout } from '../slices/authSlice';
 
 export const store = configureStore({
   reducer: {
     categories: categoriesReducer,
+    good: goodReducer,
+    auth: authReducer
   },
 });
 
@@ -22,7 +25,11 @@ export const store = configureStore({
 // const totalReducer = combineReducers(reducers);
 
 // const store = createStore(totalReducer, applyMiddleware(thunk));
-// store.subscribe(() => console.log(store.getState()));
+store.subscribe(() => console.log(store.getState()));
+const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiaWQiOiI2Mzc3ZTEzM2I3NGUxZjVmMmVjMWMxMjUiLCJsb2dpbiI6InRlc3Q1IiwiYWNsIjpbIjYzNzdlMTMzYjc0ZTFmNWYyZWMxYzEyNSIsInVzZXIiXX0sImlhdCI6MTY2ODgxMjQ1OH0.t1eQlRwkcP7v9JxUPMo3dcGKprH-uy8ujukNI7xE3A0"
+
+store.dispatch(login(token));
+store.dispatch(logout());
 // store.dispatch(actionCategories());
 // // store.dispatch(actionFullLogin('test457', '123123'));
 

+ 30 - 0
js21 react/my-react-app/src/redux/slices/authSlice.js

@@ -0,0 +1,30 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+const autnSlice = createSlice({
+  name: 'auth',
+  initialState: {
+    token: null,
+    payload: null,
+  },
+  reducers: {
+    login(state, action) {
+      const token = action.payload;
+      try {
+        let mediumStr = token.split('.')[1];
+        let result = JSON.parse(atob(mediumStr));
+        state.token = token;
+        state.payload = result;
+        // return { ...state, 'token': token, 'payload': result };
+      } catch (e) {
+        state.token = null;
+        state.payload = null;
+      }
+    },
+    logout(state) {
+      state.token = null;
+      state.payload = null;
+    }
+  }
+})
+export const {login, logout} = autnSlice.actions;
+export default autnSlice.reducer;

+ 3 - 3
js21 react/my-react-app/src/redux/slices/categoriesSlice.js

@@ -24,24 +24,24 @@ const categoriesSlice = createSlice({
   reducers: {},
   extraReducers(builder) {
     builder
-      .addCase(fetchCategories.pending, (state, action) => {
+      .addCase(fetchCategories.pending, (state) => {
         state.status = 'loading';
       })
       .addCase(fetchCategories.fulfilled, (state, action) => {
         state.status = 'succeeded';
         state.categories = action.payload;
-        // action?.payload;
       })
       .addCase(fetchCategories.rejected, (state, action) => {
         state.status = 'failed';
         state.error = action.error.message;
       })
-      .addCase(fetchCategoryById.pending, (state, action) => {
+      .addCase(fetchCategoryById.pending, (state) => {
         state.status = 'loading';
       })
       .addCase(fetchCategoryById.fulfilled, (state, action) => {
         state.status = 'succeeded';
         state.selectedCategory = action.payload;
+
       })
       .addCase(fetchCategoryById.rejected, (state, action) => {
         state.status = 'failed';

+ 38 - 0
js21 react/my-react-app/src/redux/slices/goodSlice.js

@@ -0,0 +1,38 @@
+import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
+import { gqlGetGood } from '../../api/gql';
+
+const initialState = {
+  good: [],
+  status: 'idle',
+  error: null,
+};
+
+export const fetchGoodById = createAsyncThunk('good/fetchGoodById', async (id) => {
+  const response = await gqlGetGood(id);
+  return response;
+});
+
+const goodSlice = createSlice({
+  name: 'good',
+  initialState,
+  reducers: {},
+  extraReducers(builder) {
+    builder
+      .addCase(fetchGoodById.pending, (state) => {
+        state.status = 'loading';
+      })
+      .addCase(fetchGoodById.fulfilled, (state, action) => {
+        state.status = 'succeeded';
+        state.good = action.payload;
+      })
+      .addCase(fetchGoodById.rejected, (state, action) => {
+        state.status = 'failed';
+        state.error = action.error.message;
+      })
+  },
+});
+
+export default goodSlice.reducer;
+
+export const selectGood = (state) => state.good.good;
+export const selectGoodStatus = (state) => state.good.status;