Jelajahi Sumber

+ SearchBar | change layout | change routing

ilya_shyian 2 tahun lalu
induk
melakukan
b887471c1b

+ 2 - 2
src/actions/actionCatById.js

@@ -68,7 +68,7 @@ export const actionCatById = (_id) => async (dispatch, getState) => {
                                           ],
                                       },
                                       {
-                                          _id: 6,
+                                          _id: 5,
                                           name: 'Good 5',
                                           description: 'adaadasda asasd asd asd asd asd ',
                                           price: '999',
@@ -82,7 +82,7 @@ export const actionCatById = (_id) => async (dispatch, getState) => {
                                           ],
                                       },
                                       {
-                                          id: 6,
+                                          _id: 6,
                                           name: 'Good 6',
                                           description: 'adaadasda asasd asd asd asd asd ',
                                           price: '999',

+ 45 - 0
src/actions/actionCatsFind.js

@@ -0,0 +1,45 @@
+import { actionPromise } from '../reducers';
+
+export const actionCatsFind =
+    ({ text = '', limit = 0, skip = 0, promiseName = 'catsFind' }) =>
+    async (dispatch, getState) => {
+        await dispatch(
+            actionPromise(
+                promiseName,
+                new Promise((resolve) => {
+                    setTimeout(
+                        Math.random() > 0.01
+                            ? resolve({
+                                  data: [
+                                      {
+                                          _id: 1,
+                                          name: 'Category 1',
+                                      },
+                                      {
+                                          _id: 2,
+                                          name: 'Category 2',
+                                      },
+                                      {
+                                          _id: 3,
+                                          name: 'Category 3',
+                                      },
+                                      {
+                                          _id: 4,
+                                          name: 'Category 4',
+                                      },
+                                  ],
+                              })
+                            : resolve({
+                                  errors: [{ message: 'Error adsasdadas' }],
+                              }),
+                        400
+                    );
+                }).then((data) => {
+                    console.log(data);
+                    if (data.errors) {
+                        throw new Error(JSON.stringify(data.errors));
+                    } else return data.data;
+                })
+            )
+        );
+    };

+ 1 - 1
src/actions/actionGoodById.js

@@ -11,7 +11,7 @@ export const actionGoodById = (_id) => async (dispatch, getState) => {
                     Math.random() > 0.01
                         ? resolve({
                               data: {
-                                  id: 6,
+                                  _id: 6,
                                   name: 'Good 6',
                                   description: 'adaadasda asasd asd asd asd asd ',
                                   price: '999',

+ 86 - 0
src/actions/actionGoodsFind.js

@@ -0,0 +1,86 @@
+import { actionPromise } from '../reducers';
+
+export const actionGoodsFind =
+    ({ text = '', limit = 0, skip = 0, promiseName = 'goodsFind' }) =>
+    async (dispatch, getState) => {
+        await dispatch(
+            actionPromise(
+                promiseName,
+                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((data) => {
+                    console.log(data);
+                    if (data.errors) {
+                        throw new Error(JSON.stringify(data.errors));
+                    } else return data.data;
+                })
+            )
+        );
+    };

+ 31 - 0
src/actions/actionLogin.js

@@ -0,0 +1,31 @@
+import { actionPromise } from '../reducers';
+import { gql } from '../helpers';
+import { actionAuthLogin } from '../reducers';
+
+export const actionLogin = (login, password) => async (dispatch, getState) => {
+    const token = await dispatch(
+        actionPromise(
+            'login',
+            new Promise((resolve) => {
+                setTimeout(
+                    Math.random() > 0.01
+                        ? resolve({
+                              data: {
+                                  login: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiaWQiOiI2MjYyY2I0MGJmOGIyMDY0MzNmNWIzZDIiLCJsb2dpbiI6InRlc3QxMTIxMSIsImFjbCI6WyI2MjYyY2I0MGJmOGIyMDY0MzNmNWIzZDIiLCJ1c2VyIl19LCJpYXQiOjE2NTIzNzcxNTF9.j462Ble3m-eycfY_QS_wBSQmR6UZ65FxrKUqkF3MBBY',
+                              },
+                          })
+                        : resolve({
+                              errors: [{ message: 'Error adsasdadas' }],
+                          }),
+                    400
+                );
+            }).then((data) => {
+                console.log(data);
+                if (data.errors) {
+                    throw new Error(JSON.stringify(data.errors));
+                } else return data.data;
+            })
+        )
+    );
+    dispatch(actionAuthLogin(token));
+};

+ 7 - 0
src/actions/actionLogout.js

@@ -0,0 +1,7 @@
+import { actionCartClear, actionPromiseClear } from '../reducers';
+import { actionAuthLogout } from '../reducers';
+
+export const actionLogout = () => async (dispatch) => {
+    dispatch(actionCartClear());
+    dispatch(actionAuthLogout());
+};

+ 87 - 0
src/components/AuthPage/AuthForm.js

@@ -0,0 +1,87 @@
+import { actionLogin } from '../../actions/actionLogin';
+
+import { useState, useEffect } from 'react';
+import { connect } from 'react-redux';
+import { MdVisibility, MdVisibilityOff } from 'react-icons/md';
+import { Box, Button, IconButton, TextField, Stack } from '@mui/material';
+import { useFormik } from 'formik';
+import * as Yup from 'yup';
+
+const signInSchema = Yup.object().shape({
+    username: Yup.string().required("Обов'язкове"),
+    password: Yup.string().required("Обов'язкове"),
+});
+
+export const AuthForm = ({ onSubmit }) => {
+    const [showPassword, setShowPassword] = useState(false);
+
+    const formik = useFormik({
+        initialValues: {
+            username: '',
+            password: '',
+        },
+        validationSchema: signInSchema,
+        validateOnChange: true,
+        onSubmit: () => {
+            onSubmit(formik.values.username, formik.values.password);
+        },
+    });
+
+    return (
+        <Box
+            className="AuthForm"
+            display="flex"
+            flexDirection="column"
+            onSubmit={formik.handleSubmit}
+            justifyContent="center"
+        >
+            <TextField
+                id="username"
+                name="username"
+                variant="outlined"
+                label="Username"
+                error={formik.touched.username && Boolean(formik.errors.username)}
+                value={formik.values.username}
+                onBlur={formik.handleBlur}
+                onChange={formik.handleChange}
+                helperText={formik.touched.username && formik.errors.username}
+                fullWidth
+                sx={{ mt: 2 }}
+            />
+
+            <TextField
+                id="password"
+                name="password"
+                variant="outlined"
+                label="Password"
+                type={showPassword ? 'text' : 'password'}
+                error={formik.touched.password && Boolean(formik.errors.password)}
+                value={formik.values.password}
+                onBlur={formik.handleBlur}
+                onChange={formik.handleChange}
+                helperText={formik.touched.password && formik.errors.password}
+                InputProps={{
+                    endAdornment: (
+                        <IconButton onClick={() => setShowPassword((prev) => !prev)} edge="end">
+                            {showPassword ? <MdVisibilityOff /> : <MdVisibility />}
+                        </IconButton>
+                    ),
+                }}
+                fullWidth
+                sx={{ mt: 2 }}
+            />
+            <Button
+                variant="contained"
+                color="primary"
+                type="submit"
+                disabled={formik.isSubmitting || !formik.isValid}
+                sx={{ mt: 2, mr: 1 }}
+                fullWidth
+            >
+                Увійти
+            </Button>
+        </Box>
+    );
+};
+
+export const CAuthForm = connect(null, { onAuth: (login, password) => actionLogin(login, password) })(AuthForm);

+ 10 - 0
src/components/AuthPage/index.js

@@ -0,0 +1,10 @@
+import { Box, Container } from '@mui/material';
+import { AuthForm } from './AuthForm';
+
+export const AuthPage = () => {
+    return (
+        <Box className="AuthPage">
+            <AuthForm />
+        </Box>
+    );
+};

+ 1 - 1
src/components/CartPage/CartItem.js

@@ -61,7 +61,7 @@ export const CartItem = ({ order, onDeleteClick }) => {
                     <IconButton onClick={() => handleChange(countInput - 1)}>
                         <AiOutlineMinus />
                     </IconButton>
-                    <Typography>{countInput}</Typography>
+                    <Typography >{countInput}</Typography>
                     <IconButton onClick={() => handleChange(countInput + 1)}>
                         <AiOutlinePlus />
                     </IconButton>

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

@@ -44,6 +44,7 @@ export const CartPage = () => {
                 </Table>
 
                 <OrderForm />
+                
             </Stack>
         </Box>
     );

+ 1 - 1
src/components/GoodPage/index.js

@@ -11,7 +11,7 @@ export const GoodPage = () => {
     const { _id = '', name = '', price = '', description = '', images = [] } = good || {};
     return (
         <Box className="GoodPage">
-            <Grid container spacing={2} className="images">
+            <Grid container spacing={4} className="images">
                 <Grid item xs={12} md={4}>
                     <Carousel showIndicators={false} showStatus={false}>
                         {(good.images || [{ url: defaultGoodImage }]).map((image) => (

+ 2 - 7
src/components/GoodsPage/index.js

@@ -3,19 +3,14 @@ import { Box } from '@mui/system';
 import { connect } from 'react-redux';
 import { useParams } from 'react-router-dom';
 import { GoodCard } from '../common/GoodCard';
+import { GoodList } from '../common/GoodList';
 
 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>
+            <GoodList goods={goods} />
         </Box>
     );
 };

+ 77 - 0
src/components/LayoutPage/index.js

@@ -0,0 +1,77 @@
+import { Box, Grid } from '@mui/material';
+import { useEffect } from 'react';
+import { connect, useDispatch } from 'react-redux';
+import { Route, Routes, useParams } from 'react-router-dom';
+import { actionCatById } from '../../actions/actionCatById';
+import { actionGoodById } from '../../actions/actionGoodById';
+import { actionGoodsFind } from '../../actions/actionGoodsFind';
+import { AdminLayoutPage } from '../admin/AdminLayoutPage';
+import { CartPage } from '../CartPage';
+import { GoodList } from '../common/GoodList';
+import { CProtectedRoute, ProtectedRoute } from '../common/ProtectedRoute';
+import { GoodPage } from '../GoodPage';
+import { CGoodsPage } from '../GoodsPage';
+import { Aside } from '../layout/Aside';
+import Content from '../layout/Content';
+import { Footer } from '../layout/Footer';
+import { Header } from '../layout/Header';
+import { MainPage } from '../MainPage';
+
+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 CGoodsList = connect((state) => ({ goods: state.promise?.pageGoodsFind?.payload || [] }))(GoodList);
+
+const GoodsListContainer = () => {
+    const params = useParams();
+    const dispatch = useDispatch();
+    useEffect(() => {
+        dispatch(actionGoodsFind({ text: params.searchData, promiseName: 'pageGoodsFind' }));
+    }, [params.searchData]);
+
+    return <CGoodsList />;
+};
+
+export const LayoutPage = () => (
+    <Box className="LayoutPage">
+        <Header />
+        <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="/cart" exact element={<CartPage />} />
+                        <Route path="/search/:searchData/" element={<GoodsListContainer />} exact />
+                        <Route path="/category/:_id" element={<GoodsPageContainer />} />
+                        <Route path="/category/" element={<GoodsPageContainer />} />
+                        <Route path="/good/:id" element={<GoodPageContainer />} />
+                        <Route
+                            path="/admin"
+                            element={
+                                <CProtectedRoute roles={['admin']} fallback="/auth">
+                                    <AdminLayoutPage />
+                                </CProtectedRoute>
+                            }
+                        />
+                    </Routes>
+                </Content>
+            </Grid>
+        </Grid>
+        <Footer />
+    </Box>
+);

+ 8 - 52
src/components/Root/index.js

@@ -1,37 +1,12 @@
-import { Route, Router, Routes, useNavigate, useParams } from 'react-router-dom';
+import { Route, Routes } from 'react-router-dom';
 
-import { Container, Box, Stack, Grid } from '@mui/material';
-
-import { Aside } from '../layout/Aside';
-import Content from '../layout/Content';
+import { Box } from '@mui/material';
 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';
-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 />;
-};
+import { AuthPage } from '../AuthPage';
+import { LayoutPage } from '../LayoutPage';
 
 const Root = ({ user = {} }) => {
     const isSignIn = true;
@@ -40,29 +15,10 @@ const Root = ({ user = {} }) => {
 
     return (
         <Box className="Root">
-            <Header />
-            <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="/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>
-                    </Content>
-                </Grid>
-            </Grid>
-
-            <Footer />
+            <Routes>
+                <Route path="/auth" element={<AuthPage />} />
+                <Route path="/*" element={<LayoutPage />} />
+            </Routes>
         </Box>
     );
 };

+ 8 - 2
src/components/admin/AdminLayoutPage/index.js

@@ -1,7 +1,13 @@
-import { Box } from '@mui/material';
+import { Box, Container } from '@mui/material';
+import { Routes } from 'react-router-dom';
+import { CProtectedRoute } from '../../common/ProtectedRoute';
 
 const AdminLayoutPage = () => {
-    return <Box></Box>;
+    return (
+        <Box className="AdminLayoutPage">
+            <Routes></Routes>
+        </Box>
+    );
 };
 
 export { AdminLayoutPage };

+ 1 - 2
src/components/common/Categories/Category.js

@@ -5,8 +5,7 @@ import { Link } from 'react-router-dom';
 export const Category = ({ category = {} }) => (
     <Fragment>
         <ListItem disablePadding>
-            <Link to={`/category/${category.id}`}></Link>
-            <ListItemButton>
+            <ListItemButton component={Link} to={`/category/${category._id}`}>
                 <ListItemText primary={category.name || ''} />
             </ListItemButton>
         </ListItem>

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

@@ -7,7 +7,9 @@ const Categories = ({ categories = [] }) => {
         <Box className="Categories">
             <List>
                 {(categories || []).map((cat) => (
-                    <Category category={cat} key={cat._id} />
+                    <Box key={cat._id}>
+                        <Category category={cat} />
+                    </Box>
                 ))}
             </List>
         </Box>

+ 9 - 0
src/components/common/Error.js

@@ -0,0 +1,9 @@
+const { Typography } = require('@mui/material');
+
+const Error = ({ children }) => (
+    <Typography variant="body1" sx={{ textAlign: 'center' }} className="error">
+        {children}
+    </Typography>
+);
+
+export { Error };

+ 3 - 1
src/components/common/GoodCardSlider/GoodCardSlider.js

@@ -24,7 +24,9 @@ export const GoodCardSlider = ({ goods = [] } = {}) => {
         <Box className="GoodCardSlider">
             <Carousel className="Slider" showThumbs={false} showStatus={false} showIndicators={false}>
                 {(goodSets || []).map((goodSet, idx) => (
-                    <GoodCardSet goods={goodSet} num={num} key={idx} />
+                    <Box key={idx}>
+                        <GoodCardSet goods={goodSet} num={num} />
+                    </Box>
                 ))}
             </Carousel>
         </Box>

+ 16 - 0
src/components/common/GoodList/index.js

@@ -0,0 +1,16 @@
+import { Box, Grid } from '@mui/material';
+import { GoodCard } from '../GoodCard';
+
+export const GoodList = ({ goods = [] } = {}) => {
+    return (
+        <Box className="GoodList">
+            <Grid container spacing={2}>
+                {(goods || []).map((good) => (
+                    <Grid item xs={3} key={good._id}>
+                        <GoodCard good={good} />
+                    </Grid>
+                ))}
+            </Grid>
+        </Box>
+    );
+};

+ 10 - 11
src/components/common/ProtectedRoute/index.js

@@ -1,15 +1,14 @@
-import { Navigate, Route } from 'react-router-dom';
+import { Navigate, Route, useLocation } from 'react-router-dom';
 import { connect } from 'react-redux';
-export const ProtectedRoute = ({ roles = [], fallback = '/', component, auth, ...routeProps }) => {
-    const WrapperComponent = (renderProps) => {
-        const C = component;
-        !!auth.length || (auth = ['anon']);
-        if (!auth.filter((role) => roles.includes(role)).length) {
-            return <Navigate to={fallback} />;
-        }
-        return <C {...renderProps} />;
-    };
-    return <Route {...routeProps} element={<WrapperComponent />} />;
+
+export const ProtectedRoute = ({ roles = ['anon'], children, fallback = '/', auth } = {}) => {
+    let location = useLocation();
+    !!auth.length || (auth = ['anon']);
+    if (!auth.filter((role) => roles.includes(role)).length) {
+        return <Navigate to={fallback} state={{ from: location }} />;
+    }
+
+    return children;
 };
 
 export const CProtectedRoute = connect((state) => ({ auth: state.auth?.payload?.sub?.acl || [] }))(ProtectedRoute);

+ 22 - 11
src/components/common/SearchBar/SearchBar.js

@@ -4,6 +4,8 @@ import { Link } from 'react-router-dom';
 import { AiOutlineCloseCircle } from 'react-icons/ai';
 
 import { Box, TextField, Button, Container, Stack, IconButton } from '@mui/material';
+import { actionGoodsFind } from '../../../actions/actionGoodsFind';
+import { actionPromiseClear } from '../../../reducers';
 
 // import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 // import { faSearch } from "@fortawesome/free-solid-svg-icons";
@@ -33,8 +35,16 @@ export const SearchBar = ({
                 inputValue.length && setIsChildrenOpen(true);
             }
         };
+        if (inputTimeout) {
+            clearTimeout(inputTimeout);
+            setInputTimeout(null);
+        }
+        setInputTimeout(
+            setTimeout(() => {
+                inputValue && onSearch(inputValue);
+            }, 700)
+        );
 
-        inputValue && onSearch(inputValue);
         setIsChildrenOpen(!!inputValue?.length);
         document.addEventListener('mousedown', checkClickOutsideHeaderSearchBar);
         return () => {
@@ -61,25 +71,26 @@ export const SearchBar = ({
                 />
 
                 {!!inputValue ? (
-                    <Link
+                    <Button
+                        component={Link}
                         className="Link"
                         to={`${searchLink}${inputValue}`}
                         onClick={() => {
                             setInputValue('');
                             onSearchButtonClick();
                         }}
+                        variant="text"
+                        color="inherit"
                     >
-                        <Button variant="text" color="inherit">
-                            Пошук
-                        </Button>
-                    </Link>
+                        Пошук
+                    </Button>
                 ) : (
                     <Button variant="text" color="inherit">
                         Пошук
                     </Button>
                 )}
             </Stack>
-            <Stack direction="row" justifyContent="center">
+            <Stack direction="row">
                 {isChildrenOpen && (
                     <R
                         onItemClick={() => {
@@ -94,7 +105,7 @@ export const SearchBar = ({
     );
 };
 
-// export const CSearchBar = connect(null, {
-//     onSearch: (text) => actionGoodsFind({ text, limit: 5 }),
-//     onSearchButtonClick: () => actionPromiseClear("goodsFind"),
-// })(SearchBar);
+export const CSearchBar = connect(null, {
+    onSearch: (text) => actionGoodsFind({ text, limit: 5 }),
+    onSearchButtonClick: () => actionPromiseClear('goodsFind'),
+})(SearchBar);

+ 4 - 6
src/components/common/SearchBar/SearchCategoryResultItem.js

@@ -1,9 +1,7 @@
-import { backendURL } from "helpers";
-import { Link } from "react-router-dom";
-import defaultGoodImage from "images/defaultGoodImage.png";
-import { Stack, Typography } from "@mui/material";
-const SearchCategoryResultItem = ({ category, onClick, link = "" } = {}) => {
-    const { _id = null, name = "" } = category || {};
+import { Link } from 'react-router-dom';
+import { Stack, Typography } from '@mui/material';
+const SearchCategoryResultItem = ({ category, onClick, link = '' } = {}) => {
+    const { _id = null, name = '' } = category || {};
 
     return (
         <Link className="Link" to={`${link}${_id}/`}>

+ 19 - 11
src/components/common/SearchBar/SearchGoodResultItem.js

@@ -1,16 +1,24 @@
-import { backendURL } from 'helpers';
 import { Link } from 'react-router-dom';
-import defaultGoodImage from 'images/defaultGoodImage.png';
+import defaultGoodImage from '../../../images/default-good-image.png';
 
 import { Grid, Box, Stack, Typography } from '@mui/material';
 const SearchGoodResultItem = ({ good, onClick, link = '' } = {}) => {
     const { _id = 0, images = [], name = '', description = '', price = '' } = good || {};
 
     return (
-        <Link className="Link" to={`${link}${_id}/`}>
-            <Stack direction="row" className="SearchGoodResultItem" onClick={() => onClick && onClick()}>
-                <Box component="img" src={images[0]?.url ? `/${images ? images[0]?.url : ''}` : defaultGoodImage} />
-                <Box sx={{ p: 1, flexGrow: 1 }}>
+        <Grid
+            container
+            component={Link}
+            to={`${link}${_id}/`}
+            className="SearchGoodResultItem Link"
+            onClick={() => onClick && onClick()}
+            spacing={1}
+        >
+            <Grid item xs={3}>
+                <Box component="img" src={images ? images[0]?.url : defaultGoodImage} />
+            </Grid>
+            <Grid item xs={8}>
+                <Box sx={{ p: 1 }}>
                     <Typography variant="body1" sx={{ flexGrow: 1 }}>
                         {name.length > 30 ? `${name.substring(0, 30)}...` : name}
                     </Typography>
@@ -18,11 +26,11 @@ const SearchGoodResultItem = ({ good, onClick, link = '' } = {}) => {
                         {description.length > 70 ? `${description.substring(0, 70)}...` : description}
                     </Typography>
                 </Box>
-                <Typography variant="body1" sx={{ display: 'flex', alignItems: 'center' }}>
-                    {price}
-                </Typography>
-            </Stack>
-        </Link>
+            </Grid>
+            <Grid item xs={1} sx={{ display: 'flex', alignItems: 'center' }}>
+                <Typography variant="body1">{price}</Typography>
+            </Grid>
+        </Grid>
     );
 };
 

+ 5 - 2
src/components/common/SearchBar/SearchResults.js

@@ -5,9 +5,12 @@ import { HiOutlineEmojiSad } from 'react-icons/hi';
 import SearchCategoryResultItem from './SearchCategoryResultItem';
 import { Divider, Paper, Stack, Typography } from '@mui/material';
 import { Box } from '@mui/system';
-import { Error } from 'components/Error';
+import { Error } from '../Error';
 
 export const SearchResults = ({ items, onItemClick, itemLink = '' }) => {
+    useEffect(() => {
+        console.log(items);
+    }, [items]);
     return (
         <Paper className="SearchResults">
             <Stack>
@@ -40,7 +43,7 @@ export const SearchResults = ({ items, onItemClick, itemLink = '' }) => {
                         []
                     )
                 ) : (
-                    <Error>Ничего не найдено</Error>
+                    <Error>Ничего не знайдено</Error>
                 )}
             </Stack>
         </Paper>

+ 2 - 2
src/components/common/SearchBar/index.js

@@ -1,7 +1,7 @@
-import { SearchBar } from './SearchBar';
+import { SearchBar, CSearchBar } from './SearchBar';
 
 // import CSearchResults, { SearchResults } from './SearchResults';
 // import SearchGoodResultItem from './SearchGoodResultItem';
 
 // export { SearchBar, SearchResults, CSearchResults, SearchGoodResultItem };
-export { SearchBar };
+export { SearchBar, CSearchBar };

+ 1 - 3
src/components/layout/Header/CartIcon/index.js

@@ -15,9 +15,7 @@ export const CartIcon = ({ cart }) => {
     return (
         <Box className="CartIcon">
             <Badge badgeContent={count} color="primary">
-                <IconButton size="medium">
-                    <MdOutlineShoppingCart className="CartLogo" />
-                </IconButton>
+                <MdOutlineShoppingCart className="CartLogo" />
             </Badge>
         </Box>
     );

+ 5 - 4
src/components/layout/Header/index.js

@@ -5,7 +5,8 @@ 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 { CSearchBar, SearchBar } from '../../common/SearchBar';
+import CSearchResults from '../../common/SearchBar/SearchResults';
 import { CCartIcon } from './CartIcon';
 
 const Header = () => {
@@ -41,11 +42,11 @@ const Header = () => {
                         </Button>
                     </Stack>
                     <Box className="SearchBarWrapper">
-                        <SearchBar />
+                        <CSearchBar render={CSearchResults} renderParams={{ itemLink: '/good/' }} />
                     </Box>
 
-                    <IconButton color="inherit" className="CartLogoButton">
-                        <Box onClick={() => setIsCartDrawerOpen(true)}>
+                    <IconButton color="inherit" className="CartLogoButton" onClick={() => setIsCartDrawerOpen(true)}>
+                        <Box>
                             <CCartIcon />
                         </Box>
                     </IconButton>

+ 1 - 0
src/helpers/delay.js

@@ -0,0 +1 @@
+export const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

+ 2 - 1
src/helpers/index.js

@@ -1,7 +1,8 @@
 import { jwtDecode } from './jwtDecode';
 import { getQuery } from './getQuery';
 import { mock } from './mock';
+import { delay } from './delay';
 
 export const query = getQuery('/');
 
-export { jwtDecode, getQuery, mock };
+export { jwtDecode, getQuery, mock, delay };

+ 41 - 0
src/index.scss

@@ -3,6 +3,24 @@
   margin:0;
 }
 
+.Link{
+  color:inherit;
+  text-decoration: none;
+}
+
+
+.AuthPage{
+
+  display: flex;
+  justify-content: center;
+  min-width: 100%;
+  height: 100vh;
+  align-items: center;
+  & .AuthForm{
+    min-width: 400px;
+  }
+}
+
 
 .carousel {
   &   .thumb {
@@ -60,6 +78,8 @@
 
   & .Header{
     margin-bottom: 30px;
+
+
     & .AppBar{
       background: white;
       color:#6750A4;
@@ -76,10 +96,31 @@
           flex-grow: 1;
           margin-left: 10px;
           & .SearchBar{
+            position: relative;
             & .SearchBarInput{
               width: 100%;
               max-width: 500px;
             }
+            & .SearchResults{
+              position: absolute;
+              width: 100%;
+              max-width: 480px;
+              background: white;
+              padding: 10px;
+              z-index: 2;
+
+              & .SearchGoodResultItem{
+                text-align: left;
+                & img{
+                  width:100%;
+                  max-height: 100px;
+                }
+                &:hover{
+                  background: #F1F2F4;
+                }
+              }
+            }
+
           }
         }
         & .CartIcon{