viktoriia.kapran 1 рік тому
батько
коміт
865d489fa2
34 змінених файлів з 578 додано та 315 видалено
  1. 22 4
      js21 react/my-react-app/src/App.js
  2. 136 25
      js21 react/my-react-app/src/api/api.js
  3. 0 43
      js21 react/my-react-app/src/api/gql.js
  4. 4 7
      js21 react/my-react-app/src/components/CartGood.js
  5. 0 28
      js21 react/my-react-app/src/components/Categories/CategoryMenu.js
  6. 0 10
      js21 react/my-react-app/src/components/Categories/CategoryMenu.scss
  7. 32 0
      js21 react/my-react-app/src/components/CategoriesMenu/CategoryMenu.js
  8. 10 0
      js21 react/my-react-app/src/components/CategoriesMenu/CategoryMenu.scss
  9. 1 1
      js21 react/my-react-app/src/components/Counter/Counter.js
  10. 2 2
      js21 react/my-react-app/src/components/GoodCard/GoodCard.js
  11. 2 5
      js21 react/my-react-app/src/components/Header.js
  12. 17 0
      js21 react/my-react-app/src/components/ItemList.js
  13. 1 1
      js21 react/my-react-app/src/components/Layout.js
  14. 27 0
      js21 react/my-react-app/src/components/OrderCard.js
  15. 20 0
      js21 react/my-react-app/src/components/OrderGood.js
  16. 1 1
      js21 react/my-react-app/src/components/Price.js
  17. 5 12
      js21 react/my-react-app/src/components/hoc/RequireAdmin.js
  18. 6 6
      js21 react/my-react-app/src/components/hoc/RequireAuth.js
  19. 4 0
      js21 react/my-react-app/src/functions/getDataString.js
  20. 0 8
      js21 react/my-react-app/src/pages/Admin.js
  21. 13 3
      js21 react/my-react-app/src/pages/Cart.js
  22. 1 2
      js21 react/my-react-app/src/pages/Category/Category.js
  23. 3 3
      js21 react/my-react-app/src/pages/Good/Good.js
  24. 11 4
      js21 react/my-react-app/src/pages/Login.js
  25. 0 12
      js21 react/my-react-app/src/pages/OrdersHistory.js
  26. 30 0
      js21 react/my-react-app/src/pages/UserPage.js
  27. 14 0
      js21 react/my-react-app/src/pages/admin/Admin.js
  28. 87 0
      js21 react/my-react-app/src/pages/admin/Categories.js
  29. 62 0
      js21 react/my-react-app/src/pages/admin/CreateCategory.js
  30. 67 0
      js21 react/my-react-app/src/pages/admin/Users.js
  31. 0 52
      js21 react/my-react-app/src/redux/actions/actions.js
  32. 0 18
      js21 react/my-react-app/src/redux/reducers/authReducer.js
  33. 0 38
      js21 react/my-react-app/src/redux/reducers/cartReducer.js
  34. 0 30
      js21 react/my-react-app/src/redux/slices/authSlice.js

+ 22 - 4
js21 react/my-react-app/src/App.js

@@ -3,10 +3,10 @@ import { Provider } from 'react-redux';
 import { Route, Routes } from 'react-router-dom';
 import MainPage from './pages/HomePage';
 import NotFound from './pages/NotFound';
-import OrdersHistory from './pages/OrdersHistory';
+import UserPage from './pages/UserPage';
 import Register from './pages/Register';
 import Login from './pages/Login';
-import Admin from './pages/Admin';
+import Admin from './pages/admin/Admin';
 import Cart from './pages/Cart';
 import Category from './pages/Category/Category';
 import Good from './pages/Good/Good';
@@ -15,6 +15,9 @@ import store from './redux/reducers';
 import { Box } from '@mui/material';
 import RequireAuth from './components/hoc/RequireAuth';
 import RequireAdmin from './components/hoc/RequireAdmin';
+import Users from './pages/admin/Users';
+import Categories from './pages/admin/Categories';
+import CreateCategory from './pages/admin/CreateCategory';
 
 
 function App() {
@@ -24,9 +27,9 @@ function App() {
         <Routes>
           <Route path='/' element={<Layout />}>
             <Route index element={<MainPage />} />
-            <Route path='/history' element={
+            <Route path='/user/:userId' element={
               <RequireAuth>
-                <OrdersHistory />
+                <UserPage />
               </RequireAuth>
             } />
             <Route path='/cart' element={<Cart />} />
@@ -35,6 +38,21 @@ function App() {
                 <Admin />
               </RequireAdmin>
             } />
+            <Route path='/admin/users' element={
+              <RequireAdmin>
+                <Users />
+              </RequireAdmin>
+            } />
+            <Route path='/admin/categories' element={
+              <RequireAdmin>
+                <Categories />
+              </RequireAdmin>
+            } />
+            <Route path='/admin/createCategory' element={
+              <RequireAdmin>
+                <CreateCategory />
+              </RequireAdmin>
+            } />
             <Route path='/register' element={<Register />} />
             <Route path='/login' element={<Login />} />
             <Route path='/:category/:categoryId' element={<Category />} />

+ 136 - 25
js21 react/my-react-app/src/api/api.js

@@ -1,5 +1,5 @@
 import { createApi } from '@reduxjs/toolkit/query/react'
-import { createSlice } from "@reduxjs/toolkit";
+import { createSlice, current } from "@reduxjs/toolkit";
 import { gql } from 'graphql-request'
 import { graphqlRequestBaseQuery } from '@rtk-query/graphql-request-base-query'; //npm install
 
@@ -27,7 +27,11 @@ export const api = createApi({
         document: gql`
                   query GetCategories{
                       CategoryFind(query: "[{\\"parent\\": null}]") {
-                        _id name
+                        _id name goods {
+                          _id name
+                        }, subCategories {
+                          name
+                        }
                       }
                     }
                   `}),
@@ -89,6 +93,89 @@ export const api = createApi({
         variables: { q: JSON.stringify([{ _id }]) }
       }),
     }),
+    getUserById: builder.query({
+      query: (_id) => ({
+        document: gql`
+                    query GetUser($q: String) {
+                        UserFindOne(query: $q) {
+                            _id
+                            login
+                            nick
+                            avatar { url }
+                            acl
+                        }
+                    }
+                `,
+        variables: { q: JSON.stringify([{ _id }]) }
+      }),
+      providesTags: (result, error, id) => ([{ type: 'User', id }])
+    }),
+    getOwnerOrder: builder.query({
+      query: () => ({
+        document: gql`
+                  query orders($q: String) {
+                    OrderFind(query: $q) {
+                      _id, total, createdAt, owner{
+                        _id, login
+                      }, orderGoods{
+                        price, count, good{
+                          name, _id, images {
+                            url
+                          }
+                        }
+                      }
+                    }
+                  }
+                  `,
+        variables: { q: JSON.stringify([{}]) }
+      })
+    }),
+    getOrderGood: builder.query({
+      query: () => ({
+        document: gql`
+                  query orders($q: String) {
+                    OrderGoodFind(query: $q) {
+                      _id, createdAt, price, total, count, goodName, good {
+                        _id, name
+                      }, order {
+                        _id, total, orderGoods {
+                          count, good {
+                            name
+                          }
+                        }
+                      }, owner {
+                        _id login
+                      }
+                    }
+                  }
+                  `,
+        variables: { q: JSON.stringify([{}]) }
+      })
+    }),
+    getGoods: builder.query({
+      query: () => ({
+        document: gql`
+                  query GetGoods($q: String) {
+                    GoodFind(query: $q) {
+                      _id, name, description, price, categories {
+                        _id
+                      }
+                    }
+                  }`,
+        variables: { q: JSON.stringify([{}]) }
+      })
+    }),
+    getUsers: builder.query({
+      query: () => ({
+        document: gql`
+                  query GetUsers($q: String) {
+                    UserFind(query: $q) {
+                      _id, login, createdAt
+                    }
+                  }`,
+        variables: { q: JSON.stringify([{}]) }
+      })
+    }),
     login: builder.mutation({
       query: ({ login, password }) => ({
         document: gql`
@@ -102,7 +189,7 @@ export const api = createApi({
     register: builder.mutation({
       query: ({ login, password }) => ({
         document: gql`
-                  mutation registration($login:String, $password: String){
+                  mutation registration($login:String, $password: String) {
                       UserUpsert(user: {login:$login, password: $password}){
                            _id login createdAt
                         }
@@ -110,27 +197,49 @@ export const api = createApi({
         variables: { login, password }
       })
     }),
-    getOwnerOrder: builder.query({
-      query: () => ({
+    createOrder: builder.mutation({
+      query: (orderGoods) => ({
         document: gql`
-                  query orders($q: String) {
-                    OrderFind(query: $q) {
-                      _id, total, createdAt, owner{
-                        _id, login
-                      }, orderGoods{
-                        price, count, good{
-                          name
-                        }
+                mutation ordering($orderGoods: OrderInput) {
+                  OrderUpsert(order: $orderGoods) {
+                    _id, total, orderGoods {
+                      good {
+                        name, _id
                       }
                     }
                   }
-                  `,
-        variables: { q: JSON.stringify([{}]) }
+                }`,
+        variables: { orderGoods: { orderGoods } }
+      })
+    }),
+    createCategory: builder.mutation({
+      query: (category) => ({
+        document: gql`
+                  mutation createCategory($category: CategoryInput) {
+                    CategoryUpsert(category: $category) {
+                      _id
+                    }
+                  }`,
+        variables: { category }
       })
-    })
+    }),
+
   }),
 });
 
+export const createCategory = category => 
+async dispatch => {
+  const payload = await dispatch(api.endpoints.createCategory.initiate(category));
+  console.log('category created', payload);
+}
+
+export const actionOrder = orderGoods =>
+  async dispatch => {
+    await dispatch(api.endpoints.createOrder.initiate(orderGoods));
+    console.log('order was created');
+    dispatch(cartSlice.actions.clearCart());
+  }
+
 function jwtDecode(token) {
   try {
     const tokenArr = token.split(".");
@@ -192,6 +301,11 @@ const initialState = {
   goodsCount: 0
 }
 
+function calculateTotalAmounts(state, currentState) {
+  state.totalAmount = currentState.goods.reduce((totalAmount, item) => totalAmount + item.good.price * item.count, 0);
+  state.goodsCount = currentState.goods.reduce((goodsCount, item) => goodsCount + item.count, 0);
+}
+
 export const cartSlice = createSlice({
   name: 'cart',
   initialState,
@@ -203,8 +317,7 @@ export const cartSlice = createSlice({
       else {
         state.goods[itemIndex].count += +action.payload.count;
       }
-      state.totalAmount += action.payload.count * action.payload.good.price;
-      state.goodsCount += action.payload.count;
+      calculateTotalAmounts(state, current(state));
     },
     decreaseGoodCount(state, action) {
       const itemIndex = getItemIndex(state, action.payload.good._id);
@@ -214,19 +327,16 @@ export const cartSlice = createSlice({
       else {
         state.goods = state.goods.filter(item => item.good._id !== action.payload.good._id);
       }
-      state.goodsCount -= 1;
-      state.totalAmount -= action.payload.good.price;
+      calculateTotalAmounts(state, current(state));
     },
     setGoodCount(state, action) {
       const itemIndex = getItemIndex(state, action.payload.good._id);
       state.goods[itemIndex].count = action.payload?.count;
-      //state.totalAmount += action.payload.count * action.payload.good.price;
-      //задать для totalAmount и goodsCount
+      calculateTotalAmounts(state, current(state));
     },
     deleteGood(state, action) {
       state.goods = state.goods.filter(item => item.good._id !== action.payload.good._id);
-      state.totalAmount -= action.payload.count * action.payload.good.price;
-      state.goodsCount -= action.payload.count;
+      calculateTotalAmounts(state, current(state));
     },
     clearCart() {
       return initialState;
@@ -236,4 +346,5 @@ export const cartSlice = createSlice({
   },
 });
 export const { addGood, deleteGood, decreaseGoodCount, setGoodCount, clearCart } = cartSlice.actions;
-export const { useGetCategoriesQuery, useGetCategoryByIdQuery, useGetGoodByIdQuery, useLoginMutation, useGetOwnerOrderQuery, useRegisterMutation } = api;
+export const { useGetCategoriesQuery, useGetCategoryByIdQuery, useGetGoodByIdQuery, useLoginMutation, useGetOwnerOrderQuery,
+  useRegisterMutation, useGetUserByIdQuery, useCreateCategoryMutation, useGetGoodsQuery, useGetUsersQuery, useGetOrderGoodQuery} = api;

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

@@ -1,43 +0,0 @@
-// const gqlLogin = (login, password) => {
-//   const loginQuery = `query login($login:String, $password:String){
-//     login(login:$login, password:$password)
-// }`;
-//   return gql(loginQuery, { login, password });
-// }
-
-// const gqlCreateUser = (login, password) => {
-//   const registrationQuery = `mutation registration($login:String, $password: String){
-//     UserUpsert(user: {login:$login, password: $password}){
-//         _id login createdAt
-//     }
-// }`;
-//   return gql(registrationQuery, { login, password });
-// }
-
-// const gqlGetOwnerOrders = () => {
-//   const ordersQuery = `query orders($q: String) {
-//     OrderFind(query: $q) {
-//       _id, total, createdAt, owner{
-//         _id, login
-//       }, orderGoods{
-//         price, count, good{
-//           name
-//         }
-//       }
-//     }
-//   }`;
-//   return gql(ordersQuery, { q: `[{}]` });
-// }
-
-// const gqlCreateOrder = (orderGoods) => {
-//   const orderQuery = `mutation ordering($orderGoods: OrderInput) {
-//     OrderUpsert(order: $orderGoods) {
-//       _id, total, orderGoods {
-//         good {
-//           name
-//         }
-//       }
-//     }
-//   }`;
-//   return gql(orderQuery, { orderGoods: { orderGoods } });
-// }

+ 4 - 7
js21 react/my-react-app/src/components/CartGood.js

@@ -6,12 +6,12 @@ import AddIcon from '@mui/icons-material/Add';
 import RemoveIcon from '@mui/icons-material/Remove';
 import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
 import IconButton from '@mui/material/IconButton';
-import { Price } from './Price';
-import { Counter } from './Counter/Counter';
+import Price from './Price';
+import Counter from './Counter/Counter';
 import { addGood, deleteGood, decreaseGoodCount, setGoodCount, clearCart } from '../api/api';
 import { useDispatch, useSelector } from 'react-redux';
-import { Image } from './Image/Image';
 import { Stack } from '@mui/material';
+import OrderGood from './OrderGood';
 
 
 export default function CartGood({ good }) {
@@ -28,10 +28,7 @@ export default function CartGood({ good }) {
     <Card key={good.good._id} sx={{ mb: 2 }}>
       <CardContent>
         <Stack direction="row" alignItems="center" spacing={2}>
-          <Box sx={{ height: '100px', width: '100px' }}>
-            <Image url={good?.good?.images[0]?.url} />
-          </Box>
-          <Box sx={{ flexGrow: 1 }}>{good.good.name}</Box>
+          <OrderGood url={good.good.images[0]?.url} name={good.good.name}/>
           <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'end' }}>
             <RemoveIcon sx={{ cursor: 'pointer' }} onClick={() => decrease({ good: good.good })} />
             <Counter value={count} onCount={(countValue) => dispatch(setGoodCount({good: good.good, count: countValue}))} />

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

@@ -1,28 +0,0 @@
-import React from 'react';
-import { Link } from 'react-router-dom';
-import './CategoryMenu.scss'
-import Skeleton from '@mui/material/Skeleton';
-import List from '@mui/material/List';
-import ListItem from '@mui/material/ListItem';
-import ListItemButton from '@mui/material/ListItemButton';
-import ListItemText from '@mui/material/ListItemText';
-import { useGetCategoriesQuery } from '../../api/api';
-
-export const CategoryMenu = () => {
-  const { data, error, isFetching } = useGetCategoriesQuery();
-
-  return (
-    <aside className='aside'>
-      {isFetching ? Array(10).fill(1).map((_, index) => <Skeleton key={index} className='skeleton' />) :
-        <List>
-          {data.CategoryFind.map(category =>
-            <ListItem key={category._id} disablePadding>
-              <ListItemButton component={Link} to={`/${category.name}/${category._id}`}>
-                <ListItemText primary={category.name} sx={{ color: '#000' }} />
-              </ListItemButton>
-            </ListItem>
-          )}
-        </List>}
-    </aside>
-  )
-}

+ 0 - 10
js21 react/my-react-app/src/components/Categories/CategoryMenu.scss

@@ -1,10 +0,0 @@
-.aside {
-  min-width: 260px;
-  max-width: 260px;
-  margin-right: 30px;
-  .skeleton {
-    padding: 10px 20px;
-    margin-bottom: 8px;
-    height: 40px;
-  }
-}

+ 32 - 0
js21 react/my-react-app/src/components/CategoriesMenu/CategoryMenu.js

@@ -0,0 +1,32 @@
+import React from 'react';
+import './CategoryMenu.scss'
+import Skeleton from '@mui/material/Skeleton';
+import { useGetCategoriesQuery } from '../../api/api';
+import { useSelector } from 'react-redux';
+import ItemList from '../ItemList';
+import List from '@mui/material/List';
+
+ const CategoryMenu = () => {
+  const { data, error, isFetching } = useGetCategoriesQuery();
+  const admin = useSelector(state => state.auth?.payload?.sub?.acl?.includes('admin'));
+  const adminMenu = ['Categories', 'Users'];
+
+  return (
+    <aside className='aside'>
+      {admin ?
+        <List>
+          {adminMenu.map((category, index ) =>
+            <ItemList key={index} url={`/admin/${category.toLowerCase()}`} text={category} />)}
+        </List>
+        :
+        (isFetching ? Array(10).fill(1).map((_, index) => <Skeleton key={index} className='skeleton' />) :
+          <List>
+            {data.CategoryFind.map(category =>
+              <ItemList key={category._id} url={`/${category.name}/${category._id}`} text={category.name} />)}
+          </List>)}
+
+    </aside>
+  )
+}
+
+export default CategoryMenu;

+ 10 - 0
js21 react/my-react-app/src/components/CategoriesMenu/CategoryMenu.scss

@@ -0,0 +1,10 @@
+.aside {
+  min-width: 230px;
+  max-width: 230px;
+  margin-right: 20px;
+  .skeleton {
+    padding: 10px 20px;
+    margin-bottom: 8px;
+    height: 52px;
+  }
+}

+ 1 - 1
js21 react/my-react-app/src/components/Counter/Counter.js

@@ -3,7 +3,7 @@ import TextField from '@mui/material/TextField';
 import Box from '@mui/material/Box';
 import './Counter.scss'
 
-export function Counter({ value, onCount }) {
+export default function Counter({ value, onCount }) {
   const [countValue, setCountValue] = useState(value);
   useEffect(() => setCountValue(value), [value]);
 

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

@@ -5,8 +5,8 @@ import CardContent from '@mui/material/CardContent';
 import { Link } from 'react-router-dom';
 import './GoodCard.scss'
 import { Image } from '../Image/Image';
-import { Price } from '../Price';
-import { Counter } from '../Counter/Counter.js';
+import Price from '../Price';
+import Counter from '../Counter/Counter.js';
 import { Box, Button } from '@mui/material';
 import { useDispatch } from 'react-redux';
 import { addGood } from '../../api/api';

+ 2 - 5
js21 react/my-react-app/src/components/Header.js

@@ -9,7 +9,7 @@ import Box from '@mui/material/Box';
 import Toolbar from '@mui/material/Toolbar';
 import { Button } from '@mui/material';
 import { useDispatch, useSelector } from 'react-redux';
-import { authSlice } from '../api/api';
+import { authSlice, cartSlice } from '../api/api';
 import DrawUserName from './DrawUserName';
 import LinkButton from './LinkButton';
 
@@ -26,6 +26,7 @@ export default function Header() {
   const goodsInCart = useSelector(state => state.cart.goodsCount);
   const onLogout = () => {
     dispatch(authSlice.actions.logout());
+    dispatch(cartSlice.actions.clearCart());
   }
   const token = useSelector(state => state.auth.token);
   return (
@@ -36,10 +37,6 @@ export default function Header() {
         </Box>
         <Box sx={{ display: 'flex', alignItems: 'center' }}>
           <DrawUserName />
-          <Box sx={{ mx: 1, my: 2 }}> {/* отображать, когда залогинен */}
-            {token &&
-              <Button to="history" component={Link} color="inherit">My orders</Button>}
-          </Box>
           <Box sx={{ mx: 1, my: 2 }}>
             <Link to="cart">
               <IconButton aria-label="cart">

+ 17 - 0
js21 react/my-react-app/src/components/ItemList.js

@@ -0,0 +1,17 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import ListItem from '@mui/material/ListItem';
+import ListItemButton from '@mui/material/ListItemButton';
+import ListItemText from '@mui/material/ListItemText';
+
+function ItemList({ url, text }) {
+  return (
+    <ListItem disablePadding>
+      <ListItemButton component={Link} to={url}>
+        <ListItemText primary={text} />
+      </ListItemButton>
+    </ListItem>
+  )
+}
+
+export default ItemList;

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

@@ -2,7 +2,7 @@ import React from 'react';
 import { Link, Outlet } from 'react-router-dom';
 import Footer from './Footer';
 import Header from './Header';
-import { CategoryMenu, ReduxCategoryMenu } from './Categories/CategoryMenu';
+import CategoryMenu from './CategoriesMenu/CategoryMenu';
 import Box from '@mui/material/Box';
 import { Container } from '@mui/material';
 

+ 27 - 0
js21 react/my-react-app/src/components/OrderCard.js

@@ -0,0 +1,27 @@
+import { Box, Card, CardContent, Stack } from '@mui/material';
+import React from 'react';
+import OrderGood from './OrderGood';
+import Price from './Price';
+import { getDateString } from '../functions/getDataString';
+
+function OrderCard({order}) {
+  return (
+    <Card sx={{ mb: 2 }}>
+      <CardContent>
+        <Stack spacing={2}>
+          {order?.orderGoods?.map(good => 
+          <OrderGood 
+          key={good?.good?._id}
+          url={good?.good?.images[0]?.url}
+          name={good?.good?.name}
+          count={good?.count}
+          price={good?.price}/>)}
+          <Price>Total: {order?.total}</Price>
+          <Box sx={{textAlign: 'end'}}>{getDateString(order?.createdAt)}</Box>
+        </Stack>
+      </CardContent>
+    </Card>
+  )
+}
+
+export default OrderCard;

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

@@ -0,0 +1,20 @@
+import { Box, Stack } from '@mui/material';
+import React from 'react';
+import { Image } from './Image/Image';
+
+function OrderGood({url, name, count, price}) {
+  return (
+    <Stack direction="row" alignItems="center" spacing={2}>
+      <Box sx={{ height: '100px', width: '100px' }}>
+        <Image url={url} />
+      </Box>
+      <Box>
+      <Box sx={{ flexGrow: 1 }}>{name}</Box>
+      <Box>Count: {count}</Box>
+      <Box>Price: {price} UAN</Box>
+      </Box>
+    </Stack>
+  )
+}
+
+export default OrderGood;

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

@@ -2,7 +2,7 @@ import { Box } from "@mui/material";
 import React from "react";
 
 
-export function Price( {children}) {
+export default function Price( {children}) {
   return (
     <Box sx={{ fontSize: '22px', fontWeight: '400', color: 'rgb(58, 78, 88)'}}>
       {children} UAH

+ 5 - 12
js21 react/my-react-app/src/components/hoc/RequireAdmin.js

@@ -1,21 +1,14 @@
-import React from 'react';
+import React, { useEffect } from 'react';
 import { useSelector } from 'react-redux';
 import { Navigate, useLocation } from 'react-router-dom';
 
 function RequireAdmin({ children }) {
   const location = useLocation();
-  const id = useSelector(state => state.auth.payload.sub.id);
-  const adminId = '6267f1f2bf8b206433f5b409';
-  if(!(id === adminId)) {
-    return <Navigate to={'/'} state={{from: location.pathname}}/>
+  const admin = useSelector(state => state.auth.payload.sub.acl.includes('admin'));
+  if (!admin) {
+    return <Navigate to={'/'} state={{ from: location.pathname }} />
   }
-  return (
-    <>
-      {id === adminId && children}
-    </>
-
-  )
-
+  return children;
 }
 
 export default RequireAdmin;

+ 6 - 6
js21 react/my-react-app/src/components/hoc/RequireAuth.js

@@ -2,12 +2,12 @@ import React from 'react';
 import { useSelector } from 'react-redux';
 import { Navigate, useLocation } from 'react-router-dom';
 
-function RequireAuth({children}) {
-const location = useLocation();
-const auth = useSelector(state => state.auth.token);
-if (!auth) {
-  return <Navigate to={'/login'} state={{from: location.pathname}}/>
-}
+function RequireAuth({ children }) {
+  const location = useLocation();
+  const auth = useSelector(state => state.auth.token);
+  if (!auth) {
+    return <Navigate to={'/login'} state={{ from: location.pathname }} />
+  }
 
   return children;
 }

+ 4 - 0
js21 react/my-react-app/src/functions/getDataString.js

@@ -0,0 +1,4 @@
+export const getDateString = (ms) => {
+  let orderCreatedAt = new Date(+ms).toUTCString();
+  return orderCreatedAt;
+}

+ 0 - 8
js21 react/my-react-app/src/pages/Admin.js

@@ -1,8 +0,0 @@
-import React from 'react'
-
-export default function Admin() {
-  return (
-    <div>Admin</div>
-  )
-}
-//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOnsiaWQiOiI2MjY3ZjFmMmJmOGIyMDY0MzNmNWI0MDkiLCJsb2dpbiI6ImFkbWluIiwiYWNsIjpbIjYyNjdmMWYyYmY4YjIwNjQzM2Y1YjQwOSIsInVzZXIiLCJhZG1pbiJdfSwiaWF0IjoxNjc5MDkzNjQ5fQ.J1vvj_0OfnBYEFdylLZdwSoXuA29KHLYjdib0z45IOI

+ 13 - 3
js21 react/my-react-app/src/pages/Cart.js

@@ -1,17 +1,26 @@
 import React, { useState } from 'react';
 import Title from '../components/Title';
-import store from '../redux/reducers';
-import { clearCart } from '../api/api';
+import { actionOrder, clearCart } from '../api/api';
 import CartGood from '../components/CartGood';
 import { useDispatch, useSelector } from 'react-redux';
 import { Box, Button } from '@mui/material';
-import { Price } from '../components/Price';
+import Price from '../components/Price';
 
 export default function Cart() {
   const goods = useSelector(state => state.cart.goods);
   const totalAmount = useSelector(state => state.cart.totalAmount);
   const dispatch = useDispatch();
   console.log(goods);
+  const createOrder = (orderGoods) => {
+    const orderGoodsDto = [];
+    orderGoods.forEach(orderGood => {
+      orderGoodsDto.push({
+        good: {_id: orderGood.good._id},
+        count: orderGood.count
+      });
+    });
+    dispatch(actionOrder(orderGoodsDto));
+  }
   return (
     <>
       <Title>Cart</Title>
@@ -29,6 +38,7 @@ export default function Cart() {
       {goods?.map((good) =>
         <CartGood good={good} key={good.good._id} />)}
         <Price>{totalAmount}</Price>
+        <Button onClick={() => createOrder(goods)}>Create order</Button>
     </>
   )
 }

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

@@ -1,4 +1,4 @@
-import React, { useEffect } from 'react'
+import React from 'react'
 import { useParams } from 'react-router-dom';
 import Title from '../../components/Title';
 import { CategoriesSection } from '../../components/CategoriesSection/CategoriesSection';
@@ -11,7 +11,6 @@ import { useGetCategoryByIdQuery } from '../../api/api';
 const Category = () => {
   const { categoryId } = useParams();
   const { data, error, isFetching } = useGetCategoryByIdQuery(categoryId);
-
   return (
     <>
       {isFetching ? <Loader /> :

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

@@ -9,8 +9,8 @@ import { useGetGoodByIdQuery } from '../../api/api';
 import { Box } from '@mui/system';
 import Carousel from 'react-material-ui-carousel';
 import { Image } from '../../components/Image/Image';
-import { Price } from '../../components/Price';
-import { Counter } from '../../components/Counter/Counter';
+import Price from '../../components/Price';
+import Counter from '../../components/Counter/Counter';
 import { Button } from '@mui/material';
 import './Good.scss';
 import { addGood, deleteGood } from '../../api/api';
@@ -34,7 +34,7 @@ const Good = () => {
           {data.GoodFindOne?.categories?.length > 0 && <CategoriesSection key={data.GoodFindOne?.categories?._id} categories={data.GoodFindOne?.categories} categoryEl='Category:' />}
           <Box sx={{ display: 'flex', my: '20px', width: '100%' }}>
             <Box sx={{ width: '50%', pr: '30px' }}>
-              <Carousel>
+              <Carousel height="300px">
                 {
                   data.GoodFindOne?.images.map((image, i) => <Image key={i} url={image?.url} />)
                 }

+ 11 - 4
js21 react/my-react-app/src/pages/Login.js

@@ -1,5 +1,5 @@
-import { Button, FormControl, Stack } from '@mui/material';
-import React, { useEffect, useState } from 'react';
+import { Stack } from '@mui/material';
+import React, { useEffect } from 'react';
 import { useDispatch, useSelector } from 'react-redux';
 import { actionFullLogin } from '../api/api';
 import LoginForm from '../components/LoginForm';
@@ -9,22 +9,29 @@ import Title from '../components/Title';
 
 const Login = () => {
   const token = useSelector(state => state.auth.token);
+  const userId = useSelector(state => state.auth?.payload?.sub?.id);
   const error = useSelector(state => state.auth.error);
+  const admin = useSelector(state => state.auth?.payload?.sub?.acl.includes('admin'));
   const dispatch = useDispatch();
   const navigate = useNavigate();
 
   useEffect(() => {
     if (token) {
-      navigate('/');
+      if (admin) {
+        navigate('/admin');
+      } else {
+        navigate(`/user/${userId}`);
+      }
     }
   }, [token]);
+
   const onLogin = (login, password) => {
     dispatch(actionFullLogin({ login, password }));
   }
 
   return (
     <>
-    <Title>Login</Title>
+      <Title>Login</Title>
       <Stack sx={{ maxWidth: '300px', width: '100%', m: '150px auto' }} spacing={2}>
         <LoginForm submit='Login' onSubmit={onLogin} />
         {error && <Alert severity="error">You entered wrong login or password!</Alert>}

+ 0 - 12
js21 react/my-react-app/src/pages/OrdersHistory.js

@@ -1,12 +0,0 @@
-import React from 'react'
-import { useGetOwnerOrderQuery } from '../api/api';
-import Title from '../components/Title';
-
-export default function OrdersHistory() {
-  const { data, error, isFetching } = useGetOwnerOrderQuery();
-  console.log(data?.GetOwnerOrder);
-  return (
-    <Title>My orders</Title>
-    
-  )
-}

+ 30 - 0
js21 react/my-react-app/src/pages/UserPage.js

@@ -0,0 +1,30 @@
+import { Box } from '@mui/material';
+import React, { useEffect, useState } from 'react'
+import { useGetOwnerOrderQuery } from '../api/api';
+import Loader from '../components/Loader';
+import Title from '../components/Title';
+import OrderCard from '../components/OrderCard';
+import { useParams } from 'react-router-dom';
+
+export default function UserPage() {
+  const { categoryId } = useParams();
+  const { data, error, isFetching, refetch } = useGetOwnerOrderQuery(categoryId);
+  const [sortedData, setSortedData] = useState();
+  useEffect(() => {
+    if (data) {
+      refetch();
+      const dataForSort = [...data?.OrderFind];
+      setSortedData(dataForSort?.sort((a, b) => b.createdAt - a.createdAt));
+    }
+  }, [data]);
+
+  return (
+    <>
+      <Title>My orders</Title>
+      {isFetching ? <Loader /> :
+        <Box>
+          {sortedData?.map(order => <OrderCard key={order._id} order={order} />)}
+        </Box>}
+    </>
+  )
+}

+ 14 - 0
js21 react/my-react-app/src/pages/admin/Admin.js

@@ -0,0 +1,14 @@
+import React from 'react';
+import { useGetGoodsQuery, useGetOrderGoodQuery, useGetOwnerOrderQuery, useGetUsersQuery } from '../../api/api';
+
+export default function Admin() {
+  // const {data, error, isFetching} = useGetOwnerOrderQuery();
+    // console.log(data?.OrderFind);
+  // const {data, isFetching} = useGetGoodsQuery();
+    // console.log('goods', data?.GoodFind);
+  // const {data} = useGetOrderGoodQuery();
+  // console.log(data?.OrderGoodFind);
+  return (
+    <div>Admin</div>
+  )
+}

+ 87 - 0
js21 react/my-react-app/src/pages/admin/Categories.js

@@ -0,0 +1,87 @@
+import React, { useEffect, useState } from 'react';
+import { useGetCategoriesQuery } from '../../api/api';
+import Loader from '../../components/Loader';
+import {
+  Button, IconButton, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip, Dialog,
+  DialogActions, DialogContent, DialogContentText
+} from '@mui/material';
+import EditIcon from '@mui/icons-material/Edit';
+import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';
+import LinkButton from '../../components/LinkButton';
+import { Link } from 'react-router-dom';
+
+function Categories() {
+  const { data, error, isFetching, refetch } = useGetCategoriesQuery();
+  const [open, setOpen] = useState([]);
+
+  useEffect(() => {
+    if (data) {
+      refetch();
+      setOpen(new Array(data.CategoryFind.length).fill(false));
+      console.log(open);
+    }
+  }, [data]);
+
+  const handleClickOpen = (event, selectedIndex) => {
+    setOpen(open.map((item, i) => i == selectedIndex));
+  };
+
+  const handleClose = () => {
+    setOpen(false);
+  };
+  console.log(data?.CategoryFind);
+  return (
+    <>
+      {isFetching || data?.CategoryFind?.length == 0 ? <Loader /> :
+        <>
+          <LinkButton to={'/admin/createCategory'} text={'Create category'} />
+          <Paper sx={{ m: 1 }}>
+            <TableContainer>
+              <Table>
+                <TableHead>
+                  <TableRow>
+                    <TableCell>ID</TableCell>
+                    <TableCell>Category</TableCell>
+                    <TableCell>Goods count</TableCell>
+                    <TableCell align='right'></TableCell>
+                    <TableCell align='right'></TableCell>
+                  </TableRow>
+                </TableHead>
+                <TableBody>
+                  {data.CategoryFind?.map((category, i) =>
+                    <TableRow key={category._id}>
+                      <TableCell>{category._id}</TableCell>
+                      <TableCell>{category.name}</TableCell>
+                      <TableCell>{category.goods?.length || 0}</TableCell>
+                      <TableCell align='right'>
+                        <IconButton component={Link} to={'/admin/createCategory'}><EditIcon /></IconButton>
+                      </TableCell>
+                      <TableCell align='right'>
+                        <IconButton onClick={(event) => handleClickOpen(event, i)}><DeleteOutlineOutlinedIcon /></IconButton>
+                        <Dialog
+                          open={open[i]}
+                          onClose={handleClose}
+                        >
+                          <DialogContent>
+                            <DialogContentText>Do you confirm category deletion?</DialogContentText>
+                          </DialogContent>
+                          <DialogActions>
+                            <Button onClick={handleClose}>No</Button>
+                            <Button onClick={handleClose} autoFocus>Yes</Button>
+                          </DialogActions>
+                        </Dialog>
+
+                      </TableCell>
+                    </TableRow>
+                  )}
+                </TableBody>
+              </Table>
+            </TableContainer>
+          </Paper>
+        </>
+      }
+    </>
+  )
+}
+
+export default Categories;

+ 62 - 0
js21 react/my-react-app/src/pages/admin/CreateCategory.js

@@ -0,0 +1,62 @@
+import { Box, Stack, TextField, FormControl, FormControlLabel, Checkbox, Button } from '@mui/material';
+import React, { useEffect, useState } from 'react';
+import { useDispatch } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
+import { createCategory } from '../../api/api';
+import Title from '../../components/Title';
+
+function CreateCategory() {
+  const [name, setName] = useState('');
+  const [subcategoryName, setSubcategoryName] = useState('');
+  const [checked, setChecked] = useState(false);
+  const navigate = useNavigate();
+
+  const handleChange = (event) => {
+    setChecked(event.target.checked);
+  };
+
+  const newCategory = {
+    name,
+    parent: null,
+    subCategories: subcategoryName.length > 0 ? [{
+      name: subcategoryName
+    }] : [],
+  }
+
+  console.log(newCategory);
+
+  const dispatch = useDispatch();
+
+  const handleSubmit = (category) => {
+    dispatch(createCategory(category));
+    navigate('/admin/categories');
+  }
+
+  return (
+    <Box sx={{ maxWidth: '500px', m: '20px auto' }} >
+      <Title>Create category</Title>
+      <Box component='form' onSubmit={handleSubmit}>
+        <Stack spacing={2}>
+          <TextField
+            value={name}
+            onChange={(e) => setName(e.target.value)}
+            label='Category name'
+          />
+          <FormControl>
+            <FormControlLabel value="subCategories" control={<Checkbox checked={checked} onChange={handleChange} />} label="Subcategories" />
+          </FormControl>
+          <TextField
+            disabled={!checked}
+            value={subcategoryName}
+            onChange={(e) => setSubcategoryName(e.target.value)}
+            label='Subcategory name'
+          />
+          <Button onClick={() => handleSubmit(newCategory)}>Add</Button>
+        </Stack>
+      </Box>
+    </Box>
+
+  )
+}
+
+export default CreateCategory;

+ 67 - 0
js21 react/my-react-app/src/pages/admin/Users.js

@@ -0,0 +1,67 @@
+import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow } from '@mui/material';
+import React, { useEffect, useState } from 'react';
+import { useGetUsersQuery } from '../../api/api';
+import Loader from '../../components/Loader';
+import { getDateString } from '../../functions/getDataString';
+
+function Users() {
+  const { data, isFetching } = useGetUsersQuery();
+  const [sortedData, setSortedData] = useState();
+  const [page, setPage] = useState(0);
+  const [rowsPerPage, setRowsPerPage] = useState(10);
+  useEffect(() => {
+    if (data) {
+      console.log(data?.UserFind);
+      const dataForSort = [...data?.UserFind];
+      setSortedData(dataForSort?.sort((a, b) => b.createdAt - a.createdAt));
+    }
+  }, [data]);
+
+  const handleChangePage = (event, newPage) => {
+    setPage(newPage);
+  };
+
+  const handleChangeRowsPerPage = (event) => {
+    setRowsPerPage(parseInt(event.target.value, 10));
+    setPage(0);
+  };
+  return (
+    <>
+      {isFetching ? <Loader /> :
+        <Paper>
+          <TableContainer>
+            <Table>
+              <TableHead>
+                <TableRow>
+                  <TableCell>ID</TableCell>
+                  <TableCell>Login</TableCell>
+                  <TableCell>Created at</TableCell>
+                </TableRow>
+              </TableHead>
+              <TableBody>
+                {sortedData?.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)?.map(user =>
+                  <TableRow key={user._id}>
+                    <TableCell>{user._id}</TableCell>
+                    <TableCell>{user.login}</TableCell>
+                    <TableCell>{getDateString(user.createdAt)}</TableCell>
+                  </TableRow>
+                )}
+              </TableBody>
+            </Table>
+          </TableContainer>
+          <TablePagination
+            rowsPerPageOptions={[10, 20, 30]}
+            component="div"
+            count={sortedData?.length}
+            rowsPerPage={rowsPerPage}
+            page={page}
+            onPageChange={handleChangePage}
+            onRowsPerPageChange={handleChangeRowsPerPage} />
+        </Paper>
+      }
+    </>
+
+  )
+}
+
+export default Users;

+ 0 - 52
js21 react/my-react-app/src/redux/actions/actions.js

@@ -1,52 +0,0 @@
-import { actionPromise } from "../reducers/promiseReducer";
-import { actionAuthLogin } from "../reducers/authReducer";
-import { actionCartClear } from "../reducers/cartReducer";
-
-
-
-
-// export const actionCategories = () =>
-//   actionPromise('categories', gqlGetCategories());
-
-// export const actionCategoryById = (id) =>
-//   actionPromise('category', gqlGetCategory(id));
-
-// export  const actionGoodById = (id) =>
-//   actionPromise('good', gqlGetGood(id));
-
-// export const actionLogin = (login, password) =>
-//   actionPromise('login', gqlLogin(login, password));
-
-// export const actionCreateUser = (login, password) =>
-//   actionPromise('register', gqlCreateUser(login, password));
-
-// export const actionOwnerOrders = () =>
-//   actionPromise('history', gqlGetOwnerOrders());
-
-// export const actionCreateOrder = (orderGoods) =>
-//   actionPromise('cart', gqlCreateOrder(orderGoods));
-
-// export const actionFullLogin = (login, password) =>
-//   async dispatch => {
-//     //dispatch возвращает то, что вернул thunk, возвращаемый actionLogin, а там промис, 
-//     //так как actionPromise возвращает асинхронную функцию
-//     const token = await dispatch(actionLogin(login, password))
-//     //проверьте что token - строка и отдайте его в actionAuthLogin
-//     console.log(token);
-//     if (typeof token === 'string') {
-//       dispatch(actionAuthLogin(token));
-//     }
-//   }
-
-// export const actionFullRegister = (login, password) =>
-//   async dispatch => {
-//     await dispatch(actionCreateUser(login, password));
-//     dispatch(actionFullLogin(login, password));
-//   }
-
-// export const actionOrder = (orderGoods) =>
-//   async dispatch => {
-//     await dispatch(actionCreateOrder(orderGoods));
-//     console.log('order was created');
-//     dispatch(actionCartClear());
-//   }

+ 0 - 18
js21 react/my-react-app/src/redux/reducers/authReducer.js

@@ -1,18 +0,0 @@
-export function authReducer(state = {}, { type, token }) {
-  if (type === 'AUTH_LOGIN') {
-    try {
-      let mediumStr = token.split('.')[1];
-      let result = JSON.parse(atob(mediumStr));
-      return { ...state, 'token': token, 'payload': result };
-    } catch (e) {
-      return {};
-    }
-  }
-  if (type === 'AUTH_LOGOUT') {
-    return {};
-  }
-  return state;
-}
-
-export const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token });
-export const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' });

+ 0 - 38
js21 react/my-react-app/src/redux/reducers/cartReducer.js

@@ -1,38 +0,0 @@
-export function cartReducer(state = {}, { type, good, count }) {
-  let goodKey, oldCount, goodValue;
-  if (good) {
-    goodKey = good['_id'];
-    oldCount = state[goodKey]?.count || 0;
-    goodValue = { good, count: oldCount };
-  }
-  if (type === 'CART_ADD') {
-    goodValue.count += +count;
-    return { ...state, [goodKey]: goodValue };
-  } else if (type === 'CART_SUB') {
-    goodValue.count -= +count;
-    if (goodValue.count <= 0) {
-      delete state[goodKey];
-      return { ...state };
-    }
-    return { ...state, [goodKey]: goodValue };
-  } else if (type === 'CART_DEL') {
-    delete state[goodKey];
-    return { ...state };
-  } else if (type === 'CART_SET') {
-    goodValue.count = +count;
-    if (goodValue.count <= 0) {
-      delete state[goodKey];
-      return { ...state };
-    }
-    return { ...state, [goodKey]: goodValue };
-  } else if (type === 'CART_CLEAR') {
-    return {};
-  }
-  return state;
-}
-
-export const actionCartAdd = (good, count = 1) => ({ type: 'CART_ADD', count, good });
-export const actionCartSub = (good, count = 1) => ({ type: 'CART_SUB', count, good });
-export const actionCartDel = (good) => ({ type: 'CART_DEL', good });
-export const actionCartSet = (good, count = 1) => ({ type: 'CART_SET', count, good });
-export const actionCartClear = () => ({ type: 'CART_CLEAR' });

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

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