浏览代码

create admin + cart page

Alex 2 年之前
父节点
当前提交
520fbc761c

+ 5 - 0
src/App.js

@@ -19,6 +19,9 @@ import ProductPage from "./pages/ProductPage";
 import WishListPage from "./pages/WishListPage";
 import SearchPage from "./pages/SearchPage";
 import MyOrdersPage from "./pages/MyOrdersPage";
+import CartPage from "./pages/CartPage";
+import {CPRoute} from "./components/CPRoute";
+import AdminPage from "./pages/AdminPage";
 
 const history = createHistory();
 
@@ -38,9 +41,11 @@ function App() {
                   <Route path="/my-account" component={MyAccountPage} />
                   <Route path="/privacy-policy" component={PrivacyPolicy} />
                   <Route path="/search" component={SearchPage} />
+                  <Route path="/basket" component={CartPage} />
                   <Route path="/wish-list" component={WishListPage} />
                   <Route path="/my-orders" component={MyOrdersPage} />
                   <Route path="/profile" component={ProfilePage} />
+                  <CPRoute roles={["admin"]} path="/admin" fallback='/my-account' component={AdminPage} />
                   <Route path="*" component={Page404} />
               </Switch>
               <Footer/>

+ 0 - 1
src/actions/ActionGoodFind.js

@@ -1,5 +1,4 @@
 import {actionSearchResult} from "../reducers/SearchReducer";
-
 const {actionPromise} = require("../reducers/PromiseReducer");
 const {gql} = require("./PathDB");
 

+ 2 - 4
src/actions/ActionLogin.js

@@ -17,8 +17,7 @@ export const actionFullLogin = (login, password) =>
         if (token){
             let user = await dispatch(actionAuthLogin(token))
             if (user) {
-                localStorage?.userId &&
-                dispatch(actionFullUserFindOne(localStorage?.userId))
+                localStorage?.userId && dispatch(actionFullUserFindOne(localStorage.userId))
             }
         }
     }
@@ -42,8 +41,7 @@ export const actionFullRegister = (login, password) =>
             if (token) {
                 let user = await dispatch(actionAuthLogin(token))
                 if (user) {
-                    localStorage?.userId &&
-                    dispatch(actionFullUserFindOne(localStorage?.userId))
+                    localStorage?.userId && dispatch(actionFullUserFindOne(localStorage.userId))
                 }
             }
         }

+ 17 - 0
src/actions/ActionOrder.js

@@ -0,0 +1,17 @@
+const {actionPromise} = require("../reducers/PromiseReducer");
+const {gql} = require("./PathDB");
+
+export const ActionOrder = (orderGoods) => {
+    return actionPromise('order', gql(`
+            mutation order($order:OrderInput){
+                 OrderUpsert(order:$order)
+                    { _id total }
+                 }
+    `, {order: {orderGoods}}))
+}
+
+export const ActionFullOrder = (card) =>
+    async (dispatch) => {
+        let orderGoods = Object.entries(card).map(([_id, {count}]) => ({good: {_id}, count}))
+        await dispatch(ActionOrder(orderGoods))
+    }

+ 36 - 0
src/components/CPRoute.jsx

@@ -0,0 +1,36 @@
+import {connect} from "react-redux";
+import {Redirect} from "react-router-dom";
+import Route from "react-router-dom/es/Route";
+
+const RRoute = ({action, component:Component,...routeProps}) => {
+    const WrapperComponent = (componentProps) => {
+        action(componentProps.match)
+        return <Component {...componentProps}/>
+    }
+    return <Route {...routeProps} component={WrapperComponent}/>
+}
+
+const CRRoute = connect(null, {action: match => ({type: 'ROUTE', match})})(RRoute)
+const ProtectedRoute =({ fallback='/',
+                         roles=['admin'],
+                         auth,
+                         component: Component,
+                         ...routeProps}) => {
+    const WrapperComponent = (componentProps) => {
+        let acl = auth?.payload?.sub?.acl
+        if (localStorage?.authToken && acl && Array.isArray(acl) && acl.length > 0) {
+            acl = acl.filter(item => {
+                if(roles.includes(item)) return item
+            })
+            if (acl.length > 0){
+                return <Component {...componentProps}/>
+            }
+        }
+        else if (localStorage?.authToken){
+            return <Component {...componentProps}/>
+        }
+        return <Redirect to={fallback}/>
+    }
+    return <CRRoute {...routeProps} component={WrapperComponent}/>
+}
+export const CPRoute = connect(state=>({auth: state.auth}))(ProtectedRoute)

+ 3 - 2
src/components/Header.jsx

@@ -12,9 +12,10 @@ import {connect} from "react-redux";
 import {actionUserRemove} from "../reducers/UserReducer";
 import {backURL} from "../actions/PathDB";
 import userDefault from "../img/header/userDefault.png"
+import {Redirect} from "react-router-dom";
 
 const pages = ['catalog', 'about us', 'our team', 'faq', 'contact']
-const settingsDefaultUserAuth = ['Profile', 'Logout']
+const settingsDefaultUserAuth = ['Profile', 'Logout', 'Admin']
 
 const Header = ({user={}}) => {
     const [anchorElNav, setAnchorElNav] = useState(null);
@@ -160,7 +161,7 @@ const Header = ({user={}}) => {
     const ButtonLogOut = ({actionLogOut, actionUserRemove}) => {
         return (
             <Button
-                onClick={() => {actionLogOut(); actionUserRemove(); handleCloseNavMenu()}}
+                onClick={() => {actionLogOut(); actionUserRemove(); handleCloseNavMenu();}}
                 style={{textDecoration: 'none', color: '#fff', textAlign: "center", width: '100%'}}
             >
                 Log out

+ 5 - 5
src/components/SetCount.jsx

@@ -1,8 +1,8 @@
 import {Box, Button} from "@mui/material";
 import {useEffect, useState} from "react";
 
-export const SetCount = ({onCount}) => {
-    let [count, setCount] = useState(1)
+export const SetCount = ({onCount, defaultValue=1, height=55, width=50}) => {
+    let [count, setCount] = useState(defaultValue)
 
     useEffect(() => {
         onCount(count)
@@ -11,16 +11,16 @@ export const SetCount = ({onCount}) => {
     return (
         <Box sx={{display: 'flex', flexWrap: 'no-wrap'}}>
             <Button
-                sx={{height: '55px', width: '50px', borderRadius: '0', color: '#000', borderColor: '#000', fontSize: '30px', fontWeight: '300'}}
+                sx={{height: `${height}px`, width: `${width}px`, borderRadius: '0', color: '#000', borderColor: '#000', fontSize: '30px', fontWeight: '300'}}
                 variant="outlined"
                 color={"inherit"}
                 onClick={() => setCount(count === 1 ? count : count-1)}
             >
                 -
             </Button>
-            <input disabled value={count} style={{boxSizing: 'border-box', height: '55px', width: '60px', textAlign: 'center', border: '0', backgroundColor: '#eaeaea'}}/>
+            <input disabled value={count} style={{boxSizing: 'border-box', height: `${height}px`, width: `${width+10}px`, textAlign: 'center', border: '0', backgroundColor: '#eaeaea'}}/>
             <Button
-                sx={{height: '55px', width: '50px', borderRadius: '0', color: '#000', borderColor: '#000', fontSize: '30px', fontWeight: '300'}}
+                sx={{height: `${height}px`, width: `${width}px`, borderRadius: '0', color: '#000', borderColor: '#000', fontSize: '30px', fontWeight: '300'}}
                 variant="outlined"
                 color={"inherit"}
                 onClick={() => setCount(count === 100 ? count : count+1)}

+ 78 - 0
src/components/TableLine.jsx

@@ -0,0 +1,78 @@
+import {Box, Button, Grid, Typography} from "@mui/material";
+import Link from "react-router-dom/es/Link";
+import {backURL} from "../actions/PathDB";
+import imgNotFound from "../img/catalog/imgNotFound.png";
+import CloseIcon from "@mui/icons-material/Close";
+
+export const ItemHeaderLine = ({text, align='left'}) => {
+    return (
+        <Typography
+            color='#616161'
+            variant='body1'
+            letterSpacing='1px'
+            textAlign={align}
+        >
+            {text || ''}
+        </Typography>
+    )
+}
+export const LinkProductItem = ({item: [_id, name, images], children=''}) => {
+    return (
+        <Link style={{textDecoration: 'none', display: 'flex', alignItems: 'center'}} to={`/good/${_id}`}>
+            <Box minWidth='60px' maxWidth='60px' height='60px' borderRadius='10px' overflow='hidden' marginRight='20px'>
+                <img style={{width: '100%', height: '100%', objectFit: 'cover'}} src={images[0]?.url ? backURL + '/' + images[0]?.url : imgNotFound} alt={name}/>
+            </Box>
+            {children ?
+                <Box display='flex' flexDirection='column' height='50px' justifyContent='space-around'>
+                    <ItemHeaderLine text={name}/>
+                    <ItemHeaderLine text={children}/>
+                </Box> :
+                <ItemHeaderLine text={name}/>
+            }
+        </Link>
+    )
+}
+
+const AddToCart = ({good, addToCart}) => {
+    return (
+        <Button
+            sx={{height: '40px', width: '70%', borderRadius: '0', color: '#000', borderColor: '#000', fontSize: '16px', fontWeight: '300'}}
+            variant="outlined"
+            color={"inherit"}
+            onClick={() => addToCart(good)}
+        >
+            ADD TO CART
+        </Button>
+    )
+}
+
+export const RemoveFromList = ({good, onRemove}) => {
+    return (
+        <Button
+            size="small"
+            color="inherit"
+            onClick={() => onRemove(good)}
+        >
+            <CloseIcon/>
+        </Button>
+    )
+}
+export const TableLine = ({columnName, role='header', customSizeCol}) => {
+    const good = {'_id': columnName[0][0], 'name': columnName[0][1], 'images': columnName[0][2], 'price': columnName[1]};
+    return (
+        <Grid container justifyContent='space-between' marginBottom='20px' alignItems='center'>
+            <Grid item xs={3} md={customSizeCol ? customSizeCol[0] : 5}>
+                {role === 'header' ? <ItemHeaderLine text={columnName[0]}/> : <LinkProductItem item={columnName[0]}/>}
+            </Grid>
+            <Grid item xs={3} md={customSizeCol ? customSizeCol[1] : 2}>
+                <ItemHeaderLine text={role === 'header' ? columnName[1] : '$'+columnName[1]} align={'center'}/>
+            </Grid>
+            <Grid item xs={3} md={customSizeCol ? customSizeCol[2] : 3} display='flex' justifyContent='center'>
+                {role === 'header' ? <ItemHeaderLine text={columnName[3]} align={'center'}/> : <AddToCart good={good} addToCart={columnName[3]}/>}
+            </Grid>
+            <Grid item xs={3} md={customSizeCol ? customSizeCol[3] : 1} display='flex' justifyContent='center'>
+                {role === 'header' ? <ItemHeaderLine text={columnName[2]} align={'center'}/> : <RemoveFromList good={good} onRemove={columnName[2]}/>}
+            </Grid>
+        </Grid>
+    )
+}

二进制
src/img/not-found/3.png


+ 13 - 0
src/pages/AdminPage.jsx

@@ -0,0 +1,13 @@
+import Breadcrumb from "../components/Breadcrumbs";
+import {connect} from "react-redux";
+import Redirect from "react-router-dom/es/Redirect";
+
+const AdminPage = ({auth}) => {
+    return(
+        <>
+            <Breadcrumb links={['admin']}/>
+        </>
+    )
+}
+const CAdminPage = connect(state=>({auth: state.auth}))(AdminPage)
+export default CAdminPage

+ 122 - 0
src/pages/CartPage.jsx

@@ -0,0 +1,122 @@
+import Breadcrumb from "../components/Breadcrumbs";
+import {connect} from "react-redux";
+import {actionCardChange, actionCardClear, actionCardRemove} from "../reducers/CartReducer";
+import {ActionFullOrder, ActionOrder} from "../actions/ActionOrder";
+import {Box, Button, Container, Divider, Grid, Typography, useMediaQuery} from "@mui/material";
+import {ItemHeaderLine, LinkProductItem, RemoveFromList, TableLine} from "../components/TableLine";
+import {NotFoundBlock} from "../components/NotFoundBlock";
+import imgUrl from "../img/not-found/3.png";
+import AddShoppingCartIcon from "@mui/icons-material/AddShoppingCart";
+import {SetCount} from "../components/SetCount";
+import {useEffect, useState} from "react";
+
+const CartGoodLine = ({item, onCartRemove, onCardChange}) => {
+    let [count, setCount] = useState(item?.count)
+    useEffect(() => {
+        onCardChange(item?.good, count)
+    }, [count])
+    return(
+        <Grid container alignItems='center' marginBottom='20px'>
+            <Grid item xs={6}>
+                <LinkProductItem item={[item?.good?._id, item?.good?.name, item?.good?.images]} children={<Typography>${item?.good?.price}</Typography>}/>
+            </Grid>
+            <Grid item xs={3} display='flex' justifyContent="center">
+                <SetCount height={40} width={40} defaultValue={item?.count} onCount={value => setCount(value)}/>
+            </Grid>
+            <Grid item xs={2}>
+                <ItemHeaderLine align={'center'} text={(`$${parseFloat(item?.good?.price * count).toFixed(2)}`) || 'NaN'}/>
+            </Grid>
+            <Grid item xs={1}>
+                <RemoveFromList good={item?.good} onRemove={onCartRemove}/>
+            </Grid>
+        </Grid>
+    )
+}
+
+const TotalPriceLine = ({title, subtitle, sizeSubtitle='body2'}) => {
+    return (
+        <Grid container display='flex' flexDirection='row' justifyContent='space-between' alignItems='center' padding='20px'>
+            <Grid item xs={6}>
+                <Typography
+                    variant='body2'
+                    color='#616161'
+                    textAlign='left'
+                >
+                    {title}
+                </Typography>
+            </Grid>
+            <Grid item xs={6}>
+                <Typography
+                    variant={sizeSubtitle}
+                    color='#000'
+                    textAlign='right'
+                >
+                    {subtitle}
+                </Typography>
+            </Grid>
+        </Grid>
+    )
+}
+
+const CartPage = ({cart, onCardChange, onCartClear, onCartRemove, onOrderUpsert}) => {
+    const matches = useMediaQuery('(max-width:768px)')
+    let rows = []
+    for (const key of Object.values(cart)) {
+        rows.push(key)
+    }
+    return (
+        <>
+            <Breadcrumb links={['cart']}/>
+            {Object.values(cart).length > 0 ?
+                <main style={{backgroundColor: "#f3f3f3", padding: matches ? "20px 0" : "50px 0", minHeight:'300px'}}>
+                        <Container maxWidth="lg">
+                            <Grid container justifyContent='space-between'>
+                                <Grid item xs={8.5}>
+                                    <TableLine columnName={['PRODUCT', 'QUANTITY', 'REMOVE', 'SUBTOTAL']} customSizeCol={[6, 3, 2, 1]}/>
+                                    <Divider sx={{marginBottom: '20px'}}/>
+                                    {rows.map(item => <CartGoodLine item={item} onCartRemove={onCartRemove} onCardChange={onCardChange}/>)}
+                                    <Divider/>
+                                </Grid>
+                                <Grid item xs={3} sx={{backgroundColor: '#fff'}} height='100%' paddingBottom='20px'>
+                                    <Typography
+                                        padding='20px'
+                                        variant='h4'
+                                        fontFamily='sarif'
+                                        letterSpacing='2px'
+                                        textAlign='center'
+                                    >
+                                        TOTAL
+                                    </Typography>
+                                    <Divider/>
+                                    <TotalPriceLine title={`${rows.length || 1} goods for the amount`} subtitle={`$${rows.reduce((a, i) => a + (i.good.price * i.count), 0)}`}/>
+                                    <TotalPriceLine title={'Cost of delivery'} subtitle={'according to the carrier\'s tariffs'}/>
+                                    <Divider/>
+                                    <TotalPriceLine title={'To pay'} subtitle={`$${rows.reduce((a, i) => a + (i.good.price * i.count), 0)}`} sizeSubtitle={'h6'}/>
+                                    <Divider sx={{marginBottom: '20px'}}/>
+                                    <Box display='flex' justifyContent='center' flexDirection='column' alignItems='center'>
+                                        <Button sx={{borderRadius: '0', width:'80%', padding: '10px 20px', marginBottom: '20px'}}
+                                                color='success'
+                                                variant="outlined"
+                                                onClick={() => onOrderUpsert(cart)}
+                                        >
+                                            confirm the order
+                                        </Button>
+                                        <Button sx={{borderRadius: '0', width:'80%', padding: '10px 20px'}}
+                                                color='warning'
+                                                variant="outlined"
+                                                onClick={() => onCartClear()}
+                                        >
+                                            cart clear
+                                        </Button>
+                                    </Box>
+                                </Grid>
+                            </Grid>
+                        </Container>
+                </main>:
+                <NotFoundBlock img={imgUrl} headerText={'YOUR CART IS CURRENTLY EMPTY'} text={<Box display='flex' alignItems='center'><Typography component='span'>Click the</Typography><AddShoppingCartIcon sx={{margin: '0 10px'}}/><Typography component='span'>icons to add products</Typography></Box>}/>
+            }
+        </>
+    )
+}
+const CCartPage = connect(state=>({cart: state.cart}), {onCardChange: actionCardChange, onCartClear: actionCardClear, onCartRemove: actionCardRemove, onOrderUpsert: ActionFullOrder})(CartPage)
+export default CCartPage

+ 46 - 53
src/pages/ProductPage.jsx

@@ -1,6 +1,6 @@
 import {useEffect, useState} from "react";
 import {connect} from "react-redux";
-import {Box, Button, Container, Divider, Grid, Paper, Typography, useMediaQuery} from "@mui/material";
+import {Box, Button, CircularProgress, Container, Divider, Grid, Paper, Typography, useMediaQuery} from "@mui/material";
 import {actionGoodFindOne} from "../actions/ActionGoodFind";
 import Switch from "react-router-dom/es/Switch";
 import Route from "react-router-dom/es/Route";
@@ -16,29 +16,10 @@ import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
 import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
 import Carousel from 'react-material-ui-carousel'
 import {backURL} from "../actions/PathDB";
-
-let defProduct = {
-    "_id": "5dc45d0b5df9d670df48cc4b",
-    "name": "Apple iPhone X 64GB Space Gray",
-    "price": 5000,
-    "images": [
-        {
-            "url": "images/00505f5f08ac113874318dee67975aa9",
-            "originalFileName": "image"
-        },
-        {
-            "url": "images/00505f5f08ac113874318dee67975aa9",
-            "originalFileName": "image"
-        },
-        {
-            "url": "images/00505f5f08ac113874318dee67975aa9",
-            "originalFileName": "image"
-        }
-    ]
-}
+import imgNotFound from "../img/catalog/imgNotFound.png";
 
 export const timeCalc = (createdAt) => {
-    let formattedTime = 0;
+    let formattedTime;
     let date = new Date(+createdAt);
     let year = date.getFullYear();
     let month = "0" + (date.getMonth()+1);
@@ -51,9 +32,9 @@ export const timeCalc = (createdAt) => {
     return formattedTime;
 }
 
-const ImageItem = ({images: {url, originalFileName}}) => {
+const ImageItem = ({images}) => {
     return (
-        <img src={backURL + '/' + url} alt={originalFileName.split('.')[0]}/>
+        <img src={images && images.url ? backURL + '/' + images.url : imgNotFound} alt={images?.originalFileName ? images.originalFileName.split('.')[0] : 'image'}/>
     )
 }
 const CarouselItem = ({images}) => {
@@ -124,13 +105,19 @@ const ProductTags = ({title, subtitle}) => {
     )
 }
 
-const AddToCart = ({good, addToCart}) => {
-    let [count, setCount] = useState(1)
+const AddToCart = ({cart, good, addToCart}) => {
+    let [count, setCount] = useState(cart[good?._id]?.count || 1)
+
+    useEffect(() => {
+        setCount(cart[good?._id]?.count || 1)
+    },[cart])
+
+    console.log(count)
     return (
         <Box width='100%' backgroundColor='#fff' padding='30px'>
             <Grid container justifyContent='space-between'>
                 <Grid xs={5} item>
-                   <SetCount onCount={value => setCount(value)}/>
+                   <SetCount defaultValue={count} onCount={value => setCount(value)}/>
                 </Grid>
                 <Grid xs={5} item>
                     <Button
@@ -139,14 +126,14 @@ const AddToCart = ({good, addToCart}) => {
                         color={"inherit"}
                         onClick={() => addToCart(good, count)}
                     >
-                        ADD TO CART
+                        {good._id in cart ? 'CHANGE CART' : 'ADD TO CART'}
                     </Button>
                 </Grid>
             </Grid>
         </Box>
     )
 }
-const CAddToCart = connect(null, {addToCart: actionCardChange})(AddToCart)
+const CAddToCart = connect(state=>({cart: state.cart}), {addToCart: actionCardChange})(AddToCart)
 
 const AddToWishList = ({good={}, wishlist ,onAddToWishList, onWishListRemove}) => {
     const flag = good?._id in wishlist
@@ -174,32 +161,38 @@ const AddToWishList = ({good={}, wishlist ,onAddToWishList, onWishListRemove}) =
 }
 const CAddToWishList = connect(state => ({wishlist: state.wishlist}), {onAddToWishList: actionWishListAdd, onWishListRemove: actionWishListRemove})(AddToWishList)
 
-const Goods = ({good:{_id, name, description, price, images, categories, createdAt}=defProduct}) => {
+const Goods = ({good}) => {
     const matches = useMediaQuery('(max-width:768px)');
 
     return (
-        <Grid container justifyContent='space-around' padding={matches ? "20px 0" : "50px 0"} >
-            <Grid xs={12} md={6} item padding='5px 70px 5px 10px'>
-                {Array.isArray(images) && images.length > 1 ? <CarouselItem images={images}/> : <Box sx={{width: '100%', display: 'flex', justifyContent: 'center', height: '340px'}}><ImageItem images={images[0]}/></Box>}
-                <Divider sx={{margin: '20px 0'}}/>
-                <ProductDescription description={description}/>
-            </Grid>
-            <Grid xs={12} md={6} item padding='5px 110px 5px 10px'>
-                <ProductTitle title={name}/>
-                <ProductPrice price={price}/>
-                <CAddToCart good={{_id, name, price, images}}/>
-                <CAddToWishList good={{_id, name, price, images}}/>
-                <Box>
-                    {_id && <ProductTags key={'SKU'} title={'SKU'} subtitle={_id}/>}
-                    {Array.isArray(categories) &&
-                        <ProductTags key={'CATEGORY'} title={'CATEGORY'} subtitle={categories.map(item => {
-                            return <Link key={item?._id} style={{color: "#000", textDecoration: 'none'}} to={`/catalog/category/${item?._id}`}>{item?.name}</Link>
-                         })}/>
-                    }
-                    {createdAt && <ProductTags key={'TIMEOFCREATION'} title={'TIME OF CREATION'} subtitle={timeCalc(createdAt)}/>}
-                </Box>
-            </Grid>
-        </Grid>
+        good && Object.values(good).length > 0  ?
+            <Grid container justifyContent='space-around' padding={matches ? "20px 0" : "50px 0"}>
+                <Grid xs={12} md={6} item padding='5px 70px 5px 10px'>
+                    {Array.isArray(good?.images) && good?.images.length > 1 ? <CarouselItem images={good?.images}/> :
+                        <Box sx={{width: '100%', display: 'flex', justifyContent: 'center', height: '340px'}}><ImageItem
+                            images={good?.images[0]}/></Box>}
+                    <Divider sx={{margin: '20px 0'}}/>
+                    <ProductDescription description={good?.description}/>
+                </Grid>
+                <Grid xs={12} md={6} item padding='5px 110px 5px 10px'>
+                    <ProductTitle title={good?.name}/>
+                    <ProductPrice price={good?.price}/>
+                    <CAddToCart good={good}/>
+                    <CAddToWishList good={good}/>
+                    <Box>
+                        {good?._id && <ProductTags key={'SKU'} title={'SKU'} subtitle={good?._id}/>}
+                        {Array.isArray(good?.categories) &&
+                            <ProductTags key={'CATEGORY'} title={'CATEGORY'} subtitle={good?.categories.map(item => {
+                                return <Link key={item?._id} style={{color: "#000", textDecoration: 'none'}}
+                                             to={`/catalog/category/${item?._id}`}>{item?.name}</Link>
+                            })}/>
+                        }
+                        {good?.createdAt && <ProductTags key={'TIMEOFCREATION'} title={'TIME OF CREATION'}
+                                                         subtitle={timeCalc(good?.createdAt)}/>}
+                    </Box>
+                </Grid>
+            </Grid>:
+            <Box sx={{height: '100%', width: '100%', display: 'flex', justifyContent:'center', alignItems:'center'}}><CircularProgress color="inherit"/></Box>
     )
 }
 const CGoods = connect(state => ({good: state.promise['goodFindOne']?.payload}))(Goods)
@@ -211,7 +204,7 @@ const BlockProduct = ({match:{params:{_id}}, getData}) => {
 
     return(
         <>
-            <main style={{backgroundColor: "#f3f3f3"}}>
+            <main style={{backgroundColor: "#f3f3f3", minHeight:'300px'}}>
                 <Breadcrumb links={['good']}/>
                 <Container maxWidth="lg">
                     <CGoods key={_id} />

+ 3 - 70
src/pages/WishListPage.jsx

@@ -1,80 +1,12 @@
 import Breadcrumb from "../components/Breadcrumbs";
-import {Box, Button, Container, Divider, Grid, Typography, useMediaQuery} from "@mui/material";
+import {Box, Container, Divider, Typography, useMediaQuery} from "@mui/material";
 import {connect} from 'react-redux';
 import {actionWishListRemove} from "../reducers/WishListReducer";
 import {actionCardChange} from "../reducers/CartReducer";
-import Link from "react-router-dom/es/Link";
-import {backURL} from "../actions/PathDB";
-import imgNotFound from "../img/catalog/imgNotFound.png";
-import CloseIcon from '@mui/icons-material/Close';
 import {NotFoundBlock} from "../components/NotFoundBlock";
 import imgUrl from "../img/not-found/2.png"
 import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorder";
-
-const ItemHeaderLine = ({text, align='left'}) => {
-    return (
-        <Typography
-            color='#616161'
-            variant='body1'
-            letterSpacing='1px'
-            textAlign={align}
-        >
-            {text || ''}
-        </Typography>
-    )
-}
-const LinkProductItem = ({item: [_id, name, images]}) => {
-    return (
-        <Link style={{textDecoration: 'none', display: 'flex', alignItems: 'center'}} to={`/good/${_id}`}>
-            <Box width='60px' height='60px' borderRadius='10px' overflow='hidden' marginRight='20px'>
-                <img style={{width: '100%', height: '100%', objectFit: 'cover'}} src={images[0]?.url ? backURL + '/' + images[0]?.url : imgNotFound} alt={name}/>
-            </Box>
-            <ItemHeaderLine text={name}/>
-        </Link>
-    )
-}
-const AddToCart = ({good, addToCart}) => {
-    return (
-        <Button
-            sx={{height: '40px', width: '70%', borderRadius: '0', color: '#000', borderColor: '#000', fontSize: '16px', fontWeight: '300'}}
-            variant="outlined"
-            color={"inherit"}
-            onClick={() => addToCart(good)}
-        >
-            ADD TO CART
-        </Button>
-    )
-}
-const RemoveFromWishList = ({good, onWishListRemove}) => {
-    return (
-        <Button
-            size="small"
-            color="inherit"
-            onClick={() => onWishListRemove(good)}
-        >
-            <CloseIcon/>
-        </Button>
-    )
-}
-const TableLine = ({columnName, role='header'}) => {
-    const good = {'_id': columnName[0][0], 'name': columnName[0][1], 'images': columnName[0][2], 'price': columnName[1]};
-    return (
-        <Grid container justifyContent='space-between' marginBottom='20px' alignItems='center'>
-            <Grid item xs={3} md={5}>
-                {role === 'header' ? <ItemHeaderLine text={columnName[0]}/> : <LinkProductItem item={columnName[0]}/>}
-            </Grid>
-            <Grid item xs={3} md={2}>
-                <ItemHeaderLine text={role === 'header' ? columnName[1] : '$'+columnName[1]} align={'center'}/>
-            </Grid>
-            <Grid item xs={3} md={3} display='flex' justifyContent='center'>
-                {role === 'header' ? <ItemHeaderLine text={columnName[3]} align={'center'}/> : <AddToCart good={good} addToCart={columnName[3]}/>}
-            </Grid>
-            <Grid item xs={3} md={1} display='flex' justifyContent='center'>
-                {role === 'header' ? <ItemHeaderLine text={columnName[2]} align={'center'}/> : <RemoveFromWishList good={good} onWishListRemove={columnName[2]}/>}
-            </Grid>
-        </Grid>
-    )
-}
+import {TableLine} from "../components/TableLine";
 
 const WishListPage = ({wishlist, addToCart, onWishListRemove}) => {
     const matches = useMediaQuery('(max-width:899px)')
@@ -84,6 +16,7 @@ const WishListPage = ({wishlist, addToCart, onWishListRemove}) => {
             rows.push(key[item])
         }
     }
+    console.log(rows)
     return (
         <>
             <Breadcrumb links={['wish list']}/>