Browse Source

add key react components + update catalog page (add sort function, add pagination)

Alex 2 năm trước cách đây
mục cha
commit
54fded9efb

+ 1 - 1
src/actions/ActionCategory.js

@@ -32,7 +32,7 @@ const actionCatById = (_id) => {
     return actionPromise('catById', gql(`query catById($q: String){
             CategoryFindOne(query: $q){
                 _id goods {
-                    _id name description price images {
+                    _id createdAt name description price images {
                         url
                     }
                 }

+ 3 - 2
src/components/Breadcrumbs.jsx

@@ -11,6 +11,7 @@ const Breadcrumb = ({links=['this page'], title}) => {
     let arr = links.map(i => {
         let link = Array.from(i).map(j => j === ' ' ? '-' : j).join('').toLowerCase()
         return  <Link
+                    key={i.toString()}
                     style={{
                         color: "#fff",
                         textDecoration: "none",
@@ -18,12 +19,12 @@ const Breadcrumb = ({links=['this page'], title}) => {
                     }}
                     to={`/${link}`}> {i.toUpperCase()}
                 </Link>
-    });
+    })
     arr.unshift(<Link style={{
         color: "#fff",
         textDecoration: "none",
         fontSize: "11px"
-    }} to="/"> HOME </Link>)
+    }} to="/" key={'homeBreadcrumbs'}> HOME </Link>)
 
     return (
         <article

+ 4 - 4
src/components/Footer.jsx

@@ -69,8 +69,8 @@ const Footer = ({contact=contactDefault, linksSocial=linksSocialDefault}) => {
                         sm={12} md={4}
                         width={matches ? "100%" : "auto"}
                     >
-                        {(contact || []).map(item =>
-                            <Contact Icon={item.icon} text={item.text} link={item.url}/>
+                        {(contact || []).map((item, index) =>
+                            <Contact key={index} Icon={item.icon} text={item.text} link={item.url}/>
                         )}
                     </Grid>
                     <Grid
@@ -90,8 +90,8 @@ const Footer = ({contact=contactDefault, linksSocial=linksSocialDefault}) => {
                             <LinkRouter to='/' className="Footer__Logo"> ABRAXAS </LinkRouter>
                         </Typography>
                         <Box>
-                            {(linksSocial || []).map(item =>
-                                <Social Icon={item.icon} link={item.url}/>
+                            {(linksSocial || []).map((item, index) =>
+                                <Social key={index} Icon={item.icon} link={item.url}/>
                             )}
                         </Box>
                     </Grid>

+ 16 - 16
src/components/Header.jsx

@@ -47,7 +47,7 @@ const Header = ({user={}, createUser}) => {
     const LinkItem = ({page, color='white'}) => {
         return (
             <Button
-                key={page}
+                key={page.toString()}
                 onClick={handleCloseNavMenu}
                 sx={{ my: 2, color: color, display: 'block'}}
             >
@@ -58,24 +58,24 @@ const Header = ({user={}, createUser}) => {
             </Button>
         )
     }
-    const IconItems = ({cart={}, size="large", color='default', valueWishList=0}) => {
+    const IconItems = ({cart={}, wishlist={}, size="large", color='default', valueWishList=0}) => {
         return (
             <>
                 <Link to='/search'>
-                    <IconButton size={size} aria-label="search" color={color}>
+                    <IconButton key={'search'} size={size} aria-label="search" color={color}>
                         <SearchIcon />
                     </IconButton>
                 </Link>
                 <Link to='/my-orders'>
-                    <IconButton size={size} aria-label="my-orders" color={color}>
+                    <IconButton key={'my-orders'} size={size} aria-label="my-orders" color={color}>
                         <ManageSearchIcon />
                     </IconButton>
                 </Link>
                 <Link to='/wish-list'>
-                    <IconButton size={size} aria-label="wish-list" color={color}>
+                    <IconButton key={'wish-list'} size={size} aria-label="wish-list" color={color}>
                         <Badge
                             color="success"
-                            badgeContent={valueWishList}
+                            badgeContent={+Object.entries(wishlist).length}
                             anchorOrigin={{
                                 vertical: 'bottom',
                                 horizontal: 'right',
@@ -86,7 +86,7 @@ const Header = ({user={}, createUser}) => {
                     </IconButton>
                 </Link>
                 <Link to='/basket'>
-                    <IconButton size={size} aria-label="basket" color={color}>
+                    <IconButton key={'basket'} size={size} aria-label="basket" color={color}>
                         <Badge
                                 color="success"
                                 badgeContent={+Object.entries(cart).reduce((a, b) => {
@@ -104,7 +104,7 @@ const Header = ({user={}, createUser}) => {
             </>
         )
     }
-    const CIconItems = connect(state => ({cart: state.cart}))(IconItems)
+    const CIconItems = connect(state => ({cart: state.cart, wishlist: state.wishlist}))(IconItems)
 
     const ItemAuth = ({link, text}) => {
         return (
@@ -118,16 +118,16 @@ const Header = ({user={}, createUser}) => {
 
     const UserIcon = ({auth}) => {
         return (
-                !localStorage.authToken ?
+                Object.entries(auth).length === 0 ?
                     <Link style={{textDecoration: 'none'}} to={'/my-account'}>
                         <IconButton sx={{ p: 0 }}>
-                            <Avatar alt="User" src={userDefault} />
+                            <Avatar alt="User default" src={userDefault} />
                         </IconButton>
                     </Link> :
                     <>
                         <Tooltip title="Open settings">
                             <IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
-                                {auth.avatar === null ?
+                                {!auth.avatar && auth.avatar === null ?
                                     <Avatar alt="User" src={userDefault}/> :
                                     <Avatar alt="User" src={user?.avatar?.url && backURL + '/' + user?.avatar?.url}/>
                                 }
@@ -151,9 +151,9 @@ const Header = ({user={}, createUser}) => {
                         >
                             {settingsDefaultUserAuth.map(item => {
                                 if (item === 'Logout') {
-                                    return <CButtonLogOut/>
+                                    return <CButtonLogOut key={'Logout'}/>
                                 }
-                                return <ItemAuth text={item}
+                                return <ItemAuth key={item.toString()} text={item}
                                                  link={Array.from(item.toLowerCase()).map(i => i === ' ' ? '-' : i).join('')}/>
                             })}
                         </Menu>
@@ -217,8 +217,8 @@ const Header = ({user={}, createUser}) => {
                             }}
                         >
                             {pages.map((page) => (
-                                <MenuItem key={page} onClick={handleCloseNavMenu}>
-                                    <LinkItem page={page} color={'#fff'}/>
+                                <MenuItem key={page.toString()} onClick={handleCloseNavMenu}>
+                                    <LinkItem key={page.toString()} page={page} color={'#fff'}/>
                                 </MenuItem>
                             ))}
                             <MenuItem onClick={handleCloseNavMenu}>
@@ -238,7 +238,7 @@ const Header = ({user={}, createUser}) => {
 
                     <Box className='Header__Links' sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
                         {pages.map((page) => (
-                            <LinkItem page={page} color={'#fff'}/>
+                            <LinkItem key={page.toString()} page={page} color={'#fff'}/>
                         ))}
                     </Box>
 

BIN
src/img/catalog/imgNotFound.png


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 2 - 2
src/pages/AboutUsPage.jsx


+ 189 - 77
src/pages/CatalogPage.jsx

@@ -1,30 +1,31 @@
-import Header from "../components/Header";
-import Footer from "../components/Footer";
 import Breadcrumb from "../components/Breadcrumbs";
 import {
     Accordion,
     AccordionDetails,
     AccordionSummary, Box, Button,
     Card, CardActionArea, CardActions, CardContent, CardMedia,
-    Container, Divider,
-    Grid,
+    Container, Divider, FormControl,
+    Grid, MenuItem, Select,
     Typography,
     useMediaQuery
 } from "@mui/material";
 import {connect} from "react-redux";
 import {actionFullCatById, actionFullRootCats} from "../actions/ActionCategory";
 import Link from "react-router-dom/es/Link";
-import Page404 from "./404Page";
 import Route from "react-router-dom/es/Route";
 import Switch from "react-router-dom/es/Switch";
 import {useEffect, useState} from "react";
 import {backURL} from "../actions/PathDB";
-import {actionCartAdd} from "../reducers/CartReducer";
+import {actionCardRemove, actionCartAdd} from "../reducers/CartReducer";
 import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
 import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorder";
 import AddShoppingCartIcon from '@mui/icons-material/AddShoppingCart';
 import {Pagination} from "@mui/material";
-
+import {actionWishListAdd, actionWishListRemove} from "../reducers/WishListReducer";
+import FavoriteIcon from '@mui/icons-material/Favorite';
+import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
+import imgUrl from "../img/not-found/1.png";
+import imgNotFound from "../img/catalog/imgNotFound.png";
 
 const CategoryItem = ({object: {_id, name, subCategories}={}}) => {
     const [expanded, setExpanded] = useState(false);
@@ -69,7 +70,7 @@ const CategoryItem = ({object: {_id, name, subCategories}={}}) => {
                         <AccordionDetails>
                             <ul style={{listStyle: 'none', padding: '0 0 0 10px', marginBottom: '10px'}}>
                                 {subCategories && Object.values(subCategories).map(item =>
-                                    <CategoryItem object={item}/>
+                                    <CategoryItem key={item['_id']} object={item}/>
                                 )}
                             </ul>
                         </AccordionDetails>
@@ -81,7 +82,7 @@ const CategoryItem = ({object: {_id, name, subCategories}={}}) => {
 }
 const CategoryAside = ({category}) => {
     return (
-        <Grid sx={{backgroundColor: '#fff', padding: '30px'}} xs={12} lg={3}>
+        <Grid sx={{backgroundColor: '#fff', padding: '30px'}} xs={12} lg={3} item>
             <Typography
                 variant='h6'
                 letterSpacing='3px'
@@ -92,14 +93,14 @@ const CategoryAside = ({category}) => {
             </Typography>
             <ul style={{listStyle: 'none', padding: '0'}}>
                 {category && Object.values(category).map(item =>
-                    <CategoryItem object={item}/>
+                    <CategoryItem key={item['_id']} object={item}/>
                 )}
             </ul>
         </Grid>
     )
 }
 
-const GoodCard = ({good:{_id, name, description, price, images}={}, onCartAdd}) => {
+const GoodCard = ({good:{_id, name, description, price, images}={}, wishlist={}, cart={}, onCartAdd, onWishListAdd, onCartRemove, onWishListRemove}) => {
     return (
         <Grid xs={12} lg={4} item margin='20px 0'>
             <Card sx={{ maxWidth: 345, height: '100%', display: 'flex', flexDirection: 'column', margin: 'auto 20px'}}>
@@ -108,10 +109,10 @@ const GoodCard = ({good:{_id, name, description, price, images}={}, onCartAdd})
                         <CardMedia sx={{marginBottom: '20px', marginTop: '20px'}}
                             component="img"
                             height="230"
-                            image={`${backURL}/${images[0].url}`}
+                            image={images[0].url ? `${backURL}/${images[0].url}` : imgNotFound}
                             alt="Good title image"
                         />
-                        <CardContent sx={{display: 'flex', flexDirection: 'column', height: '180px', justifyContent: 'space-between'}}>
+                        <CardContent sx={{display: 'flex', flexDirection: 'column', height: '200px', justifyContent: 'space-between'}}>
                             <Typography
                                 textAlign='center'
                                 fontFamily='sarif'
@@ -121,7 +122,7 @@ const GoodCard = ({good:{_id, name, description, price, images}={}, onCartAdd})
                                 sx={{textTransform: 'uppercase', flexGrow: '1'}}
                                 color='#000'
                             >
-                               {name}
+                               {name.length > 30 ? name.split(' ').splice(0, 6).join(' ') : name}
                             </Typography>
                             <Typography textAlign='center' variant="body2" color='#616161' marginBottom='20px' sx={{ flexGrow: '0'}}>
                                 {description.length > 60 ?
@@ -136,72 +137,185 @@ const GoodCard = ({good:{_id, name, description, price, images}={}, onCartAdd})
                     </Link>
                 </CardActionArea>
                 <CardActions sx={{flexGrow: '0', justifyContent: 'space-between'}}>
-                    <Button onClick={() => onCartAdd({_id, name, price, images})} size="small" color="primary">
-                        <AddShoppingCartIcon />
+                    <Button onClick={() => {_id in cart ? onCartRemove({_id, name, price, images}) : onCartAdd({_id, name, price, images})}} size="small" color="primary">
+                        {_id in cart ? <ShoppingCartIcon/> : <AddShoppingCartIcon />}
                     </Button>
-                    <Button onClick={() => console.log('nice')} size="small" color="primary">
-                        <FavoriteBorderIcon />
+                    <Button onClick={() => {_id in wishlist ? onWishListRemove({_id, name, price, images}) : onWishListAdd({_id, name, price, images})}} size="small" color="primary">
+                        {_id in wishlist ? <FavoriteIcon/> : <FavoriteBorderIcon />}
                     </Button>
                 </CardActions>
             </Card>
         </Grid>
     )
 }
-const CGoodCard = connect(null, {onCartAdd: actionCartAdd})(GoodCard)
+const CGoodCard = connect(state => ({wishlist: state.wishlist, cart: state.cart}),
+    {onCartAdd: actionCartAdd, onWishListAdd: actionWishListAdd, onCartRemove: actionCardRemove, onWishListRemove: actionWishListRemove})(GoodCard)
+
+const GoodNotFound = () => {
+    const matches2 = useMediaQuery('(max-width:450px)');
+    return (
+        <Container maxWidth="lg">
+            <Box sx={{
+                backgroundColor: "#fff",
+                height: matches2 ? "250px" : "350px",
+                display: "flex",
+                flexDirection: "column",
+                justifyContent: "center",
+                alignItems: "center"
+            }}>
+                <img style={{
+                    maxWidth: matches2 ? "100px" : "150px"
+                }} src={imgUrl} alt="PAGE NOT FOUND"/>
+                <Typography
+                    variant={matches2 ? "h6" : "h5"}
+                    fontFamily="sarif"
+                    fontWeight="300"
+                    marginBottom="20px"
+                    marginTop="20px"
+                    textAlign="center"
+                >
+                    OOPS! THAT PAGE CAN’T BE FOUND
+                </Typography>
+                <Typography
+                    variant={matches2 ? "body1" : "h7"}
+                    textAlign="center"
+                    fontWeight="300"
+                >
+                    The page you are trying to reach is not available.
+                </Typography>
+            </Box>
+        </Container>
+    )
+}
 
 const Goods = ({_id='5dc49f4d5df9d670df48cc64', category={}}) => {
-    // const itemsPerPage = 6
-    // const [page, setPage] = useState(1)
-    // const [count, setCount] = useState(1)
-    // const handleChange = (event, value) => {
-    //     setPage(value);
-    // }
-   return (
-       <>
-           {(Object.values(category) || []).map(item => {
-               if (item['_id'] === _id) {
-                   if(!item.goods || item['goods'] === null) {
-                       return <div>not info</div>
-                   }
-                   if (item.goods) {
-                       // setCount(item['goods'].length / itemsPerPage)
-                       return (item['goods'] || []).map((good, index) => <CGoodCard key={index} good={good}/>)
-                   }
-               }
-               else if(item['subCategories'] !== null) {
-                   return (item['subCategories'] || []).map(subItem => {
-                       if (subItem['_id'] === _id) {
-                           if(subItem['goods'] === null) return <div>not info</div>
-                           // setCount(subItem['goods'].length / itemsPerPage)
-                           return (subItem['goods'] || []).map(good => <CGoodCard good={good}/>)
-                       }
-                       else if(subItem['subCategories'] !== null) {
-                           return (subItem['subCategories'] || []).map(subSubItem => {
-                               if (subSubItem['_id'] === _id) {
-                                   if(subSubItem['goods'] === null) return <div>not info</div>
-                                   // setCount(subSubItem['goods'].length / itemsPerPage)
-                                   return (subSubItem['goods'] || []).map(good => <CGoodCard good={good}/>)
-                               }
-                           })
-                       }
-                   })
-               }
-           })}
-           {/*<Divider />*/}
-           {/*<Box display='flex' justifyContent='center' width='100%'>*/}
-           {/*    <Pagination*/}
-           {/*        count={count}*/}
-           {/*        page={page}*/}
-           {/*        onChange={handleChange}*/}
-           {/*        defaultPage={1}*/}
-           {/*        color="primary"*/}
-           {/*        size="large"*/}
-           {/*        showFirstButton*/}
-           {/*        showLastButton*/}
-           {/*    />*/}
-           {/*</Box>*/}
-       </>
-   )
+    const itemsPerPage = 6
+    const [page, setPage] = useState(1)
+    const [count, setCount] = useState(1)
+    const [goods, setGoods] = useState([])
+    const [sort, setSort] = useState(0)
+
+    const sortDefault = () => {
+        setGoods([goods[0].sort((a, b) => a['name'] > b['name'] ? 1 : -1)])
+    }
+    const sortLatest = () => {
+        setGoods([goods[0].sort((a, b) => b['createdAt'] > a['createdAt'] ? 1 : -1)])
+    }
+    const sortLowToHigh = () => {
+        setGoods([goods[0].sort((a, b) => a['price'] > b['price'] ? 1 : -1)])
+    }
+    const sortHighToLow = () => {
+        setGoods([goods[0].sort((a, b) => b['price'] > a['price'] ? 1 : -1)])
+    }
+
+    const handleChange = (event, value) => {
+        setPage(value);
+    }
+    const handleChangeSelect = (event) => {
+        setSort(event.target.value);
+        if (event.target.value === 0) sortDefault()
+        else if (event.target.value === 1) sortLatest()
+        else if (event.target.value === 2) sortLowToHigh()
+        else if (event.target.value === 3) sortHighToLow()
+    }
+
+    useEffect(() => {
+        let arr = (Object.values(category) || []).map(item => {
+            if (item['_id'] === _id) {
+                if (Array.isArray(item?.goods) && item?.goods.length > 0) {
+                    setCount(Math.ceil(item.goods.length / itemsPerPage))
+                    return item.goods
+                }
+            }
+            else if(Array.isArray(item['subCategories'])) {
+                let arr = item['subCategories'].map(subItem => {
+                    if (subItem['_id'] === _id) {
+                        if (Array.isArray(subItem?.goods) && subItem?.goods.length > 0) {
+                            setCount(Math.ceil(subItem.goods.length / itemsPerPage))
+                            return subItem.goods
+                        }
+                    }
+                    else if(Array.isArray(subItem['subCategories'])) {
+                        let arr = subItem['subCategories'].map(subSubItem => {
+                            if (subSubItem['_id'] === _id) {
+                                if (Array.isArray(subSubItem?.goods && subSubItem?.goods.length > 0)) {
+                                    setCount(Math.ceil(subSubItem.goods.length / itemsPerPage))
+                                    return subSubItem.goods
+                                }
+                            }
+                            else {
+                                return 0
+                            }
+                        }).filter(item => item)
+                        return arr.length > 0 ? [...arr[0]] : 0
+                    }
+                    else {
+                        return 0
+                    }
+                }).filter(item => item)
+                return arr.length > 0 ? [...arr[0]] : 0
+            }
+            else {
+                return 0
+            }
+        }).filter(item => item)
+        setGoods(arr)
+    }, [_id, category])
+
+    return (
+        <>
+            {(goods.length > 0 ?
+                <Box sx={{height:'100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-between'}}>
+                    <Box display='flex' alignItems='center' justifyContent='space-between' padding='0 20px'>
+                        <Typography
+                            variant='body1'
+                            color='#616161'
+                            letterSpacing='1px'
+                        >
+                            SHOWING {goods[0].length > itemsPerPage ? `${((page-1) * itemsPerPage)+1}-${page * itemsPerPage > goods[0].length ? goods[0].length : page * itemsPerPage}` : goods[0].length} OF {goods[0].length} RESULTS
+                        </Typography>
+                        <FormControl variant="standard">
+                            <Select
+                                labelId="demo-simple-select-label"
+                                id="demo-simple-select"
+                                value={sort}
+                                label="Sort"
+                                onChange={handleChangeSelect}
+                                sx={{textTransform: 'uppercase', color: '#616161'}}
+                            >
+                                <MenuItem value={0}>Default sorting</MenuItem>
+                                <MenuItem value={1}>Sort by latest</MenuItem>
+                                <MenuItem value={2}>Sort by price: low to high</MenuItem>
+                                <MenuItem value={3}>Sort by price: high to low</MenuItem>
+                            </Select>
+                        </FormControl>
+                    </Box>
+                    <Box flexGrow='1'>
+                        <Grid container justifyContent='space-between'>
+                            {[...goods[0]].slice((page - 1) * itemsPerPage, page * itemsPerPage)
+                                     .map(good => <CGoodCard key={good['_id']} good={good}/>)}
+                        </Grid>
+                    </Box>
+                    <Box width='100%' flexGrow='0'>
+                        <Divider sx={{margin: '20px'}}/>
+                        <Box display='flex' justifyContent='center' width='100%'>
+                            <Pagination
+                                count={count}
+                                page={page}
+                                onChange={handleChange}
+                                defaultPage={1}
+                                color="primary"
+                                size="large"
+                                showFirstButton
+                                showLastButton
+                            />
+                        </Box>
+                    </Box>
+                </Box>
+                : <GoodNotFound/>)
+            }
+        </>
+    )
 }
 const CGoods = connect(state => ({category: state.category}))(Goods)
 
@@ -210,19 +324,17 @@ const BlockGood = ({match:{params:{_id}}, getData}) => {
         getData(_id)
     },[_id, getData])
     return(
-        <Grid container justifyContent='space-between'>
-            <CGoods _id={_id} />
-        </Grid>
+        <CGoods key={_id} _id={_id} />
     )
 }
-export const CBlockGood= connect(null, {getData: actionFullCatById})(BlockGood)
+const CBlockGood= connect(null, {getData: actionFullCatById})(BlockGood)
 
 const Products = () => {
     return (
-        <Grid xs={12} lg={9}>
+        <Grid xs={12} lg={9} item>
             <Switch>
                 <Route path="/catalog/category/:_id" component={CBlockGood} />
-                <Route path="*" component={Page404} />
+                <Route path="*" component={GoodNotFound} />
             </Switch>
         </Grid>
     )

+ 6 - 6
src/pages/ContactPage.jsx

@@ -90,7 +90,7 @@ function TabPanel(props) {
         >
             {value === index && (
                 <Box sx={{ p: 3 }}>
-                    <Typography>{children}</Typography>
+                    {children}
                 </Box>
             )}
         </div>
@@ -128,7 +128,7 @@ const AddressItem = ({item}) => {
                         >
                             PHONES
                         </Typography>
-                        {item[1].phones.map(i => <Typography lineHeight='1.7em' fontWeight='400' variant="body1">{i.toString()}</Typography>)}
+                        {item[1].phones.map((i, index) => <Typography key={index} lineHeight='1.7em' fontWeight='400' variant="body1">{i.toString()}</Typography>)}
                     </Grid>
                     <Grid marginBottom='30px' item xs={12} sm={6}>
                         <Typography
@@ -148,7 +148,7 @@ const AddressItem = ({item}) => {
                         >
                             EMAIL
                         </Typography>
-                        {item[1].email.map(i => <Typography lineHeight='1.7em' fontWeight='400' variant="body1">{i.toString()}</Typography>)}
+                        {item[1].email.map(i => <Typography key={i.toString()} lineHeight='1.7em' fontWeight='400' variant="body1">{i.toString()}</Typography>)}
                     </Grid>
                     <Grid marginBottom='30px' item xs={12} sm={6}>
                         <Typography
@@ -159,7 +159,7 @@ const AddressItem = ({item}) => {
                             SOCIAL NETWORKS
                         </Typography>
                         {(item[1]["social networks"] || []).map(item =>
-                            <Social Icon={item.icon} link={item.url}/>
+                            <Social key={item.url} Icon={item.icon} link={item.url}/>
                         )}
                     </Grid>
                 </Grid>
@@ -193,13 +193,13 @@ const Contact = ({address=defaultAddress}) => {
                                 centered
                             >
                                 {Object.keys(address).map((value, index) => {
-                                    return <Tab sx={{borderBottom: '1px solid #dedede'}} label={value.toString()} {...a11yProps(index)} />
+                                    return <Tab key={index} sx={{borderBottom: '1px solid #dedede'}} label={value.toString()} {...a11yProps(index)} />
                                 })}
                             </Tabs>
                         </Box>
                         {Object.entries(address).map((item, index) => {
                             return (
-                                <TabPanel value={value} index={index}>
+                                <TabPanel key={index} value={value} index={index}>
                                     <Grid sx={{backgroundColor: '#fff', padding: '0px 0px', marginLeft: '-10px'}} container spacing={2}>
                                         <AddressItem key={index} item={item} />
                                         <Grid sx={{padding: '0px 0px !important', position: 'relative', height: matches ? '300px': '500px'}} item xs={12} md={6}>

+ 2 - 2
src/pages/OurTeamPage.jsx

@@ -124,7 +124,7 @@ const ItemTeam = ({item: {img, name, spec, desc, links, phone, email}, breakpoin
                     {desc}
                 </Typography>
                 <Box marginBottom='30px'>
-                    {links.map(item => <Social Icon={item.icon} link={item.url}/>)}
+                    {links.map(item => <Social key={item.url} Icon={item.icon} link={item.url}/>)}
                 </Box>
                 <Box
                     display='flex'
@@ -172,7 +172,7 @@ const OurTeamPage = ({specialist=defaultTeam}) => {
             <main style={{backgroundColor: "#f3f3f3", padding: matches ? "20px 0" : "50px 0"}}>
                 <Container maxWidth="lg">
                     <Grid sx={{padding: '0px 0px'}} container justifyContent='space-between'>
-                        {(specialist).map(item =>  <ItemTeam item={item} breakpoint={matches}/>)}
+                        {(specialist).map(item => <ItemTeam key={item?.name} item={item} breakpoint={matches}/>)}
                     </Grid>
                 </Container>
             </main>

+ 3 - 1
src/reducers/CombineReducers.js

@@ -4,11 +4,13 @@ import {PromiseReducer} from "./PromiseReducer";
 import {CartReducer} from "./CartReducer";
 import {UserReducer} from "./UserReducer";
 import {CategoryReducer} from "./CategoryReducer";
+import {WishListReducer} from "./WishListReducer";
 
 export const rootReducer = combineReducers({
     auth: AuthReducer,
     promise: PromiseReducer,
     cart: CartReducer,
     user: UserReducer,
-    category: CategoryReducer
+    category: CategoryReducer,
+    wishlist: WishListReducer
 })

+ 30 - 0
src/reducers/WishListReducer.js

@@ -0,0 +1,30 @@
+export const WishListReducer = (state = {}, { type, good = {}}) => {
+    const { _id } = good
+    const types = {
+        WISHLIST_ADD() {
+            return {
+                ...state,
+                [_id]: {
+                    good
+                }
+            }
+        },
+        WISHLIST_REMOVE() {
+            let { [_id]: remove, ...newState } = state
+            return {
+                ...newState
+            }
+        },
+        WISHLIST_CLEAR() {
+            return {}
+        },
+    }
+    if (type in types) {
+        return types[type]()
+    }
+    return state
+}
+
+export const actionWishListAdd = (good) => ({type: "WISHLIST_ADD", good})
+export const actionWishListRemove = (good) => ({type: 'WISHLIST_REMOVE', good})
+export const actionWishListClear = () => ({type: 'WISHLIST_CLEAR'})