ilya_shyian 2 lat temu
rodzic
commit
1555c130a1

+ 50 - 0
package-lock.json

@@ -18,6 +18,7 @@
         "react-dom": "^18.1.0",
         "react-icons": "^4.3.1",
         "react-redux": "^8.0.1",
+        "react-responsive-carousel": "^3.2.23",
         "react-router-dom": "^6.3.0",
         "react-scripts": "5.0.1",
         "redux": "^4.2.0",
@@ -5409,6 +5410,11 @@
       "version": "1.2.2",
       "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA=="
     },
+    "node_modules/classnames": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
+      "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
+    },
     "node_modules/clean-css": {
       "version": "5.3.0",
       "integrity": "sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ==",
@@ -13736,6 +13742,17 @@
         "react": "^18.1.0"
       }
     },
+    "node_modules/react-easy-swipe": {
+      "version": "0.0.21",
+      "resolved": "https://registry.npmjs.org/react-easy-swipe/-/react-easy-swipe-0.0.21.tgz",
+      "integrity": "sha512-OeR2jAxdoqUMHIn/nS9fgreI5hSpgGoL5ezdal4+oO7YSSgJR8ga+PkYGJrSrJ9MKlPcQjMQXnketrD7WNmNsg==",
+      "dependencies": {
+        "prop-types": "^15.5.8"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/react-error-overlay": {
       "version": "6.0.11",
       "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
@@ -13800,6 +13817,16 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/react-responsive-carousel": {
+      "version": "3.2.23",
+      "resolved": "https://registry.npmjs.org/react-responsive-carousel/-/react-responsive-carousel-3.2.23.tgz",
+      "integrity": "sha512-pqJLsBaKHWJhw/ItODgbVoziR2z4lpcJg+YwmRlSk4rKH32VE633mAtZZ9kDXjy4wFO+pgUZmDKPsPe1fPmHCg==",
+      "dependencies": {
+        "classnames": "^2.2.5",
+        "prop-types": "^15.5.8",
+        "react-easy-swipe": "^0.0.21"
+      }
+    },
     "node_modules/react-router": {
       "version": "6.3.0",
       "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",
@@ -20568,6 +20595,11 @@
       "version": "1.2.2",
       "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA=="
     },
+    "classnames": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
+      "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
+    },
     "clean-css": {
       "version": "5.3.0",
       "integrity": "sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ==",
@@ -26292,6 +26324,14 @@
         "scheduler": "^0.22.0"
       }
     },
+    "react-easy-swipe": {
+      "version": "0.0.21",
+      "resolved": "https://registry.npmjs.org/react-easy-swipe/-/react-easy-swipe-0.0.21.tgz",
+      "integrity": "sha512-OeR2jAxdoqUMHIn/nS9fgreI5hSpgGoL5ezdal4+oO7YSSgJR8ga+PkYGJrSrJ9MKlPcQjMQXnketrD7WNmNsg==",
+      "requires": {
+        "prop-types": "^15.5.8"
+      }
+    },
     "react-error-overlay": {
       "version": "6.0.11",
       "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
@@ -26328,6 +26368,16 @@
       "version": "0.11.0",
       "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
     },
+    "react-responsive-carousel": {
+      "version": "3.2.23",
+      "resolved": "https://registry.npmjs.org/react-responsive-carousel/-/react-responsive-carousel-3.2.23.tgz",
+      "integrity": "sha512-pqJLsBaKHWJhw/ItODgbVoziR2z4lpcJg+YwmRlSk4rKH32VE633mAtZZ9kDXjy4wFO+pgUZmDKPsPe1fPmHCg==",
+      "requires": {
+        "classnames": "^2.2.5",
+        "prop-types": "^15.5.8",
+        "react-easy-swipe": "^0.0.21"
+      }
+    },
     "react-router": {
       "version": "6.3.0",
       "integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",

+ 1 - 0
package.json

@@ -14,6 +14,7 @@
     "react-dom": "^18.1.0",
     "react-icons": "^4.3.1",
     "react-redux": "^8.0.1",
+    "react-responsive-carousel": "^3.2.23",
     "react-router-dom": "^6.3.0",
     "react-scripts": "5.0.1",
     "redux": "^4.2.0",

+ 1 - 0
src/App.js

@@ -19,4 +19,5 @@ function App() {
     );
 }
 
+store.subscribe(() => console.log(store.getState()));
 export default App;

+ 88 - 0
src/actions/actionGoodsPopular.js

@@ -0,0 +1,88 @@
+import { mock, query } from '../helpers';
+
+import { actionPromise } from '../reducers';
+
+export const actionGoodsPopular = () => async (dispatch, getState) => {
+    dispatch(
+        actionPromise(
+            'goodsPopular',
+            new Promise((resolve) => {
+                setTimeout(
+                    Math.random() > 0.01
+                        ? resolve({
+                              data: [
+                                  {
+                                      _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;
+                })
+        )
+    );
+};

+ 2 - 0
src/actions/actionPageStart.js

@@ -1,7 +1,9 @@
+import { actionGoodsPopular } from './actionGoodsPopular';
 import { actionRootCats } from './actionRootCats';
 
 export const actionPageStart = () => async (dispatch, getState) => {
     dispatch(actionRootCats());
+    dispatch(actionGoodsPopular());
 
     // const {
     //     auth: { token },

+ 9 - 9
src/actions/actionRootCats.js

@@ -12,20 +12,20 @@ export const actionRootCats = () => async (dispatch, getState) => {
                         ? resolve({
                               data: [
                                   {
-                                      id: 1,
-                                      name: 'Categoty 1',
+                                      _id: 1,
+                                      name: 'Category 1',
                                   },
                                   {
-                                      id: 2,
-                                      name: 'Categoty 2',
+                                      _id: 2,
+                                      name: 'Category 2',
                                   },
                                   {
-                                      id: 3,
-                                      name: 'Categoty 3',
+                                      _id: 3,
+                                      name: 'Category 3',
                                   },
                                   {
-                                      id: 4,
-                                      name: 'Categoty 4',
+                                      _id: 4,
+                                      name: 'Category 4',
                                   },
                               ],
                           })
@@ -40,7 +40,7 @@ export const actionRootCats = () => async (dispatch, getState) => {
                     console.log(data);
                     if (data.errors) {
                         throw new Error(JSON.stringify(data.errors));
-                    } else return data.data;
+                    } else return Object.values(data.data);
                 })
         )
     );

+ 27 - 0
src/components/MainPage/index.js

@@ -0,0 +1,27 @@
+import { Box, Stack, Typography } from '@mui/material';
+import { useEffect } from 'react';
+import { useSelector } from 'react-redux';
+import MainPageImage from '../../images/main-page-image.png';
+import { GoodCardSlider } from '../common/GoodCardSlider';
+
+const MainPage = () => {
+    const popularGoods = useSelector((state) => state.promise?.goodsPopular?.payload || []);
+
+    return (
+        <Box className="MainPage">
+            <Stack spacing={3}>
+                <Box component="img" src={MainPageImage} className="MainPageImage" />
+                <Box>
+                    <Typography variant="h5" color="#79747E" textAlign="left">
+                        Популярні товари
+                    </Typography>
+                </Box>
+                <Box>
+                    <GoodCardSlider goods={popularGoods} />
+                </Box>
+            </Stack>
+        </Box>
+    );
+};
+
+export { MainPage };

+ 22 - 17
src/components/Root/index.js

@@ -1,38 +1,43 @@
 import { Route, Router, Routes } from 'react-router-dom';
-import { useDispatch } from 'react-redux';
-import { Container, Box, Stack } from '@mui/material';
-import { Fragment } from 'react';
-import { CProtectedRoute } from '../common/ProtectedRoute';
-import { AdminLayoutPage } from '../admin/AdminLayoutPage';
-import { actionRootCats } from '../../actions/actionRootCats';
+
+import { Container, Box, Stack, Grid } from '@mui/material';
+
 import { Aside } from '../layout/Aside';
 import Content from '../layout/Content';
+import styles from 'react-responsive-carousel/lib/styles/carousel.min.css';
 
 import { store } from '../../reducers';
 import { Header } from '../layout/Header';
 import { Footer } from '../layout/Footer';
+import { MainPage } from '../MainPage';
+import { actionPageStart } from '../../actions/actionPageStart';
 
 const Root = ({ user = {} }) => {
     const isSignIn = true;
-    store.dispatch(actionRootCats());
+    store.dispatch(actionPageStart());
 
     return (
         <Box className="Root">
             <Header />
-            <Stack direction="row">
-                <Aside />
-                <Content>
-                    {/* <Routes>
-                        <Route path="/" exact />
-                        <Route path="/good/:id" />
+            <Grid container columns={14} rows={1}>
+                <Grid xs={3} item>
+                    <Aside />
+                </Grid>
+                <Grid xs={11} item>
+                    <Content>
+                        <Routes>
+                            <Route path="/" exact element={<MainPage />} />
+                            {/* <Route path="/good/:id" />
                         <Route path="/category/:id" />
                         <Route path="/category/" />
                         <Route path="/good/" />
 
-                        <CProtectedRoute path="/admin" component={AdminLayoutPage} roles={['admin']} />
-                    </Routes> */}
-                </Content>
-            </Stack>
+                        <CProtectedRoute path="/admin" component={AdminLayoutPage} roles={['admin']} /> */}
+                        </Routes>
+                    </Content>
+                </Grid>
+            </Grid>
+
             <Footer />
         </Box>
     );

+ 32 - 0
src/components/common/BuyButton/index.js

@@ -0,0 +1,32 @@
+import { Box, Button } from '@mui/material';
+import { useEffect, useState } from 'react';
+import { connect } from 'react-redux';
+import { actionCartAdd, actionCartDelete } from '../../../reducers';
+
+export const BuyButton = ({ onClick, onDeleteClick, good, cart }) => {
+    const [inCart, setInCart] = useState(false);
+
+    useEffect(() => {
+        setInCart(!!(cart[good._id] && cart[good._id].count) || false);
+    }, [good, cart]);
+    return (
+        <Box className="BuyButton ">
+            {inCart ? (
+                <Button onClick={() => onDeleteClick(good)} variant="outlined">
+                    Вже у кошику
+                </Button>
+            ) : (
+                <Button onClick={() => onClick(good)} variant="contained" className="button">
+                    Купити
+                </Button>
+            )}
+        </Box>
+    );
+};
+
+const CBuyButton = connect((state) => ({ cart: state.cart || {} }), {
+    onClick: (good) => actionCartAdd(good),
+    onDeleteClick: (good) => actionCartDelete(good),
+})(BuyButton);
+
+export { CBuyButton };

+ 1 - 4
src/components/common/Categories/index.js

@@ -3,14 +3,11 @@ import { useEffect } from 'react';
 import { Category } from './Category';
 
 const Categories = ({ categories = [] }) => {
-    useEffect(() => {
-        console.log(categories);
-    }, [categories]);
     return (
         <Box className="Categories">
             <List>
                 {(categories || []).map((cat) => (
-                    <Category category={cat} key={cat.id} />
+                    <Category category={cat} key={cat._id} />
                 ))}
             </List>
         </Box>

+ 36 - 0
src/components/common/GoodCard/index.js

@@ -0,0 +1,36 @@
+import { Button, Card, CardActionArea, CardActions, CardContent, CardMedia, Typography } from '@mui/material';
+import { connect } from 'react-redux';
+import defaultGoodImage from '../../../images/default-good-image.png';
+import { actionCartAdd } from '../../../reducers';
+import { CBuyButton } from '../BuyButton';
+
+const GoodCard = ({ good = {} }) => {
+    return (
+        <Card className="GoodCard">
+            <CardActionArea>
+                <CardMedia
+                    component="img"
+                    height="140"
+                    image={`${good.images ? good.images[0]?.url : defaultGoodImage}`}
+                />
+                <CardContent>
+                    <Typography gutterBottom variant="body1" component="div" color="#1C1B1F" textAlign="left">
+                        Назва: {good.name}
+                    </Typography>
+                    <Typography variant="body2" color="text.secondary" textAlign="left">
+                        Ціна: {good.price}
+                    </Typography>
+                </CardContent>
+            </CardActionArea>
+            <CardActions>
+                <CBuyButton good={good} key={good._id} />
+            </CardActions>
+        </Card>
+    );
+};
+
+const CGoodCard = connect(null, {
+    handleOnClick: (good) => actionCartAdd(good),
+})(GoodCard);
+
+export { GoodCard, CGoodCard };

+ 14 - 0
src/components/common/GoodCardSlider/GoodCardSet.js

@@ -0,0 +1,14 @@
+import { Box, Stack } from '@mui/material';
+import { CGoodCard } from '../GoodCard';
+
+export const GoodCardSet = ({ goods = [], num = 4 } = {}) => {
+    return (
+        <Stack className="GoodCardSet" direction="row">
+            {(goods || []).map((good) => (
+                <Box sx={{ width: `${Math.floor(100 / (num || 1))}%`, padding: '10px' }} key={good?._id}>
+                    <CGoodCard good={good} buyButton={false} />
+                </Box>
+            ))}
+        </Stack>
+    );
+};

+ 35 - 0
src/components/common/GoodCardSlider/GoodCardSlider.js

@@ -0,0 +1,35 @@
+import { Carousel } from 'react-responsive-carousel';
+import { GoodCardSet } from './GoodCardSet';
+import { useState, useEffect } from 'react';
+import { Box } from '@mui/material';
+export const GoodCardSlider = ({ goods = [] } = {}) => {
+    const [goodSets, setGoodSets] = useState([]);
+    const num = 5;
+
+    useEffect(() => {
+        if (goods?.length) {
+            let goodSets = [];
+            for (let i = 0; i < goods.slice(0, 20).length; i += num) {
+                if (i + num > goods.length && goods.length % num !== 0) {
+                    goodSets.push(goods.slice(i, goods.length));
+                    break;
+                }
+                goodSets.push(goods.slice(i, i + num));
+            }
+            setGoodSets(goodSets);
+        }
+    }, [goods]);
+
+    return (
+        <Box className="GoodCardSlider">
+            <Carousel className="Slider" showThumbs={false} showStatus={false} showIndicators={false}>
+                {(goodSets || []).map((goodSet, idx) => (
+                    <GoodCardSet goods={goodSet} num={num} key={idx} />
+                ))}
+            </Carousel>
+        </Box>
+    );
+};
+// autoPlay={true}
+// autoPlaySpeed={1000}
+// transitionDuration={500}

+ 3 - 0
src/components/common/GoodCardSlider/index.js

@@ -0,0 +1,3 @@
+import { GoodCardSlider } from "./GoodCardSlider";
+
+export { GoodCardSlider };

+ 1 - 1
src/helpers/getQuery.js

@@ -13,5 +13,5 @@ export const getQuery =
             .then((data) => {
                 if (data.errors) {
                     throw new Error(JSON.stringify(data.errors));
-                } else return Object.values(data.data);
+                } else return Object.values(data.data[0]);
             });

BIN
src/images/default-good-image.png


BIN
src/images/main-page-image.png


+ 0 - 3
src/images/shopping-logo-svgrepo-com 1.svg:Zone.Identifier

@@ -1,3 +0,0 @@
-[ZoneTransfer]
-ZoneId=3
-HostUrl=https://www.figma.com/

+ 16 - 2
src/index.scss

@@ -3,6 +3,12 @@
   margin:0;
 }
 
+.GoodCard{
+
+  & .BuyButton{
+    margin-left: auto;
+  }
+}
 
 .App{
   & .Header{
@@ -42,8 +48,7 @@
   }
   & .Aside{
     margin-left: 50px;
-    width:100%;
-    max-width: 250px;
+
 
     & .body{
       padding: 10px 0px 100px 0px;
@@ -63,6 +68,15 @@
     border-radius: 5px;
     flex:1;
     border:1px solid #C9C5CA ;
+
+    & .MainPage{
+      padding: 10px;
+      & .MainPageImage{
+        width: 1005;
+        border-radius: 10px;
+      }
+
+    }
   }
 
   & .Footer{