Explorar el Código

react-dropzone dependency

viktoriia.kapran hace 1 año
padre
commit
e2b5301876
Se han modificado 32 ficheros con 1146 adiciones y 572 borrados
  1. 59 0
      js21 react/my-react-app/package-lock.json
  2. 1 0
      js21 react/my-react-app/package.json
  3. 19 1
      js21 react/my-react-app/src/App.js
  4. 0 424
      js21 react/my-react-app/src/api/api.js
  5. 3 3
      js21 react/my-react-app/src/components/CartGood.js
  6. 2 2
      js21 react/my-react-app/src/components/CategoriesMenu/CategoryMenu.js
  7. 1 1
      js21 react/my-react-app/src/components/DrawUserName.js
  8. 2 2
      js21 react/my-react-app/src/components/EditCategory.js
  9. 2 2
      js21 react/my-react-app/src/components/GoodCard/GoodCard.js
  10. 17 10
      js21 react/my-react-app/src/components/Header.js
  11. 2 2
      js21 react/my-react-app/src/components/LinkButton.js
  12. 54 9
      js21 react/my-react-app/src/components/LoginForm.js
  13. 31 17
      js21 react/my-react-app/src/pages/CartPage.js
  14. 1 1
      js21 react/my-react-app/src/pages/CategoryPage/CategoryPage.js
  15. 110 0
      js21 react/my-react-app/src/pages/EditProfilePage.js
  16. 4 4
      js21 react/my-react-app/src/pages/GoodPage/GoodPage.js
  17. 6 6
      js21 react/my-react-app/src/pages/LoginPage.js
  18. 5 4
      js21 react/my-react-app/src/pages/RegisterPage.js
  19. 5 3
      js21 react/my-react-app/src/pages/UserPage.js
  20. 1 1
      js21 react/my-react-app/src/pages/admin/AdminPage.js
  21. 100 16
      js21 react/my-react-app/src/pages/admin/CategoriesPage.js
  22. 2 15
      js21 react/my-react-app/src/pages/admin/CreateCategoryPage.js
  23. 95 0
      js21 react/my-react-app/src/pages/admin/CreateGoodPage.js
  24. 1 1
      js21 react/my-react-app/src/pages/admin/EditCategoryPage.js
  25. 9 0
      js21 react/my-react-app/src/pages/admin/EditGoodPage.js
  26. 35 18
      js21 react/my-react-app/src/pages/admin/GoodsPage.js
  27. 17 20
      js21 react/my-react-app/src/pages/admin/OrdersPage.js
  28. 3 7
      js21 react/my-react-app/src/pages/admin/UsersPage.js
  29. 434 0
      js21 react/my-react-app/src/store/api.js
  30. 55 0
      js21 react/my-react-app/src/store/authSlice.js
  31. 65 0
      js21 react/my-react-app/src/store/cartSlice.js
  32. 5 3
      js21 react/my-react-app/src/redux/reducers/index.js

+ 59 - 0
js21 react/my-react-app/package-lock.json

@@ -21,6 +21,7 @@
         "graphql-request": "^3.4.0",
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
+        "react-dropzone": "^14.2.3",
         "react-material-ui-carousel": "^3.4.2",
         "react-redux": "^8.0.5",
         "react-router-dom": "^6.8.2",
@@ -5515,6 +5516,14 @@
         "node": ">= 4.0.0"
       }
     },
+    "node_modules/attr-accept": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+      "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/autoprefixer": {
       "version": "10.4.13",
       "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
@@ -8524,6 +8533,17 @@
         "webpack": "^4.0.0 || ^5.0.0"
       }
     },
+    "node_modules/file-selector": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
+      "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
+      "dependencies": {
+        "tslib": "^2.4.0"
+      },
+      "engines": {
+        "node": ">= 12"
+      }
+    },
     "node_modules/filelist": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
@@ -14998,6 +15018,22 @@
         "react": "^18.2.0"
       }
     },
+    "node_modules/react-dropzone": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
+      "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
+      "dependencies": {
+        "attr-accept": "^2.2.2",
+        "file-selector": "^0.6.0",
+        "prop-types": "^15.8.1"
+      },
+      "engines": {
+        "node": ">= 10.13"
+      },
+      "peerDependencies": {
+        "react": ">= 16.8 || 18.0.0"
+      }
+    },
     "node_modules/react-error-overlay": {
       "version": "6.0.11",
       "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
@@ -21886,6 +21922,11 @@
       "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
       "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="
     },
+    "attr-accept": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+      "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg=="
+    },
     "autoprefixer": {
       "version": "10.4.13",
       "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
@@ -24091,6 +24132,14 @@
         "schema-utils": "^3.0.0"
       }
     },
+    "file-selector": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
+      "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
+      "requires": {
+        "tslib": "^2.4.0"
+      }
+    },
     "filelist": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
@@ -28581,6 +28630,16 @@
         "scheduler": "^0.23.0"
       }
     },
+    "react-dropzone": {
+      "version": "14.2.3",
+      "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
+      "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
+      "requires": {
+        "attr-accept": "^2.2.2",
+        "file-selector": "^0.6.0",
+        "prop-types": "^15.8.1"
+      }
+    },
     "react-error-overlay": {
       "version": "6.0.11",
       "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",

+ 1 - 0
js21 react/my-react-app/package.json

@@ -16,6 +16,7 @@
     "graphql-request": "^3.4.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
+    "react-dropzone": "^14.2.3",
     "react-material-ui-carousel": "^3.4.2",
     "react-redux": "^8.0.5",
     "react-router-dom": "^6.8.2",

+ 19 - 1
js21 react/my-react-app/src/App.js

@@ -11,7 +11,7 @@ import CartPage from './pages/CartPage';
 import CategoryPage from './pages/CategoryPage/CategoryPage';
 import GoodPage from './pages/GoodPage/GoodPage';
 import Layout from './components/Layout';
-import store from './redux/reducers';
+import store from './store';
 import { Box } from '@mui/material';
 import RequireAuth from './components/hoc/RequireAuth';
 import RequireAdmin from './components/hoc/RequireAdmin';
@@ -21,6 +21,9 @@ import CreateCategoryPage from './pages/admin/CreateCategoryPage';
 import EditCategoryPage from './pages/admin/EditCategoryPage';
 import GoodsPage from './pages/admin/GoodsPage';
 import OrdersPage from './pages/admin/OrdersPage';
+import CreateGoodPage from './pages/admin/CreateGoodPage';
+import EditGoodPage from './pages/admin/EditGoodPage';
+import EditProfilePage from './pages/EditProfilePage';
 
 
 function App() {
@@ -35,6 +38,11 @@ function App() {
                 <UserPage />
               </RequireAuth>
             } />
+            <Route path='/user/:userId/edit' element={
+              <RequireAuth>
+                <EditProfilePage />
+              </RequireAuth>
+            } />
             <Route path='/cart' element={<CartPage />} />
             <Route path='/admin' element={
               <RequireAdmin>
@@ -71,6 +79,16 @@ function App() {
                 <GoodsPage />
               </RequireAdmin>
             } />
+            <Route path='/admin/good' element={
+              <RequireAdmin>
+                <CreateGoodPage />
+              </RequireAdmin>
+            } />
+            <Route path='/admin/good/:goodId' element={
+              <RequireAdmin>
+                <EditGoodPage />
+              </RequireAdmin>
+            } />
             <Route path='/register' element={<RegisterPage />} />
             <Route path='/login' element={<LoginPage />} />
             <Route path='/:category/:categoryId' element={<CategoryPage />} />

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

@@ -1,424 +0,0 @@
-import { createApi } from '@reduxjs/toolkit/query/react'
-import { createSlice, current } from "@reduxjs/toolkit";
-import { gql } from 'graphql-request'
-import { graphqlRequestBaseQuery } from '@rtk-query/graphql-request-base-query'; //npm install
-
-
-const API_URL = "http://shop-roles.node.ed.asmer.org.ua/graphql";
-
-const 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 || null;
-  if (token) {
-    headers.set("Authorization", `Bearer ${token}`);
-  }
-  return headers;
-}
-
-export const api = createApi({
-  reducerPath: 'api',
-  baseQuery: graphqlRequestBaseQuery({
-    url: API_URL,
-    prepareHeaders
-  }),
-  tagTypes: ['Category', 'Order', 'Good'],
-  endpoints: (builder) => ({
-    getCategories: builder.query({
-      query: () => ({
-        document: gql`
-                  query GetCategories{
-                      CategoryFind(query: "[{\\"parent\\": null}]") {
-                        _id name goods {
-                          _id name
-                        }, subCategories {
-                          name
-                        }
-                      }
-                    }
-                  `}),
-      providesTags: ['Category'],
-    }),
-    getCategoryById: builder.query({
-      query: (_id) => ({
-        document: gql`
-                  query GetCategory($q: String) {
-                      CategoryFindOne(query: $q) {
-                        _id
-                        name,
-                        goods{
-                          name,
-                          _id,
-                          images{
-                            _id,
-                            url
-                          },
-                          price
-                        },
-                        parent {
-                          _id,
-                          name
-                        },
-                        subCategories{
-                          name,
-                          _id
-                          subCategories{
-                            name,
-                            _id
-                          }
-                        }
-                      }
-                  }
-                  `,
-        variables: { q: JSON.stringify([{ _id }]) }
-      }),
-    }),
-    getGoodById: builder.query({
-      query: (_id) => ({
-        document: gql`
-                  query GetGood($q: String) {
-                    GoodFindOne(query: $q) {
-                      _id,
-                      name,
-                      categories{
-                        _id,
-                        name
-                      },
-                      description,
-                      price,
-                      images{
-                        _id,
-                        url
-                      }
-                    }
-                   }
-                  `,
-        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: ({ limit, skip }) => ({
-        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([{}, { limit: [limit], skip: [skip] }]) }
-      }),
-      providesTags: ['Order'],
-    }),
-    getOrderCount: builder.query({
-      query: () => ({
-        document: gql`
-                  query getOrderCount($q: String) {
-                    OrderCount(query: $q)
-                  }`,
-        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, name
-                      }
-                    }
-                  }`,
-        variables: { q: JSON.stringify([{}]) }
-      }),
-      providesTags: ['Good'],
-    }),
-    getUserCount: builder.query({
-      query: () => ({
-        document: gql`
-                  query GetUserCount($q: String) {
-                    UserCount(query: $q)
-                  }`,
-        variables: { q: JSON.stringify([{}]) }
-      })
-    }),
-    getUsers: builder.query({
-      query: ({ skip, limit }) => ({
-        document: gql`
-                  query GetUsers($q: String) {
-                    UserFind(query: $q) {
-                      _id, login, createdAt
-                    }
-                  }`,
-        variables: { q: JSON.stringify([{}, { skip: [skip], limit: [limit] }]) }
-      })
-    }),
-    login: builder.mutation({
-      query: ({ login, password }) => ({
-        document: gql`
-                  query login($login: String, $password: String) {
-                      login(login: $login, password: $password) 
-                  }
-                  `,
-        variables: { login, password }
-      })
-    }),
-    register: builder.mutation({
-      query: ({ login, password }) => ({
-        document: gql`
-                  mutation registration($login:String, $password: String) {
-                      UserUpsert(user: {login:$login, password: $password}){
-                           _id login createdAt
-                        }
-                  }`,
-        variables: { login, password }
-      })
-    }),
-    createOrder: builder.mutation({
-      query: (orderGoods) => ({
-        document: gql`
-                mutation ordering($orderGoods: OrderInput) {
-                  OrderUpsert(order: $orderGoods) {
-                    _id, total, orderGoods {
-                      good {
-                        name, _id
-                      }
-                    }
-                  }
-                }`,
-        variables: { orderGoods: { orderGoods } }
-      }),
-      invalidatesTags: ['Order'],
-    }),
-    createCategory: builder.mutation({
-      query: (category) => ({
-        document: gql`
-                  mutation createCategory($category: CategoryInput) {
-                    CategoryUpsert(category: $category) {
-                      _id
-                    }
-                  }`,
-        variables: { category }
-      }),
-      invalidatesTags: ['Category'],
-    }),
-    deleteCategory: builder.mutation({
-      query: (category) => ({
-        document: gql`
-                  mutation deleteCategory($category: CategoryInput) {
-                    CategoryDelete(category: $category) {
-                      _id, name, goods {
-                        name, _id
-                      }
-                    }
-                  }`,
-        variables: { category }
-      }),
-      invalidatesTags: ['Category'],
-    }),
-    createGood: builder.mutation({
-      query: (good) => ({
-        document: gql`
-                  mutation createGood($good: GoodInput) {
-                    GoodUpsert(good: $good) {
-                      _id
-                    }
-                  }`,
-        variables: { good }
-      }),
-      invalidatesTags: ['Good']
-    }),
-    deleteGood: builder.mutation({
-      query: (good) => ({
-        document: gql`
-                  mutation deleteGood($good: GoodInput) {
-                    GoodDelete(good: $good) {
-                      _id, name, images {
-                        url, _id
-                      }, price, description
-                    }
-                  }`,
-        variables: { good }
-      }),
-      invalidatesTags: ['Good']
-    }),
-  }),
-});
-
-export const createGood = good =>
-  async dispatch => {
-    await dispatch(api.endpoints.createGood.initiate(good));
-  }
-
-
-
-export const createCategory = category =>
-  async dispatch => {
-    await dispatch(api.endpoints.createCategory.initiate(category));
-  }
-
-export const deleteCategory = category =>
-  async dispatch => {
-    await dispatch(api.endpoints.deleteCategory.initiate(category));
-  }
-
-export const actionOrder = orderGoods =>
-  async dispatch => {
-    await dispatch(api.endpoints.createOrder.initiate(orderGoods));
-    dispatch(cartSlice.actions.clearCart());
-  }
-
-function jwtDecode(token) {
-  try {
-    const tokenArr = token.split(".");
-    const tokenJsonStr = atob(tokenArr[1]);
-    const tokenJson = JSON.parse(tokenJsonStr);
-    return tokenJson;
-  }
-  catch (error) { }
-}
-export const authSlice = createSlice({
-  name: 'auth',
-  initialState: {},
-  reducers: {
-    login(state, { payload }) {
-      const tokenPayload = jwtDecode(payload);
-      if (tokenPayload) {
-        state.token = payload;
-        state.payload = tokenPayload;
-        state.error = false;
-      }
-    },
-    logout() {
-      return {};
-    }
-  },
-  extraReducers: (builder) => {
-    builder.addMatcher(
-      api.endpoints.login.matchFulfilled,
-      (state, { payload }) => {
-        if (!payload.login) {
-          state.error = true;
-        }
-      }
-    )
-  },
-})
-
-export const actionFullLogin = ({ login, password }) =>
-  async (dispatch) => {
-    const payload = await dispatch(api.endpoints.login.initiate({ login, password }))
-    if (payload.data.login)
-      dispatch(authSlice.actions.login(payload.data.login));
-  }
-
-export const actionFullRegister = (login, password) =>
-  async dispatch => {
-    const payload = await dispatch(api.endpoints.register.initiate(login, password));
-    console.log(payload);
-    dispatch(actionFullLogin(login, password));
-  }
-
-const getItemIndex = (state, idToFind) => {
-  const idsArr = state.goods.map(item => item.good._id);
-  return idsArr.indexOf(idToFind);
-}
-const initialState = {
-  goods: [],
-  totalAmount: 0,
-  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,
-  reducers: {
-    addGood(state, action) {
-      const itemIndex = getItemIndex(state, action.payload.good._id);
-      if (itemIndex && itemIndex < 0)
-        state.goods.push(action.payload);
-      else {
-        state.goods[itemIndex].count += +action.payload.count;
-      }
-      calculateTotalAmounts(state, current(state));
-    },
-    decreaseGoodCount(state, action) {
-      const itemIndex = getItemIndex(state, action.payload.good._id);
-      if (state.goods[itemIndex].count > 1) {
-        state.goods[itemIndex].count -= 1;
-      }
-      else {
-        state.goods = state.goods.filter(item => item.good._id !== action.payload.good._id);
-      }
-      calculateTotalAmounts(state, current(state));
-    },
-    setGoodCount(state, action) {
-      const itemIndex = getItemIndex(state, action.payload.good._id);
-      state.goods[itemIndex].count = action.payload?.count;
-      calculateTotalAmounts(state, current(state));
-    },
-    deleteGood(state, action) {
-      state.goods = state.goods.filter(item => item.good._id !== action.payload.good._id);
-      calculateTotalAmounts(state, current(state));
-    },
-    clearCart() {
-      return initialState;
-    }
-
-
-  },
-});
-export const { addGood, deleteGood, decreaseGoodCount, setGoodCount, clearCart } = cartSlice.actions;
-export const { useGetCategoriesQuery, useGetCategoryByIdQuery, useGetGoodByIdQuery, useLoginMutation, useGetOwnerOrderQuery,
-  useRegisterMutation, useGetUserByIdQuery, useCreateCategoryMutation, useGetGoodsQuery, useGetUsersQuery, useGetOrderGoodQuery,
-  useGetUserCountQuery, useGetOrderCountQuery } = api;

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

@@ -8,7 +8,7 @@ import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined
 import IconButton from '@mui/material/IconButton';
 import Price from './Price';
 import Counter from './Counter/Counter';
-import { addGood, deleteGood, decreaseGoodCount, setGoodCount, clearCart } from '../api/api';
+import { addGoodToCart, deleteGoodFromCart, decreaseGoodCount, setGoodCount, clearCart } from '../store/cartSlice';
 import { useDispatch, useSelector } from 'react-redux';
 import { Stack } from '@mui/material';
 import OrderGood from './OrderGood';
@@ -32,12 +32,12 @@ export default function CartGood({ good }) {
           <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}))} />
-            <AddIcon sx={{ cursor: 'pointer' }} onClick={() => dispatch(addGood({ good: good.good, count: 1 }))} />
+            <AddIcon sx={{ cursor: 'pointer' }} onClick={() => dispatch(addGoodToCart({ good: good.good, count: 1 }))} />
           </Box>
           <Box>
             <Price>{totalSum}</Price>
           </Box>
-          <IconButton onClick={() => dispatch(deleteGood({ good: good.good, count }))}>
+          <IconButton onClick={() => dispatch(deleteGoodFromCart({ good: good.good, count }))}>
             <DeleteOutlineOutlinedIcon />
           </IconButton>
         </Stack>

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

@@ -1,7 +1,7 @@
 import React from 'react';
 import './CategoryMenu.scss'
 import Skeleton from '@mui/material/Skeleton';
-import { useGetCategoriesQuery } from '../../api/api';
+import { useGetCategoriesQuery } from '../../store/api';
 import { useSelector } from 'react-redux';
 import ItemList from '../ItemList';
 import List from '@mui/material/List';
@@ -9,7 +9,7 @@ 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', 'Goods', 'Orders'];
+  const adminMenu = ['Categories', 'Goods', 'Orders', 'Users' ];
 
   return (
     <aside className='aside'>

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

@@ -1,7 +1,7 @@
 import React from "react";
 import { useSelector } from 'react-redux';
 import Box from '@mui/material/Box';
-import store from '../redux/reducers';
+import store from '../store';
 
 const DrawUserName = () => {
   const token = useSelector(state => state.auth.token);

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

@@ -2,7 +2,7 @@ import { Box, Button, Checkbox, FormControl, InputLabel, ListItemText, MenuItem,
 import React, { useState } from 'react';
 import { useDispatch } from 'react-redux';
 import { useNavigate } from 'react-router-dom';
-import { createCategory, useGetCategoriesQuery } from '../api/api';
+import { createCategory, useGetCategoriesQuery } from '../store/api';
 
 const ITEM_HEIGHT = 48;
 const ITEM_PADDING_TOP = 8;
@@ -70,7 +70,7 @@ function EditCategory({buttonText, nameCategory, subCategories, categoryId}) {
             ))}
           </Select>
         </FormControl>
-        <Button sx={{ maxWidth: '200px', width: '100%', mx: 'auto' }} onClick={() => handleSubmit(category)}>{buttonText}</Button>
+        <Button variant="contained" sx={{ maxWidth: '200px', width: '100%', mx: 'auto' }} onClick={() => handleSubmit(category)}>{buttonText}</Button>
       </Stack>
     </Box>
   )

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

@@ -9,7 +9,7 @@ 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';
+import { addGoodToCart } from '../../store/cartSlice';
 
 
 
@@ -28,7 +28,7 @@ export default function GoodCard({ good }) {
         <Price>{good?.price}</Price>
         <Counter value={count} onCount={(countValue) => setCount(countValue)} />
         <CardActions>
-          <Button variant="contained" onClick={() => {dispatch(addGood({ good, count: +count }))}}>Add to cart</Button>
+          <Button variant="contained" onClick={() => {dispatch(addGoodToCart({ good, count: +count }))}}>Add to cart</Button>
         </CardActions>
       </CardContent>
     </Card>

+ 17 - 10
js21 react/my-react-app/src/components/Header.js

@@ -3,13 +3,13 @@ import { Link } from 'react-router-dom';
 import Badge from '@mui/material/Badge';
 import { styled } from '@mui/material/styles';
 import IconButton from '@mui/material/IconButton';
-import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import { ShoppingCart, AccountCircle } from '@mui/icons-material/';
 import AppBar from '@mui/material/AppBar';
 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, cartSlice } from '../api/api';
+import { authSlice } from '../store/authSlice';
+import { cartSlice } from '../store/cartSlice';
 import DrawUserName from './DrawUserName';
 import LinkButton from './LinkButton';
 
@@ -29,6 +29,8 @@ export default function Header() {
     dispatch(cartSlice.actions.clearCart());
   }
   const token = useSelector(state => state.auth.token);
+  const userId = useSelector(state => state.auth?.payload?.sub?.id);
+  const admin = useSelector(state => state.auth?.payload?.sub?.acl.includes('admin'));
   return (
     <AppBar position="static">
       <Toolbar>
@@ -39,21 +41,26 @@ export default function Header() {
           <DrawUserName />
           <Box sx={{ mx: 1, my: 2 }}>
             <Link to="cart">
-              <IconButton aria-label="cart">
+              <IconButton>
                 <StyledBadge badgeContent={goodsInCart} color="primary">
-                  <ShoppingCartIcon sx={{ color: 'white', fontSize: '30px' }} />
+                  <ShoppingCart sx={{ color: 'white', fontSize: '30px' }} />
                 </StyledBadge>
               </IconButton>
             </Link>
           </Box>
           {token ?
-            <LinkButton to="/" text="Logout" click={onLogout}/> : <>
-              <LinkButton to="login" text="Login" />
-              <LinkButton to="register" text="Registration" />
+            <>
+              {!admin && <Link to={`/user/${userId}`}>
+                <IconButton><AccountCircle sx={{ color: 'white', fontSize: '30px' }} /></IconButton>
+              </Link>}
+              <LinkButton to="/" click={onLogout}>Logout</LinkButton>
+            </>
+            :
+            <>
+              <LinkButton to="login">Login</LinkButton>
+              <LinkButton to="register">Registration</LinkButton>
             </>
           }
-
-          {/* <button id="logout" className="button">Logout</button> */}
         </Box>
       </Toolbar>
     </AppBar>

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

@@ -2,10 +2,10 @@ import { Box, Button } from '@mui/material';
 import React from 'react';
 import { Link } from 'react-router-dom';
 
-export default function LinkButton({to, text, click} ) {
+export default function LinkButton({to, children, click} ) {
   return (
     <Box sx={{ mx: 1, my: 2 }}>
-              <Button to={to} component={Link} color="inherit" onClick={click}>{text}</Button>
+              <Button to={to} component={Link} color="inherit" onClick={click}>{children}</Button>
             </Box>
   )
 }

+ 54 - 9
js21 react/my-react-app/src/components/LoginForm.js

@@ -4,34 +4,58 @@ import OutlinedInput from '@mui/material/OutlinedInput';
 import IconButton from '@mui/material/IconButton';
 import Visibility from '@mui/icons-material/Visibility';
 import VisibilityOff from '@mui/icons-material/VisibilityOff';
-import { FormControl, Button } from '@mui/material';
+import { FormControl, Button, Box, Stack, Alert } from '@mui/material';
+import { useLocation } from 'react-router-dom';
 
-const LoginForm = ({submit, onSubmit}) => {
+const LoginForm = ({ submit, onSubmit }) => {
+  const location = useLocation();
+  console.log(location);
   const [login, setLogin] = useState('');
   const [password, setPassword] = useState('');
+  const [confirmPassword, setConfirmPassword] = useState('');
   const [showPassword, setShowPassword] = useState(false);
+  const [showConfirmPassword, setShowConfirmPassword] = useState(false);
   const handleClickShowPassword = () => setShowPassword((show) => !show);
+  const handleClickShowConfirmPassword = () => setShowConfirmPassword((show) => !show);
+
+  const handleMouseDownConfirmPassword = (event) => {
+    event.preventDefault();
+  };
+
   const handleMouseDownPassword = (event) => {
     event.preventDefault();
   };
+
+  const formSubmit = () => {
+    if (location.pathname === '/login') {
+      onSubmit(login, password);
+    } else {
+      if (login && password === confirmPassword) {
+        onSubmit(login, password);
+      }
+    }
+
+  }
+
   return (
-    <>
-      <FormControl variant="outlined">
+    <Box component='form' onSubmit={formSubmit}>
+      <Stack>
+      <FormControl variant="outlined" sx={{mb: '20px'}}>
         <OutlinedInput
           placeholder="login"
           type="text"
           value={login}
           onChange={e => setLogin(e.target.value)} />
       </FormControl>
-      <FormControl variant="outlined">
+      <FormControl variant="outlined" sx={{mb: '20px'}}>
         <OutlinedInput
           placeholder="password"
-          type={showPassword ? 'text' : 'password'} value={password}
+          type={showPassword ? 'text' : 'password'}
+          value={password}
           onChange={e => setPassword(e.target.value)}
           endAdornment={
             <InputAdornment position="end">
               <IconButton
-                aria-label="toggle password visibility"
                 onClick={handleClickShowPassword}
                 onMouseDown={handleMouseDownPassword}
                 edge="end"
@@ -41,8 +65,29 @@ const LoginForm = ({submit, onSubmit}) => {
             </InputAdornment>
           } />
       </FormControl>
-      <Button variant="contained" onClick={() => onSubmit(login, password)}>{submit}</Button>
-      </>
+      {location.pathname === '/register' &&
+        <FormControl variant="outlined" sx={{mb: '20px'}}>
+          <OutlinedInput
+            placeholder="Confirm password"
+            type={showConfirmPassword ? 'text' : 'password'}
+            error={password !== confirmPassword}
+            value={confirmPassword}
+            onChange={e => setConfirmPassword(e.target.value)}
+            endAdornment={
+              <InputAdornment position="end">
+                <IconButton
+                  onClick={handleClickShowConfirmPassword}
+                  onMouseDown={handleMouseDownConfirmPassword}
+                  edge="end"
+                >
+                  {showConfirmPassword ? <VisibilityOff /> : <Visibility />}
+                </IconButton>
+              </InputAdornment>
+            } />
+        </FormControl>}
+      <Button variant="contained" onClick={formSubmit}>{submit}</Button>
+      </Stack>
+    </Box>
 
   )
 }

+ 31 - 17
js21 react/my-react-app/src/pages/CartPage.js

@@ -1,6 +1,6 @@
 import React, { useState } from 'react';
 import Title from '../components/Title';
-import { actionOrder, clearCart } from '../api/api';
+import { actionOrder, clearCart } from '../store/cartSlice';
 import CartGood from '../components/CartGood';
 import { useDispatch, useSelector } from 'react-redux';
 import { Box, Button } from '@mui/material';
@@ -9,13 +9,14 @@ import Price from '../components/Price';
 export default function CartPage() {
   const goods = useSelector(state => state.cart.goods);
   const totalAmount = useSelector(state => state.cart.totalAmount);
+  const token = useSelector(state => state.auth.token);
   const dispatch = useDispatch();
   console.log(goods);
   const createOrder = (orderGoods) => {
     const orderGoodsDto = [];
     orderGoods.forEach(orderGood => {
       orderGoodsDto.push({
-        good: {_id: orderGood.good._id},
+        good: { _id: orderGood.good._id },
         count: orderGood.count
       });
     });
@@ -24,21 +25,34 @@ export default function CartPage() {
   return (
     <>
       <Title>Cart</Title>
-      <Box sx={{ display: 'flex', justifyContent: 'flex-end', m: '4px' }}>
-        <Button
-          variant="text"
-          size='small'
-          color='inherit'
-          sx={{ maxWidth: '100px' }}
-          onClick={() => dispatch(clearCart())}
-        >
-          Clear cart
-        </Button>
-      </Box>
-      {goods?.map((good) =>
-        <CartGood good={good} key={good.good._id} />)}
-        <Price>{totalAmount}</Price>
-        <Button onClick={() => createOrder(goods)}>Create order</Button>
+      {goods?.length > 0 ?
+        <>
+          <Box sx={{ display: 'flex', justifyContent: 'flex-end', m: '4px' }}>
+            <Button
+              variant="text"
+              size='small'
+              color='inherit'
+              sx={{ maxWidth: '100px' }}
+              onClick={() => dispatch(clearCart())}
+            >
+              Clear cart
+            </Button>
+          </Box>
+          {goods?.map((good) =>
+            <CartGood good={good} key={good.good._id} />)}
+          <Price>{totalAmount}</Price>
+          <Box sx={{ mt: '20px', textAlign: 'end' }}>
+            {token ? <Button onClick={() => createOrder(goods)}>Create order</Button>
+              :
+              <Box sx={{ fontStyle: 'italic' }}>You need to login/register</Box>}
+          </Box>
+        </>
+        :
+        <Box sx={{textAlign: 'center', m: '100px auto 0', fontSize: '30px', backgroundColor: '#d5d5d5',
+        color: '#fff', p: '50px 30px', maxWidth: '420px', borderRadius: '10px'}}>
+          Cart is empty!
+        </Box>
+      }
     </>
   )
 }

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

@@ -6,7 +6,7 @@ import GoodCard from '../../components/GoodCard/GoodCard';
 import './Category.scss';
 import Loader from '../../components/Loader';
 import { Box } from "@mui/material";
-import { useGetCategoryByIdQuery } from '../../api/api';
+import { useGetCategoryByIdQuery } from '../../store/api';
 
 const CategoryPage = () => {
   const { categoryId } = useParams();

+ 110 - 0
js21 react/my-react-app/src/pages/EditProfilePage.js

@@ -0,0 +1,110 @@
+import { Avatar, Box, Button, FormControl, IconButton, InputAdornment, OutlinedInput } from '@mui/material';
+import { Edit, Visibility, VisibilityOff } from '@mui/icons-material';
+import React, { useState } from 'react';
+import Title from '../components/Title';
+import { Stack } from '@mui/system';
+
+function EditProfilePage() {
+  const [nick, setNick] = useState('');
+  const [password, setPassword] = useState('');
+  const [confirmPassword, setConfirmPassword] = useState('');
+  const [showPassword, setShowPassword] = useState(false);
+  const [showConfirmPassword, setShowConfirmPassword] = useState(false);
+  const handleClickShowPassword = () => setShowPassword((show) => !show);
+  const handleClickShowConfirmPassword = () => setShowConfirmPassword((show) => !show);
+
+  const handleMouseDownConfirmPassword = (event) => {
+    event.preventDefault();
+  };
+
+  const handleMouseDownPassword = (event) => {
+    event.preventDefault();
+  };
+
+  return (
+    <Box>
+      <Title>Edit profile</Title>
+      <Box sx={{ display: 'flex', alignItems: 'center', mb: '40px' }}>
+        <Avatar sx={{ width: '150px', height: '150px' }} />
+        <Button sx={{ ml: '20px' }}><Edit />  Edit avatar</Button>
+      </Box>
+      <Box component='form' sx={{ display: 'flex', alignItems: 'center', mb: '40px' }}>
+        <FormControl variant="outlined">
+          <OutlinedInput
+            placeholder="New nick"
+            type="text"
+            value={nick}
+            onChange={e => setNick(e.target.value)} />
+        </FormControl>
+        <Button sx={{ ml: '20px' }}>Change nick</Button>
+      </Box>
+      <Box component='form' sx={{ display: 'flex', alignItems: 'center' }}>
+        <Stack>
+          <FormControl variant="outlined" sx={{ mb: '20px' }}>
+            <OutlinedInput
+              placeholder="password"
+              type={showPassword ? 'text' : 'password'}
+              value={password}
+              onChange={e => setPassword(e.target.value)}
+              endAdornment={
+                <InputAdornment position="end">
+                  <IconButton
+                    onClick={handleClickShowPassword}
+                    onMouseDown={handleMouseDownPassword}
+                    edge="end"
+                  >
+                    {showPassword ? <VisibilityOff /> : <Visibility />}
+                  </IconButton>
+                </InputAdornment>
+              } />
+          </FormControl>
+          <FormControl variant="outlined" sx={{ mb: '20px' }}>
+            <OutlinedInput
+              placeholder="Confirm password"
+              type={showConfirmPassword ? 'text' : 'password'}
+              error={password !== confirmPassword}
+              value={confirmPassword}
+              onChange={e => setConfirmPassword(e.target.value)}
+              endAdornment={
+                <InputAdornment position="end">
+                  <IconButton
+                    onClick={handleClickShowConfirmPassword}
+                    onMouseDown={handleMouseDownConfirmPassword}
+                    edge="end"
+                  >
+                    {showConfirmPassword ? <VisibilityOff /> : <Visibility />}
+                  </IconButton>
+                </InputAdornment>
+              } />
+          </FormControl>
+        </Stack>
+        <Button sx={{ ml: '20px' }}>Change password</Button>
+      </Box>
+    </Box>
+  )
+}
+
+export default EditProfilePage;
+
+{/* 
+
+const ShowNick = () => {
+    const {data, isFetching} = useGetUserByIdQuery('632205aeb74e1f5f2ec1a320')
+    if (isFetching){
+        return <h1>Loading</h1>
+    }
+    console.log(data)
+    return (
+        <h1>NICK: {data && data.UserFindOne && data.UserFindOne.nick}</h1>
+    )
+}
+    const SetNick = () => {
+    const [nick, setNickState] = useState('')
+    const dispatch = useDispatch()
+    return (
+        <div>
+            <input value={nick} onChange={e => setNickState(e.target.value)}/>
+            <button onClick={() => dispatch(actionSetNick(nick))}>Save</button>
+        </div>
+    )
+} */}

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

@@ -5,7 +5,7 @@ import { useDispatch } from 'react-redux';
 import { CategoriesSection } from '../../components/CategoriesSection/CategoriesSection';
 import Loader from '../../components/Loader';
 import Title from '../../components/Title';
-import { useGetGoodByIdQuery } from '../../api/api';
+import { useGetGoodByIdQuery } from '../../store/api';
 import { Box } from '@mui/system';
 import Carousel from 'react-material-ui-carousel';
 import { Image } from '../../components/Image/Image';
@@ -13,7 +13,7 @@ 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';
+import { addGoodToCart, deleteGoodFromCart } from '../../store/cartSlice';
 
 const GoodPage = () => {
   const { goodId } = useParams();
@@ -21,10 +21,10 @@ const GoodPage = () => {
   const { data, error, isFetching } = useGetGoodByIdQuery(goodId);
   const dispatch = useDispatch();
   const add = () => {
-    dispatch(addGood({good: data.GoodFindOne, count: +count}));
+    dispatch(addGoodToCart({good: data.GoodFindOne, count: +count}));
   }
   const deleteG = () => {
-    dispatch(deleteGood({good: data.GoodFindOne}));
+    dispatch(deleteGoodFromCart({good: data.GoodFindOne}));
   }
   return (
     <>

+ 6 - 6
js21 react/my-react-app/src/pages/LoginPage.js

@@ -1,12 +1,11 @@
-import { Stack } from '@mui/material';
+import { Box, Stack } from '@mui/material';
 import React, { useEffect } from 'react';
 import { useDispatch, useSelector } from 'react-redux';
-import { actionFullLogin } from '../api/api';
+import { actionFullLogin } from '../store/authSlice';
 import LoginForm from '../components/LoginForm';
 import Alert from '@mui/material/Alert';
 import { useNavigate } from 'react-router-dom';
 import Title from '../components/Title';
-
 const LoginPage = () => {
   const token = useSelector(state => state.auth.token);
   const userId = useSelector(state => state.auth?.payload?.sub?.id);
@@ -15,6 +14,7 @@ const LoginPage = () => {
   const dispatch = useDispatch();
   const navigate = useNavigate();
 
+
   useEffect(() => {
     if (token) {
       if (admin) {
@@ -32,10 +32,10 @@ const LoginPage = () => {
   return (
     <>
       <Title>Login</Title>
-      <Stack sx={{ maxWidth: '300px', width: '100%', m: '150px auto' }} spacing={2}>
+      <Box sx={{ maxWidth: '300px', width: '100%', m: '150px auto' }}>
         <LoginForm submit='Login' onSubmit={onLogin} />
-        {error && <Alert severity="error">You entered wrong login or password!</Alert>}
-      </Stack>
+        {error && <Alert sx={{mt: '20px'}} severity="error">You entered wrong login or password!</Alert>}
+      </Box>
     </>
   )
 }

+ 5 - 4
js21 react/my-react-app/src/pages/RegisterPage.js

@@ -1,7 +1,7 @@
-import { Stack } from '@mui/material';
+import { Box, Stack } from '@mui/material';
 import React, { useEffect } from 'react';
 import { useDispatch, useSelector } from 'react-redux';
-import { actionFullRegister } from '../api/api';
+import { actionFullRegister } from '../store/authSlice';
 import LoginForm from '../components/LoginForm';
 import { useNavigate } from 'react-router-dom';
 import Title from '../components/Title';
@@ -11,6 +11,7 @@ export default function RegisterPage() {
   const dispatch = useDispatch();
   const navigate = useNavigate();
 
+  //СДЕЛАТЬ ПРОВЕРКУ ОШИБКИ ЕСЛИ ПОЛЬЗОВАТЕЛЬ СУЩЕСТВУЕТ И ВЫВОДИТЬ СООБЩЕНИЕ, ЕСЛИ КЛИКАЕШЬ НА КНОПКУ ДО ТОГО КАК ЗАПОЛНИЛ ДАННЫЕ
   useEffect(() => {
     if (token) {
       navigate('/');
@@ -24,9 +25,9 @@ export default function RegisterPage() {
   return (
     <>
     <Title>Registration</Title>
-      <Stack sx={{ maxWidth: '300px', width: '100%', m: '150px auto' }} spacing={2}>
+      <Box sx={{ maxWidth: '300px', width: '100%', m: '150px auto' }}>
         <LoginForm submit='Registration' onSubmit={onRegister} />
-      </Stack>
+      </Box>
     </>
   )
 }

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

@@ -1,14 +1,15 @@
 import { Box } from '@mui/material';
 import React, { useEffect, useState } from 'react'
-import { useGetOwnerOrderQuery } from '../api/api';
+import { useGetOrdersByOwnerIdQuery } from '../store/api';
 import Loader from '../components/Loader';
 import Title from '../components/Title';
 import OrderCard from '../components/OrderCard';
 import { useParams } from 'react-router-dom';
+import LinkButton from '../components/LinkButton';
 
 export default function UserPage() {
-  const { categoryId } = useParams();
-  const { data, error, isFetching} = useGetOwnerOrderQuery(categoryId);
+  const { userId } = useParams();
+  const { data, error, isFetching } = useGetOrdersByOwnerIdQuery(userId);
   const [sortedData, setSortedData] = useState();
   useEffect(() => {
     if (data) {
@@ -19,6 +20,7 @@ export default function UserPage() {
 
   return (
     <>
+      <LinkButton to={`/user/${userId}/edit`}>Edit profile</LinkButton>
       <Title>My orders</Title>
       {isFetching ? <Loader /> :
         <Box>

+ 1 - 1
js21 react/my-react-app/src/pages/admin/AdminPage.js

@@ -1,5 +1,5 @@
 import React from 'react';
-import { useGetGoodsQuery, useGetOrderGoodQuery, useGetOwnerOrderQuery, useGetUsersQuery } from '../../api/api';
+import { useGetGoodsQuery, useGetOrderGoodQuery, useGetOwnerOrderQuery, useGetUsersQuery } from '../../store/api';
 
 export default function AdminPage() {
   // const {data, error, isFetching} = useGetOwnerOrderQuery();

+ 100 - 16
js21 react/my-react-app/src/pages/admin/CategoriesPage.js

@@ -1,21 +1,35 @@
 import React, { useState } from 'react';
-import { deleteCategory, useGetCategoriesQuery } from '../../api/api';
+import { deleteCategory, useGetCategoriesQuery, useGetCategoryCountQuery } from '../../store/api';
 import Loader from '../../components/Loader';
 import {
   Button, IconButton, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip, Dialog,
-  DialogActions, DialogContent, DialogContentText
+  DialogActions, DialogContent, DialogContentText, TablePagination, Collapse
 } from '@mui/material';
 import { DeleteOutlineOutlined, Edit, KeyboardArrowDown, KeyboardArrowUp } from '@mui/icons-material';
 import { Link } from 'react-router-dom';
 import LinkButton from '../../components/LinkButton';
 import { useDispatch } from 'react-redux';
+import { Box } from '@mui/system';
+import { Image } from '../../components/Image/Image';
 
 function CategoriesPage() {
+  const { data: categoryCount } = useGetCategoryCountQuery();
   const { data, error, isFetching } = useGetCategoriesQuery();
+  const [page, setPage] = useState(0);
+  const [rowsPerPage, setRowsPerPage] = useState(5);
   const [open, setOpen] = useState(false);
   const [selectedId, setSelectedId] = useState('');
   const dispatch = useDispatch();
 
+  const handleChangePage = (event, newPage) => {
+    setPage(newPage);
+  };
+
+  const handleChangeRowsPerPage = (event) => {
+    setRowsPerPage(parseInt(event.target.value));
+    setPage(0);
+  };
+
   const openModalWindow = (categoryId) => {
     setSelectedId(categoryId);
     setOpen(true);
@@ -27,22 +41,79 @@ function CategoriesPage() {
   };
 
   const onDelitionSubmit = () => {
-    const category = data?.CategoryFind?.find((category => category._id === selectedId));
+    const category = data?.CategoryFind?.find(category => category._id === selectedId);
     dispatch(deleteCategory(category));
     setOpen(false);
   }
 
+  const Row = ({ _id, name, goods, category }) => {
+    const [open, setOpen] = useState(false);
+    return (
+      <>
+        <TableRow>
+          <TableCell>
+            <IconButton
+              size="small"
+              onClick={() => setOpen(!open)}
+            >
+              {open ? <KeyboardArrowUp /> : <KeyboardArrowDown />}
+            </IconButton>
+          </TableCell>
+          <TableCell>{_id}</TableCell>
+          <TableCell>{name}</TableCell>
+          <TableCell>{goods?.length}</TableCell>
+          <TableCell align='right'>
+            <IconButton component={Link} to={`/admin/category/${category._id}`}><Edit /></IconButton>
+          </TableCell>
+          <TableCell align='right'>
+            <IconButton onClick={() => openModalWindow(category._id)}><DeleteOutlineOutlined /></IconButton>
+          </TableCell>
+        </TableRow>
+        <TableRow>
+          <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
+            <Collapse in={open} timeout="auto" unmountOnExit>
+              <Box sx={{ margin: 1 }}>
+                <Table size="small">
+                  <TableHead>
+                    <TableRow>
+                    <TableCell></TableCell>
+                    <TableCell >Good</TableCell>
+                    <TableCell >Price</TableCell>
+                    </TableRow>
+                  </TableHead>
+                  <TableBody>
+                    {goods?.map(good =>
+                      <TableRow key={good?._id}>
+                        <TableCell>
+                          <Box sx={{ height: '50px', width: '50px' }}>
+                            <Image url={good?.images[0]?.url} />
+                          </Box>
+                        </TableCell>
+                        <TableCell >{good?.name}</TableCell>
+                        <TableCell >{good?.price}</TableCell>
+                      </TableRow>)}
+                  </TableBody>
+                </Table>
+              </Box>
+            </Collapse>
+          </TableCell>
+        </TableRow>
+      </>
+    )
+  }
+
   console.log(data?.CategoryFind);
   return (
     <>
       {isFetching ? <Loader /> :
         <>
-          <LinkButton to={'/admin/category'} text={'Create category'} />
+          <LinkButton to={'/admin/category'}>Create category</LinkButton>
           <Paper sx={{ m: 1 }}>
             <TableContainer>
               <Table>
                 <TableHead>
                   <TableRow>
+                    <TableCell></TableCell>
                     <TableCell>ID</TableCell>
                     <TableCell>Category</TableCell>
                     <TableCell>Goods count</TableCell>
@@ -51,18 +122,23 @@ function CategoriesPage() {
                   </TableRow>
                 </TableHead>
                 <TableBody>
-                  {data.CategoryFind?.map(category =>
-                    <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/category/${category._id}`}><Edit /></IconButton>
-                      </TableCell>
-                      <TableCell align='right'>
-                        <IconButton onClick={() => openModalWindow(category._id)}><DeleteOutlineOutlined /></IconButton>
-                      </TableCell>
-                    </TableRow>
+                  {data?.CategoryFind?.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)?.map(category =>
+                    <Row
+                      key={category?._id}
+                      name={category?.name}
+                      goods={category?.goods}
+                      category={category} />
+                    // <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/category/${category._id}`}><Edit /></IconButton>
+                    //   </TableCell>
+                    //   <TableCell align='right'>
+                    //     <IconButton onClick={() => openModalWindow(category._id)}><DeleteOutlineOutlined /></IconButton>
+                    //   </TableCell>
+                    // </TableRow>
                   )}
                   <Dialog
                     open={open}
@@ -79,6 +155,14 @@ function CategoriesPage() {
                 </TableBody>
               </Table>
             </TableContainer>
+            <TablePagination
+              rowsPerPageOptions={[5, 10, 20]}
+              component="div"
+              count={categoryCount?.CategoryCount || 0}
+              rowsPerPage={rowsPerPage}
+              page={page}
+              onPageChange={handleChangePage}
+              onRowsPerPageChange={handleChangeRowsPerPage} />
           </Paper>
         </>
       }

+ 2 - 15
js21 react/my-react-app/src/pages/admin/CreateCategoryPage.js

@@ -1,21 +1,8 @@
-import { Box, Stack, TextField, Button, FormControl, InputLabel, Select, OutlinedInput, MenuItem, ListItemText, Checkbox } from '@mui/material';
-import React, { useState } from 'react';
-import { useDispatch } from 'react-redux';
-import { useNavigate } from 'react-router-dom';
-import { createCategory, useGetCategoriesQuery } from '../../api/api';
+import { Box } from '@mui/material';
+import React from 'react';
 import EditCategory from '../../components/EditCategory';
 import Title from '../../components/Title';
 
-const ITEM_HEIGHT = 48;
-const ITEM_PADDING_TOP = 8;
-const MenuProps = {
-  PaperProps: {
-    style: {
-      maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
-      width: 250,
-    },
-  },
-};
 
 
 function CreateCategoryPage() {

+ 95 - 0
js21 react/my-react-app/src/pages/admin/CreateGoodPage.js

@@ -0,0 +1,95 @@
+import { Box, Button, FormControl, InputLabel, MenuItem, Select, Stack, TextField } from '@mui/material';
+import React, { useState } from 'react';
+import { useDispatch } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
+import { useGetCategoriesQuery } from '../../store/api';
+import Title from '../../components/Title';
+import ImageUploader from '../../components/ImageUploader';
+
+
+const ITEM_HEIGHT = 48;
+const ITEM_PADDING_TOP = 8;
+const MenuProps = {
+  PaperProps: {
+    style: {
+      maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
+      width: 250,
+    },
+  },
+};
+
+function CreateGoodPage() {
+  const { data } = useGetCategoriesQuery();
+  const [goodName, setGoodName] = useState('');
+  const [selectedCategory, setSelectedCategory] = useState('');
+  const [goodPrice, setGoodPrice] = useState(0);
+  const [goodDescription, setGoodDescription] = useState('');
+  const navigate = useNavigate();
+  const dispatch = useDispatch();
+
+
+  const handleCategoryChange = (event) => {
+    setSelectedCategory(event.target.value);
+  };
+  const handlePriceChange = (event) => {
+    setGoodPrice(event.target.value);
+  };
+  const handleDescriptionChange = (event) => {
+    setGoodDescription(event.target.value);
+  }
+  const handleSubmit = () => {
+
+  }
+
+
+  return (
+    <Box sx={{ maxWidth: '450px', m: '20px auto', width: '100%' }} >
+      <Title>Create good</Title>
+      <Box component='form' onSubmit={handleSubmit}>
+        <Stack>
+          <TextField
+            sx={{ m: '0 auto 20px', width: '100%' }}
+            value={goodName}
+            onChange={(e) => setGoodName(e.target.value)}
+            label='Good name'
+          />
+          <FormControl
+            sx={{ m: '0 auto 20px', width: '100%' }}>
+            <InputLabel>Category</InputLabel>
+            <Select
+              value={selectedCategory}
+              onChange={handleCategoryChange}
+              MenuProps={MenuProps}
+            >
+              {data?.CategoryFind?.map((category) => (
+                <MenuItem key={category._id} value={category}>
+                  {category?.name}
+                </MenuItem>
+              ))}
+            </Select>
+          </FormControl>
+          <TextField
+            sx={{ m: '0 auto 20px', width: '100%' }}
+            type='number'
+            label='Price'
+            value={goodPrice}
+            onChange={handlePriceChange}
+          />
+          <TextField
+            sx={{ m: '0 auto 20px', width: '100%' }}
+            label="Description"
+            multiline
+            rows={4}
+            value={goodDescription}
+            onChange={handleDescriptionChange}
+          />
+          <ImageUploader />
+
+          <Button variant="contained" sx={{ maxWidth: '200px', width: '100%', m: '20px auto 0' }} >Add</Button>
+        </Stack>
+      </Box>
+    </Box >
+  )
+}
+
+export default CreateGoodPage;

+ 1 - 1
js21 react/my-react-app/src/pages/admin/EditCategoryPage.js

@@ -1,7 +1,7 @@
 import { Box } from '@mui/system';
 import React from 'react';
 import { useParams } from 'react-router-dom';
-import { useGetCategoryByIdQuery } from '../../api/api';
+import { useGetCategoryByIdQuery } from '../../store/api';
 import EditCategory from '../../components/EditCategory';
 import Loader from '../../components/Loader';
 import Title from '../../components/Title';

+ 9 - 0
js21 react/my-react-app/src/pages/admin/EditGoodPage.js

@@ -0,0 +1,9 @@
+import React from 'react';
+
+function EditGoodPage() {
+  return (
+    <div>EditGoodPage</div>
+  )
+}
+
+export default EditGoodPage;

+ 35 - 18
js21 react/my-react-app/src/pages/admin/GoodsPage.js

@@ -1,17 +1,23 @@
-import { DeleteOutlineOutlined, Edit } from '@mui/icons-material';
-import { Button, Dialog, DialogActions, DialogContent, DialogContentText, IconButton, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow } from '@mui/material';
+import { DeleteOutlineOutlined, Edit, KeyboardArrowUp, KeyboardArrowDown } from '@mui/icons-material';
+import {
+  Button, Dialog, DialogActions, DialogContent, DialogContentText, IconButton, Paper, Table, TableBody, TableCell,
+  TableContainer, TableHead, TablePagination, TableRow
+} from '@mui/material';
+import { Box } from '@mui/system';
 import React, { useState } from 'react';
-import { useDispatch } from 'react-redux';
 import { Link } from 'react-router-dom';
-import { useGetGoodsQuery } from '../../api/api';
+import { deleteGood, useGetGoodCountQuery, useGetGoodsQuery } from '../../store/api';
 import LinkButton from '../../components/LinkButton';
 import Loader from '../../components/Loader';
+import { Image } from '../../components/Image/Image';
+import { useDispatch } from 'react-redux';
 
 function GoodsPage() {
-  const { data, isFetching } = useGetGoodsQuery();
-  const [open, setOpen] = useState(false);
   const [page, setPage] = useState(0);
   const [rowsPerPage, setRowsPerPage] = useState(10);
+  const { data: goodCount } = useGetGoodCountQuery();
+  const { data, isFetching } = useGetGoodsQuery({ skip: page * rowsPerPage, limit: rowsPerPage });
+  const [open, setOpen] = useState(false);
   const [selectedId, setSelectedId] = useState('');
   const dispatch = useDispatch();
 
@@ -20,7 +26,7 @@ function GoodsPage() {
   };
 
   const handleChangeRowsPerPage = (event) => {
-    setRowsPerPage(parseInt(event.target.value, 10));
+    setRowsPerPage(parseInt(event.target.value));
     setPage(0);
   };
 
@@ -33,37 +39,48 @@ function GoodsPage() {
     setSelectedId('');
     setOpen(false);
   };
+
+  const onDeletionSubmit = () => {
+    dispatch(deleteGood({_id: selectedId}));
+    setOpen(false);
+  }
+
   return (
     <>
       {isFetching ? <Loader /> :
         <>
-          <LinkButton to={'/admin/good'} text={'Create good'} />
+          <LinkButton to={'/admin/good'}>Create good</LinkButton>
           <Paper sx={{ m: 1 }}>
             <TableContainer>
               <Table>
                 <TableHead>
                   <TableRow>
-                    <TableCell>ID</TableCell>
+                    <TableCell></TableCell>
                     <TableCell>Good</TableCell>
+                    <TableCell>Price</TableCell>
                     <TableCell>Category</TableCell>
                     <TableCell align='right'></TableCell>
                     <TableCell align='right'></TableCell>
                   </TableRow>
                 </TableHead>
                 <TableBody>
-                  {data.GoodFind?.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)?.map(good =>
+                  {data?.GoodFind?.map(good =>
                     <TableRow key={good?._id}>
-                      <TableCell>{good?._id}</TableCell>
+                      <TableCell>
+                        <Box sx={{ height: '80px', width: '80px' }}>
+                          <Image url={good?.images[0]?.url} />
+                        </Box>
+                      </TableCell>
                       <TableCell>{good?.name}</TableCell>
-                      <TableCell>{good?.categories?.map(category => category?.name)}</TableCell>
+                      <TableCell>{good?.price}</TableCell>
+                      <TableCell>{good?.categories?.map(category => category?.name || 'no category')}</TableCell>
                       <TableCell align='right'>
-                        <IconButton component={Link} to={`/admin/good/${good?._id}`}><Edit /></IconButton>
+                        <IconButton component={Link} to={`/admin/category/${good._id}`}><Edit /></IconButton>
                       </TableCell>
                       <TableCell align='right'>
-                        <IconButton onClick={() => openModalWindow(good?._id)}><DeleteOutlineOutlined /></IconButton>
+                        <IconButton onClick={() => openModalWindow(good._id)}><DeleteOutlineOutlined /></IconButton>
                       </TableCell>
-                    </TableRow>
-                  )}
+                    </TableRow>)}
                   <Dialog
                     open={open}
                     onClose={closeModalWindow}
@@ -72,7 +89,7 @@ function GoodsPage() {
                       <DialogContentText>Do you confirm good deletion?</DialogContentText>
                     </DialogContent>
                     <DialogActions>
-                      <Button onClick={''}>Yes</Button>
+                      <Button onClick={() => onDeletionSubmit()}>Yes</Button>
                       <Button onClick={closeModalWindow}>No</Button>
                     </DialogActions>
                   </Dialog>
@@ -82,7 +99,7 @@ function GoodsPage() {
             <TablePagination
               rowsPerPageOptions={[10, 20, 30]}
               component="div"
-              count={data?.GoodFind?.length}
+              count={goodCount?.GoodCount || 0}
               rowsPerPage={rowsPerPage}
               page={page}
               onPageChange={handleChangePage}

+ 17 - 20
js21 react/my-react-app/src/pages/admin/OrdersPage.js

@@ -1,6 +1,6 @@
 import { Collapse, IconButton, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow } from '@mui/material';
 import React, { useEffect, useState } from 'react';
-import { useGetOrderCountQuery, useGetOwnerOrderQuery } from '../../api/api';
+import { useGetOrderCountQuery, useGetOrdersQuery } from '../../store/api';
 import Loader from '../../components/Loader';
 import { getDateString } from '../../functions/getDataString';
 import { KeyboardArrowDown, KeyboardArrowUp } from '@mui/icons-material';
@@ -10,12 +10,7 @@ function OrdersPage() {
   const [page, setPage] = useState(0);
   const [rowsPerPage, setRowsPerPage] = useState(50);
   const { data: orderCount } = useGetOrderCountQuery();
-  const { data, isFetching, refetch } = useGetOwnerOrderQuery({ skip: page * rowsPerPage, limit: rowsPerPage });
-
-
-  useEffect(() => {
-    refetch();
-  }, [page || rowsPerPage]);
+  const { data, isFetching, } = useGetOrdersQuery({ skip: page * rowsPerPage, limit: rowsPerPage });
 
   const handleChangePage = (event, newPage) => {
     setPage(newPage);
@@ -26,7 +21,7 @@ function OrdersPage() {
     setPage(0);
   };
 
-  const Row = ({id, login, total, createdAt, orderGoods}) => {
+  const Row = ({ id, login, total, createdAt, orderGoods }) => {
     const [open, setOpen] = useState(false);
     return (
       <>
@@ -50,12 +45,14 @@ function OrdersPage() {
               <Box sx={{ margin: 1 }}>
                 <Table size="small">
                   <TableHead>
-                    <TableCell >Good</TableCell>
-                    <TableCell >Price</TableCell>
-                    <TableCell >Count</TableCell>
+                    <TableRow>
+                      <TableCell >Good</TableCell>
+                      <TableCell >Price</TableCell>
+                      <TableCell >Count</TableCell>
+                    </TableRow>
                   </TableHead>
                   <TableBody>
-                    {orderGoods?.map(good => 
+                    {orderGoods?.map(good =>
                       <TableRow key={good?.good?._id}>
                         <TableCell >{good.good?.name}</TableCell>
                         <TableCell >{good?.price}</TableCell>
@@ -89,13 +86,13 @@ function OrdersPage() {
               </TableHead>
               <TableBody>
                 {data?.OrderFind?.map(order =>
-                  <Row 
-                  key={order._id}
-                  id={order?._id}
-                  login={order?.owner?.login}
-                  total={order?.total}
-                  createdAt={order?.createdAt}
-                  orderGoods={order?.orderGoods}/>
+                  <Row
+                    key={order?._id}
+                    id={order?._id}
+                    login={order?.owner?.login}
+                    total={order?.total}
+                    createdAt={order?.createdAt}
+                    orderGoods={order?.orderGoods} />
                 )}
               </TableBody>
             </Table>
@@ -103,7 +100,7 @@ function OrdersPage() {
           <TablePagination
             rowsPerPageOptions={[50, 100, 200]}
             component="div"
-            count={orderCount?.OrderCount}
+            count={orderCount?.OrderCount || 0}
             rowsPerPage={rowsPerPage}
             page={page}
             onPageChange={handleChangePage}

+ 3 - 7
js21 react/my-react-app/src/pages/admin/UsersPage.js

@@ -1,6 +1,6 @@
 import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow } from '@mui/material';
 import React, { useEffect, useState } from 'react';
-import { useGetUserCountQuery, useGetUsersQuery } from '../../api/api';
+import { useGetUserCountQuery, useGetUsersQuery } from '../../store/api';
 import Loader from '../../components/Loader';
 import { getDateString } from '../../functions/getDataString';
 
@@ -8,11 +8,7 @@ function UsersPage() {
   const [page, setPage] = useState(0);
   const [rowsPerPage, setRowsPerPage] = useState(50);
   const { data: userCount } = useGetUserCountQuery();
-  const { data, isFetching, refetch } = useGetUsersQuery({skip: page * rowsPerPage, limit: rowsPerPage});
-  
-  useEffect(() => {
-    refetch();
-  }, [page || rowsPerPage]);
+  const { data, isFetching } = useGetUsersQuery({skip: page * rowsPerPage, limit: rowsPerPage});
 
   const handleChangePage = (event, newPage) => {
     setPage(newPage);
@@ -49,7 +45,7 @@ function UsersPage() {
           <TablePagination
             rowsPerPageOptions={[50, 100, 200]}
             component="div"
-            count={userCount?.UserCount}
+            count={userCount?.UserCount || 0}
             rowsPerPage={rowsPerPage}
             page={page}
             onPageChange={handleChangePage}

+ 434 - 0
js21 react/my-react-app/src/store/api.js

@@ -0,0 +1,434 @@
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
+import { gql } from 'graphql-request';
+import { graphqlRequestBaseQuery } from '@rtk-query/graphql-request-base-query'; //npm install
+
+
+const BASE_URL = "http://shop-roles.node.ed.asmer.org.ua";
+
+const 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 || null;
+  if (token) {
+    headers.set("Authorization", `Bearer ${token}`);
+  }
+  return headers;
+}
+
+
+export const imageApi = createApi({
+  reducerPath: 'imageApi',
+  baseQuery: fetchBaseQuery({
+    baseUrl: BASE_URL,
+    prepareHeaders
+  }),
+  endpoints: (builder) => ({
+    uploadImage: builder.mutation({
+      query: (payload) => ({
+        url: '/upload',
+        method: 'POST',
+        body: payload
+      }),
+    }),
+  }),
+});
+
+
+export const api = createApi({
+  reducerPath: 'api',
+  baseQuery: graphqlRequestBaseQuery({
+    url: BASE_URL + '/graphql',
+    prepareHeaders
+  }),
+  tagTypes: ['Category', 'Order', 'Good', 'User'],
+  endpoints: (builder) => ({
+    getCategories: builder.query({
+      query: () => ({
+        document: gql`
+                  query GetCategories{
+                      CategoryFind(query: "[{\\"parent\\": null}]") {
+                        _id name goods {
+                          _id name price images {
+                            url
+                          }
+                        }, subCategories {
+                          name
+                        }
+                      }
+                    }
+                  `}),
+      providesTags: ['Category'],
+    }),
+    getCategoryCount: builder.query({
+      query: () => ({
+        document: gql`
+                  query getCategoryCount($q: String) {
+                    CategoryCount(query: $q)
+                  }`,
+        variables: { q: JSON.stringify([{}]) }
+      }),
+      providesTags: ['Category'],
+    }),
+    getCategoryById: builder.query({
+      query: (_id) => ({
+        document: gql`
+                  query GetCategory($q: String) {
+                      CategoryFindOne(query: $q) {
+                        _id
+                        name,
+                        goods{
+                          name,
+                          _id,
+                          images{
+                            _id,
+                            url
+                          },
+                          price
+                        },
+                        parent {
+                          _id,
+                          name
+                        },
+                        subCategories{
+                          name,
+                          _id
+                          subCategories{
+                            name,
+                            _id
+                          }
+                        }
+                      }
+                  }
+                  `,
+        variables: { q: JSON.stringify([{ _id }]) }
+      }),
+      providesTags: ['Category'],
+    }),
+    getGoodById: builder.query({
+      query: (_id) => ({
+        document: gql`
+                  query GetGood($q: String) {
+                    GoodFindOne(query: $q) {
+                      _id,
+                      name,
+                      categories{
+                        _id,
+                        name
+                      },
+                      description,
+                      price,
+                      images{
+                        _id,
+                        url
+                      }
+                    }
+                   }
+                  `,
+        variables: { q: JSON.stringify([{ _id }]) }
+      }),
+      providesTags: ['Good'],
+    }),
+    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: ['User']
+    }),
+    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: ['User']
+    }),
+    setPassword: builder.mutation({
+      query: ({ _id, password }) => ({
+        document: gql`
+                  mutation SetPassword($_id:String, $password: String) {
+                    UserUpsert(user: {_id: $_id, password: $password}) {
+                      _id
+                    }
+                  }`,
+        variables: { _id, password }
+      }),
+      invalidatesTags: ['User']
+    }),
+    getOrdersByOwnerId: 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([{}]) }
+      }),
+      providesTags: ['Order'],
+    }),
+    getOrders: builder.query({
+      query: ({ limit, skip }) => ({
+        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([{}, { limit: [limit], skip: [skip] }]) }
+      }),
+      providesTags: ['Order'],
+    }),
+    getOrderCount: builder.query({
+      query: () => ({
+        document: gql`
+                    query getOrderCount($q: String) {
+                      OrderCount(query: $q)
+                    }`,
+        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([{}]) }
+      }),
+      providesTags: ['Order'],
+    }),
+    getGoods: builder.query({
+      query: ({ skip, limit }) => ({
+        document: gql`
+                    query GetGoods($q: String) {
+                      GoodFind(query: $q) {
+                        _id, name, description, price, categories {
+                          _id, name
+                        }, images {
+                          url
+                        }
+                      }
+                    }`,
+        variables: { q: JSON.stringify([{}, { skip: [skip], limit: [limit] }]) }
+      }),
+      providesTags: ['Good'],
+    }),
+    getGoodCount: builder.query({
+      query: () => ({
+        document: gql`
+                    query GetGoodCount($q: String) {
+                      GoodCount(query: $q)
+                    }`,
+        variables: { q: JSON.stringify([{}]) }
+      }),
+      providesTags: ['Good'],
+    }),
+    getUserCount: builder.query({
+      query: () => ({
+        document: gql`
+                    query GetUserCount($q: String) {
+                      UserCount(query: $q)
+                    }`,
+        variables: { q: JSON.stringify([{}]) }
+      })
+    }),
+    getUsers: builder.query({
+      query: ({ skip, limit }) => ({
+        document: gql`
+                    query GetUsers($q: String) {
+                      UserFind(query: $q) {
+                        _id, login, createdAt
+                      }
+                    }`,
+        variables: { q: JSON.stringify([{}, { skip: [skip], limit: [limit] }]) }
+      }),
+      providesTags: ['User'],
+    }),
+    login: builder.mutation({
+      query: ({ login, password }) => ({
+        document: gql`
+                    query login($login: String, $password: String) {
+                        login(login: $login, password: $password) 
+                    }
+                    `,
+        variables: { login, password }
+      })
+    }),
+    register: builder.mutation({
+      query: ({ login, password }) => ({
+        document: gql`
+                    mutation registration($login:String, $password: String) {
+                        UserUpsert(user: {login:$login, password: $password}){
+                            _id login createdAt
+                          }
+                    }`,
+        variables: { login, password }
+      }),
+      invalidatesTags: ['User'],
+    }),
+    createOrder: builder.mutation({
+      query: (orderGoods) => ({
+        document: gql`
+                  mutation ordering($orderGoods: OrderInput) {
+                    OrderUpsert(order: $orderGoods) {
+                      _id, total, orderGoods {
+                        good {
+                          name, _id
+                        }
+                      }
+                    }
+                  }`,
+        variables: { orderGoods: { orderGoods } }
+      }),
+      invalidatesTags: ['Order'],
+    }),
+    createCategory: builder.mutation({
+      query: (category) => ({
+        document: gql`
+                    mutation createCategory($category: CategoryInput) {
+                      CategoryUpsert(category: $category) {
+                        _id
+                      }
+                    }`,
+        variables: { category }
+      }),
+      invalidatesTags: ['Category'],
+    }),
+    deleteCategory: builder.mutation({
+      query: (category) => ({
+        document: gql`
+                    mutation deleteCategory($category: CategoryInput) {
+                      CategoryDelete(category: $category) {
+                        _id, name, goods {
+                          name, _id
+                        }
+                      }
+                    }`,
+        variables: { category }
+      }),
+      invalidatesTags: ['Category'],
+    }),
+    createGood: builder.mutation({
+      query: (good) => ({
+        document: gql`
+                    mutation createGood($good: GoodInput) {
+                      GoodUpsert(good: $good) {
+                        _id
+                      }
+                    }`,
+        variables: { good }
+      }),
+      invalidatesTags: ['Good']
+    }),
+    deleteGood: builder.mutation({
+      query: (good) => ({
+        document: gql`
+                    mutation deleteGood($good: GoodInput) {
+                      GoodDelete(good: $good) {
+                        _id, name, images {
+                          url, _id
+                        }, price, description
+                      }
+                    }`,
+        variables: { good }
+      }),
+      invalidatesTags: ['Good']
+    }),
+  }),
+});
+
+export const setNick = (nick) =>
+  async (dispatch, getState) => {
+    const auth = getState().auth;
+    if (auth.token) {
+      dispatch(api.endpoints.setNick.initiate({ _id: auth.payload.sub.id, nick }));
+    }
+  }
+
+export const setPassword = (password) =>
+  async (dispatch, getState) => {
+    const auth = getState().auth;
+    if (auth.token) {
+      dispatch(api.endpoints.setPassword.initiate({ _id: auth.payload.sub.id, password }));
+    }
+  }
+
+export const aboutMe = () =>
+  async (dispatch, getState) => {
+    const auth = getState().auth;
+    if (auth.token) {
+      dispatch(api.endpoints.getUserById.initiate(auth.payload.sub.id));
+    }
+  }
+
+export const createGood = good =>
+  async dispatch =>
+    await dispatch(api.endpoints.createGood.initiate(good));
+
+
+export const deleteGood = good =>
+  async dispatch => {
+    const payload = await dispatch(api.endpoints.deleteGood.initiate(good));
+    console.log('deleted', payload);
+  }
+
+export const createCategory = category =>
+  async dispatch =>
+    await dispatch(api.endpoints.createCategory.initiate(category));
+
+
+export const deleteCategory = category =>
+  async dispatch =>
+    await dispatch(api.endpoints.deleteCategory.initiate(category));
+
+export const { useGetCategoriesQuery, useGetCategoryByIdQuery, useGetGoodByIdQuery, useLoginMutation, useGetOrdersByOwnerIdQuery,
+  useRegisterMutation, useGetUserByIdQuery, useCreateCategoryMutation, useGetGoodsQuery, useGetUsersQuery, useGetOrderGoodQuery,
+  useGetUserCountQuery, useGetOrderCountQuery, useGetGoodCountQuery, useGetCategoryCountQuery, useGetOrdersQuery } = api;
+
+export const { useUploadImageMutation } = imageApi;

+ 55 - 0
js21 react/my-react-app/src/store/authSlice.js

@@ -0,0 +1,55 @@
+import { createSlice } from "@reduxjs/toolkit";
+import { api } from "./api";
+
+export const authSlice = createSlice({
+  name: 'auth',
+  initialState: {},
+  reducers: {
+    login(state, { payload }) {
+      const tokenPayload = jwtDecode(payload);
+      if (tokenPayload) {
+        state.token = payload;
+        state.payload = tokenPayload;
+        state.error = false;
+      }
+    },
+    logout() {
+      return {};
+    }
+  },
+  extraReducers: (builder) => {
+    builder.addMatcher(
+      api.endpoints.login.matchFulfilled,
+      (state, { payload }) => {
+        if (!payload.login) {
+          state.error = true;
+        }
+      }
+    )
+  },
+});
+
+function jwtDecode(token) {
+  try {
+    const tokenArr = token.split(".");
+    const tokenJsonStr = atob(tokenArr[1]);
+    const tokenJson = JSON.parse(tokenJsonStr);
+    return tokenJson;
+  }
+  catch (error) { }
+}
+
+
+export const actionFullLogin = ({ login, password }) =>
+  async (dispatch) => {
+    const payload = await dispatch(api.endpoints.login.initiate({ login, password }))
+    if (payload.data.login)
+      dispatch(authSlice.actions.login(payload.data.login));
+  }
+
+export const actionFullRegister = (login, password) =>
+  async dispatch => {
+    const payload = await dispatch(api.endpoints.register.initiate(login, password));
+    console.log(payload);
+    dispatch(actionFullLogin(login, password));
+  }

+ 65 - 0
js21 react/my-react-app/src/store/cartSlice.js

@@ -0,0 +1,65 @@
+import { createSlice, current } from "@reduxjs/toolkit";
+import { api } from "./api";
+
+
+export const actionOrder = orderGoods =>
+  async dispatch => {
+    await dispatch(api.endpoints.createOrder.initiate(orderGoods));
+    dispatch(cartSlice.actions.clearCart());
+  }
+
+const getItemIndex = (state, idToFind) => {
+  const idsArr = state.goods.map(item => item.good._id);
+  return idsArr.indexOf(idToFind);
+}
+const initialState = {
+  goods: [],
+  totalAmount: 0,
+  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,
+  reducers: {
+    addGoodToCart(state, action) {
+      const itemIndex = getItemIndex(state, action.payload.good._id);
+      if (itemIndex && itemIndex < 0)
+        state.goods.push(action.payload);
+      else {
+        state.goods[itemIndex].count += +action.payload.count;
+      }
+      calculateTotalAmounts(state, current(state));
+    },
+    decreaseGoodCount(state, action) {
+      const itemIndex = getItemIndex(state, action.payload.good._id);
+      if (state.goods[itemIndex].count > 1) {
+        state.goods[itemIndex].count -= 1;
+      }
+      else {
+        state.goods = state.goods.filter(item => item.good._id !== action.payload.good._id);
+      }
+      calculateTotalAmounts(state, current(state));
+    },
+    setGoodCount(state, action) {
+      const itemIndex = getItemIndex(state, action.payload.good._id);
+      state.goods[itemIndex].count = action.payload?.count;
+      calculateTotalAmounts(state, current(state));
+    },
+    deleteGoodFromCart(state, action) {
+      state.goods = state.goods.filter(item => item.good._id !== action.payload.good._id);
+      calculateTotalAmounts(state, current(state));
+    },
+    clearCart() {
+      return initialState;
+    }
+
+
+  },
+});
+export const { addGoodToCart, deleteGoodFromCart, decreaseGoodCount, setGoodCount, clearCart } = cartSlice.actions;

+ 5 - 3
js21 react/my-react-app/src/redux/reducers/index.js

@@ -1,7 +1,8 @@
 import { configureStore } from '@reduxjs/toolkit';
-import { api, authSlice, cartSlice } from '../../api/api';
+import { api, imageApi } from './api';
+import { authSlice } from '../store/authSlice';
+import { cartSlice } from '../store/cartSlice';
 import storage from 'redux-persist/lib/storage';
-import { combineReducers } from 'redux';
 
 import {
   persistReducer, persistCombineReducers, persistStore, FLUSH,
@@ -22,6 +23,7 @@ const persistedReducer = persistCombineReducers(
   persistConfig,
   {
     [api.reducerPath]: api.reducer,
+    [imageApi.reducerPath]: imageApi.reducer,
     [authSlice.name]: authSlice.reducer,
     [cartSlice.name]: cartSlice.reducer,
   });
@@ -31,7 +33,7 @@ export const store = configureStore({
   reducer: persistedReducer,//это combineReducers
   middleware: (getDefaultMiddleware) =>
     [...getDefaultMiddleware({ serializableCheck: { ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER] } }),
-    api.middleware],
+    api.middleware, imageApi.middleware],
 });
 
 const persistor = persistStore(store);