Pārlūkot izejas kodu

update prifile page (load image-nick-login-password)

Alex 2 gadi atpakaļ
vecāks
revīzija
550a2d564a

+ 59 - 0
package-lock.json

@@ -26,6 +26,7 @@
         "prop-types": "^15.8.0",
         "react": "^17.0.2",
         "react-dom": "^17.0.2",
+        "react-dropzone": "^11.5.1",
         "react-material-ui-carousel": "^3.1.1",
         "react-redux": "^7.2.6",
         "react-router-dom": "^5.3.0",
@@ -5176,6 +5177,14 @@
         "node": ">= 4.5.0"
       }
     },
+    "node_modules/attr-accept": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+      "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/autoprefixer": {
       "version": "10.4.0",
       "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.0.tgz",
@@ -8323,6 +8332,17 @@
         "webpack": "^4.0.0 || ^5.0.0"
       }
     },
+    "node_modules/file-selector": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.2.4.tgz",
+      "integrity": "sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA==",
+      "dependencies": {
+        "tslib": "^2.0.3"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
     "node_modules/filelist": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz",
@@ -15100,6 +15120,22 @@
         "react": "17.0.2"
       }
     },
+    "node_modules/react-dropzone": {
+      "version": "11.5.1",
+      "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-11.5.1.tgz",
+      "integrity": "sha512-eNhttdq4ZDe3eKbXAe54Opt+sbtqmNK5NWTHf/l5d+1TdZqShJ8gMjBrya00qx5zkI//TYxRhu1d9pemTgaWwg==",
+      "dependencies": {
+        "attr-accept": "^2.2.1",
+        "file-selector": "^0.2.2",
+        "prop-types": "^15.7.2"
+      },
+      "engines": {
+        "node": ">= 10"
+      },
+      "peerDependencies": {
+        "react": ">= 16.8"
+      }
+    },
     "node_modules/react-error-overlay": {
       "version": "6.0.10",
       "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz",
@@ -22296,6 +22332,11 @@
       "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
       "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
     },
+    "attr-accept": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+      "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg=="
+    },
     "autoprefixer": {
       "version": "10.4.0",
       "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.0.tgz",
@@ -24640,6 +24681,14 @@
         "schema-utils": "^3.0.0"
       }
     },
+    "file-selector": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.2.4.tgz",
+      "integrity": "sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA==",
+      "requires": {
+        "tslib": "^2.0.3"
+      }
+    },
     "filelist": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz",
@@ -29447,6 +29496,16 @@
         "scheduler": "^0.20.2"
       }
     },
+    "react-dropzone": {
+      "version": "11.5.1",
+      "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-11.5.1.tgz",
+      "integrity": "sha512-eNhttdq4ZDe3eKbXAe54Opt+sbtqmNK5NWTHf/l5d+1TdZqShJ8gMjBrya00qx5zkI//TYxRhu1d9pemTgaWwg==",
+      "requires": {
+        "attr-accept": "^2.2.1",
+        "file-selector": "^0.2.2",
+        "prop-types": "^15.7.2"
+      }
+    },
     "react-error-overlay": {
       "version": "6.0.10",
       "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz",

+ 1 - 0
package.json

@@ -21,6 +21,7 @@
     "prop-types": "^15.8.0",
     "react": "^17.0.2",
     "react-dom": "^17.0.2",
+    "react-dropzone": "^11.5.1",
     "react-material-ui-carousel": "^3.1.1",
     "react-redux": "^7.2.6",
     "react-router-dom": "^5.3.0",

+ 70 - 0
src/actions/ActionUploadFile.js

@@ -0,0 +1,70 @@
+import {actionPromise} from "../reducers/PromiseReducer";
+import {backURL, gql} from "./PathDB";
+import {actionFullUserFindOne} from "./ActionUserFind";
+
+const actionUploadFile = file => {
+    let fd = new FormData()
+    fd.append('photo',file)
+    return actionPromise('uploadFile', fetch(`${backURL}/upload`, {
+        method: "POST",
+        headers: localStorage.authToken ? {Authorization: 'Bearer ' + localStorage.authToken} : {},
+        body: fd
+    }).then(res => res.json(), err => console.log(err)))
+}
+
+export const actionSetAvatar = file =>
+     async (dispatch, getState) => {
+         let result = await dispatch(actionUploadFile(file))
+         if (result) {
+             let value = await dispatch(actionPromise('setAvatar', gql(`mutation setAvatar($myid: String, $imageid: ID){
+                    UserUpsert(user:{
+                                    _id: $myid, 
+                                    avatar: {_id: $imageid}})
+                    {
+                        _id, avatar {
+                            _id
+                        }
+                    }
+                }`, {myid: getState().user._id, imageid: result._id})))
+             if (value){
+                 await dispatch(actionFullUserFindOne(getState().user._id))
+             }
+         }
+     }
+
+
+export const actionSetLogin = login =>
+    async (dispatch, getState) => {
+        let value = await dispatch(actionPromise('setNewLogin', gql(`mutation setNewLogin($myid: String, $login: String){
+                    UserUpsert(user:{_id: $myid, login: $login}){
+                        _id
+                    }
+                }`, {myid: getState().user._id, login: login})))
+        if (value){
+            await dispatch(actionFullUserFindOne(getState().user._id))
+        }
+    }
+
+export const actionSetNick = nick =>
+    async (dispatch, getState) => {
+        let value = await dispatch(actionPromise('setNewNick', gql(`mutation setNewNick($myid: String, $nick: String){
+                    UserUpsert(user:{_id: $myid, nick: $nick}){
+                        _id
+                    }
+                }`, {myid: getState().user._id, nick: nick})))
+        if (value){
+            await dispatch(actionFullUserFindOne(getState().user._id))
+        }
+    }
+
+export const actionSetPassword = password =>
+    async (dispatch, getState) => {
+        let value = await dispatch(actionPromise('setNewPassword', gql(`mutation setNewPassword($myid: String, $password: String){
+                    UserUpsert(user:{_id: $myid, password: $password}){
+                        _id
+                    }
+                }`, {myid: getState().user._id, password: password})))
+        if (value){
+            await dispatch(actionFullUserFindOne(getState().user._id))
+        }
+    }

+ 2 - 3
src/components/Header.jsx

@@ -12,10 +12,9 @@ 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', 'Admin']
+const settingsDefaultUserAuth = ['Profile', 'Admin', 'Logout']
 
 const Header = ({user={}}) => {
     const [anchorElNav, setAnchorElNav] = useState(null);
@@ -162,7 +161,7 @@ const Header = ({user={}}) => {
         return (
             <Button
                 onClick={() => {actionLogOut(); actionUserRemove(); handleCloseNavMenu();}}
-                style={{textDecoration: 'none', color: '#fff', textAlign: "center", width: '100%'}}
+                style={{textDecoration: 'none', textTransform: 'capitalize', fontWeight: '400', color: '#fff', textAlign: "center", width: '100%'}}
             >
                 Log out
             </Button>

+ 112 - 44
src/pages/CartPage.jsx

@@ -1,7 +1,7 @@
 import Breadcrumb from "../components/Breadcrumbs";
 import {connect} from "react-redux";
 import {actionCardChange, actionCardClear, actionCardRemove} from "../reducers/CartReducer";
-import {ActionFullOrder, ActionOrder} from "../actions/ActionOrder";
+import {ActionFullOrder} 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";
@@ -9,6 +9,8 @@ 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";
+import {actionClearOrder} from "../reducers/PromiseReducer";
+import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
 
 const CartGoodLine = ({item, onCartRemove, onCardChange}) => {
     let [count, setCount] = useState(item?.count)
@@ -58,59 +60,125 @@ const TotalPriceLine = ({title, subtitle, sizeSubtitle='body2'}) => {
     )
 }
 
-const CartPage = ({cart, onCardChange, onCartClear, onCartRemove, onOrderUpsert}) => {
+const BlockTotal = ({auth ,cart, rows, onOrderUpsert, onCartClear}) => {
+    return (
+        <>
+            <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); onCartClear()}}
+                        disabled={Object.entries(auth).length === 0}
+                >
+                    {Object.entries(auth).length === 0 ? 'you need to log in' : 'confirm the order'}
+                </Button>
+                <Button sx={{borderRadius: '0', width:'80%', padding: '10px 20px'}}
+                        color='warning'
+                        variant="outlined"
+                        onClick={() => onCartClear()}
+                >
+                    cart clear
+                </Button>
+            </Box>
+        </>
+    )
+}
+const CBlockTotal = connect(state=>({auth: state.auth}))(BlockTotal)
+
+const CartPage = ({order, cart, onCardChange, onCartClear, onCartRemove, onOrderUpsert, actionClearOrder}) => {
     const matches = useMediaQuery('(max-width:768px)')
     let rows = []
     for (const key of Object.values(cart)) {
         rows.push(key)
     }
+    useEffect(() => {
+        if (order && Object.entries(order).length > 0) {
+            actionClearOrder('order')
+        }
+    },[cart])
+
+
     return (
         <>
             <Breadcrumb links={['cart']}/>
-            {Object.values(cart).length > 0 ?
+            {Object.values(cart).length > 0 || order ?
                 <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>
+                            {order && Object.entries(order).length > 0 ?
+                                <Box display='flex' height='300px' flexDirection='column' alignItems='center' justifyContent='space-around'>
+                                    {order.error ?
+                                        <Typography variant='h5' textAlign='center'>Error, try again</Typography> :
+                                        <>
+                                            <Typography
+                                                variant='h5'
+                                                textAlign='center'
+                                            >
+                                                Order successfully completed
+                                            </Typography>
+                                            <CheckCircleOutlineIcon/>
+                                            <Typography
+                                                variant='h4'
+                                                textAlign='center'
+                                                fontFamily='sarif'
+                                                letterSpacing='2px'
+                                                sx={{textTransform: 'uppercase'}}
+                                            >
+                                                Thanks for your order!
+                                            </Typography>
+                                            <Typography
+                                                variant='body1'
+                                                textAlign='center'
+                                                color="#616161"
+                                            >
+                                                Attention! Shipping is paid separately upon receipt of the goods.
+                                            </Typography>
+                                            <Typography
+                                                variant='body1'
+                                                textAlign='center'
+                                                color="#616161"
+                                            >
+                                                Your order number: {order.payload?._id || 1}
+                                            </Typography>
+                                            <Typography
+                                                variant='body1'
+                                                textAlign='center'
+                                                color="#616161"
+                                            >
+                                                For the amount: ${+order.payload?.total || 0}
+                                            </Typography>
+                                        </>
+                                    }
+                                </Box>:
+                                <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'>
+                                        <CBlockTotal cart={cart} rows={rows} onCartClear={onCartClear}
+                                                     onOrderUpsert={onOrderUpsert}/>
+                                    </Grid>
                                 </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>}/>
@@ -118,5 +186,5 @@ const CartPage = ({cart, onCardChange, onCartClear, onCartRemove, onOrderUpsert}
         </>
     )
 }
-const CCartPage = connect(state=>({cart: state.cart}), {onCardChange: actionCardChange, onCartClear: actionCardClear, onCartRemove: actionCardRemove, onOrderUpsert: ActionFullOrder})(CartPage)
+const CCartPage = connect(state=>({cart: state.cart, order: state.promise?.order}), {onCardChange: actionCardChange, onCartClear: actionCardClear, onCartRemove: actionCardRemove, onOrderUpsert: ActionFullOrder, actionClearOrder: actionClearOrder})(CartPage)
 export default CCartPage

+ 1 - 1
src/pages/CatalogPage.jsx

@@ -151,7 +151,7 @@ const GoodCard = ({good:{_id, name, description, price, images}={}, wishlist={},
 const CGoodCard = connect(state => ({wishlist: state.wishlist, cart: state.cart}),
     {onCartAdd: actionCartAdd, onWishListAdd: actionWishListAdd, onCartRemove: actionCardRemove, onWishListRemove: actionWishListRemove})(GoodCard)
 
-const Goods = ({_id='5dc49f4d5df9d670df48cc64', category={}}) => {
+const Goods = ({_id, category={}}) => {
     const itemsPerPage = 6
     const [page, setPage] = useState(1)
     const [count, setCount] = useState(1)

+ 42 - 36
src/pages/MyOrdersPage.jsx

@@ -53,11 +53,11 @@ const AccordionItem = ({data}) => {
                     {!status && <Box sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '200px'}}>
                         {data['orderGoods'] && data['orderGoods'].map((item, index, array) => {
                             if (index < 2) {
-                                return <Box sx={{width:'60px', height:'60px'}}>
-                                            <img src={item?.good?.images && Array.isArray(item.good.images) ? `${backURL}/${item.good.images[0].url}` : imgNotFound}
-                                                 alt={'image'}
-                                                 style={{width: '100%', height: '100%', objectFit:'cover'}}
-                                            />
+                                {console.log(item.good.images[0].url)}
+                                return <Box minWidth='60px' maxWidth='60px' height='60px' borderRadius='10px' overflow='hidden' marginRight='20px'>
+                                            <img style={{width: '100%', height: '100%', objectFit: 'cover'}}
+                                                 src={item.good.images && item.good.images[0].url ? backURL + '/' + item.good.images[0].url : imgNotFound}
+                                                 alt={'image'}/>
                                         </Box>
                             }
                             else if (index === 2) {
@@ -119,11 +119,10 @@ const AccordionItem = ({data}) => {
                             return <Grid container alignItems='center' marginBottom='20px'>
                                 <Grid item md={7}>
                                     <Link style={{textDecoration: 'none', display: 'flex', alignItems: 'center', color: '#616161'}} to={`/good/${item?.good?._id}`}>
-                                        <Box sx={{width:'60px', height:'60px'}} marginRight='10px'>
-                                            <img src={item?.good?.images && Array.isArray(item.good.images) ? `${backURL}/${item.good.images[0].url}` : imgNotFound}
-                                                 alt={'image'}
-                                                 style={{width: '100%', height: '100%', objectFit:'cover'}}
-                                            />
+                                        <Box minWidth='60px' maxWidth='60px' height='60px' borderRadius='10px' overflow='hidden' marginRight='20px'>
+                                            <img style={{width: '100%', height: '100%', objectFit: 'cover'}}
+                                                 src={item.good.images && item.good.images[0].url ? backURL + '/' + item.good.images[0].url : imgNotFound}
+                                                 alt={'image'}/>
                                         </Box>
                                         <Typography
                                             variant='body1'
@@ -187,8 +186,7 @@ const AccordionItem = ({data}) => {
     )
 }
 
-const MyOrdersPage = ({orders, onFindOrders, onOrdersClear}) => {
-    const itemsPerPage = 10
+const MainOrders = ({itemsPerPage=10, orders, onFindOrders, onOrdersClear}) => {
     const [page, setPage] = useState(1)
     const [count, setCount] = useState(1)
     const handleChange = (event, value) => {
@@ -207,39 +205,47 @@ const MyOrdersPage = ({orders, onFindOrders, onOrdersClear}) => {
 
     if(Object.entries(orders).length === 0) onFindOrders()
 
+    return (
+        <>
+            {Object.entries(orders).length === 0 ?
+                <Box sx={{height: '100%', width: '100%', display: 'flex', justifyContent:'center', alignItems:'center'}}><CircularProgress color="inherit"/></Box> :
+                Object.entries(orders?.orderResult).length > 0 ?
+                    <Box>
+                        {Object.values(orders.orderResult).slice((page - 1) * itemsPerPage, page * itemsPerPage).map(item => <AccordionItem data={item}/>)}
+                        <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>
+                    :
+                    <NotFoundBlock headerText={'OOPS! ORDERS CAN’T BE FOUND'} text={'No order has been made yet.'}/>
+            }
+        </>
+    )
+}
+export const CMainOrders = connect(state=>({orders: state.myorders}), {onFindOrders: actionFullOrderFind, onOrdersClear: actionMyOrderClear})(MainOrders)
+
+const MyOrdersPage = () => {
     const matches = useMediaQuery('(max-width:768px)')
     return (
         <>
             <Breadcrumb links={['My orders']} />
             <main style={{backgroundColor: "#f3f3f3", padding: matches ? "20px 0" : "50px 0", minHeight:'300px'}}>
                 <Container maxWidth="lg" sx={{position:'relative'}}>
-                    {Object.entries(orders).length === 0 ?
-                        <Box sx={{height: '100%', width: '100%', display: 'flex', justifyContent:'center', alignItems:'center'}}><CircularProgress color="inherit"/></Box> :
-                        Object.entries(orders?.orderResult).length > 0 ?
-                            <Box>
-                                {Object.values(orders.orderResult).slice((page - 1) * itemsPerPage, page * itemsPerPage).map(item => <AccordionItem data={item}/>)}
-                                <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>
-                        :
-                        <NotFoundBlock headerText={'OOPS! ORDERS CAN’T BE FOUND'} text={'No order has been made yet.'}/>
-                    }
+                    <CMainOrders />
                 </Container>
             </main>
         </>
     )
 }
-const CMyOrdersPage = connect(state=>({orders: state.myorders}), {onFindOrders: actionFullOrderFind, onOrdersClear: actionMyOrderClear})(MyOrdersPage)
 
-export default CMyOrdersPage
+export default MyOrdersPage

+ 92 - 23
src/pages/ProfilePage.jsx

@@ -4,7 +4,7 @@ import {Avatar, Box, Button, Container, Grid, TextField, Typography, useMediaQue
 import Redirect from "react-router-dom/es/Redirect";
 import Tabs from "@mui/material/Tabs";
 import Tab from "@mui/material/Tab";
-import {useState} from "react";
+import {createRef, useCallback, useEffect, useState} from "react";
 import PropTypes from "prop-types";
 import ManageAccountsIcon from '@mui/icons-material/ManageAccounts';
 import CancelIcon from '@mui/icons-material/Cancel';
@@ -12,6 +12,17 @@ import SendAndArchiveIcon from '@mui/icons-material/SendAndArchive';
 import {actionAuthLogout} from "../reducers/AuthReducer";
 import {actionUserRemove} from "../reducers/UserReducer";
 import {backURL} from "../actions/PathDB";
+import {CMainOrders} from "./MyOrdersPage";
+import {CMainWishList} from "./WishListPage";
+import Dropzone, {useDropzone} from 'react-dropzone';
+import {
+    actionFullUserUpdate,
+    actionSetAvatar,
+    actionSetLogin,
+    actionSetNick, actionSetPassword,
+    actionUploadFile
+} from "../actions/ActionUploadFile";
+import {actionFullUserFindOne} from "../actions/ActionUserFind";
 
 function TabPanel(props) {
     const { children, value, index, ...other } = props;
@@ -68,21 +79,79 @@ const ItemTabsAccountDefault = ({title, content}) => {
         </Grid>
     )
 }
-const FormUpload = ({user, time, setStatus}) => {
+const MyDropzone = ({onLoad}) => {
+    const [files, setFiles] = useState([]);
+
+    const {getRootProps, getInputProps, isDragActive} = useDropzone({accept: 'image/*', onDrop: acceptedFiles => {
+            setFiles(acceptedFiles.map(file => Object.assign(file, {
+                preview: URL.createObjectURL(file)
+            })));
+        }})
+
+    const thumbs = files.map(file => (
+        <div key={file.name} style={{display: 'inline-flex', borderRadius: 2,border: '1px solid #eaeaea',marginBottom: 8, marginRight: 8, width: 500, height: 500, padding: 4, boxSizing: 'border-box'}}>
+            <div style={{display: 'flex', minWidth: 0, overflow: 'hidden'}}>
+                <img src={file.preview} style={{display: 'block', width: 'auto', height: '100%', objectFit: 'cover', objectPosition: 'center center'}} alt={'avatar'}/>
+            </div>
+        </div>
+    ));
+
+    useEffect(() => {
+        files.forEach(file => URL.revokeObjectURL(file.preview));
+        onLoad(files)
+    }, [files]);
+
     return (
-        <form action="/upload" method="post" encType="multipart/form-data" id='formProfile'>
-            <Grid container spacing={2} justifyContent={'space-between'} alignItems={'center'} textAlign={'center'}>
-                <TextField sx={{color: '#000'}} label={'Login'} variant="outlined" value={user?.login}/>
-                <TextField sx={{color: '#000'}} label={'Nick'} variant="outlined" value={user?.nick} />
-                <Button variant="contained" component="Avatar">Choose a new avatar<input type="file" hidden/></Button>
-                <ItemTabsAccountDefault title={'Status account'} content={user?.acl[2] || user?.acl[1]}/>
-                <ItemTabsAccountDefault title={'Account creation date'} content={time}/>
-                <Grid item xs={12} md={4} display='flex' justifyContent={'center'} alignItems={'center'}>
+        <section style={{display: 'flex', flexDirection: 'column', justifyContent: 'space-between', alignItems: 'center', width:"100%", borderRadius: '20px', padding: '20px'}}>
+            <div style={{width:"100%", height: "100%", border: '1px dashed #616161', borderRadius: '20px', padding: '20px'}} {...getRootProps()}>
+                <input {...getInputProps()} />
+                {isDragActive ?
+                    <Typography variant='body1' color='#616161'>Drop the file here ...</Typography> :
+                    <Typography variant='body1' color='#616161'>Drag 'n' drop image files here, or click to select file</Typography>
+                }
+                <aside>
+                    {thumbs}
+                </aside>
+            </div>
+        </section>
+    )
+}
+
+const FormUpload = ({user, setStatus, setLogin, setNick, setPassword, setImage}) => {
+    const [loginValue, setLoginValue] = useState(user?.login || '')
+    const [nickValue, setNickValue] = useState(user?.nick || '')
+    const [passwordValue, setPasswordValue] = useState('')
+    const [fileValue, setFileValue] = useState('')
+    return (
+            <Grid container spacing={2} justifyContent={'center'} textAlign='center' flexDirection='column'>
+                <Grid item xs={12} display='flex' justifyContent='space-between' alignItems='center' marginBottom='30px'>
+                    <TextField sx={{color: '#000'}} label={'Login'} variant="outlined" placeholder={user?.login || ''} onChange={e => setLoginValue(e.target.value)}/>
+                    <TextField sx={{color: '#000'}} label={'Nick'} variant="outlined" placeholder={user?.nick || ''} onChange={e => setNickValue(e.target.value)}/>
+                    <TextField sx={{color: '#000'}} label={'Password'} type='password' variant="outlined" onChange={e => setPasswordValue(e.target.value)}/>
+                </Grid>
+                <Grid item xs={12} display='flex' justifyContent='space-between' alignItems='center' marginBottom='30px'>
+                    <MyDropzone onLoad={value => setFileValue(value)}/>
+                </Grid>
+                <Grid item xs={12} display='flex' justifyContent='center' alignItems='center'>
                     <Button
                         style={{ color: '#1976d2'}}
                         fullWidth
                         type='submit'
-                        marginRight='20px'
+                        onClick={() => {
+                            if (loginValue !== user?.login) {
+                                setLogin(loginValue)
+                            }
+                            if (nickValue !== user?.nick) {
+                                setNick(nickValue)
+                            }
+                            if (passwordValue){
+                                setPassword(passwordValue)
+                            }
+                            if (Array.isArray(fileValue) && fileValue[0]) {
+                                setImage(fileValue[0]);
+                            }
+                            setStatus(false)
+                        }}
                     >
                         <SendAndArchiveIcon style={{marginRight: '5px'}}/>
                         Save
@@ -91,17 +160,17 @@ const FormUpload = ({user, time, setStatus}) => {
                         style={{ color: '#1976d2'}}
                         fullWidth
                         onClick={() => setStatus(false)}
-                        marginRight='20px'
                     >
                         <CancelIcon style={{marginRight: '5px'}}/>
                         Cancel
                     </Button>
                 </Grid>
             </Grid>
-        </form>
     )
 }
-const AccountDetails = ({user, time}) => {
+const CFormUpload = connect(null, {setLogin: actionSetLogin, setNick: actionSetNick, setPassword: actionSetPassword, setImage: actionSetAvatar, userUpdate: actionFullUserFindOne})(FormUpload)
+
+const AccountDetails = ({promise, user, time}) => {
     const [status, setStatus] = useState(false)
 
     return (
@@ -113,8 +182,7 @@ const AccountDetails = ({user, time}) => {
                 <ItemTabsAccountDefault title={'Account creation date'} content={time}/>
                 <ItemTabsAccountDefault title={'Avatar'}
                                  content={
-                                     // eslint-disable-next-line no-mixed-operators
-                                     (user?.avatar?.url && <Avatar style={{margin: '0 auto'}} alt="User" src={backURL + '/' + user?.avatar?.url}/> || 'Not installed')
+                                     (user?.avatar?.url ? <Avatar style={{margin: '0 auto'}} alt="User" src={backURL + '/' + user?.avatar?.url}/> : 'Not installed')
                                  }
                 />
                 <Grid item xs={12} md={4}>
@@ -131,12 +199,13 @@ const AccountDetails = ({user, time}) => {
                         Edit data
                     </Typography>
                 </Grid>
+                {!promise.setNewLogin?.payload && promise.setNewLogin?.status === 'RESOLVED' && <Typography width='100%' textAlign='center' color='red'>this login already exists</Typography>}
             </Grid> :
-            <FormUpload user={user} time={time} setStatus={value => setStatus(value)}/>
+            <CFormUpload user={user} setStatus={value => setStatus(value)}/>
     )
 }
 
-const ProfilePage = ({user = {}, authLogOut, userLogOut}) => {
+const ProfilePage = ({user = {}, promise, authLogOut, userLogOut}) => {
     const matches = useMediaQuery('(max-width:899px)')
     const matches2 = useMediaQuery('(max-width:768px)')
     const [value, setValue] = useState(0)
@@ -149,7 +218,7 @@ const ProfilePage = ({user = {}, authLogOut, userLogOut}) => {
     if (Object.keys(user).length !== 0) {
         let date = new Date(+user.createdAt);
         let year = date.getFullYear();
-        let month = "0" + date.getMonth();
+        let month = "0" + (date.getMonth()+1);
         let day = "0" + date.getDate();
         let hours = "0" + date.getHours();
         let minutes = "0" + date.getMinutes();
@@ -192,13 +261,13 @@ const ProfilePage = ({user = {}, authLogOut, userLogOut}) => {
                                 <Button onClick={() => {authLogOut(); userLogOut()}}>Logout</Button>
                             </Tabs>
                             <TabPanel value={value} index={0}>
-                                <AccountDetails user={user} time={formattedTime}/>
+                                <AccountDetails user={user} promise={promise} time={formattedTime}/>
                             </TabPanel>
                             <TabPanel value={value} index={1}>
-                                my orders
+                                <CMainOrders itemsPerPage={5}/>
                             </TabPanel>
                             <TabPanel value={value} index={2}>
-                                wish list
+                                <CMainWishList color={'#fff'}/>
                             </TabPanel>
                         </Box>
                     </Container>
@@ -206,6 +275,6 @@ const ProfilePage = ({user = {}, authLogOut, userLogOut}) => {
             </>
     )
 }
-const CProfilePage = connect(state => ({user: state.user}), {authLogOut: actionAuthLogout, userLogOut: actionUserRemove})(ProfilePage)
+const CProfilePage = connect(state => ({user: state.user, promise: state.promise}), {authLogOut: actionAuthLogout, userLogOut: actionUserRemove})(ProfilePage)
 
 export default CProfilePage

+ 21 - 14
src/pages/WishListPage.jsx

@@ -8,7 +8,7 @@ import imgUrl from "../img/not-found/2.png"
 import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorder";
 import {TableLine} from "../components/TableLine";
 
-const WishListPage = ({wishlist, addToCart, onWishListRemove}) => {
+const MainWishList = ({wishlist, addToCart, onWishListRemove, color='#f3f3f3'}) => {
     const matches = useMediaQuery('(max-width:899px)')
     let rows = []
     for (const key of Object.values(wishlist)) {
@@ -16,25 +16,32 @@ const WishListPage = ({wishlist, addToCart, onWishListRemove}) => {
             rows.push(key[item])
         }
     }
-    console.log(rows)
     return (
         <>
-            <Breadcrumb links={['wish list']}/>
             {Object.values(wishlist).length > 0 ?
-                <main style={{backgroundColor: "#f3f3f3", padding: matches ? "20px 0" : "50px 0"}}>
-                    <Container maxWidth="lg">
-                        <TableLine columnName={['PRODUCT', 'PRICE', 'REMOVE', 'ADD TO CART']}/>
-                        <Divider sx={{marginBottom: '20px'}}/>
-                        {rows.map(item => <TableLine columnName={[[item?._id, item?.name, item?.images], item?.price, onWishListRemove, addToCart]} role={'item'}/>)}
-                        <Divider/>
-                    </Container>
-                </main> :
-                <NotFoundBlock img={imgUrl} headerText={'YOUR WISHLIST IS CURRENTLY EMPTY'} text={<Box display='flex' alignItems='center'><Typography component='span'>Click the</Typography><FavoriteBorderIcon sx={{margin: '0 10px'}}/><Typography component='span'>icons to add products</Typography></Box>}/>
+                    <main style={{backgroundColor: {color}, padding: matches ? "20px 0" : "50px 0"}}>
+                        <Container maxWidth="lg">
+                            <TableLine columnName={['PRODUCT', 'PRICE', 'REMOVE', 'ADD TO CART']}/>
+                            <Divider sx={{marginBottom: '20px'}}/>
+                            {rows.map(item => <TableLine columnName={[[item?._id, item?.name, item?.images], item?.price, onWishListRemove, addToCart]} role={'item'}/>)}
+                            <Divider/>
+                        </Container>
+                    </main> :
+                    <NotFoundBlock img={imgUrl} headerText={'YOUR WISHLIST IS CURRENTLY EMPTY'} text={<Box display='flex' alignItems='center'><Typography component='span'>Click the</Typography><FavoriteBorderIcon sx={{margin: '0 10px'}}/><Typography component='span'>icons to add products</Typography></Box>}/>
             }
         </>
     )
 }
-const CWishListPage = connect(state => ({wishlist: state.wishlist}), {addToCart: actionCardChange, onWishListRemove: actionWishListRemove})(WishListPage)
+export const CMainWishList = connect(state => ({wishlist: state.wishlist}), {addToCart: actionCardChange, onWishListRemove: actionWishListRemove})(MainWishList)
+
+const WishListPage = () => {
+    return (
+        <>
+            <Breadcrumb links={['wish list']}/>
+            <CMainWishList/>
+        </>
+    )
+}
 
-export default CWishListPage
+export default WishListPage
 // TODO MOBILE VERSION

+ 7 - 0
src/reducers/PromiseReducer.js

@@ -5,12 +5,19 @@ export const PromiseReducer = (state = {}, { type, status, payload, error, name
             [name]: { status, payload, error }
         }
     }
+    if (type === 'PROMISE_REMOVE') {
+        let { [name]: remove, ...newState } = state
+        return {
+            ...newState
+        }
+    }
     return state
 }
 
 const actionPending = name => ({ type: 'PROMISE', status: 'PENDING', name })
 const actionResolved = (name, payload) => ({ type: 'PROMISE', status: 'RESOLVED', name, payload })
 const actionRejected = (name, error) => ({ type: 'PROMISE', status: 'REJECTED', name, error })
+export const actionClearOrder = (name) => ({ type: 'PROMISE_REMOVE', status: 'RESOLVED', name})
 
 export const actionPromise = (name, promise) =>
     async dispatch => {