Prechádzať zdrojové kódy

+ auth | + orderList

ilya_shyian 1 rok pred
rodič
commit
74b6c0e0a3

+ 29 - 0
src/actions/actionAboutMe.js

@@ -0,0 +1,29 @@
+import { gql } from "../helpers";
+import { actionPromise } from "../reducers";
+
+export const actionAboutMe = () => async (dispatch, getState) => {
+  const {
+    auth: {
+      payload: {
+        sub: { id },
+      },
+    },
+  } = getState();
+  await dispatch(
+    actionPromise(
+      "aboutMe",
+      gql(
+        `query AboutMe($q:String){
+                        UserFindOne(query:$q){
+                            _id username avatar{
+                                _id url
+                            }
+                        }
+                    }`,
+        {
+          q: JSON.stringify([{ _id: id }]),
+        }
+      )
+    )
+  );
+};

+ 4 - 4
src/actions/actionCategoryUpdate.js

@@ -1,7 +1,7 @@
-import { actionCatAll } from './actionCatAll';
-import { actionCategoryUpsert } from './actionCategoryUpsert';
+import { actionCatAll } from "./actionCatAll";
+import { actionCategoryUpsert } from "./actionCategoryUpsert";
 
 export const actionCategoryUpdate = (good) => async (dispatch, getState) => {
-    await dispatch(actionCategoryUpsert(good));
-    await setTimeout(() => dispatch(actionCatAll()), 1000);
+  await dispatch(actionCategoryUpsert(good));
+  await dispatch(actionCatAll());
 };

+ 12 - 7
src/actions/actionLogin.js

@@ -1,11 +1,14 @@
-import { actionPromise } from '../reducers';
-import { backendURL, gql } from '../helpers';
-import { actionAuthLogin } from '../reducers';
+import { actionPromise } from "../reducers";
+import { backendURL, gql } from "../helpers";
+import { actionAuthLogin } from "../reducers";
+import { actionAboutMe } from "./actionAboutMe";
+import { actionLogout } from "./actionLogout";
 
 export const actionLogin = (username, password) => async (dispatch, getState) => {
+    await dispatch(actionLogout());
     const token = await dispatch(
         actionPromise(
-            'login',
+            "login",
             gql(
                 `mutation Login($username:String!,$password:String!){
                     tokenAuth(username:$username,password:$password){
@@ -16,9 +19,11 @@ export const actionLogin = (username, password) => async (dispatch, getState) =>
             )
         )
     );
-    if (typeof token === 'string') {
-        dispatch(actionAuthLogin(token));
+    if (typeof token === "string") {
+        await dispatch(actionAuthLogin(token));
     } else {
-        dispatch(actionAuthLogin(token.token));
+        await dispatch(actionAuthLogin(token.token));
     }
+
+    await dispatch(actionAboutMe());
 };

+ 5 - 4
src/actions/actionLogout.js

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

+ 6 - 6
src/actions/actionOrderUpdate.js

@@ -1,8 +1,8 @@
-import { actionCartClear, actionPromiseClear } from '../reducers';
-import { actionOrdersAll } from './actionOrdersAll';
-import { actionOrderUpsert } from './actionOrderUpsert';
+import { actionCartClear, actionPromiseClear } from "../reducers";
+import { actionOrdersAll } from "./actionOrdersAll";
+import { actionOrderUpsert } from "./actionOrderUpsert";
 
-export const actionOrderUpdate = (order) => async (dispatch, getState) => {
-    await dispatch(actionOrderUpsert(order));
-    await dispatch(actionOrdersAll());
+export const actionOrderUpdate = (orderGoods) => async (dispatch, getState) => {
+  await dispatch(actionOrderUpsert(orderGoods));
+  await dispatch(actionOrdersAll());
 };

+ 34 - 32
src/actions/actionOrderUpsert.js

@@ -1,35 +1,37 @@
-import { backendURL } from '../helpers';
-import { actionPromise } from '../reducers';
+import { backendURL, gql } from "../helpers";
+import { actionCartClear, actionPromise } from "../reducers";
 
-export const actionOrderUpsert = (order) => async (dispatch) => {
-    const formData = new FormData();
-    order._id && formData.append('_id', order._id);
-    formData.append('orderGoods', JSON.stringify(order.orderGoods || []));
-    formData.append('email', order.email);
-    formData.append('phoneNumber', order.phoneNumber);
-    formData.append('address', order.address);
-    formData.append('delivery', order.delivery);
-    formData.append('name', order.name);
-    formData.append('surname', order.surname);
-    formData.append('status', order.status);
+export const actionOrderUpsert = (orderGoods) => async (dispatch, getState) => {
+  if (!orderGoods.length) {
+    return;
+  }
+  await dispatch(
+    actionPromise(
+      "orderUpsert",
+      gql(
+        `mutation newOrder($order:OrderInput!){
+        OrderUpsert(order:$order){
+          _id price
+        }
+      }
+      `,
+        {
+          order: {
+            orderGoods: orderGoods.map((orderGood) => ({
+              count: orderGood.count,
+              good: { _id: orderGood.good._id },
+            })),
+          },
+        }
+      )
+    )
+  );
+  let {
+    promise: { orderUpsert },
+  } = getState();
 
-    dispatch(
-        actionPromise(
-            'orderUpsert',
-            fetch(`${backendURL}/order/`, {
-                method: 'POST',
-                headers: {
-                    accept: 'application/json',
-                    ...(localStorage.authToken ? { Authorization: 'Bearer ' + localStorage.authToken } : {}),
-                },
-                body: formData,
-            })
-                .then((res) => res.json())
-                .then((data) => {
-                    if (data.errors) {
-                        throw new Error(JSON.stringify(data.errors));
-                    } else return data.data;
-                })
-        )
-    );
+  if (orderUpsert.status === "FULFILLED") {
+    dispatch(actionCartClear());
+    // dispatch(actionOrders(token));
+  }
 };

+ 21 - 0
src/actions/actionOrders.js

@@ -0,0 +1,21 @@
+import { actionPromise } from "../reducers";
+import { gql } from "../helpers";
+export const actionOrders = () => (dispatch) =>
+  dispatch(
+    actionPromise(
+      "orders",
+      gql(`
+            query orders{
+                OrderFind(query:"[{}]"){
+                    _id price createdAt status orderGoods{
+                        _id count price good{
+                            name _id price images{
+                                url _id
+                            }
+                        }
+                    }
+                }
+            }
+          `)
+    )
+  );

+ 20 - 15
src/actions/actionOrdersAll.js

@@ -7,25 +7,30 @@ export const actionOrdersAll =
         dispatch(
             actionPromise(
                 promiseName,
-                fetch(
-                    `${backendURL}/orders/?limit=${limit}&skip=${skip}${orderBy && `&orderBy=` + orderBy}${
-                        status ? `&status=` + status : ''
+                gql(
+                    `query OrdersAll($query:String){
+                        OrderFind(query: $query){
+                            _id status price 
+                            owner{
+                                _id username
+                            }
+                            orderGoods{
+                                _id count good{
+                                    _id name price 
+                                }
+                            }
+                        }
                     }`,
                     {
-                        method: 'GET',
-                        headers: {
-                            accept: 'application/json',
-                            'Content-Type': 'application/json',
-                            ...(localStorage.authToken ? { Authorization: 'Bearer ' + localStorage.authToken } : {}),
-                        },
+                        query: JSON.stringify([
+                            {},
+                            {
+                                limit: !!limit ? limit : 100,
+                                skip: skip,
+                            },
+                        ]),
                     }
                 )
-                    .then((res) => res.json())
-                    .then((data) => {
-                        if (data.errors) {
-                            throw new Error(JSON.stringify(data.errors));
-                        } else return data.data;
-                    })
             )
         );
     };

+ 17 - 6
src/actions/actionPageStart.js

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

+ 28 - 0
src/actions/actionRegister.js

@@ -0,0 +1,28 @@
+import { gql } from "../helpers";
+import { actionPromise } from "../reducers";
+import { actionLogin } from "./actionLogin";
+
+export const actionRegister = (username, password) => async (dispatch, getState) => {
+  await dispatch(
+    actionPromise(
+      "register",
+      gql(
+        `mutation register($username:String,$password:String){
+                               UserUpsert(user:{username:$username,password:$password}){
+                                   _id username
+                               }
+                           }`,
+        {
+          username,
+          password,
+        }
+      )
+    )
+  );
+  const {
+    promise: { register },
+  } = getState();
+  if (register.status === "FULFILLED") {
+    dispatch(actionLogin(username, password));
+  }
+};

+ 23 - 23
src/components/AuthPage/AuthForm.js

@@ -1,13 +1,13 @@
-import { actionLogin } from '../../actions/actionLogin';
+import { actionLogin } from "../../actions/actionLogin";
 
-import { useState, useEffect, useContext } from 'react';
-import { connect, useSelector } 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';
-import { UIContext } from '../UIContext';
-import { Navigate, useNavigate } from 'react-router-dom';
+import { useState, useEffect, useContext } from "react";
+import { connect, useSelector } 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";
+import { UIContext } from "../UIContext";
+import { Navigate, useNavigate } from "react-router-dom";
 
 const signInSchema = Yup.object().shape({
     username: Yup.string().required("Обов'язкове"),
@@ -18,16 +18,16 @@ export const AuthForm = ({ onSubmit = null, promiseStatus, promisePayload, serve
     const [showPassword, setShowPassword] = useState(false);
     const { setAlert } = useContext(UIContext);
     const navigate = useNavigate();
-    const token = useSelector((state) => state.auth?.token || null);
+    const isAdmin = useSelector((state) => state.auth?.payload?.sub?.acl || []).includes("admin");
 
-    if (token) {
-        navigate('/admin');
+    if (isAdmin) {
+        navigate("/admin");
     }
 
     const formik = useFormik({
         initialValues: {
-            username: '',
-            password: '',
+            username: "",
+            password: "",
         },
         validationSchema: signInSchema,
         validateOnChange: true,
@@ -37,28 +37,28 @@ export const AuthForm = ({ onSubmit = null, promiseStatus, promisePayload, serve
     });
 
     useEffect(() => {
-        if (promiseStatus === 'FULFILLED') {
+        if (promiseStatus === "FULFILLED") {
             formik.setSubmitting(false);
             if (promisePayload) {
                 setAlert({
                     show: true,
-                    severity: 'success',
-                    message: 'Готово',
+                    severity: "success",
+                    message: "Готово",
                 });
             } else {
                 setAlert({
                     show: true,
-                    severity: 'error',
-                    message: 'Не вірні дані',
+                    severity: "error",
+                    message: "Не вірні дані",
                 });
             }
         }
-        if (promiseStatus === 'REJECTED') {
-            const errorMessage = serverErrors.reduce((prev, curr) => prev + '\n' + curr.message, '');
+        if (promiseStatus === "REJECTED") {
+            const errorMessage = serverErrors.reduce((prev, curr) => prev + "\n" + curr.message, "");
             formik.setSubmitting(false);
             setAlert({
                 show: true,
-                severity: 'error',
+                severity: "error",
                 message: errorMessage,
             });
         }
@@ -92,7 +92,7 @@ export const AuthForm = ({ onSubmit = null, promiseStatus, promisePayload, serve
                 name="password"
                 variant="outlined"
                 label="Password"
-                type={showPassword ? 'text' : 'password'}
+                type={showPassword ? "text" : "password"}
                 error={formik.touched.password && Boolean(formik.errors.password)}
                 value={formik.values.password}
                 onBlur={formik.handleBlur}

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

@@ -1,192 +0,0 @@
-import { Box, Grid, TextField, MenuItem, Button, Alert, Snackbar } from '@mui/material';
-import * as Yup from 'yup';
-import { useFormik } from 'formik';
-import { connect, useDispatch } from 'react-redux';
-import { actionCartClear, actionPromiseClear } from '../../../reducers';
-import { actionOrderUpdate } from '../../../actions/actionOrderUpdate';
-import { useContext, useEffect, useState } from 'react';
-import { UIContext } from '../../UIContext';
-
-const deliveryOptions = [
-    { label: 'Нова пошта', value: 'nova-poshta' },
-    { label: 'Justin', value: 'justin' },
-];
-
-const phoneRegExp =
-    /^((\\+[1-9]{1,4}[ \\-]*)|(\\([0-9]{2,3}\\)[ \\-]*)|([0-9]{2,4})[ \\-]*)*?[0-9]{3,4}?[ \\-]*[0-9]{3,4}?$/;
-const orderSchema = Yup.object().shape({
-    name: Yup.string().min(3, 'не меньше 3 символів').max(22, 'не більше 22 символів').required("обов'язкове"),
-    surname: Yup.string().min(3, 'не меньше 3 символів').max(22, 'не більше 22 символів').required("обов'язкове"),
-    email: Yup.string().email('не вірний формат').required("обов'язкове"),
-    address: Yup.string().required("обов'язкове"),
-    phoneNumber: Yup.string().matches(phoneRegExp, 'не вірний формат').required("обов'язкове"),
-    delivery: Yup.string()
-        .required("обов'язкове")
-        .oneOf(
-            deliveryOptions.map((option) => option.value),
-            'не знайдено'
-        ),
-});
-
-export const OrderForm = ({ onSubmit = null, promiseStatus = null, serverErrors = [] } = {}) => {
-    const { setAlert } = useContext(UIContext);
-    const formik = useFormik({
-        initialValues: {
-            name: '',
-            surname: '',
-            email: '',
-            address: '',
-            phoneNumber: '',
-            delivery: '',
-        },
-        validationSchema: orderSchema,
-        validateOnChange: true,
-        onSubmit: () => {
-            onSubmit(formik.values);
-        },
-    });
-    const dispatch = useDispatch();
-
-    useEffect(() => {
-        if (promiseStatus === 'FULFILLED') {
-            formik.setSubmitting(false);
-            setAlert({
-                show: true,
-                severity: 'success',
-                message: 'Готово',
-            });
-            dispatch(actionPromiseClear('orderUpsert'));
-            dispatch(actionCartClear());
-        }
-        if (promiseStatus === 'REJECTED') {
-            const errorMessage = serverErrors.reduce((prev, curr) => prev + '\n' + curr.message, '');
-            setAlert({
-                show: true,
-                severity: 'error',
-                message: errorMessage,
-            });
-            formik.setSubmitting(false);
-        }
-    }, [promiseStatus]);
-
-    return (
-        <Box className="OrderForm" component="form" onSubmit={formik.handleSubmit}>
-            <Grid container spacing={2} rowSpacing={1}>
-                <Grid item xs={6}>
-                    <TextField
-                        id="name"
-                        name="name"
-                        variant="outlined"
-                        label="Ім'я"
-                        size="small"
-                        error={formik.touched.name && Boolean(formik.errors.name)}
-                        value={formik.values.name}
-                        onBlur={formik.handleBlur}
-                        onChange={formik.handleChange}
-                        helperText={formik.touched.name && formik.errors.name}
-                        fullWidth
-                    />
-                </Grid>
-                <Grid item xs={6}>
-                    <TextField
-                        id="surname"
-                        name="surname"
-                        variant="outlined"
-                        label="Прізвище"
-                        size="small"
-                        error={formik.touched.surname && Boolean(formik.errors.surname)}
-                        value={formik.values.surname}
-                        onBlur={formik.handleBlur}
-                        onChange={formik.handleChange}
-                        helperText={formik.touched.surname && formik.errors.surname}
-                        fullWidth
-                    />
-                </Grid>
-                <Grid item xs={6}>
-                    <TextField
-                        id="email"
-                        name="email"
-                        variant="outlined"
-                        label="Email"
-                        size="small"
-                        error={formik.touched.email && Boolean(formik.errors.email)}
-                        value={formik.values.email}
-                        onBlur={formik.handleBlur}
-                        onChange={formik.handleChange}
-                        helperText={formik.touched.email && formik.errors.email}
-                        fullWidth
-                    />
-                </Grid>
-                <Grid item xs={6}>
-                    <TextField
-                        id="phoneNumber"
-                        name="phoneNumber"
-                        variant="outlined"
-                        label="Номер телефону"
-                        size="small"
-                        error={formik.touched.phoneNumber && Boolean(formik.errors.phoneNumber)}
-                        value={formik.values.phoneNumber}
-                        onBlur={formik.handleBlur}
-                        onChange={formik.handleChange}
-                        helperText={formik.touched.phoneNumber && formik.errors.phoneNumber}
-                        fullWidth
-                    />
-                </Grid>
-                <Grid item xs={6}>
-                    <TextField
-                        id="address"
-                        name="address"
-                        variant="outlined"
-                        label="Адреса доставки"
-                        size="small"
-                        error={formik.touched.address && Boolean(formik.errors.address)}
-                        value={formik.values.address}
-                        onBlur={formik.handleBlur}
-                        onChange={formik.handleChange}
-                        helperText={formik.touched.address && formik.errors.address}
-                        fullWidth
-                    />
-                </Grid>
-                <Grid item xs={6}>
-                    <TextField
-                        id="delivery"
-                        name="delivery"
-                        variant="outlined"
-                        label="Тип доставкі"
-                        size="small"
-                        extAlign="left"
-                        select
-                        value={formik.values.delivery}
-                        error={formik.touched.delivery && Boolean(formik.errors.delivery)}
-                        onBlur={formik.handleBlur}
-                        onChange={formik.handleChange}
-                        helperText={formik.touched.delivery && formik.errors.delivery}
-                        fullWidth
-                    >
-                        {deliveryOptions.map((option) => (
-                            <MenuItem key={option.value} value={option.value} t>
-                                {option.label}
-                            </MenuItem>
-                        ))}
-                    </TextField>
-                </Grid>
-
-                <Grid item xs={12} display="flex" justifyContent="flex-end">
-                    <Button variant="contained" type="submit" disabled={!formik.isValid || formik.isSubmitting}>
-                        Підтвердити
-                    </Button>
-                </Grid>
-            </Grid>
-        </Box>
-    );
-};
-
-export const COrderForm = connect(
-    (state) => ({
-        promiseStatus: state.promise.orderUpsert?.status || null,
-        serverErrors: state.promise?.orderUpsert?.error || [],
-    }),
-    {
-        onClose: () => actionPromiseClear('orderUpsert'),
-    }
-)(OrderForm);

+ 94 - 58
src/components/CartPage/index.js

@@ -1,65 +1,101 @@
-import { Box, Button, Stack, Table, TableBody, TableCell, TableRow, Typography } from '@mui/material';
-import { useEffect } from 'react';
-import { useDispatch, useSelector } from 'react-redux';
-import { useNavigate } from 'react-router-dom';
-import { actionOrderUpdate } from '../../actions/actionOrderUpdate';
-import { actionOrderUpsert } from '../../actions/actionOrderUpsert';
-import { actionCartDelete } from '../../reducers';
-import { CartItem } from './CartItem';
-import { COrderForm, OrderForm } from './OrderForm';
+import { Box, Button, Stack, Table, TableBody, TableCell, TableRow, Typography } from "@mui/material";
+import { useFormik } from "formik";
+import { useContext, useEffect } from "react";
+import { connect, useDispatch, useSelector } from "react-redux";
+import { useNavigate } from "react-router-dom";
+import { actionOrderUpdate } from "../../actions/actionOrderUpdate";
+import { actionOrderUpsert } from "../../actions/actionOrderUpsert";
+import { actionCartDelete } from "../../reducers";
+import { UIContext } from "../UIContext";
+import { CartItem } from "./CartItem";
 
-export const CartPage = () => {
-    const cart = useSelector((state) => state.cart || {});
-    const sum = Object.entries(cart).reduce((prev, [_id, order]) => prev + order.count * order.good.price, 0);
-    const dispatch = useDispatch();
-    const navigate = useNavigate();
+export const CartPage = ({ onConfirm, promiseStatus, serverErrors }) => {
+  const cart = useSelector((state) => state.cart || {});
+  const { setAlert } = useContext(UIContext);
+  const sum = Object.entries(cart).reduce((prev, [_id, order]) => prev + order.count * order.good.price, 0);
+  const dispatch = useDispatch();
+  const navigate = useNavigate();
 
-    useEffect(() => {
-        if (!Object.entries(cart).length) {
-            navigate('/');
-        }
-    }, []);
+  const formik = useFormik({
+    initialValues: {},
+    onSubmit: () => {
+      onConfirm && Object.keys(cart).length && onConfirm(Object.values(cart));
+    },
+  });
 
-    useEffect(() => {
-        !Object.keys(cart).length && navigate('/');
-    }, [cart]);
+  useEffect(() => {
+    if (!Object.entries(cart).length) {
+      navigate("/");
+    }
+  }, []);
 
-    return (
-        <Box className="CartPage">
-            <Stack spacing={2}>
-                <Typography>Оформлення замовлення</Typography>
-                <Table className="table">
-                    <TableBody>
-                        {Object.entries(cart).map(([_id, order]) => (
-                            <CartItem
-                                order={order}
-                                onDeleteClick={(good) => dispatch(actionCartDelete(good))}
-                                key={_id}
-                            />
-                        ))}
+  useEffect(() => {
+    !Object.keys(cart).length && navigate("/");
+  }, [cart]);
 
-                        <TableRow>
-                            <TableCell colSpan={3}>
-                                <Typography variant="body1" bold>
-                                    Всього:
-                                </Typography>
-                            </TableCell>
-                            <TableCell>
-                                <Typography textAlign="center">{sum} ₴</Typography>
-                            </TableCell>
-                            <TableCell></TableCell>
-                        </TableRow>
-                    </TableBody>
-                </Table>
+  useEffect(() => {
+    if (promiseStatus === "FULFILLED") {
+      formik.setSubmitting(false);
+      setAlert({
+        show: true,
+        severity: "success",
+        message: "Готово",
+      });
+    }
+    if (promiseStatus === "REJECTED") {
+      const errorMessage = serverErrors.reduce((prev, curr) => prev + "\n" + curr.message, "");
+      formik.setSubmitting(false);
+      setAlert({
+        show: true,
+        severity: "error",
+        message: errorMessage,
+      });
+    }
+  }, [promiseStatus]);
 
-                <COrderForm
-                    onSubmit={(order) => {
-                        const orderToSubmit = order;
-                        orderToSubmit.orderGoods = Object.values(cart);
-                        dispatch(actionOrderUpsert(orderToSubmit));
-                    }}
-                />
-            </Stack>
-        </Box>
-    );
+  return (
+    <Box className="CartPage" component="form" onSubmit={formik.handleSubmit}>
+      <Stack spacing={2}>
+        <Typography>Оформлення замовлення</Typography>
+        <Table className="table">
+          <TableBody>
+            {Object.entries(cart).map(([_id, order]) => (
+              <CartItem order={order} onDeleteClick={(good) => dispatch(actionCartDelete(good))} key={_id} />
+            ))}
+
+            <TableRow>
+              <TableCell colSpan={3}>
+                <Typography variant="body1" bold>
+                  Всього:
+                </Typography>
+              </TableCell>
+              <TableCell>
+                <Typography textAlign="center">{sum} ₴</Typography>
+              </TableCell>
+              <TableCell></TableCell>
+            </TableRow>
+            <TableRow>
+              <TableCell colSpan={4}></TableCell>
+
+              <TableCell>
+                <Button variant="contained" disabled={formik.isSubmitting} type="submit">
+                  Підтвердити
+                </Button>
+              </TableCell>
+            </TableRow>
+          </TableBody>
+        </Table>
+      </Stack>
+    </Box>
+  );
 };
+
+export const CCartPage = connect(
+  (state) => ({
+    promiseStatus: state.promise.orderUpsert?.status || null,
+    serverErrors: state.promise.orderUpsert?.error || null,
+  }),
+  {
+    onConfirm: (orderGoods) => actionOrderUpdate(orderGoods),
+  }
+)(CartPage);

+ 19 - 0
src/components/DashboardPage/DashboardOrder/DashboardOrderGood/index.js

@@ -0,0 +1,19 @@
+import { Box, Grid, Stack, Typography } from "@mui/material";
+import defaultGoodImage from "../../../../images/default-good-image.png";
+
+export const DashboardOrderGood = ({ orderGood }) => {
+    const { good, count, price } = orderGood || [];
+    return (
+        <Box className="DashboardOrderGood">
+            <Grid container spacing={4}>
+                <Grid item xs={2}>
+                    <Box component="img" src={good?.images[0]?.url ? `/${good?.images[0]?.url}` : defaultGoodImage} />
+                </Grid>
+                <Grid item xs={10}>
+                    <Typography textAlign="left">Назва: {good?.name || "-"}</Typography>
+                    <Typography textAlign="left">Ціна: {`${good?.price} x ${count} = ${price}` || "-"}</Typography>
+                </Grid>
+            </Grid>
+        </Box>
+    );
+};

+ 20 - 0
src/components/DashboardPage/DashboardOrder/index.js

@@ -0,0 +1,20 @@
+import { Box, Divider, Paper, Stack, Typography } from "@mui/material";
+import { DashboardOrderGood } from "./DashboardOrderGood";
+
+export const DashboardOrder = ({ order }) => {
+    const { price = null, createdAt, orderGoods = [] } = order || {};
+    return (
+        <Paper className="DashboardOrder">
+            <Stack direction="vertical" justifyContent="space-between">
+                <Typography textAlign="left">Дата: {new Date(+createdAt).toDateString()}</Typography>
+                <Typography textAlign="left">Сума: {price || " - "}</Typography>
+            </Stack>
+            <Divider sx={{ my: 2 }} />
+            <Stack spacing={2}>
+                {(orderGoods || []).map((orderGood) => (
+                    <DashboardOrderGood orderGood={orderGood} key={orderGood._id} />
+                ))}
+            </Stack>
+        </Paper>
+    );
+};

+ 14 - 0
src/components/DashboardPage/index.js

@@ -0,0 +1,14 @@
+import { Stack } from "@mui/material";
+import { connect } from "react-redux";
+import { Error } from "../common/Error";
+import { DashboardOrder } from "./DashboardOrder";
+
+export const DashboardPage = ({ orders = [] }) => {
+    return (
+        <Stack className="DashboardPage" spacing={4}>
+            {!!orders.length ? orders.map((order) => <DashboardOrder order={order} key={order._id} />) : <Error>Пока пусто </Error>}
+        </Stack>
+    );
+};
+
+export const CDashboardPage = connect((state) => ({ orders: state.promise?.orders?.payload || [] }))(DashboardPage);

+ 42 - 24
src/components/LayoutPage/index.js

@@ -1,22 +1,23 @@
-import { Box, Grid } from '@mui/material';
-import { useEffect } from 'react';
-import { connect, useDispatch } from 'react-redux';
-import { Navigate, Route, Routes, useLocation, 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 { Error404 } from '../common/Error404';
-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';
+import { Box, Grid } from "@mui/material";
+import { useEffect } from "react";
+import { connect, useDispatch } from "react-redux";
+import { Navigate, Route, Routes, useLocation, 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 { CCartPage } from "../CartPage";
+import { Error404 } from "../common/Error404";
+import { GoodList } from "../common/GoodList";
+import { CProtectedRoute, ProtectedRoute } from "../common/ProtectedRoute";
+import { CDashboardPage } from "../DashboardPage";
+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();
@@ -42,7 +43,7 @@ const GoodsListContainer = () => {
     const params = useParams();
     const dispatch = useDispatch();
     useEffect(() => {
-        dispatch(actionGoodsFind({ text: params.searchData, promiseName: 'pageGoodsFind' }));
+        dispatch(actionGoodsFind({ text: params.searchData, promiseName: "pageGoodsFind" }));
     }, [params.searchData]);
 
     return <CGoodsList />;
@@ -54,16 +55,24 @@ export const LayoutPage = () => {
         <Box className="LayoutPage">
             <Header />
             <Grid container columns={14} rows={1}>
-                {!!location.pathname.match(/(\/categor)|(\/good)|(\/order)|(\/admin)+/) && (
+                {!!location.pathname.match(/(\/categor)|(\/good)|(\/order)|(\/admin)|(\/dashboard)+/) && (
                     <Grid xs={3} item>
                         <Aside />
                     </Grid>
                 )}
-                <Grid xs={location.pathname.match(/(\/categor)|(\/good)|(\/order)|(\/admin)+/) ? 11 : 14} item>
+                <Grid xs={location.pathname.match(/(\/categor)|(\/good)|(\/order)|(\/admin)|(\/dashboard)+/) ? 11 : 14} item>
                     <Content>
                         <Routes>
                             <Route path="/" exact element={<MainPage />} />
-                            <Route path="/cart" exact element={<CartPage />} />
+                            <Route
+                                path="/cart"
+                                exact
+                                element={
+                                    <CProtectedRoute roles={["user"]} fallback="/auth">
+                                        <CCartPage />
+                                    </CProtectedRoute>
+                                }
+                            />
                             <Route path="/search/:searchData/" element={<GoodsListContainer />} exact />
                             <Route path="/category/:_id" element={<GoodsPageContainer />} />
                             <Route path="/category/" element={<GoodsPageContainer />} />
@@ -72,11 +81,20 @@ export const LayoutPage = () => {
                                 path="/admin/*"
                                 exact
                                 element={
-                                    <CProtectedRoute roles={['admin']} fallback="/auth">
+                                    <CProtectedRoute roles={["admin"]} fallback="/auth">
                                         <AdminLayoutPage />
                                     </CProtectedRoute>
                                 }
                             />
+                            <Route
+                                path="/dashboard/"
+                                exact
+                                element={
+                                    <CProtectedRoute roles={["user"]} fallback="/auth">
+                                        <CDashboardPage />
+                                    </CProtectedRoute>
+                                }
+                            />
                             <Route path="*" element={<Navigate to="/404" />} />
                         </Routes>
                     </Content>

+ 107 - 0
src/components/common/AuthModal/LoginForm.js

@@ -0,0 +1,107 @@
+import { actionLogin } from "../../../actions/actionLogin";
+
+import { useState, useEffect, useContext } 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";
+import { UIContext } from "../../UIContext";
+
+const signInSchema = Yup.object().shape({
+  username: Yup.string().min(3, "Too Short!").max(15, "Too Long!").required("Required"),
+  password: Yup.string().min(3, "Too Short!").max(15, "Too Long!").required("Required"),
+});
+
+export const LoginForm = ({ onLogin, onRegisterButtonClick, promiseStatus, serverErrors }) => {
+  const [showPassword, setShowPassword] = useState(false);
+  const { setAlert } = useContext(UIContext);
+
+  const formik = useFormik({
+    initialValues: {
+      username: "",
+      password: "",
+      repeatPassword: "",
+    },
+    validationSchema: signInSchema,
+    validateOnChange: true,
+    onSubmit: () => {
+      onLogin(formik.values.username, formik.values.password);
+    },
+  });
+
+  useEffect(() => {
+    if (promiseStatus === "FULFILLED") {
+      formik.setSubmitting(false);
+      setAlert({
+        show: true,
+        severity: "success",
+        message: "Готово",
+      });
+    }
+    if (promiseStatus === "REJECTED") {
+      const errorMessage = serverErrors.reduce((prev, curr) => prev + "\n" + curr.message, "");
+      formik.setSubmitting(false);
+      setAlert({
+        show: true,
+        severity: "error",
+        message: errorMessage,
+      });
+    }
+  }, [promiseStatus]);
+
+  return (
+    <Box className="LoginForm" display="flex" flexDirection="column" alignItems="center" component="form" onSubmit={formik.handleSubmit}>
+      <TextField
+        id="username"
+        name="username"
+        variant="standard"
+        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="standard"
+        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 }}
+      />
+
+      <Stack direction="row" justifyContent="flex-end" sx={{ width: "100%" }}>
+        <Button variant="text" color="primary" type="submit" disabled={formik.isSubmitting || !formik.isValid} sx={{ mt: 2, mr: 1 }}>
+          Войти
+        </Button>
+
+        <Button variant="text" onClick={onRegisterButtonClick} sx={{ mt: 2 }}>
+          Регистрация
+        </Button>
+      </Stack>
+    </Box>
+  );
+};
+
+export const CLoginForm = connect(
+  (state) => ({ promiseStatus: state.promise?.login?.status || null, serverErrors: state.promise?.login?.error || [] }),
+  { onLogin: (login, password) => actionLogin(login, password) }
+)(LoginForm);

+ 132 - 0
src/components/common/AuthModal/RegisterForm.js

@@ -0,0 +1,132 @@
+import { useState, useEffect, useContext } from "react";
+import { connect } from "react-redux";
+// import { actionRegister } from 'actions';
+import { MdVisibility, MdVisibilityOff } from "react-icons/md";
+import { Box, Button, IconButton, TextField, Typography } from "@mui/material";
+import { useFormik } from "formik";
+import * as Yup from "yup";
+import { actionRegister } from "../../../actions/actionRegister";
+import { UIContext } from "../../UIContext";
+
+const signUpSchema = Yup.object().shape({
+  username: Yup.string().min(3, "Too Short!").max(15, "Too Long!").required("Required"),
+  password: Yup.string().min(3, "Too Short!").max(15, "Too Long!").required("Required"),
+  repeatPassword: Yup.string()
+    .min(3, "Too Short!")
+    .max(15, "Too Long!")
+    .required("Required")
+    .oneOf([Yup.ref("password")], "Your passwords do not match."),
+});
+
+export const RegisterForm = ({ serverErrors, promiseStatus, onRegister, onLoginButtonClick }) => {
+  const [showPassword, setShowPassword] = useState(false);
+  const { setAlert } = useContext(UIContext);
+  const formik = useFormik({
+    initialValues: {
+      username: "",
+      password: "",
+      repeatPassword: "",
+    },
+    validationSchema: signUpSchema,
+    validateOnChange: true,
+    onSubmit: () => {
+      onRegister(formik.values.username, formik.values.password);
+    },
+  });
+
+  useEffect(() => {
+    if (promiseStatus === "FULFILLED") {
+      formik.setSubmitting(false);
+      setAlert({
+        show: true,
+        severity: "success",
+        message: "Готово",
+      });
+    }
+    if (promiseStatus === "REJECTED") {
+      const errorMessage = serverErrors.reduce((prev, curr) => prev + "\n" + curr.message, "");
+      formik.setSubmitting(false);
+      setAlert({
+        show: true,
+        severity: "error",
+        message: errorMessage,
+      });
+    }
+  }, [promiseStatus]);
+
+  return (
+    <Box className="RegisterForm" display="flex" flexDirection="column" alignItems="center" component="form" onSubmit={formik.handleSubmit}>
+      <TextField
+        id="username"
+        name="username"
+        variant="standard"
+        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="standard"
+        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 }}
+      />
+
+      <TextField
+        id="repeatPassword"
+        name="repeatPassword"
+        variant="standard"
+        label="Repeat password"
+        type={showPassword ? "text" : "password"}
+        error={formik.touched.repeatPassword && Boolean(formik.errors.repeatPassword)}
+        value={formik.values.repeatPassword}
+        onBlur={formik.handleBlur}
+        onChange={formik.handleChange}
+        helperText={formik.touched.repeatPassword && formik.errors.repeatPassword}
+        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} fullWidth sx={{ mt: 2 }}>
+        Зарегистрироваться
+      </Button>
+      <Button variant="text" onClick={onLoginButtonClick}>
+        <Typography>Увійти</Typography>
+      </Button>
+    </Box>
+  );
+};
+
+export const CRegisterForm = connect(
+  (state) => ({ promiseStatus: state.promise?.register?.status || null, serverErrors: state.promise?.register?.error || [] }),
+  {
+    onRegister: (login, password) => actionRegister(login, password),
+  }
+)(RegisterForm);

+ 24 - 0
src/components/common/AuthModal/index.js

@@ -0,0 +1,24 @@
+import { useEffect, useState } from "react";
+import { useSelector } from "react-redux";
+import { Modal } from "../Modal";
+import { CLoginForm } from "./LoginForm";
+import { CRegisterForm } from "./RegisterForm";
+
+export const AuthModal = ({ open, onClose }) => {
+  const [selectedForm, setSelectedForm] = useState("login");
+  const token = useSelector((state) => state.auth?.token || null);
+
+  useEffect(() => {
+    token && onClose();
+  }, [token]);
+
+  return (
+    <Modal open={open} onClose={onClose} maxWidth={400}>
+      {selectedForm === "login" ? (
+        <CLoginForm onRegisterButtonClick={() => setSelectedForm("register")} />
+      ) : selectedForm === "register" ? (
+        <CRegisterForm onLoginButtonClick={() => setSelectedForm("login")} />
+      ) : null}
+    </Modal>
+  );
+};

+ 8 - 0
src/components/common/Ava/index.js

@@ -0,0 +1,8 @@
+import { Avatar } from "@mui/material";
+import { useSelector } from "react-redux";
+import defaultAvatarImage from "../../../images/default-avatar-image.png";
+
+export const Ava = () => {
+  const path = useSelector((state) => state.promise.aboutMe?.payload?.avatar?.url || null);
+  return <Avatar src={path ? `${path}` : defaultAvatarImage} />;
+};

+ 10 - 5
src/components/common/BuyButton/index.js

@@ -1,14 +1,18 @@
-import { Box, Button } from '@mui/material';
-import { useEffect, useState } from 'react';
-import { connect } from 'react-redux';
-import { actionCartAdd, actionCartDelete } from '../../../reducers';
+import { Box, Button } from "@mui/material";
+import { useEffect, useState } from "react";
+import { connect, useSelector } from "react-redux";
+import { actionCartAdd, actionCartDelete } from "../../../reducers";
+import { AuthModal } from "../AuthModal";
 
 export const BuyButton = ({ onClick, onDeleteClick, good, cart }) => {
     const [inCart, setInCart] = useState(false);
+    const token = useSelector((state) => state.auth.token || null);
+    const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
 
     useEffect(() => {
         setInCart(!!(cart[good._id] && cart[good._id].count) || false);
     }, [good, cart]);
+
     return (
         <Box className="BuyButton ">
             {inCart ? (
@@ -16,7 +20,7 @@ export const BuyButton = ({ onClick, onDeleteClick, good, cart }) => {
                     Вже у кошику
                 </Button>
             ) : good.amount > 0 ? (
-                <Button onClick={() => onClick(good)} variant="contained" className="button">
+                <Button onClick={() => (token ? onClick(good) : setIsAuthModalOpen(true))} variant="contained" className="button">
                     Купити
                 </Button>
             ) : (
@@ -24,6 +28,7 @@ export const BuyButton = ({ onClick, onDeleteClick, good, cart }) => {
                     Немає в наявності
                 </Button>
             )}
+            <AuthModal open={isAuthModalOpen} onClose={() => setIsAuthModalOpen(false)} />
         </Box>
     );
 };

+ 12 - 12
src/components/common/Modal/index.js

@@ -1,15 +1,15 @@
-import { Box } from '@mui/system';
-import ReactDOM from 'react-dom';
+import { Box } from "@mui/system";
+import ReactDOM from "react-dom";
 
 export const Modal = ({ children, open, onClose, maxWidth = 700 }) => {
-    return open
-        ? ReactDOM.createPortal(
-              <Box className="Modal" onClick={() => onClose && onClose()}>
-                  <Box className="modalContent" onClick={(e) => e.stopPropagation()} style={{ maxWidth }}>
-                      {children}
-                  </Box>
-              </Box>,
-              document.body
-          )
-        : null;
+  return open
+    ? ReactDOM.createPortal(
+        <Box className="Modal" onClick={() => onClose && onClose()}>
+          <Box className="modalContent" onClick={(e) => e.stopPropagation()} style={{ maxWidth }}>
+            {children}
+          </Box>
+        </Box>,
+        document.body
+      )
+    : null;
 };

+ 8 - 0
src/components/layout/Header/AvatarButton/index.js

@@ -0,0 +1,8 @@
+import { IconButton } from "@mui/material";
+import { Ava } from "../../../common/Ava";
+
+export const AvatarButton = ({ onClick }) => (
+  <IconButton className="AvatarButton" onClick={onClick}>
+    <Ava />
+  </IconButton>
+);

+ 66 - 50
src/components/layout/Header/index.js

@@ -1,56 +1,72 @@
-import { AppBar, Box, Button, IconButton, Stack, TextField, Toolbar, Typography } from '@mui/material';
-import { useState } from 'react';
+import { AppBar, Box, Button, IconButton, Stack, TextField, Toolbar, Typography } from "@mui/material";
+import { useState } from "react";
 
-import { useSelector } from 'react-redux';
-import { Link } from 'react-router-dom';
-import { ReactComponent as ShoppingLogo } from '../../../images/shopping-logo.svg';
-import { DrawerCart } from '../../common/DrawerCart/DrawerCart';
-import { CSearchBar, SearchBar } from '../../common/SearchBar';
-import { CSearchResults } from '../../common/SearchBar/SearchResults';
-import { CCartIcon } from './CartIcon';
-import { LogoutIcon } from './LogoutIcon';
+import { useSelector } from "react-redux";
+import { Link, Navigate, useNavigate } from "react-router-dom";
+import { ReactComponent as ShoppingLogo } from "../../../images/shopping-logo.svg";
+import { AuthModal } from "../../common/AuthModal";
+import { Ava } from "../../common/Ava";
+import { DrawerCart } from "../../common/DrawerCart/DrawerCart";
+import { CSearchBar, SearchBar } from "../../common/SearchBar";
+import { CSearchResults } from "../../common/SearchBar/SearchResults";
+import { AvatarButton } from "./AvatarButton";
+import { CCartIcon } from "./CartIcon";
+import { LogoutIcon } from "./LogoutIcon";
 
 const Header = () => {
-    const rootCats = useSelector((state) => state?.promise?.rootCats?.payload || []);
-    const [isCartDrawerOpen, setIsCartDrawerOpen] = useState(false);
-    return (
-        <Box className="Header">
-            <AppBar position="static" className="AppBar">
-                <Toolbar variant="dense" className="ToolBar">
-                    <IconButton component={Link} to="/">
-                        <ShoppingLogo className="Logo" />
-                    </IconButton>
-                    <Stack direction="row" spacing={2}>
-                        <Button variant="text" color="inherit" component={Link} to="/">
-                            <Typography variant="body1" component="div">
-                                Головна
-                            </Typography>
-                        </Button>
-                        <Button
-                            variant="text"
-                            color="inherit"
-                            component={Link}
-                            to={rootCats[0] ? `/category/${rootCats[0]._id}` : '/'}
-                        >
-                            <Typography variant="body1" component="div">
-                                Товари
-                            </Typography>
-                        </Button>
-                    </Stack>
-                    <Box className="SearchBarWrapper">
-                        <CSearchBar render={CSearchResults} renderParams={{ itemLink: '/good/' }} />
-                    </Box>
-                    <LogoutIcon />
-                    <IconButton color="inherit" className="CartLogoButton" onClick={() => setIsCartDrawerOpen(true)}>
-                        <Box>
-                            <CCartIcon />
-                        </Box>
-                    </IconButton>
-                </Toolbar>
-            </AppBar>
-            <DrawerCart isOpen={isCartDrawerOpen} onClose={() => setIsCartDrawerOpen(false)} />
-        </Box>
-    );
+  const rootCats = useSelector((state) => state?.promise?.rootCats?.payload || []);
+  const [isCartDrawerOpen, setIsCartDrawerOpen] = useState(false);
+  const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
+  const navigate = useNavigate();
+  const token = useSelector((state) => state?.auth?.token || null);
+
+  return (
+    <Box className="Header">
+      <AppBar position="static" className="AppBar">
+        <Toolbar variant="dense" className="ToolBar">
+          <IconButton component={Link} to="/">
+            <ShoppingLogo className="Logo" />
+          </IconButton>
+          <Stack direction="row" spacing={2}>
+            <Button variant="text" color="inherit" component={Link} to="/">
+              <Typography variant="body1" component="div">
+                Головна
+              </Typography>
+            </Button>
+            <Button variant="text" color="inherit" component={Link} to={rootCats[0] ? `/category/${rootCats[0]._id}` : "/"}>
+              <Typography variant="body1" component="div">
+                Товари
+              </Typography>
+            </Button>
+          </Stack>
+          <Box className="SearchBarWrapper">
+            <CSearchBar render={CSearchResults} renderParams={{ itemLink: "/good/" }} />
+          </Box>
+          <Stack direction="row" spacing={3}>
+            {token ? (
+              <Stack direction="row" spacing={3}>
+                <LogoutIcon />
+                <AvatarButton onClick={() => navigate("/dashboard/")} />
+              </Stack>
+            ) : (
+              <Button variant="text" color="inherit" onClick={() => setIsAuthModalOpen(true)}>
+                <Typography variant="body1" component="div">
+                  Увійти
+                </Typography>
+              </Button>
+            )}
+            <IconButton color="inherit" className="CartLogoButton" onClick={() => setIsCartDrawerOpen(true)}>
+              <Box>
+                <CCartIcon />
+              </Box>
+            </IconButton>
+          </Stack>
+        </Toolbar>
+      </AppBar>
+      <DrawerCart isOpen={isCartDrawerOpen} onClose={() => setIsCartDrawerOpen(false)} />
+      <AuthModal open={isAuthModalOpen} onClose={() => setIsAuthModalOpen(false)} />
+    </Box>
+  );
 };
 
 export { Header };

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


+ 4 - 0
src/images/defaultAvatarImage.png:Zone.Identifier

@@ -0,0 +1,4 @@
+[ZoneTransfer]
+ZoneId=3
+ReferrerUrl=https://www.google.com/
+HostUrl=https://avatarko.ru/img/kartinka/1/multfilm_gomer.png

+ 19 - 1
src/index.scss

@@ -15,11 +15,12 @@
   left: 0;
   top: 0;
   width: 100%;
-  height: 100%;
+  height: 100vw;
   overflow: auto;
   background-color: rgba(0,0,0,0.1);
 
 
+
   & .modalContent {
     background-color: #fefefe;
     margin: 15% auto;
@@ -122,6 +123,12 @@
       & .ToolBar{
         padding-left: 50px;
         padding-right: 50px;
+
+        & .AvatarButton{
+          height: 46px;
+          width: 46px;
+        }
+
         & .Logo{
           width:50px;
           height:50px;
@@ -211,6 +218,17 @@
     flex:1;
     border:1px solid #C9C5CA ;
 
+    & .DashboardPage{
+      padding: 20px;
+      & .DashboardOrder{
+        padding:20px;
+        & .DashboardOrderGood{
+          & img{
+           width:100%;
+          }
+        }
+      }
+    }
 
     & .AdminLayoutPage{
       padding: 10px;