2 کامیت‌ها 25a08d57d2 ... e88aa47a15

نویسنده SHA1 پیام تاریخ
  viktoriia.kapran e88aa47a15 change thunk to useMutation 1 سال پیش
  viktoriia.kapran c1ea793b0e dnd done 1 سال پیش

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

@@ -8,6 +8,9 @@
       "name": "my-react-app",
       "version": "0.1.0",
       "dependencies": {
+        "@dnd-kit/core": "^6.0.8",
+        "@dnd-kit/sortable": "^7.0.2",
+        "@dnd-kit/utilities": "^3.2.1",
         "@emotion/react": "^11.10.6",
         "@emotion/styled": "^11.10.6",
         "@mui/icons-material": "^5.11.11",
@@ -17,6 +20,7 @@
         "@testing-library/jest-dom": "^5.16.5",
         "@testing-library/react": "^13.4.0",
         "@testing-library/user-event": "^13.5.0",
+        "array-move": "^4.0.0",
         "graphql": "^15.5.0",
         "graphql-request": "^3.4.0",
         "react": "^18.2.0",
@@ -2146,6 +2150,55 @@
         "postcss-selector-parser": "^6.0.10"
       }
     },
+    "node_modules/@dnd-kit/accessibility": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.0.1.tgz",
+      "integrity": "sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==",
+      "dependencies": {
+        "tslib": "^2.0.0"
+      },
+      "peerDependencies": {
+        "react": ">=16.8.0"
+      }
+    },
+    "node_modules/@dnd-kit/core": {
+      "version": "6.0.8",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.0.8.tgz",
+      "integrity": "sha512-lYaoP8yHTQSLlZe6Rr9qogouGUz9oRUj4AHhDQGQzq/hqaJRpFo65X+JKsdHf8oUFBzx5A+SJPUvxAwTF2OabA==",
+      "dependencies": {
+        "@dnd-kit/accessibility": "^3.0.0",
+        "@dnd-kit/utilities": "^3.2.1",
+        "tslib": "^2.0.0"
+      },
+      "peerDependencies": {
+        "react": ">=16.8.0",
+        "react-dom": ">=16.8.0"
+      }
+    },
+    "node_modules/@dnd-kit/sortable": {
+      "version": "7.0.2",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-7.0.2.tgz",
+      "integrity": "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==",
+      "dependencies": {
+        "@dnd-kit/utilities": "^3.2.0",
+        "tslib": "^2.0.0"
+      },
+      "peerDependencies": {
+        "@dnd-kit/core": "^6.0.7",
+        "react": ">=16.8.0"
+      }
+    },
+    "node_modules/@dnd-kit/utilities": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.1.tgz",
+      "integrity": "sha512-OOXqISfvBw/1REtkSK2N3Fi2EQiLMlWUlqnOK/UpOISqBZPWpE6TqL+jcPtMOkE8TqYGiURvRdPSI9hltNUjEA==",
+      "dependencies": {
+        "tslib": "^2.0.0"
+      },
+      "peerDependencies": {
+        "react": ">=16.8.0"
+      }
+    },
     "node_modules/@emotion/babel-plugin": {
       "version": "11.10.6",
       "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz",
@@ -5416,6 +5469,17 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/array-move": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/array-move/-/array-move-4.0.0.tgz",
+      "integrity": "sha512-+RY54S8OuVvg94THpneQvFRmqWdAHeqtMzgMW6JNurHxe8rsS07cHQdfGkXnTUXiBcyZ0j3SiDIxxj0RPiqCkQ==",
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/array-union": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -19466,6 +19530,41 @@
       "integrity": "sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==",
       "requires": {}
     },
+    "@dnd-kit/accessibility": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.0.1.tgz",
+      "integrity": "sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==",
+      "requires": {
+        "tslib": "^2.0.0"
+      }
+    },
+    "@dnd-kit/core": {
+      "version": "6.0.8",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.0.8.tgz",
+      "integrity": "sha512-lYaoP8yHTQSLlZe6Rr9qogouGUz9oRUj4AHhDQGQzq/hqaJRpFo65X+JKsdHf8oUFBzx5A+SJPUvxAwTF2OabA==",
+      "requires": {
+        "@dnd-kit/accessibility": "^3.0.0",
+        "@dnd-kit/utilities": "^3.2.1",
+        "tslib": "^2.0.0"
+      }
+    },
+    "@dnd-kit/sortable": {
+      "version": "7.0.2",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-7.0.2.tgz",
+      "integrity": "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==",
+      "requires": {
+        "@dnd-kit/utilities": "^3.2.0",
+        "tslib": "^2.0.0"
+      }
+    },
+    "@dnd-kit/utilities": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.1.tgz",
+      "integrity": "sha512-OOXqISfvBw/1REtkSK2N3Fi2EQiLMlWUlqnOK/UpOISqBZPWpE6TqL+jcPtMOkE8TqYGiURvRdPSI9hltNUjEA==",
+      "requires": {
+        "tslib": "^2.0.0"
+      }
+    },
     "@emotion/babel-plugin": {
       "version": "11.10.6",
       "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz",
@@ -21846,6 +21945,11 @@
         "is-string": "^1.0.7"
       }
     },
+    "array-move": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/array-move/-/array-move-4.0.0.tgz",
+      "integrity": "sha512-+RY54S8OuVvg94THpneQvFRmqWdAHeqtMzgMW6JNurHxe8rsS07cHQdfGkXnTUXiBcyZ0j3SiDIxxj0RPiqCkQ=="
+    },
     "array-union": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",

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

@@ -3,6 +3,9 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "@dnd-kit/core": "^6.0.8",
+    "@dnd-kit/sortable": "^7.0.2",
+    "@dnd-kit/utilities": "^3.2.1",
     "@emotion/react": "^11.10.6",
     "@emotion/styled": "^11.10.6",
     "@mui/icons-material": "^5.11.11",
@@ -12,6 +15,7 @@
     "@testing-library/jest-dom": "^5.16.5",
     "@testing-library/react": "^13.4.0",
     "@testing-library/user-event": "^13.5.0",
+    "array-move": "^4.0.0",
     "graphql": "^15.5.0",
     "graphql-request": "^3.4.0",
     "react": "^18.2.0",

+ 119 - 0
js21 react/my-react-app/src/components/Dnd.js

@@ -0,0 +1,119 @@
+import React, { useEffect, useState } from 'react';
+
+import {
+  DndContext,
+  KeyboardSensor,
+  PointerSensor,
+  useSensor,
+  useSensors, useDroppable
+} from "@dnd-kit/core";
+import { sortableKeyboardCoordinates, rectSortingStrategy, SortableContext, useSortable, horizontalListSortingStrategy } from "@dnd-kit/sortable";
+import { CSS } from "@dnd-kit/utilities";
+import { arrayMoveImmutable } from 'array-move';
+
+
+
+const SortableItem = (props) => {
+  const {
+    attributes,
+    listeners,
+    setNodeRef,
+    transform,
+    transition
+  } = useSortable({ id: props.id });
+
+  const itemStyle = {
+    transform: CSS.Transform.toString(transform),
+    transition,
+    //width: 110,
+    //height: 30,
+    //display: "flex",
+    //alignItems: "center",
+    //paddingLeft: 5,
+    //border: "1px solid gray",
+    //borderRadius: 5,
+    //marginBottom: 5,
+    //userSelect: "none",
+    cursor: "grab",
+    //boxSizing: "border-box"
+  };
+
+  const Render = props.render
+
+  return (
+    <div style={itemStyle} ref={setNodeRef} {...attributes} {...listeners}>
+      <Render {...{ [props.itemProp]: props.item }} />
+    </div>
+  );
+};
+
+
+const Droppable = ({ id, items, itemProp, keyField, render }) => {
+  const { setNodeRef } = useDroppable({ id });
+
+  const droppableStyle = {
+    //padding: "20px 10px",
+    //border: "1px solid black",
+    //borderRadius: "5px",
+    //minWidth: 110
+  };
+
+  return (
+    <SortableContext id={id} items={items} strategy={rectSortingStrategy}>
+      {items.map((item) => (
+        <SortableItem render={render} key={item[keyField]} id={item}
+          itemProp={itemProp} item={item} />
+      ))}
+    </SortableContext>
+  );
+};
+
+
+function Dnd({ items: startItems, render, itemProp, keyField, onChange, horizontal }) {
+  const [items, setItems] = useState(
+    startItems
+  );
+  useEffect(() => setItems(startItems), [startItems])
+
+  useEffect(() => {
+    if (typeof onChange === 'function') {
+      onChange(items)
+    }
+  }, [items])
+
+  const sensors = useSensors(
+    useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
+    useSensor(KeyboardSensor, {
+      coordinateGetter: sortableKeyboardCoordinates
+    })
+  );
+
+  const handleDragEnd = ({ active, over }) => {
+    const activeIndex = active.data.current.sortable.index;
+    const overIndex = over.data.current?.sortable.index || 0;
+
+    setItems((items) => {
+      return arrayMoveImmutable(items, activeIndex, overIndex)
+    });
+  }
+
+  const containerStyle = { display: horizontal ? "flex" : '' };
+
+  return (
+    <DndContext
+      sensors={sensors}
+      onDragEnd={handleDragEnd}
+    >
+      <div style={containerStyle}>
+        <Droppable id="aaa"
+          items={items}
+          itemProp={itemProp}
+          keyField={keyField}
+          render={render} />
+      </div>
+    </DndContext>
+  );
+}
+
+
+export default Dnd;

+ 18 - 14
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 '../store/api';
+import { useCreateCategoryMutation, useGetCategoriesQuery } from '../store/api';
 
 const ITEM_HEIGHT = 48;
 const ITEM_PADDING_TOP = 8;
@@ -15,10 +15,11 @@ const MenuProps = {
   },
 };
 
-function EditCategory({buttonText, nameCategory, subCategories, categoryId}) {
+function EditCategory({ buttonText, nameCategory, subCategories, categoryId }) {
   const { data } = useGetCategoriesQuery();
   const [name, setName] = useState(nameCategory || '');
   const [selectedCategories, setSelectedCategories] = useState(subCategories || []);
+  const [createCategory, result] = useCreateCategoryMutation();
 
   const navigate = useNavigate();
 
@@ -28,18 +29,21 @@ function EditCategory({buttonText, nameCategory, subCategories, categoryId}) {
     setSelectedCategories([...event.target.value]);
   };
 
-  const category = {
-    name,
-    parent: null,
-    subCategories: selectedCategories,
-  }
-  if (categoryId) {
-    category._id = categoryId;
-  }
 
-  const handleSubmit = (category) => {
-    dispatch(createCategory(category));
-    navigate('/admin/categories');
+
+  const handleSubmit = () => {
+    const category = {
+      name,
+      parent: null,
+      subCategories: selectedCategories,
+    }
+    if (categoryId) {
+      category._id = categoryId;
+    }
+    createCategory(category).then(response => {
+      navigate('/admin/categories');
+    });
+
   }
 
   return (
@@ -70,7 +74,7 @@ function EditCategory({buttonText, nameCategory, subCategories, categoryId}) {
             ))}
           </Select>
         </FormControl>
-        <Button variant="contained" sx={{ maxWidth: '200px', width: '100%', mx: 'auto' }} onClick={() => handleSubmit(category)}>{buttonText}</Button>
+        <Button variant="contained" sx={{ maxWidth: '200px', width: '100%', mx: 'auto' }} onClick={() => handleSubmit()}>{buttonText}</Button>
       </Stack>
     </Box>
   )

+ 110 - 0
js21 react/my-react-app/src/components/EditGood.js

@@ -0,0 +1,110 @@
+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 ImageUploader from '../components/ImageUploader';
+import { useCreateGoodMutation, useGetCategoriesQuery } from '../store/api';
+
+
+const ITEM_HEIGHT = 48;
+const ITEM_PADDING_TOP = 8;
+const MenuProps = {
+  PaperProps: {
+    style: {
+      maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
+      width: 250,
+    },
+  },
+};
+
+function EditGood({ name, category, price, description, goodImages, goodId, buttonText }) {
+  const { data } = useGetCategoriesQuery();
+  const [goodName, setGoodName] = useState(name || '');
+  const [selectedCategory, setSelectedCategory] = useState(category || '');
+  console.log(category)
+  const [goodPrice, setGoodPrice] = useState(price || 0);
+  const [goodDescription, setGoodDescription] = useState(description || '');
+  const [images, setImages] = useState(goodImages || []);
+  console.log(images);
+  const navigate = useNavigate();
+  const [createGood, result] = useCreateGoodMutation();
+
+
+  const handleCategoryChange = (event) => {
+    setSelectedCategory(event.target.value);
+  };
+  const handlePriceChange = (event) => {
+    setGoodPrice(event.target.value);
+  };
+  const handleDescriptionChange = (event) => {
+    setGoodDescription(event.target.value);
+  }
+
+
+  const handleSubmit = () => {
+
+    const good = {
+      name: goodName,
+      categories: [selectedCategory],
+      price: +goodPrice,
+      description: goodDescription,
+      images: images.map(image => ({ _id: image._id }))
+    };
+    if (goodId) {
+      good._id = goodId;
+    }
+    createGood(good).then(response => {
+      navigate('/admin/goods');
+    });
+  }
+
+
+  return (
+    <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}
+            renderValue={(selected => selectedCategory?.name)}
+          >
+            {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 passImages={goodImages}onChange={(images) => setImages(images)} />
+
+        <Button variant="contained" sx={{ maxWidth: '200px', width: '100%', m: '20px auto 0' }} onClick={() => handleSubmit()}>{buttonText}</Button>
+      </Stack>
+    </Box>
+  )
+}
+
+export default EditGood;

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

@@ -8,8 +8,8 @@ import AppBar from '@mui/material/AppBar';
 import Box from '@mui/material/Box';
 import Toolbar from '@mui/material/Toolbar';
 import { useDispatch, useSelector } from 'react-redux';
-import { authSlice } from '../store/authSlice';
-import { cartSlice } from '../store/cartSlice';
+import { logout } from '../store/authSlice';
+import { clearCart } from '../store/cartSlice';
 import DrawUserName from './DrawUserName';
 import LinkButton from './LinkButton';
 
@@ -25,8 +25,8 @@ export default function Header() {
   const dispatch = useDispatch();
   const goodsInCart = useSelector(state => state.cart.goodsCount);
   const onLogout = () => {
-    dispatch(authSlice.actions.logout());
-    dispatch(cartSlice.actions.clearCart());
+    dispatch(logout());
+    dispatch(clearCart());
   }
   const token = useSelector(state => state.auth.token);
   const userId = useSelector(state => state.auth?.payload?.sub?.id);

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

@@ -1,9 +1,9 @@
 import React from "react";
 import './Image.scss';
 
-export function Image({url}) {
+export function Image({url, click}) {
 
   return (
-      <img className="good-image" src={`http://shop-roles.node.ed.asmer.org.ua/${url}`}/>
+      <img className="good-image" src={`http://shop-roles.node.ed.asmer.org.ua/${url}`} onClick={click}/>
   )
 }

+ 34 - 14
js21 react/my-react-app/src/components/ImageUploader.js

@@ -1,8 +1,11 @@
 import React, { useCallback, useMemo, useState } from 'react';
-import { useUploadImageMutation } from '../store/api';
+import { imageApi, useUploadImageMutation } from '../store/api';
 import { useDropzone } from 'react-dropzone';
 import { Image } from './Image/Image';
 import { Box } from '@mui/material';
+import Dnd from './Dnd';
+import { useSelector } from 'react-redux';
+
 
 const baseStyle = {
   flex: 1,
@@ -32,15 +35,32 @@ const rejectStyle = {
   borderColor: '#ff1744'
 };
 
-function ImageUploader() {
-  const [images, setImages] = useState([]);
+const ImageOnDnd = ({ image, onDelete }) =>
+  <Box sx={{ width: '100px', mr: '20px' }} key={image._id}>
+    <Image url={image?.url} click={() => onDelete(image)}/>
+  </Box>
+
+function ImageUploader ({passImages, onChange}) {
+  const [images, setImages] = useState(passImages || []);
   const [uploadImage, result] = useUploadImageMutation();
+  console.log('result', result);
+
+  // const onDrop = useCallback(acceptedFiles => {
+  //   filesUpload(acceptedFiles).then(imagesResponse => {
+  //     setImages(images.concat(imagesResponse));
+  //   });
+  // }, [])
+
+  const updateImages = images => {
+    setImages(images);
+    onChange(images);
+  }
 
-  const onDrop = useCallback(acceptedFiles => {
+  const onDrop = acceptedFiles => {
     filesUpload(acceptedFiles).then(imagesResponse => {
-      setImages(images.concat(imagesResponse));
+      updateImages(images.concat(imagesResponse));
     });
-  }, [])
+  }
   const { getRootProps, getInputProps, isDragActive, isFocused, isDragAccept, isDragReject } = useDropzone({ accept: { 'image/*': [] }, onDrop });
 
   const style = useMemo(() => ({
@@ -58,6 +78,7 @@ function ImageUploader() {
     const formData = new FormData();
     formData.append('photo', file);
     return uploadImage(formData).then(response => {
+      console.log('response', response);
       return Promise.resolve(response.data);
     });
   }
@@ -65,6 +86,9 @@ function ImageUploader() {
   const filesUpload = (files) => {
     return Promise.all(files.map(fileUpload));
   }
+  const deleteImage = image => updateImages(images.filter(i => i != image));
+
+  const localImage = ({ image }) => <ImageOnDnd image={image} onDelete={imgToDelete => deleteImage(imgToDelete)}/>
 
   return (
     <>
@@ -76,15 +100,11 @@ function ImageUploader() {
             <p>Drag 'n' drop some files here, or click to select files</p>
         }
       </div>
-      <Box sx={{display: 'flex'}}>
-        {images && images.map(image =>
-          <Box sx={{ width: '60px' }}>
-            <Image key={image?._id} url={image?.url} />
-          </Box>
-        )}
+      <Box sx={{ display: 'flex', m: '20px auto 0' }}>
+        <Dnd items={images} render={localImage} onChange={images => updateImages(images)} itemProp="image" keyField="_id" horizontal/>
       </Box>
     </>
   )
-}
+  }
 
-export default ImageUploader;
+  export default ImageUploader;

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

@@ -1,15 +1,17 @@
 import React, { useState } from 'react';
 import Title from '../components/Title';
-import { actionOrder, clearCart } from '../store/cartSlice';
+import { clearCart } from '../store/cartSlice';
 import CartGood from '../components/CartGood';
 import { useDispatch, useSelector } from 'react-redux';
 import { Box, Button } from '@mui/material';
 import Price from '../components/Price';
+import { useCreateOrderMutation } from '../store/api';
 
 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 [createOrderAction, result] = useCreateOrderMutation();
   const dispatch = useDispatch();
   console.log(goods);
   const createOrder = (orderGoods) => {
@@ -20,7 +22,9 @@ export default function CartPage() {
         count: orderGood.count
       });
     });
-    dispatch(actionOrder(orderGoodsDto));
+    createOrderAction(orderGoodsDto).then(response => {
+      dispatch(clearCart());
+    });
   }
   return (
     <>

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

@@ -1,16 +1,18 @@
 import { Box, Stack } from '@mui/material';
 import React, { useEffect } from 'react';
 import { useDispatch, useSelector } from 'react-redux';
-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';
+import { useLoginMutation } from '../store/api';
+import { loginAction } from '../store/authSlice';
 const LoginPage = () => {
   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 [fullLogin, result] = useLoginMutation();
   const dispatch = useDispatch();
   const navigate = useNavigate();
 
@@ -26,7 +28,9 @@ const LoginPage = () => {
   }, [token]);
 
   const onLogin = (login, password) => {
-    dispatch(actionFullLogin({ login, password }));
+    fullLogin({ login, password }).then(response => {
+      dispatch(loginAction(response.data.login));
+    });
   }
 
   return (

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

@@ -1,13 +1,16 @@
-import { Box, Stack } from '@mui/material';
+import { Box } from '@mui/material';
 import React, { useEffect } from 'react';
 import { useDispatch, useSelector } from 'react-redux';
-import { actionFullRegister } from '../store/authSlice';
+import { loginAction } from '../store/authSlice';
 import LoginForm from '../components/LoginForm';
 import { useNavigate } from 'react-router-dom';
 import Title from '../components/Title';
+import { useLoginMutation, useRegisterMutation } from '../store/api';
 
 export default function RegisterPage() {
   const token = useSelector(state => state.auth.token);
+  const [fullRegister, resultMut] = useRegisterMutation();
+  const [fullLogin, result] = useLoginMutation();
   const dispatch = useDispatch();
   const navigate = useNavigate();
 
@@ -19,12 +22,16 @@ export default function RegisterPage() {
   }, [token]);
 
   const onRegister = (login, password) => {
-    dispatch(actionFullRegister({ login, password }));
+    fullRegister({ login, password }).then(res => {
+      fullLogin({ login, password }). then(response => {
+        dispatch(loginAction(response.data.login));
+      })
+    });
   }
 
   return (
     <>
-    <Title>Registration</Title>
+      <Title>Registration</Title>
       <Box sx={{ maxWidth: '300px', width: '100%', m: '150px auto' }}>
         <LoginForm submit='Registration' onSubmit={onRegister} />
       </Box>

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

@@ -2,12 +2,6 @@ import React from 'react';
 import { useGetGoodsQuery, useGetOrderGoodQuery, useGetOwnerOrderQuery, useGetUsersQuery } from '../../store/api';
 
 export default function AdminPage() {
-  // 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>
   )

+ 14 - 10
js21 react/my-react-app/src/pages/admin/CategoriesPage.js

@@ -1,5 +1,5 @@
 import React, { useState } from 'react';
-import { deleteCategory, useGetCategoriesQuery, useGetCategoryCountQuery } from '../../store/api';
+import { useDeleteCategoryMutation, useGetCategoriesQuery, useGetCategoryCountQuery } from '../../store/api';
 import Loader from '../../components/Loader';
 import {
   Button, IconButton, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip, Dialog,
@@ -8,7 +8,6 @@ import {
 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';
 
@@ -19,7 +18,7 @@ function CategoriesPage() {
   const [rowsPerPage, setRowsPerPage] = useState(5);
   const [open, setOpen] = useState(false);
   const [selectedId, setSelectedId] = useState('');
-  const dispatch = useDispatch();
+  const [deleteCategory, result] = useDeleteCategoryMutation();
 
   const handleChangePage = (event, newPage) => {
     setPage(newPage);
@@ -40,10 +39,11 @@ function CategoriesPage() {
     setOpen(false);
   };
 
-  const onDelitionSubmit = () => {
+  const onDeletionSubmit = () => {
     const category = data?.CategoryFind?.find(category => category._id === selectedId);
-    dispatch(deleteCategory(category));
-    setOpen(false);
+    deleteCategory(category).then(response => {
+      setOpen(false);
+    });
   }
 
   const Row = ({ _id, name, goods, category }) => {
@@ -76,9 +76,10 @@ function CategoriesPage() {
                 <Table size="small">
                   <TableHead>
                     <TableRow>
-                    <TableCell></TableCell>
-                    <TableCell >Good</TableCell>
-                    <TableCell >Price</TableCell>
+                      <TableCell></TableCell>
+                      <TableCell >Good</TableCell>
+                      <TableCell >Price</TableCell>
+                      <TableCell align='right'></TableCell>
                     </TableRow>
                   </TableHead>
                   <TableBody>
@@ -91,6 +92,9 @@ function CategoriesPage() {
                         </TableCell>
                         <TableCell >{good?.name}</TableCell>
                         <TableCell >{good?.price}</TableCell>
+                        <TableCell align='right'>
+                          <IconButton component={Link} to={`/admin/good/${good._id}`}><Edit /></IconButton>
+                        </TableCell>
                       </TableRow>)}
                   </TableBody>
                 </Table>
@@ -148,7 +152,7 @@ function CategoriesPage() {
                       <DialogContentText>Do you confirm category deletion?</DialogContentText>
                     </DialogContent>
                     <DialogActions>
-                      <Button onClick={() => onDelitionSubmit()}>Yes</Button>
+                      <Button onClick={() => onDeletionSubmit()}>Yes</Button>
                       <Button onClick={closeModalWindow}>No</Button>
                     </DialogActions>
                   </Dialog>

+ 5 - 85
js21 react/my-react-app/src/pages/admin/CreateGoodPage.js

@@ -1,93 +1,13 @@
-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 { Box } from '@mui/material';
+import React from 'react';
+import EditGood from '../../components/EditGood';
 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 = () => {
-
-  }
-
 
+const CreateGoodPage = () => {
   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>
+      <EditGood buttonText='Add' />
     </Box >
   )
 }

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

@@ -1,8 +1,31 @@
+import { Box } from '@mui/material';
 import React from 'react';
+import { useParams } from 'react-router-dom';
+import EditGood from '../../components/EditGood';
+import Title from '../../components/Title';
+import { useGetGoodByIdQuery } from '../../store/api';
+import Loader from '../../components/Loader';
 
 function EditGoodPage() {
+  const { goodId } = useParams();
+  const { data, isFetching } = useGetGoodByIdQuery(goodId);
   return (
-    <div>EditGoodPage</div>
+    <>
+      {isFetching ? <Loader /> :
+        <Box sx={{ maxWidth: '450px', m: '20px auto', width: '100%' }} >
+          <Title>Create good</Title>
+          <EditGood
+            buttonText='Edit'
+            goodId={goodId}
+            name={data?.GoodFindOne?.name}
+            category={data?.GoodFindOne?.categories[0]}
+            price={data?.GoodFindOne?.price}
+            description={data?.GoodFindOne?.description}
+            goodImages={data?.GoodFindOne?.images}
+          />
+        </Box >
+      }
+    </>
   )
 }
 

+ 8 - 7
js21 react/my-react-app/src/pages/admin/GoodsPage.js

@@ -1,4 +1,4 @@
-import { DeleteOutlineOutlined, Edit, KeyboardArrowUp, KeyboardArrowDown } from '@mui/icons-material';
+import { DeleteOutlineOutlined, Edit} from '@mui/icons-material';
 import {
   Button, Dialog, DialogActions, DialogContent, DialogContentText, IconButton, Paper, Table, TableBody, TableCell,
   TableContainer, TableHead, TablePagination, TableRow
@@ -6,11 +6,10 @@ import {
 import { Box } from '@mui/system';
 import React, { useState } from 'react';
 import { Link } from 'react-router-dom';
-import { deleteGood, useGetGoodCountQuery, useGetGoodsQuery } from '../../store/api';
+import { useDeleteGoodMutation, 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 [page, setPage] = useState(0);
@@ -19,7 +18,8 @@ function GoodsPage() {
   const { data, isFetching } = useGetGoodsQuery({ skip: page * rowsPerPage, limit: rowsPerPage });
   const [open, setOpen] = useState(false);
   const [selectedId, setSelectedId] = useState('');
-  const dispatch = useDispatch();
+  const [deleteGood, result] = useDeleteGoodMutation();
+
 
   const handleChangePage = (event, newPage) => {
     setPage(newPage);
@@ -41,8 +41,9 @@ function GoodsPage() {
   };
 
   const onDeletionSubmit = () => {
-    dispatch(deleteGood({_id: selectedId}));
-    setOpen(false);
+    deleteGood({_id: selectedId}).then(response => {
+      setOpen(false);
+    });
   }
 
   return (
@@ -75,7 +76,7 @@ function GoodsPage() {
                       <TableCell>{good?.price}</TableCell>
                       <TableCell>{good?.categories?.map(category => category?.name || 'no category')}</TableCell>
                       <TableCell align='right'>
-                        <IconButton component={Link} to={`/admin/category/${good._id}`}><Edit /></IconButton>
+                        <IconButton component={Link} to={`/admin/good/${good._id}`}><Edit /></IconButton>
                       </TableCell>
                       <TableCell align='right'>
                         <IconButton onClick={() => openModalWindow(good._id)}><DeleteOutlineOutlined /></IconButton>

+ 3 - 46
js21 react/my-react-app/src/store/api.js

@@ -383,52 +383,9 @@ export const api = createApi({
   }),
 });
 
-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;
+  useRegisterMutation, useGetUserByIdQuery, useCreateCategoryMutation, useDeleteCategoryMutation, useGetGoodsQuery, useGetUsersQuery,
+  useGetOrderGoodQuery, useGetUserCountQuery, useGetOrderCountQuery, useGetGoodCountQuery, useGetCategoryCountQuery, useGetOrdersQuery,
+  useCreateGoodMutation, useDeleteGoodMutation, useCreateOrderMutation } = api;
 
 export const { useUploadImageMutation } = imageApi;

+ 2 - 15
js21 react/my-react-app/src/store/authSlice.js

@@ -5,7 +5,7 @@ export const authSlice = createSlice({
   name: 'auth',
   initialState: {},
   reducers: {
-    login(state, { payload }) {
+    loginAction(state, { payload }) {
       const tokenPayload = jwtDecode(payload);
       if (tokenPayload) {
         state.token = payload;
@@ -39,17 +39,4 @@ function jwtDecode(token) {
   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));
-  }
+  export const {loginAction, logout} = authSlice.actions;

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

@@ -1,13 +1,6 @@
 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);