Browse Source

admin layout with roting and drawer menu

Graf15 1 year ago
parent
commit
646f26309b

+ 1 - 1
my-diplom/src/App.css

@@ -6,7 +6,7 @@
 
 body {
     margin: 0;
-    padding: 10px;
+    padding: 0;
 
 }
 

+ 31 - 22
my-diplom/src/App.js

@@ -1,4 +1,4 @@
-import { Route, Routes, Link } from "react-router-dom";
+import { Route, Routes } from "react-router-dom";
 import LayoutPublic from "./components/LayoutPublic";
 import './App.css';
 import HomePage from "./components/public-pages/HomePage";
@@ -7,6 +7,10 @@ import { CssBaseline } from "@mui/material";
 import ProductOnePage from "./components/public-pages/ProductOnePage";
 import LoginPage from "./components/public-pages/LoginPage"
 import ProfilePage from "./components/public-pages/ProfilePage";
+import Registration from "./components/public-pages/Registration";
+import RequireAuth from "./hoc/RequireAuth";
+import NoRequireAuth from "./hoc/NoRequireAuth";
+import LayoutAdmin from "./components/adminPage/LayoutAdmin";
 
 
 
@@ -22,12 +26,34 @@ function App() {
           <Route path="about" element={<p>о магазине</p>}/>
           <Route path="category/:catId/*" element={<CategoryOnePage/>}/>
           <Route path="product/:prodId/*" element={<ProductOnePage/>}/>
-          <Route path="login" element={<LoginPage/>}/>
-          <Route path="profile" element={<ProfilePage/>}/>
-        </Route>
-        <Route path='admin/*' element={<p>дашборд</p>}>
 
+          <Route path="login" element={
+          <NoRequireAuth>
+            <LoginPage/>
+          </NoRequireAuth>
+          }/>
+
+          <Route path="profile" element={
+          <RequireAuth>
+            <ProfilePage/>
+          </RequireAuth>
+          }/>
+
+          <Route path="registration" element={
+            <NoRequireAuth>
+              <Registration/>
+            </NoRequireAuth>
+            }/>
+        </Route>
+        <Route path='admin' element={<LayoutAdmin />}>
+          <Route index element={<p>index</p>}/>
+          <Route path='*' element={<p>Страница не найдена</p>}/>
         </Route>
+        {/* <Route path='admin/*' element={
+        <RequireAuthIsAdmin>
+          <p>админка</p>
+        </RequireAuthIsAdmin>
+        /> */}
       </Routes>
     </>
   );
@@ -37,20 +63,3 @@ function App() {
 export default App;
 
 
-{/* <>
-<Link to={"/"}>Home</Link>
-<Link to={"admin"}>Admin</Link>
-<Link to={"about"}>About</Link>
-
-<Routes>
-  <Route path='/'>
-    <Route index element={<p>домашняя станица</p>} />
-    <Route path="*" element={<LayoutPublic />}>
-      <Route path="about" element={<p>о магазине</p>}/>
-    </Route>
-    <Route path='admin'>
-      <Route index element={<p>дашборд</p>} />
-    </Route>
-  </Route>
-</Routes>
-</> */}

+ 83 - 50
my-diplom/src/components/Basket.jsx

@@ -1,7 +1,10 @@
 import { ShoppingBasket } from "@mui/icons-material"
-import { Box, Button, Divider, Drawer, InputAdornment, List, ListItem, ListItemIcon, ListItemText, TextField } from "@mui/material"
+import { Box, Button, Divider, Drawer, InputAdornment, List, ListItem, ListItemIcon, ListItemText, TextField, Typography } from "@mui/material"
+import { lightGreen } from "@mui/material/colors"
 import { useDispatch, useSelector } from "react-redux"
+import { Link } from "react-router-dom"
 import { CartAdd, CartClear, CartDel, CartIncOne, CartSet } from "../store/cartSlice"
+import { actionOrderUpsert } from "./actions/actions"
 
 const Basket = (props) => {
     const {
@@ -10,52 +13,62 @@ const Basket = (props) => {
     } = props
 
     const cartState = Object.values(useSelector(state => state.cart))
+    const cartStateForOrderUpsert = cartState.map(
+        (value) => {
+            return {
+                count: value.count,
+                good: {_id: value.good._id}
+            }
+        }
+        )
+        console.log (cartStateForOrderUpsert)
+    const userAuth = useSelector((state) => state.auth.user)
 
     const dispatch = useDispatch()
 
-    const productsRender = ({product, count}) => {
-        
+    const productsRender = ({ good, count }) => {
+
         return (
-        <ListItem key={product._id} sx={{display:"block"}}>
-            <Box sx={{ margin: '10px 5px' }}>
-                <div style={{ 
-                    height: '50px',
-                    width: '50px',
-                    backgroundSize: 'contain',
-                            backgroundRepeat: 'no-repeat',
-                            backgroundPosition: 'center',
-      backgroundImage: `url(${product?.images ? `http://shop-roles.node.ed.asmer.org.ua/${product.images[0].url}` : "https://archive.org/download/no-photo-available/no-photo-available.png" })` 
-    }}></div>
-                <div>{product.name}</div>
-            </Box>
-            <Box>
-                <Button variant="outlined" size="small" onClick={()=> dispatch(CartAdd({product}))} sx={{height:"40px", margin: "3px"}}>+</Button>
-                <TextField
-                    sx={{height:"40px", margin: "3px"}}
-                    size="small"
-                    type="number"
-                    value={count}
-                    InputProps={{
-                        endAdornment: <InputAdornment position="start">шт</InputAdornment>,
-                        inputProps: { min: "1", step: "1" },
-                    }}
-                    onChange={(e) => dispatch(CartSet({product, count: e.target.value}))}
-                >
+            <ListItem key={good._id} sx={{ display: "block" }}>
+                <Box sx={{ margin: '10px 5px' }}>
+                    <div style={{
+                        height: '50px',
+                        width: '50px',
+                        backgroundSize: 'contain',
+                        backgroundRepeat: 'no-repeat',
+                        backgroundPosition: 'center',
+                        backgroundImage: `url(${good?.images ? `http://shop-roles.node.ed.asmer.org.ua/${good.images[0].url}` : "https://archive.org/download/no-photo-available/no-photo-available.png"})`
+                    }}></div>
+                    <div>{good.name}</div>
+                </Box>
+                <Box>
+                    <Button variant="outlined" size="small" onClick={() => dispatch(CartAdd({ good }))} sx={{ height: "40px", margin: "3px" }}>+</Button>
+                    <TextField
+                        size="small"
+                        sx={{ height: "40px", width: "120px", margin: "3px" }}
+                        type="number"
+                        value={count}
+                        InputProps={{
+                            endAdornment: <InputAdornment position="start">шт</InputAdornment>,
+                            inputProps: { min: "1", step: "1" },
+                        }}
+                        onChange={(e) => dispatch(CartSet({ good, count: e.target.value }))}
+                    >
 
-                </TextField>
-                <Button
-                variant="outlined"
-                size="small"
-                onClick={() => dispatch(CartIncOne({product}))}
-                disabled = {count <=1 ? true: false}
-                sx={{height:"40px", margin: "3px"}}
-                >-</Button>
-                <Button variant="outlined" size="small" onClick={() => dispatch(CartDel({product}))} sx={{height:"40px", margin: "3px"}}>удалить</Button>
-            </Box>
-            <Divider />
-        </ListItem>
-    )
-                    }
+                    </TextField>
+                    <Button
+                        variant="outlined"
+                        size="small"
+                        onClick={() => dispatch(CartIncOne({ good }))}
+                        disabled={count <= 1 ? true : false}
+                        sx={{ height: "40px", margin: "3px" }}
+                    >-</Button>
+                    <Button variant="outlined" size="small" onClick={() => dispatch(CartDel({ good }))} sx={{ height: "40px", margin: "3px" }}>удалить</Button>
+                </Box>
+                <Divider sx={{ marginTop: "20px" }} />
+            </ListItem>
+        )
+    }
 
     {/* cartState.map((product) => (
                         <div key={Math.random()}>
@@ -95,23 +108,43 @@ const Basket = (props) => {
             anchor="right"
             open={cartOpen}
             onClose={closeCart}
+            sx={{position: "relative" }}
+
         >
-            <List sx={{ width: "400px" }}>
-                <ListItem>
-                    <ListItemIcon>
+            <List sx={{
+
+                display: "flex",
+                justifyContent: "space-between",
+                position: "fixed",
+                backgroundColor: "white",
+                zIndex: 1,
+            }}>
+                <ListItem >
+                    <ListItemIcon sx={{ minWidth: "30px" }}>
                         <ShoppingBasket />
                     </ListItemIcon>
                     <ListItemText primary="Корзина" />
-                    <Button variant="outlined" onClick={() => dispatch(CartClear())}>Очитить корзину</Button>
                 </ListItem>
+                <ListItem>
+                <Button sx={{ mr: 1 }}  variant="outlined" onClick={() => dispatch(CartClear())}>Очитить корзину</Button>
+                    {userAuth ? 
+                    <Button variant="outlined" onClick={() => dispatch(actionOrderUpsert(cartStateForOrderUpsert))}>Оформить заказ</Button> :
+                    <Button disabled variant="outlined" >Оформить заказ</Button>
+                    }                    
+                </ListItem>
+
             </List>
+            
             <Divider />
 
-            <List>
-            { cartState.length ? 
-                cartState.map(productsRender) : <ListItem>Ваша корзина пока пуста! </ListItem>
-            }
+            <List sx={{ mt: "80px",  minWidth: "350px",
+                maxWidth: "600px"  }}>
+                {!userAuth && <Typography mt={'10px'} ml={1} color={"red"}>Для оформления заказа необходимa <Link to='/login'>авторизация</Link></Typography>}
+                {cartState.length ?
+                    cartState.map(productsRender) : <ListItem>Ваша корзина пока пуста! </ListItem>
+                }
             </List>
+
         </Drawer>
     )
 }

+ 1 - 1
my-diplom/src/components/CardProduct.jsx

@@ -47,7 +47,7 @@ const CardProduct = ({product}) => {
                         size="small"
                         variant="contained"
                         fullWidth
-                        onClick={ ()=> dispatch(CartAdd({product, count: 1}))}
+                        onClick={ ()=> dispatch(CartAdd({good:product, count: 1}))}
                     >
                         <ShoppingBasketSharp sx={{ mr: 1 }} />В корзину
                     </Button>

+ 20 - 11
my-diplom/src/components/LayoutPublic.jsx

@@ -3,21 +3,30 @@ import PublicHeader from "./PublicHeader"
 import PublicFooter from "./PublicFooter"
 import Basket from "./Basket"
 import { useState } from "react"
+import { Box, Grid } from "@mui/material"
 
 const LayoutPublic = () => {
     const [isCartOpen, setCartOpen] = useState(false)
     return(
-        <>
-        <PublicHeader 
-            handleCart={()=> setCartOpen(true)}
-        />
-        <Outlet/>
-        <PublicFooter/>
-        <Basket 
-        cartOpen={isCartOpen}
-        closeCart={()=> setCartOpen(false)}
-        />
-        </>
+        <Grid
+            container
+            direction="column"
+            justifyContent="space-between"
+            alignItems="stretch"
+            sx={{minHeight: "100vh"}}
+        >
+            <PublicHeader
+                handleCart={() => setCartOpen(true)}
+            />
+            <Grid item sx={{flex: "1"}} container>
+            <Outlet />
+            </Grid>
+            <PublicFooter />
+            <Basket
+                cartOpen={isCartOpen}
+                closeCart={() => setCartOpen(false)}
+            />
+        </Grid>
     )
 }
 

+ 203 - 0
my-diplom/src/components/OrdersTableCollaps copy.jsx

@@ -0,0 +1,203 @@
+import * as React from 'react';
+import Paper from '@mui/material/Paper';
+import Table from '@mui/material/Table';
+import TableBody from '@mui/material/TableBody';
+import TableCell from '@mui/material/TableCell';
+import TableContainer from '@mui/material/TableContainer';
+import TableHead from '@mui/material/TableHead';
+import TablePagination from '@mui/material/TablePagination';
+import TableRow from '@mui/material/TableRow';
+import { Box, Collapse, IconButton, Typography } from '@mui/material';
+import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
+import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
+
+
+const columns = [
+  {id: '_', label: '',},
+  { id: '_id', label: 'Номер заказа', minWidth: 170 },
+  {
+     id: 'createdAt',
+     label: 'Дата',
+     minWidth: 100,
+     format: (value) => {
+        const createdAtFormat = new Date(+value)
+        const year = createdAtFormat.getFullYear()
+        const month = createdAtFormat.getMonth() < 9 ? "0" + (createdAtFormat.getMonth() + 1) : createdAtFormat.getMonth() + 1
+        const day = createdAtFormat.getDate() < 10 ? "0" + (createdAtFormat.getDate()) : createdAtFormat.getDate()
+        const hours = createdAtFormat.getHours() < 10 ? "0" + (createdAtFormat.getHours()) : createdAtFormat.getHours()
+        const minutes = createdAtFormat.getMinutes() < 10 ? "0" + (createdAtFormat.getMinutes()) : createdAtFormat.getMinutes()
+        const createdAtForTable = `${year}.${month}.${day} ${hours}:${minutes} `
+        return createdAtForTable
+     }
+     },
+  {
+    id: 'total',
+    label: 'Сумма',
+    minWidth: 170,
+    align: 'right',
+    format: (value) => value.toLocaleString('en-US'),
+  },
+  {
+    id: 'orderGoods',
+    label: 'Количество товаров',
+    minWidth: 170,
+    align: 'right',
+    format: (value) => {
+
+        return Array.isArray(value) ? value?.length : value
+        // на момент написания на сайте были битые заказы с orderGoods: null
+    },
+  },
+];
+
+
+
+export default function StickyHeadTable({rows = []}) {
+  const [page, setPage] = React.useState(0);
+  const [rowsPerPage, setRowsPerPage] = React.useState(10);
+
+  
+
+  const handleChangePage = (event, newPage) => {
+    setPage(newPage);
+  };
+
+  const handleChangeRowsPerPage = (event) => {
+    setRowsPerPage(+event.target.value);
+    setPage(0);
+  };
+
+  return (
+    <Paper sx={{ width: '100%', overflow: 'hidden' }}>
+      <TableContainer sx={{ maxHeight: 440 }}>
+        <Table stickyHeader size="small" aria-label="sticky table">
+          <TableHead>
+            <TableRow>
+              {columns.map((column) => (
+                <TableCell
+                  key={column.id}
+                  align={column.align}
+                  style={{ minWidth: column.minWidth }}
+                >
+                  {column.label}
+                </TableCell>
+              ))}
+            </TableRow>
+          </TableHead>
+          <TableBody>
+            {rows
+              .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
+              .map((row) => {
+                return (
+                  <Row row ={row}></Row>
+                );
+              })}
+          </TableBody>
+        </Table>
+      </TableContainer>
+      <TablePagination
+        rowsPerPageOptions={[10, 25, 100]}
+        component="div"
+        count={rows.length}
+        rowsPerPage={rowsPerPage}
+        page={page}
+        onPageChange={handleChangePage}
+        onRowsPerPageChange={handleChangeRowsPerPage}
+      />
+    </Paper>
+  );
+}
+// єто біло в табе боди стр 83
+// <TableRow hover role="checkbox" tabIndex={-1} key={row._id}>
+//                    {columns.map((column) => {
+//                        const value = row[column.id]
+//                        {/* console.log('value', value) */}
+                     
+//                        return (
+//                          <TableCell key={column.id} align={column.align}>
+//                              {column.format ? column.format(value) : value}
+//                          </TableCell>
+//                        );
+//                      })}
+//                    </TableRow> 
+
+function Row(props) {
+  const { row } = props;
+  const [open, setOpen] = React.useState(false);
+ 
+
+  return (
+    <React.Fragment>
+      <TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
+        <TableCell>
+          <IconButton
+            aria-label="expand row"
+            size="small"
+            onClick={() => setOpen(!open)}
+          >
+            {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
+          </IconButton>
+        </TableCell>
+        <TableCell component="th" scope="row">
+          {row._id}
+        </TableCell>
+        <TableCell align="right">{row. createdAt}</TableCell>
+        <TableCell align="right">{row.total}</TableCell>
+      </TableRow>
+      <TableRow>
+        <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
+          <Collapse in={open} timeout="auto" unmountOnExit>
+            <Box sx={{ margin: 1 }}>
+              <Typography variant="h6" gutterBottom component="div">
+                Подробности заказа
+              </Typography>
+              <Table size="small" aria-label="purchases">
+                <TableHead>
+                  <TableRow>
+                    <TableCell>ID товара</TableCell>
+                    <TableCell>Название</TableCell>
+                    <TableCell align="right">Количество (шт)</TableCell>
+                    <TableCell align="right">Цена (грн.)</TableCell>
+                    <TableCell align="right">Сумма (грн.)</TableCell>
+                  </TableRow>
+                </TableHead>
+                <TableBody>
+
+                {row.orderGoods ? 
+                  row.orderGoods.map((orderGood) => {
+                    console.log('orderGood true', orderGood)
+                    if(orderGood){
+                      return (
+                        <TableRow key={orderGood._id}>
+                        <TableCell component="th" scope="row">
+                        {orderGood?._id}
+                      </TableCell>
+                      <TableCell >
+                        {orderGood?.goodName || ''}
+                      </TableCell>
+                      <TableCell align="right">
+                        {orderGood?.count}
+                      </TableCell>
+                      <TableCell align="right">
+                        {orderGood?.price}
+                      </TableCell>
+                      <TableCell align="right">
+                        {orderGood?.total}
+                      </TableCell>
+                        </TableRow>
+                      )
+                    }
+                    } )
+                :
+                null 
+                }
+
+                </TableBody>
+              </Table>
+            </Box>
+          </Collapse>
+        </TableCell>
+      </TableRow>
+    </React.Fragment>
+  );
+}

+ 187 - 0
my-diplom/src/components/OrdersTableCollaps.jsx

@@ -0,0 +1,187 @@
+import * as React from 'react';
+import Paper from '@mui/material/Paper';
+import Table from '@mui/material/Table';
+import TableBody from '@mui/material/TableBody';
+import TableCell from '@mui/material/TableCell';
+import TableContainer from '@mui/material/TableContainer';
+import TableHead from '@mui/material/TableHead';
+import TablePagination from '@mui/material/TablePagination';
+import TableRow from '@mui/material/TableRow';
+import { Box, Collapse, IconButton, Typography } from '@mui/material';
+import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
+import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
+import { useGetOrdersCountQuery, useGetUserOrdersQuery } from '../store/categoriesApi';
+
+
+
+
+
+function TimestampConv(value) {
+  const createdAtFormat = new Date(+value)
+  const year = createdAtFormat.getFullYear()
+  const month = createdAtFormat.getMonth() < 9 ? "0" + (createdAtFormat.getMonth() + 1) : createdAtFormat.getMonth() + 1
+  const day = createdAtFormat.getDate() < 10 ? "0" + (createdAtFormat.getDate()) : createdAtFormat.getDate()
+  const hours = createdAtFormat.getHours() < 10 ? "0" + (createdAtFormat.getHours()) : createdAtFormat.getHours()
+  const minutes = createdAtFormat.getMinutes() < 10 ? "0" + (createdAtFormat.getMinutes()) : createdAtFormat.getMinutes()
+  const createdAtForTable = `${year}.${month}.${day} ${hours}:${minutes} `
+  return createdAtForTable
+}
+
+
+export default function StickyHeadTable() {
+  const [page, setPage] = React.useState(0);
+  const [rowsPerPage, setRowsPerPage] = React.useState(10);
+  const {data: OrderCount} = useGetOrdersCountQuery()
+  const count = OrderCount?.OrderCount || 1
+  const orders = useGetUserOrdersQuery({limit: rowsPerPage, skip: page * rowsPerPage})
+  const rows = orders?.data ? Object.values(orders.data)[0] : []
+  
+  // React.useEffect(
+  //   () => {refetch()}, [page || rowsPerPage]
+  // )
+
+  //const orders = useGetUserOrdersQuery({limit: rowsPerPage, skip: page * rowsPerPage})
+  
+  
+  // const rows = orders?.data ? Object.values(orders.data)[0] : null
+  // console.log(orders)
+  const handleChangePage = (event, newPage) => {
+    setPage(newPage);
+  };
+
+  const handleChangeRowsPerPage = (event) => {
+    setRowsPerPage(+event.target.value);
+    setPage(0);
+  };
+
+  return (
+    <Paper sx={{ width: '100%', overflow: 'hidden', display: "flex", flexDirection: "column", alignItems: "center" }}>
+      <Typography variant='h4'>История заказов</Typography>
+      <TablePagination
+        rowsPerPageOptions={[10, 25, 50, 100]}
+        component="div"
+        count={OrderCount?.OrderCount || 0}
+        rowsPerPage={rowsPerPage}
+        page={page}
+        onPageChange={handleChangePage}
+        onRowsPerPageChange={handleChangeRowsPerPage}
+      />
+      <TableContainer sx={{ maxHeight: 600, maxWidth: 1000 }}>
+        <Table stickyHeader size="small" aria-label="sticky table">
+          <TableHead>
+            <TableRow>
+              <TableCell />
+              <TableCell align="left">Номер заказа</TableCell>
+              <TableCell align="center">Дата</TableCell>
+              <TableCell align="center">Сумма</TableCell>
+              <TableCell align="center">Количество товаров</TableCell>
+            </TableRow>
+          </TableHead>
+          <TableBody>
+            {/* {rows
+              .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
+              .map((row) => {
+                return (
+                  <Row row={row}></Row>
+                );
+              })} */}
+              {rows.map((row) => {
+                return (
+                  <Row row={row} key={row._id}></Row>
+                );
+              })}
+          </TableBody>
+        </Table>
+      </TableContainer>
+      <TablePagination
+        rowsPerPageOptions={[10, 25, 100]}
+        component="div"
+        count={OrderCount?.OrderCount || 0}
+        rowsPerPage={rowsPerPage}
+        page={page}
+        onPageChange={handleChangePage}
+        onRowsPerPageChange={handleChangeRowsPerPage}
+      />
+    </Paper>
+  );
+}
+
+function Row(props) {
+  const { row } = props;
+  const [open, setOpen] = React.useState(false);
+
+  return (
+    <React.Fragment>
+      <TableRow sx={{ '*': { borderBottom: 'unset' } }}>
+        <TableCell>
+          <IconButton
+            aria-label="expand row"
+            size="small"
+            onClick={() => setOpen(!open)}
+          >
+            {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
+          </IconButton>
+        </TableCell>
+        <TableCell component="th" scope="row" align="left">
+          {row._id}
+        </TableCell>
+        <TableCell align="center">{TimestampConv(row.createdAt)}</TableCell>
+        <TableCell align="center">{row.total}</TableCell>
+        <TableCell align='center'>{Array.isArray(row.orderGoods) ? row.orderGoods?.length : 0}</TableCell>
+      </TableRow>
+      <TableRow>
+        <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
+          <Collapse in={open} timeout="auto" unmountOnExit>
+            <Box sx={{ margin: 1 }}>
+              <Typography variant="h6" gutterBottom component="div">
+                Подробности заказа
+              </Typography>
+              <Table size="small" aria-label="purchases">
+                <TableHead>
+                  <TableRow>
+                    <TableCell>ID товара</TableCell>
+                    <TableCell>Название</TableCell>
+                    <TableCell align="right">Количество (шт)</TableCell>
+                    <TableCell align="right">Цена (грн.)</TableCell>
+                    <TableCell align="right">Сумма (грн.)</TableCell>
+                  </TableRow>
+                </TableHead>
+                <TableBody>
+
+                  {row.orderGoods ?
+                    row.orderGoods.map((orderGood) => {
+                      if (orderGood) {
+                        return (
+                          <TableRow key={orderGood._id}>
+                            <TableCell component="th" scope="row">
+                              {orderGood?._id}
+                            </TableCell>
+                            <TableCell >
+                              {orderGood?.goodName || ''}
+                            </TableCell>
+                            <TableCell align="right">
+                              {orderGood?.count}
+                            </TableCell>
+                            <TableCell align="right">
+                              {orderGood?.price}
+                            </TableCell>
+                            <TableCell align="right">
+                              {orderGood?.total}
+                            </TableCell>
+                          </TableRow>
+                        )
+                      }
+                    })
+                    :
+                    null
+                  }
+
+                </TableBody>
+              </Table>
+            </Box>
+          </Collapse>
+        </TableCell>
+      </TableRow>
+    </React.Fragment>
+  );
+}

+ 25 - 0
my-diplom/src/components/ProfileMutationForm.jsx

@@ -0,0 +1,25 @@
+import { Button, Input, TextField } from "@mui/material"
+import { useState } from "react"
+import { useDispatch } from "react-redux"
+import { actionSetNick } from "./actions/actions"
+
+const ProfileMutationForm = ({user}) => {
+    const [nick, setNick] = useState(user?.nick || "" )
+    const dispatch = useDispatch()
+
+    return(
+        <>
+        <TextField
+         id="nick"
+         label="Nick"
+         variant="outlined"
+         value={nick}
+         onChange={(event)=>setNick(event.target.value)}
+         size='small'
+         />
+        <Button onClick={()=>dispatch(actionSetNick(nick))} sx={{minHeight: '40px'}}>Редактировать</Button>
+        </>
+    )
+}
+
+export default ProfileMutationForm

+ 1 - 1
my-diplom/src/components/PublicHeader.jsx

@@ -24,7 +24,7 @@ const PublicHeader = ({handleCart, orderLenght}) => {
                 </Box>
 
                 <PublicHeaderSearch />
-                <RouterLink to="/login" color="white">
+                <RouterLink to="/profile" color="white">
                 <IconButton sx={{color: "white"}}>
                     <Person />
                 </IconButton>

+ 171 - 0
my-diplom/src/components/Test1.jsx

@@ -0,0 +1,171 @@
+import * as React from 'react';
+import PropTypes from 'prop-types';
+import Box from '@mui/material/Box';
+import Collapse from '@mui/material/Collapse';
+import IconButton from '@mui/material/IconButton';
+import Table from '@mui/material/Table';
+import TableBody from '@mui/material/TableBody';
+import TableCell from '@mui/material/TableCell';
+import TableContainer from '@mui/material/TableContainer';
+import TableHead from '@mui/material/TableHead';
+import TableRow from '@mui/material/TableRow';
+import Typography from '@mui/material/Typography';
+import Paper from '@mui/material/Paper';
+import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
+import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
+
+// function createData(name, calories, fat, carbs, protein, price) {
+//   return {
+//     name,
+//     calories,
+//     fat,
+//     carbs,
+//     protein,
+//     price,
+//     history: [
+//       {
+//         date: '2020-01-05',
+//         customerId: '11091700',
+//         amount: 3,
+//       },
+//       {
+//         date: '2020-01-02',
+//         customerId: 'Anonymous',
+//         amount: 1,
+//       },
+//     ],
+//   };
+// }
+
+function Row(props) {
+  const { row } = props;
+  const [open, setOpen] = React.useState(false);
+ 
+
+  return (
+    <React.Fragment>
+      <TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
+        <TableCell>
+          <IconButton
+            aria-label="expand row"
+            size="small"
+            onClick={() => setOpen(!open)}
+          >
+            {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
+          </IconButton>
+        </TableCell>
+        <TableCell component="th" scope="row">
+          {row._id}
+        </TableCell>
+        <TableCell align="right">{row. createdAt}</TableCell>
+        <TableCell align="right">{row.total}</TableCell>
+      </TableRow>
+      <TableRow>
+        <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
+          <Collapse in={open} timeout="auto" unmountOnExit>
+            <Box sx={{ margin: 1 }}>
+              <Typography variant="h6" gutterBottom component="div">
+                Подробности заказа
+              </Typography>
+              <Table size="small" aria-label="purchases">
+                <TableHead>
+                  <TableRow>
+                    <TableCell>ID товара</TableCell>
+                    <TableCell>Название</TableCell>
+                    <TableCell align="right">Количество (шт)</TableCell>
+                    <TableCell align="right">Цена (грн.)</TableCell>
+                    <TableCell align="right">Сумма (грн.)</TableCell>
+                  </TableRow>
+                </TableHead>
+                <TableBody>
+
+                {row.orderGoods ? 
+                  row.orderGoods.map((orderGood) => {
+                    console.log('orderGood true', orderGood)
+                    if(orderGood){
+                      return (
+                        <TableRow key={orderGood._id}>
+                        <TableCell component="th" scope="row">
+                        {orderGood?._id}
+                      </TableCell>
+                      <TableCell >
+                        {orderGood?.goodName || ''}
+                      </TableCell>
+                      <TableCell align="right">
+                        {orderGood?.count}
+                      </TableCell>
+                      <TableCell align="right">
+                        {orderGood?.price}
+                      </TableCell>
+                      <TableCell align="right">
+                        {orderGood?.total}
+                      </TableCell>
+                        </TableRow>
+                      )
+                    }
+                    } )
+                :
+                null 
+                }
+
+                </TableBody>
+              </Table>
+            </Box>
+          </Collapse>
+        </TableCell>
+      </TableRow>
+    </React.Fragment>
+  );
+}
+
+// Row.propTypes = {
+//   row: PropTypes.shape({
+//     calories: PropTypes.number.isRequired,
+//     carbs: PropTypes.number.isRequired,
+//     fat: PropTypes.number.isRequired,
+//     history: PropTypes.arrayOf(
+//       PropTypes.shape({
+//         amount: PropTypes.number.isRequired,
+//         customerId: PropTypes.string.isRequired,
+//         date: PropTypes.string.isRequired,
+//       }),
+//     ).isRequired,
+//     name: PropTypes.string.isRequired,
+//     price: PropTypes.number.isRequired,
+//     protein: PropTypes.number.isRequired,
+//   }).isRequired,
+// };
+
+// const rows = [
+//   createData('Frozen yoghurt', 159, 6.0, 24, 4.0, 3.99),
+//   createData('Ice cream sandwich', 237, 9.0, 37, 4.3, 4.99),
+//   createData('Eclair', 262, 16.0, 24, 6.0, 3.79),
+//   createData('Cupcake', 305, 3.7, 67, 4.3, 2.5),
+//   createData('Gingerbread', 356, 16.0, 49, 3.9, 1.5),
+// ];
+
+export default function CollapsibleTable({rows}) {
+
+
+  return (
+    <TableContainer component={Paper}>
+      <Table aria-label="collapsible table">
+        <TableHead>
+          <TableRow>
+            <TableCell />
+            <TableCell>Dessert (100g serving)</TableCell>
+            <TableCell align="right">Calories</TableCell>
+            <TableCell align="right">Fat&nbsp;(g)</TableCell>
+            <TableCell align="right">Carbs&nbsp;(g)</TableCell>
+            <TableCell align="right">Protein&nbsp;(g)</TableCell>
+          </TableRow>
+        </TableHead>
+        <TableBody>
+          {rows.map((row) => (
+            <Row key={row._id} row={row} />
+          ))}
+        </TableBody>
+      </Table>
+    </TableContainer>
+  );
+}

+ 64 - 1
my-diplom/src/components/actions/actions.jsx

@@ -1,3 +1,8 @@
+import { useLocation, useNavigate } from 'react-router-dom';
+import { categoriesApi, useGetLoginMutation } from '../../store/categoriesApi';
+import { setCredentials } from '../../store/authSlice';
+import { CartClear } from '../../store/cartSlice';
+
 export function getGql (endpoint){
     let headers = {
         'Content-Type': 'application/json;charset=utf-8',
@@ -234,4 +239,62 @@ gql(`mutation ($arrGoods: [OrderGoodInput]){
 }`, {
   arrGoods 
 }
-)
+)
+
+// єкшены
+
+export const actionSetNick = (nick) =>
+  async (dispatch, getState) => {
+    const auth = getState().auth
+    if (auth.token) {
+      dispatch(categoriesApi.endpoints.setNick.initiate({ _id: auth.user.sub.id, nick }))
+    }
+  }
+
+  export const actionSetNewUser = (userData) =>
+  async (dispatch, getState) => {
+
+    // const navigate = useNavigate()
+    // const location = useLocation()
+    // const fromPage = location.state?.from?.pathname || '/'
+
+    console.log('userData', userData)
+    const response = await dispatch(categoriesApi.endpoints.setNewUser.initiate(userData))
+    console.log('setNewUser response',response)
+    const payload = response?.data?.UserUpsert
+    console.log('payload setNewUser', payload)
+    const {data: tokenResponseData} = await dispatch( categoriesApi.endpoints.getLogin.initiate({
+        login: userData.login,
+        password: userData.password
+    }))
+    console.log(tokenResponseData)
+    if (tokenResponseData?.login) {
+      const user = jwtDecode(tokenResponseData?.login)
+            if (user) {
+                dispatch(setCredentials({ user, token: tokenResponseData.login  }))
+                console.log(user.sub.id)
+                // navigate(fromPage, {replace: true})
+                return true
+            }
+    }
+
+  }
+
+  function jwtDecode(token) {
+    let result;
+    try {
+        let secondPartToken = token.split('.')[1];
+        result = JSON.parse(atob(secondPartToken));
+    } catch (e) {
+    }
+
+    return result;
+}
+
+export const actionOrderUpsert = (arrGoods) =>
+async (dispatch, getState) => {
+    const res = await dispatch(categoriesApi.endpoints.setOrderUpsert.initiate(arrGoods))
+    if (res?.data?.OrderUpsert) {
+      dispatch(CartClear())
+    }
+  }

+ 40 - 0
my-diplom/src/components/adminPage/AdminHeader.jsx

@@ -0,0 +1,40 @@
+import { ShoppingBasket, Person, Menu } from "@mui/icons-material"
+import { AppBar, IconButton, Toolbar, Link, Box, Badge, Button, Typography } from "@mui/material"
+import { Link as RouterLink} from "react-router-dom"
+import { useSelector, useDispatch } from "react-redux"
+import { setCloseAdminMenu, setIsOpenAdminMenu } from "../../store/adminMenuSlice"
+
+
+
+
+const AdminHeader = ({handleCart, orderLenght}) => {
+    
+    const dispatch = useDispatch()
+   
+
+    return (
+
+        <AppBar position="static" sx={{mb:2}}>
+            <Toolbar>
+            <IconButton
+                    color="inherit"
+                    onClick={() => dispatch(setIsOpenAdminMenu())}
+                    sx={{mr:1}}>
+                    <Menu/>
+
+                </IconButton>
+                <Box sx={{ flexGrow: 1}}>
+                    <Link to="/admin" underline="none" variant="h5" sx={{ color: "white" }} component={RouterLink} >
+                        My Shop <Typography component={'span'}>(панель администратора)</Typography>
+                    </Link>
+                </Box>
+
+
+                
+            </Toolbar>
+        </AppBar>
+
+    )
+}
+
+export default AdminHeader

+ 66 - 0
my-diplom/src/components/adminPage/AdminMenu.jsx

@@ -0,0 +1,66 @@
+import { Close, ShoppingBasket } from "@mui/icons-material"
+import { Box, Button, Divider, Drawer, IconButton, InputAdornment, List, ListItem, ListItemIcon, ListItemText, TextField, Typography } from "@mui/material"
+import { lightGreen } from "@mui/material/colors"
+import { useDispatch, useSelector } from "react-redux"
+import { Link } from "react-router-dom"
+import { setCloseAdminMenu } from "../../store/adminMenuSlice"
+
+
+const AdminMenu = (props) => {
+
+    const adminMenuIsOpen = (useSelector(state => state.adminMenu.isOpen))
+    console.log('adminMenuIsOpen', adminMenuIsOpen)
+    const dispatch = useDispatch()
+    
+    return (
+        <Drawer
+            anchor="left"
+            open={adminMenuIsOpen}
+            onClose={()=> dispatch(setCloseAdminMenu()) }
+            sx={{position: "relative" }}
+
+        >
+        <Box display={"flex"} justifyContent={"end"} mt={0.5} mb={0.5}>
+            <IconButton size="small" onClick={()=> dispatch(setCloseAdminMenu())}>
+                <Close fontSize="small"/>
+            </IconButton>
+        </Box>
+            <List sx={{
+
+                // display: "flex",
+                // justifyContent: "space-between",
+                backgroundColor: "red",
+                zIndex: 1,
+                width: '100px'
+            }}>
+                {/* <ListItem >
+                    <ListItemIcon sx={{ minWidth: "30px" }}>
+                        <ShoppingBasket />
+                    </ListItemIcon>
+                    <ListItemText primary="Корзина" />
+                </ListItem>
+                <ListItem>
+                <Button sx={{ mr: 1 }}  variant="outlined" onClick={() => dispatch(CartClear())}>Очитить корзину</Button>
+                    {userAuth ? 
+                    <Button variant="outlined" onClick={() => dispatch(actionOrderUpsert(cartStateForOrderUpsert))}>Оформить заказ</Button> :
+                    <Button disabled variant="outlined" >Оформить заказ</Button>
+                    }                    
+                </ListItem>
+
+            </List>
+            
+            <Divider />
+
+            <List sx={{ mt: "80px",  minWidth: "350px",
+                maxWidth: "600px"  }}>
+                {!userAuth && <Typography mt={'10px'} ml={1} color={"red"}>Для оформления заказа необходимa <Link to='/login'>авторизация</Link></Typography>}
+                {cartState.length ?
+                    cartState.map(productsRender) : <ListItem>Ваша корзина пока пуста! </ListItem>
+                } */}
+            </List>
+
+        </Drawer>
+    )
+}
+
+export default AdminMenu

+ 27 - 0
my-diplom/src/components/adminPage/LayoutAdmin.js

@@ -0,0 +1,27 @@
+import { useSelector } from "react-redux";
+import { Navigate, Outlet, useLocation } from "react-router-dom"
+import AdminHeader from "./AdminHeader";
+import AdminMenu from "./AdminMenu";
+
+const LayoutAdmin = ({children}) => {
+    const location = useLocation()
+    const fromPage = location.state?.from?.pathname || '/'
+    console.log(fromPage)
+
+    const user = useSelector(state => state.auth.user)
+    console.log('RequireAuthIsAdmin user' , user)
+    const role = user?.sub?.acl[2]
+    console.log('RequireAuthIsAdmin role', role)
+
+    if (role !== "admin"){
+         return <Navigate to='/login' state={{from: location}} />
+     }
+    return <>
+    <AdminHeader/>
+    <p>зашли</p>
+    <Outlet/>
+    <AdminMenu/>
+    </>
+}
+
+export default LayoutAdmin

+ 81 - 18
my-diplom/src/components/public-pages/CategoryOnePage.jsx

@@ -1,19 +1,61 @@
-import { Link, useParams } from "react-router-dom"
+import { useParams } from "react-router-dom"
 import CategoryOneMenu from "../CategoryOneMenu"
-import { useState, useEffect } from "react"
+import { useState } from "react"
 import CategoryOneBreadcrumbs from "../CategoryOneBreadcrumbs"
-import { useGetCategoriesQuery, useGetCategoryQuery } from "../../store/categoriesApi"
+import { useGetCategoryQuery, useGetGoodCountQuery, useGetGoodFindQuery } from "../../store/categoriesApi"
 import CardProduct from "../CardProduct"
-import { Grid } from "@mui/material"
+import { Grid, MenuItem, Pagination, Select } from "@mui/material"
 
 
 const CategoryOnePage = () => {
+    let { catId } = useParams()
+    const { data: categoryPayload = null, isLoading } = useGetCategoryQuery(catId)
+    const category = categoryPayload ? Object.values(categoryPayload)[0] : null
+    const [page, setPage] = useState(1);
+    const [limit, setLimit] = useState(6)
+    const [sortSelectIndex, setSortSelectIndex] = useState(1)
+    const { data: goodCountPayload} = useGetGoodCountQuery({categoryId: catId })
+    const goodCount = goodCountPayload ? Object.values(goodCountPayload)[0] : null
 
-    let {catId} = useParams()
+    const sortQeryArr = [
+        {
+            categoryId: catId,
+            sort: {price: 1},
+            skip: (page - 1) * limit, 
+            limit
+        },
+        {
+            categoryId: catId,
+            sort: {price: -1},
+            skip: (page - 1) * limit,
+            limit
+        },
+        {
+            categoryId: catId,
+            sort: {name: 1},
+            skip: (page - 1) * limit,
+            limit
+        },
+        {
+            categoryId: catId,
+            sort: {createdAt: 1},
+            skip: (page - 1) * limit,
+            limit
+        },
+    ]
+        // sort должен быть обьект вида {поле сортировки: 1 или -1} где 1 сортировка по возрастаниюб -1 по убыванию.
+        // limit число от 1 до 100
+        // categoryId обязательный параметр иначе вернет пустой массив goods
 
-    const {data: payload = null, isLoading} = useGetCategoryQuery(catId)
-    console.log(isLoading)
-    const category = payload ? Object.values(payload)[0] : null
+    const handleChange = (event, value) => {
+        setPage(value);
+    };
+    const handleChangeSelect = (event) => {
+        setSortSelectIndex(event.target.value);
+    };
+
+    const { data: payload = null } = useGetGoodFindQuery(sortQeryArr[sortSelectIndex - 1])
+    const goods = payload ? Object.values(payload)[0] : null
 
     const CardProductRender = (product) => (
         <CardProduct
@@ -24,16 +66,37 @@ const CategoryOnePage = () => {
 
     return (
         isLoading ? <div> прелоадер </div> :
-        <Grid container spacing={2} alignItems="stretch" >
-            <Grid item xs={12} sm={4} md={3} align-self="stretch">
-                <CategoryOneMenu category={category?.subCategories} />
-            </Grid>
-            <Grid item xs={12} sm={8} md={9} container spacing={2} >
-                <CategoryOneBreadcrumbs parentCategory={category?.parent} thisCategory={category} />
-                {category?.goods && category.goods.map(CardProductRender)}
-            </Grid>
-        </Grid>
+            <>
+                <Grid container spacing={2} alignItems="stretch" >
+                    <Grid item xs={12} sm={6} md={4} lg={3} align-self="stretch">
+                         <CategoryOneMenu category={category?.subCategories} /> 
+                    </Grid>
+                    <Grid item xs={12} sm={6} md={8} lg={9} container spacing={2} >
+                        <CategoryOneBreadcrumbs parentCategory={category?.parent} thisCategory={category} />
+                        <Grid item xs={12} >
+                            <Select 
+                                value={sortSelectIndex}
+                                onChange={handleChangeSelect}
+                            >
+                                <MenuItem value={1}>По цене вверх</MenuItem>
+                                <MenuItem value={2}>По цене вниз</MenuItem>
+                                <MenuItem value={3}>По названию</MenuItem>
+                                <MenuItem value={4}>Вначале новые</MenuItem>
+                            </Select>
+                        </Grid>
+                        {goods && goods.map(CardProductRender)}
+                        <Grid item xs={12} >
+                            <Pagination count={Math.ceil(goodCount/limit)} page={page} onChange={handleChange} color="primary" variant="outlined"
+                                sx={{ '& *': { justifyContent: 'center' } }} />
+                        </Grid>
+
+                    </Grid>
+
+                </Grid>
+            </>
+
     )
 }
 
-export default CategoryOnePage
+export default CategoryOnePage
+

+ 2 - 8
my-diplom/src/components/public-pages/HomePage.jsx

@@ -8,16 +8,10 @@ const HomePage = () => {
         
         
         <Grid container spacing={2} alignItems="stretch" >
-            <Grid item xs={12} sm={4} md={3} align-self= "stretch">
+            <Grid item xs={12} sm={8} md={3} align-self= "stretch">
                 <CategoryMenu />
             </Grid>
-            <Grid item xs={12} sm={8} md={9} container spacing={2} >
-            {/* <CardProduct />
-            <CardProduct />
-            <CardProduct />
-            <CardProduct />
-            <CardProduct /> */}
-           
+            <Grid item xs={12} sm={4} container spacing={2} >
 
             </Grid>
         </Grid>

+ 7 - 2
my-diplom/src/components/public-pages/LoginPage.jsx

@@ -16,7 +16,7 @@ import { lightGreen } from '@mui/material/colors';
 import { categoriesApi, useGetLoginMutation } from '../../store/categoriesApi';
 import { useDispatch, useSelector } from 'react-redux';
 import { setCredentials } from '../../store/authSlice';
-import { Navigate } from 'react-router-dom';
+import { Navigate, Link as RoterLink, useNavigate, useLocation } from 'react-router-dom';
 import { Alert, Snackbar } from '@mui/material';
 import { useState } from 'react';
 
@@ -36,6 +36,10 @@ function jwtDecode(token) {
 const theme = createTheme();
 
 export default function SignIn() {
+    const navigate = useNavigate()
+    const location = useLocation()
+    const fromPage = location.state?.from?.pathname || '/'
+    console.log(fromPage)
 
     const [open, setOpen] = useState(false);
 
@@ -67,6 +71,7 @@ export default function SignIn() {
                 dispatch(setCredentials({ user, token }))
                 console.log(user.sub.id)
                 dispatch( categoriesApi.endpoints.getUserById.initiate(user.sub.id))
+                navigate(fromPage, {replace: true})
             }
             else {
                 setOpen(true)
@@ -145,7 +150,7 @@ export default function SignIn() {
                                             </Link>
                                         </Grid>
                                         <Grid item>
-                                            <Link href="#" variant="body2">
+                                            <Link to="/registration" variant="body2" component={RoterLink}>
                                                 {"У Вас нет аккаунта? Зарегистрироваться"}
                                             </Link>
                                         </Grid>

+ 40 - 18
my-diplom/src/components/public-pages/ProfilePage.jsx

@@ -1,26 +1,48 @@
-import { Button, Snackbar } from "@mui/material"
+import { Box, Button } from "@mui/material"
+import { margin } from "@mui/system";
 import { useState } from "react";
-import { useSelector } from "react-redux";
-import { useGetUserByIdQuery, useGetUserQuery } from "../../store/categoriesApi";
+import { useDispatch, useSelector } from "react-redux";
+import { useNavigate } from "react-router-dom";
+import { setCredentialsInitial, setNull } from "../../store/authSlice";
+import { useGetOrdersCountQuery, useGetUserByIdQuery } from "../../store/categoriesApi";
+import OrdersTableCollaps from "../OrdersTableCollaps";
+import ProfileMutationForm from "../ProfileMutationForm";
+import Test1 from "../Test1"
 
 const ProfilePage = () => {
-  const userAuth = useSelector((state)=> state.auth.user)
-  const _id = userAuth ?  userAuth.sub.id : null
-  const {data: payload = null} = useGetUserByIdQuery(_id)
+  const dispatch = useDispatch()
+  const userAuth = useSelector((state) => state.auth.user)
+  const navigate = useNavigate()
+  const [EditProfileIsOpen, setEditProfileIsOpen] = useState(false)
+
+  const _id = userAuth ? userAuth.sub.id : null
+  const userRole = userAuth ? userAuth.sub.acl[2] || userAuth.sub.acl[1] : null
+  console.log(userRole)
+  const { data: payload = null } = useGetUserByIdQuery(_id)
   const user = payload ? Object.values(payload)[0] : null
-  console.log(user)
 
-    return (
-    <div>
-    {!user ? <></> :
-    <>
-     <h2>{user.nick}</h2>
-     <div>{'login: ' + user.login}</div> 
-     </>
-     }
-    </div>
-    
-    )
+
+  return (
+    <Box width={'100%'} >
+      <Box width={'100%'} mb={2} p={1} sx={{ display: 'flex', justifyContent: "space-between" }}>
+        <h2>привет: {user?.nick}!</h2>
+        <Box sx={{ '& > *': { margin: '2px' } }}>
+          {userRole === "admin" && <Button variant="outlined" onClick={() => navigate('/admin')}>панель администратора</Button>}
+          {userRole === "admin" && <Button variant="outlined" onClick={() => navigate('/admin/sdfg')}>панель администратора1</Button>}
+          {userRole === "admin" && <Button variant="outlined" onClick={() => navigate('/admin/sdfg')}>панель администратора1</Button>}
+          <Button variant="outlined" onClick={() => {setEditProfileIsOpen(!EditProfileIsOpen) }}>редактировать профиль</Button>
+          <Button variant="outlined" onClick={() => { dispatch(setCredentialsInitial()); navigate('/') }}>выйти</Button>
+        </Box>
+      </Box>
+      <Box sx={{margin: '50px 10px'}}>
+        {user && EditProfileIsOpen && <ProfileMutationForm user = {user}/>}
+      </Box>
+      
+      <Box >
+        <OrdersTableCollaps />
+      </Box>
+    </Box>
+  )
 }
 
 export default ProfilePage

+ 152 - 0
my-diplom/src/components/public-pages/Registration.jsx

@@ -0,0 +1,152 @@
+import * as React from 'react';
+import Avatar from '@mui/material/Avatar';
+import Button from '@mui/material/Button';
+import CssBaseline from '@mui/material/CssBaseline';
+import TextField from '@mui/material/TextField';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import Checkbox from '@mui/material/Checkbox';
+import Link from '@mui/material/Link';
+import Grid from '@mui/material/Grid';
+import Box from '@mui/material/Box';
+import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
+import Typography from '@mui/material/Typography';
+import Container from '@mui/material/Container';
+import { createTheme, ThemeProvider } from '@mui/material/styles';
+import { lightGreen } from '@mui/material/colors';
+import { useDispatch } from 'react-redux';
+import { actionSetNewUser } from '../actions/actions';
+import { useLocation, useNavigate } from 'react-router-dom';
+
+// function Copyright(props) {
+//   return (
+//     <Typography variant="body2" color="text.secondary" align="center" {...props}>
+//       {'Copyright © '}
+//       <Link color="inherit" href="https://mui.com/">
+//         Your Website
+//       </Link>{' '}
+//       {new Date().getFullYear()}
+//       {'.'}
+//     </Typography>
+//   );
+// }
+
+const theme = createTheme();
+
+export default function Registration() {
+  const navigate = useNavigate()
+    const location = useLocation()
+    const fromPage = location.state?.from?.pathname || '/'
+
+  const dispatch = useDispatch()
+
+  const handleSubmit = async (event) => {
+    event.preventDefault();
+    const data = new FormData(event.currentTarget);
+    console.log({
+      login: data.get('login'),
+      password: data.get('password'),
+      nick: data.get('nick')
+    });
+     const response = await dispatch(actionSetNewUser({
+      login: data.get('login'),
+      password: data.get('password'),
+      nick: data.get('nick')
+    }))
+    console.log('response in element', response)
+    if (response){
+      navigate(fromPage, {replace: true})
+    }
+
+
+  };
+
+  return (
+    <ThemeProvider theme={theme}>
+      <Container component="main" maxWidth="xs">
+        <CssBaseline />
+        <Box
+          sx={{
+            marginTop: 2,
+            display: 'flex',
+            flexDirection: 'column',
+            alignItems: 'center',
+          }}
+        >
+          <Avatar sx={{ m: 1, bgcolor: lightGreen[800] }}>
+            <LockOutlinedIcon />
+          </Avatar>
+          <Typography component="h1" variant="h5">
+            Регистрация
+          </Typography>
+          <Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 3 }}>
+            <Grid container spacing={2}>
+              <Grid item xs={12} sm={6}>
+                <TextField
+                  // autoComplete="given-name"
+                  name="login"
+                  required
+                  fullWidth
+                  id="login"
+                  label="Login или Email"
+                  autoFocus
+                />
+              </Grid>
+              <Grid item xs={12} sm={6}>
+                <TextField
+                  fullWidth
+                  id="nick"
+                  label="Nick"
+                  name="nick"
+                  // autoComplete="family-name"
+                />
+              </Grid>
+              {/* <Grid item xs={12}>
+                <TextField
+                  required
+                  fullWidth
+                  id="email"
+                  label="Email Address"
+                  name="email"
+                  autoComplete="email"
+                />
+              </Grid> */}
+              <Grid item xs={12}>
+                <TextField
+                  required
+                  fullWidth
+                  name="password"
+                  label="Пароль"
+                  type="password"
+                  id="password"
+                  // autoComplete="new-password"
+                />
+              </Grid>
+              {/* <Grid item xs={12}>
+                <FormControlLabel
+                  control={<Checkbox value="allowExtraEmails" color="primary" />}
+                  label="I want to receive inspiration, marketing promotions and updates via email."
+                />
+              </Grid> */}
+            </Grid>
+            <Button
+              type="submit"
+              fullWidth
+              variant="contained"
+              sx={{ mt: 3, mb: 2 }}
+            >
+              Создать аккаунт
+            </Button>
+            <Grid container justifyContent="flex-end">
+              <Grid item>
+                <Link href="#" variant="body2">
+                  Уже есть аккаунт? Войти
+                </Link>
+              </Grid>
+            </Grid>
+          </Box>
+        </Box>
+        {/* <Copyright sx={{ mt: 5 }} /> */}
+      </Container>
+    </ThemeProvider>
+  );
+}

+ 14 - 0
my-diplom/src/hoc/NoRequireAuth.js

@@ -0,0 +1,14 @@
+import { useSelector } from "react-redux";
+import { Navigate, useLocation } from "react-router-dom"
+
+const NoRequireAuth = ({children}) => {
+    const location = useLocation();
+    const auth = useSelector(state => state.auth.user)
+
+    if (auth){
+        return <Navigate to='/profile' state={{from: location}} />
+    }
+    return children
+}
+
+export default NoRequireAuth

+ 14 - 0
my-diplom/src/hoc/RequireAuth.js

@@ -0,0 +1,14 @@
+import { useSelector } from "react-redux";
+import { Navigate, useLocation } from "react-router-dom"
+
+const RequireAuth = ({children}) => {
+    const location = useLocation();
+    const auth = useSelector(state => state.auth.user)
+
+    if (!auth){
+        return <Navigate to='/login' state={{from: location}} />
+    }
+    return children
+}
+
+export default RequireAuth

+ 1 - 0
my-diplom/src/index.css

@@ -1,5 +1,6 @@
 body {
   margin: 0;
+  padding: 0;
   font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
     'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
     sans-serif;

+ 23 - 0
my-diplom/src/store/adminMenuSlice.js

@@ -0,0 +1,23 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+const initialState = { isOpen: false }
+
+const adminMenuSlice = createSlice({
+  name: 'adminMenu',
+  initialState,
+  reducers: {
+    setOpenAdminMenu: (state, action) => {
+      state.isOpen = true
+    },
+    setCloseAdminMenu: (state, action) => {
+      return initialState
+    },
+    setIsOpenAdminMenu: (state, action) => {
+      state.isOpen = !state.isOpen
+    },
+  },
+})
+
+export const { setOpenAdminMenu, setCloseAdminMenu, setIsOpenAdminMenu } = adminMenuSlice.actions
+
+export default adminMenuSlice.reducer

+ 11 - 2
my-diplom/src/store/authSlice.js

@@ -1,16 +1,25 @@
 import { createSlice } from "@reduxjs/toolkit";
 
+const initialState = { user: null, token: null }
+
 const authSlice = createSlice({
     name: 'auth',
-    initialState: { user: null, token: null },
+    initialState,
     reducers: {
       setCredentials: (state, { payload: { user, token } }) => {
         state.user = user
         state.token = token
       },
+      setCredentialsInitial: (state) => {
+        return initialState
+      },
+      setNull: () => {
+        return { user: null, token: null }
+      }
+
     },
   })
 
-export const { setCredentials } = authSlice.actions
+export const { setCredentials, setCredentialsInitial, setNull } = authSlice.actions
 
 export default authSlice.reducer

+ 16 - 16
my-diplom/src/store/cartSlice.js

@@ -7,36 +7,36 @@ const cartSlice = createSlice({
     initialState,
     reducers: {
         CartAdd(state, action) {
-            const {product, count=1} = action.payload
-            if (product._id in state) {
-                state[product._id].count += count
+            const {good, count=1} = action.payload
+            if (good._id in state) {
+                state[good._id].count += count
             } else {
-            state[product._id] = {product, count}
+            state[good._id] = {good, count}
             }
         },
         CartIncOne(state, action) {
-            const { product, count = 1 } = action.payload
-            if (product._id in state) {
-                if ((state[product._id].count - count) <= 0) {
-                    delete state[product._id]
+            const { good, count = 1 } = action.payload
+            if (good._id in state) {
+                if ((state[good._id].count - count) <= 0) {
+                    delete state[good._id]
                 } else {
-                    state[product._id].count -= count
+                    state[good._id].count -= count
                 }
             }
         },
         CartDel(state, action) {
-            const { product, count = 1 } = action.payload
-            if (product._id in state) {
-                delete state[product._id]
+            const { good, count = 1 } = action.payload
+            if (good._id in state) {
+                delete state[good._id]
             }
           },
         
          CartSet(state, action) {
-            const { product, count = 1 } = action.payload
-            if (product._id in state && count <= 0) {
-                delete state[product._id]
+            const { good, count = 1 } = action.payload
+            if (good._id in state && count <= 0) {
+                delete state[good._id]
             } else {
-                state[product._id].count = count
+                state[good._id].count = count
             }
         },
         CartClear() { 

+ 197 - 62
my-diplom/src/store/categoriesApi.js

@@ -1,24 +1,24 @@
 import { createApi } from '@reduxjs/toolkit/query/react'
 import { gql } from 'graphql-request'
-import {graphqlRequestBaseQuery} from '@rtk-query/graphql-request-base-query'
+import { graphqlRequestBaseQuery } from '@rtk-query/graphql-request-base-query'
 
 export const categoriesApi = createApi({
-    reducerPath: 'categoriesApi',
-    baseQuery: graphqlRequestBaseQuery({ 
-      url: 'http://shop-roles.node.ed.asmer.org.ua/graphql',
-      prepareHeaders:(headers, { getState }) => {
-        // By default, if we have a token in the store, let's use that for authenticated requests
-        const token = getState().auth.token;
-        if (token) {
-            headers.set("Authorization", `Bearer ${token}`);
-        }
-        return headers;
+  reducerPath: 'categoriesApi',
+  baseQuery: graphqlRequestBaseQuery({
+    url: 'http://shop-roles.node.ed.asmer.org.ua/graphql',
+    prepareHeaders: (headers, { getState }) => {
+      // By default, if we have a token in the store, let's use that for authenticated requests
+      const token = getState().auth.token;
+      if (token) {
+        headers.set("Authorization", `Bearer ${token}`);
+      }
+      return headers;
     }
-    }),
-    endpoints: (builder) => ({
-        getCategories: builder.query({
-            query: () => ({
-                document: gql`query baseCategory($searchNullparent: String){
+  }),
+  endpoints: (builder) => ({
+    getCategories: builder.query({
+      query: () => ({
+        document: gql`query baseCategory($searchNullparent: String){
                     CategoryFind(query: $searchNullparent){
                       _id name parent {
                         _id
@@ -26,14 +26,14 @@ export const categoriesApi = createApi({
                       }
                     }
                   }`,
-                variables: {
-                    searchNullparent: JSON.stringify([{parent: null}]),
-                 },
-            })
-        }),
-        getCategory: builder.query({
-            query: (_id) => ({
-                document: gql`query CategoryFindOne($qCategoryId: String) {
+        variables: {
+          searchNullparent: JSON.stringify([{ parent: null }]),
+        },
+      })
+    }),
+    getCategory: builder.query({
+      query: (_id) => ({
+        document: gql`query CategoryFindOne($qCategoryId: String) {
                         CategoryFindOne(query: $qCategoryId){
                           _id
                           name
@@ -57,14 +57,14 @@ export const categoriesApi = createApi({
                           }
                         }
                       }`,
-                variables: {
-                    qCategoryId: JSON.stringify([{ _id }])
-                }
-            })
-        }),
-        getProduct: builder.query({
-            query: (_id) => ({
-                document: gql`query oneProduct($GoodId: String){
+        variables: {
+          qCategoryId: JSON.stringify([{ _id }])
+        }
+      })
+    }),
+    getProduct: builder.query({
+      query: (_id) => ({
+        document: gql`query oneProduct($GoodId: String){
                     GoodFindOne(query: $GoodId){
                       _id
                       description
@@ -78,25 +78,25 @@ export const categoriesApi = createApi({
                       }
                     }  
                   }`,
-                variables: {
-                    GoodId: JSON.stringify( [{ _id }] )
-                }
-            })
-        }),
-        getLogin: builder.mutation({
-            query: ({login, password}) => ({
-                document: gql`query login($login:String, $password:String){
+        variables: {
+          GoodId: JSON.stringify([{ _id }])
+        }
+      })
+    }),
+    getLogin: builder.mutation({
+      query: ({ login, password }) => ({
+        document: gql`query login($login:String, $password:String){
                     login(login:$login, password:$password)
                   }`,
-                variables: {
-                    login,
-                    password
-                }
-            })
-        }),
-        getUser: builder.query({
-          query: (_id) => ({
-              document: gql`query oneUser($UserId: String){
+        variables: {
+          login,
+          password
+        }
+      })
+    }),
+    getUser: builder.query({
+      query: (_id) => ({
+        document: gql`query oneUser($UserId: String){
                 UserFindOne(query: $UserId){
                     _id
                     login
@@ -105,14 +105,14 @@ export const categoriesApi = createApi({
                     
                   }
                 }`,
-              variables: {
-                UserId: JSON.stringify( [{ _id }] )
-              }
-          })
-      }),
-      getUserById: builder.query({
-        query: (_id) => ({
-            document: gql`
+        variables: {
+          UserId: JSON.stringify([{ _id }])
+        }
+      })
+    }),
+    getUserById: builder.query({
+      query: (_id) => ({
+        document: gql`
                 query GetUser($q: String) {
                     UserFindOne(query: $q) {
                         _id
@@ -122,11 +122,130 @@ export const categoriesApi = createApi({
                     }
                 }
             `,
-            variables: {q: JSON.stringify([{_id}])}
-        }),
-        providesTags: (result, error, id) =>  ( [{ type: 'User', id}])
+        variables: { q: JSON.stringify([{ _id }]) }
+      }),
+      providesTags: (result, error, id) => ([{ type: 'User', id }])
+    }),
+    getUserOrders: builder.query({
+      query: ({limit, skip}) => ({
+        document: gql`
+        query( $q: String ) {
+          OrderFind(query: $q) {
+              _id
+              createdAt
+              total
+              orderGoods {
+                _id
+                createdAt
+                price
+                count
+                goodName
+                total
+              }
+          }
+      }`,
+        variables: {
+          q: JSON.stringify([{}, {limit: [limit], skip: [skip]}])
+         }
+      }),
+      providesTags: (result, error, id) => ([{ type: 'UserOrders' }])
+    }),
+    getOrdersCount: builder.query({
+      query: () => ({
+        document: gql`
+        query {
+          OrderCount(query: "[{}]")
+        }
+        `,
+        variables: {}
+      }),
+      providesTags: (result, error, id) => ([{ type: 'UserOrders' }])
     }),
-    })
+    setNick: builder.mutation({
+      query: ({ _id, nick }) => ({
+        document: gql`
+              mutation SetNick($_id:String, $nick: String){
+                  UserUpsert(user: {_id: $_id, nick: $nick}){
+                      _id, nick
+                  }
+              }
+          `,
+        variables: { _id, nick }
+      }),
+      invalidatesTags: (result, error, arg) => ([{ type: 'User', id: arg._id }])
+    }),
+    setNewUser: builder.mutation({
+      query: ({ login, password, ...args }) => ({
+        document: gql`
+        mutation registration($login:String, $password: String,  $nick: String){
+          UserUpsert(user: {login: $login, password: $password, nick: $nick}){
+              _id login createdAt nick
+          }
+        }
+          `,
+        variables: { login, password, nick: args?.nick || ""}
+      }),
+      // invalidatesTags: (result, error, arg) => ([{ type: 'User', id: arg._id }])
+    }),
+    setOrderUpsert: builder.mutation({
+      query: (arrGoods) => ({
+        document: gql`
+        mutation ($arrGoods: [OrderGoodInput]){
+          OrderUpsert(order: {orderGoods: $arrGoods}) {
+            _id, total, orderGoods {
+              good {
+                name
+              }
+            }
+          }
+        }
+          `,
+        variables: { arrGoods }
+      }),
+      invalidatesTags: (result, error, arg) => ([{ type: 'UserOrders' }])
+    }),
+    getGoodFind: builder.query({
+      query: ({categoryId = null, sort = null, limit = null, skip = null}) => ({
+        document: gql`
+        query($q: String) {
+          GoodFind(query:$q){
+      _id
+      name
+      price
+      createdAt
+      description
+      categories{
+        _id
+      }
+      images{
+        url
+      }
+    }
+    }
+        `,
+        variables: {
+          q: JSON.stringify([{"categories._id": categoryId}, {limit:[limit], sort :[sort], skip: [skip]  }]) 
+          // sort должен быть обьект вида {поле сортировки: 1 или -1} где 1 сортировка по возрастаниюб -1 по убыванию.
+          // limit число от 1 до 100
+          // categoryId обязательный параметр иначе вернет пустой массив goods
+        }
+      }),
+      providesTags: (result, error, id) => ([{ type: 'UserOrders' }])
+    }),
+    getGoodCount: builder.query({
+      query: ({categoryId = null}) => ({
+        document: gql`
+        query($q: String) {
+          GoodCount(query: $q)
+        }
+        `,
+        variables: {
+          q: JSON.stringify([{"categories._id": categoryId}])
+        }
+      }),
+    }),
+    
+  })
 })
 
 export const {
@@ -135,6 +254,22 @@ export const {
   useGetProductQuery,
   useGetLoginMutation,
   useGetUserQuery,
-  useGetUserByIdQuery
+  useGetUserByIdQuery,
+  useGetUserOrdersQuery,
+  useGetOrdersCountQuery,
+  useGetGoodFindQuery,
+  useGetGoodCountQuery,
 } = categoriesApi
 
+/*
+поиск товаров с сортировкой по возрастанию и лимитами
+query {
+        GoodFind(query:"[{\"categories._id\":\"62c94b10b74e1f5f2ec1a0dd\"}, {\"limit\":[20], \"sort\":[{\"price\":1}]  }]"){
+    name price
+  }
+  }
+  
+*/
+
+
+

+ 2 - 0
my-diplom/src/store/index.js

@@ -1,6 +1,7 @@
 import { configureStore } from "@reduxjs/toolkit";
 import cartReducer from './cartSlice'
 import authReducer from './authSlice'
+import adminMenuReducer from './adminMenuSlice'
 import { categoriesApi } from './categoriesApi'
 
  
@@ -8,6 +9,7 @@ export default configureStore({
     reducer:{
         cart: cartReducer,
         auth: authReducer,
+        adminMenu: adminMenuReducer,
         [categoriesApi.reducerPath]: categoriesApi.reducer,
     },
         middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(categoriesApi.middleware)