unknown 3 lat temu
rodzic
commit
61613f7fc9

Plik diff jest za duży
+ 1 - 1
.eslintcache


+ 1 - 1
src/components/HomePage/LeftBar/AddContact/index.tsx

@@ -45,7 +45,7 @@ const AddContact = ({setIMenu}:IAddContact) => {
   const handleAddContact = async () => {
     dispatch(asyncAddContact(number))
     setNumber('')
-    setIMenu(2)  
+    setIMenu(1)  
   }
 
   const isValidNumber = () => {

+ 272 - 0
src/components/HomePage/LeftBar/ChatsList/ChatItem/index.tsx

@@ -0,0 +1,272 @@
+import { makeStyles,Typography } from '@material-ui/core'
+import { useState } from 'react';
+import { useDispatch } from 'react-redux';
+import { styled } from '@mui/material/styles';
+import Menu from '@mui/material/Menu';
+import MenuItem from '@mui/material/MenuItem';
+import NotificationsNoneIcon from '@mui/icons-material/NotificationsNone';
+import VolumeOffIcon from '@mui/icons-material/VolumeOff';
+import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
+import ListItemButton from '@mui/material/ListItemButton';
+import Avatar from '@mui/material/Avatar';
+import ListItemText from '@mui/material/ListItemText';
+import ListItemIcon from '@mui/material/ListItemIcon';
+import Badge from '@mui/material/Badge';
+import DoneAllIcon from '@mui/icons-material/DoneAll';
+
+import { muteChat,removeChatForBoth } from '../../../../../api-data';
+import { TChat } from '../../../../../typescript/redux/chats/types';
+import { firstLetter,slicedWord,timeStampEU } from '../../../../../helpers';
+
+const StyledMenu = styled((props:any) => (
+  <Menu
+    elevation={0}
+    anchorOrigin={{
+      vertical: 'top',
+      horizontal: 'right',
+    }}
+    transformOrigin={{
+      vertical: 'bottom',
+      horizontal: 'right',
+    }}
+    {...props}
+  />
+))(({ theme }:any) => ({
+  '& .MuiPaper-root': {
+    borderRadius: 10,
+    marginTop: theme.spacing(0),
+    minWidth: 220,
+    color:
+      theme.palette.mode === 'light' ? 'rgb(55, 65, 81)' : theme.palette.grey[500],
+    boxShadow:
+      'rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px',
+    '& .MuiMenu-list': {
+      padding: '14px 14px',
+    },
+    '& .MuiMenuItem-root': {
+      marginBottom: theme.spacing(1),
+      '& .MuiSvgIcon-root': {
+        fontSize: 21,
+        color: theme.palette.text.secondary,
+        marginRight: theme.spacing(4),
+      }
+    },
+  },
+}));
+
+const StyledBadge = styled(Badge)(({ theme }) => ({
+  '& .MuiBadge-badge': {
+    backgroundColor: '#44b700',
+    color: '#44b700',
+    boxShadow: `0 0 0 2px ${theme.palette.background.paper}`,
+    '&::after': {
+      position: 'absolute',
+      top: 0,
+      left: 0,
+      width: '100%',
+      height: '100%',
+      borderRadius: '50%',
+      animation: 'ripple 1.2s infinite ease-in-out',
+      border: '1px solid currentColor',
+      content: '""',
+    },
+  },
+  '@keyframes ripple': {
+    '0%': {
+      transform: 'scale(.8)',
+      opacity: 1,
+    },
+    '100%': {
+      transform: 'scale(2.4)',
+      opacity: 0,
+    },
+  },
+}));
+
+const useStyles = makeStyles({
+  listItemInnerText: {
+    display: 'flex',
+    alignContent: 'center',
+    alignItems: 'center',
+    flexWrap: 'nowrap',
+  },
+  listItemInnerText__icon: {
+    marginLeft: 5,
+    color: '#959595',
+  },
+  listItem_iconAvatar: {
+    marginRight:10
+  },
+  listItem_iconRight: {
+    marginRight: 10,
+    display: 'flex',
+    alignItems: 'center',
+    justifyContent: 'center',
+    alignContent: 'center',
+    flexDirection: 'column'
+  },
+  listItem_iconTimeChecked: {
+    display: 'flex',
+    flexWrap: 'nowrap',
+    alignItems: 'center',
+    justifyContent: 'center',
+    alignContent: 'center',
+    marginBottom:2
+  },
+  listItem_iconRightBtn: {
+    background: '#0ac40a',
+    borderRadius: '50%',
+    color: '#ffffff',
+    border: 'none',
+    height: 24,
+    width: 24,
+    textAlign: 'center',
+    display: 'flex',
+    alignItems: 'center',
+    justifyContent: 'center',
+    alignContent: 'center',
+    fontSize: 12,
+    marginLeft: 'auto',
+    '&:hover': {
+      outline: 'solid 3px #3ee415',
+    }
+  },
+    listItem_iconRightBtnMute: {
+    background: '#a7aaa7',
+    borderRadius: '50%',
+    color: '#ffffff',
+    border: 'none',
+    height: 24,
+    width: 24,
+    textAlign: 'center',
+    display: 'flex',
+    alignItems: 'center',
+    justifyContent: 'center',
+    alignContent: 'center',
+    fontSize: 12,
+    marginLeft: 'auto',
+    '&:hover': {
+      outline: 'solid 3px #cccbcb',
+    }
+  },
+  listItem_iconRightBtnHidden: {
+    background: 'inherit',
+    borderRadius: '50%',
+    border: 'none',
+    height: 24,
+    width: 24,
+    textAlign: 'center',
+    display: 'flex',
+    alignItems: 'center',
+    justifyContent: 'center',
+    alignContent: 'center',
+    fontSize: 12,
+    marginLeft: 'auto', 
+  },
+  listItem_icon_time: {
+    fontSize: 12,
+    marginLeft: 5,
+    color: '#1b1b1b'
+  },
+  listItem_typing: {
+    color: '#4d4d4d',
+    animation: 'ripple 4s infinite ease-in-out',   
+  },  
+  listItem_dots: {
+    color: '#1b1b1b',
+    fontWeight: 'bold',
+    display:'inline-block',
+    fontFamily: 'monospace',
+    clipPath: 'inset(0 3ch 0 0)',
+    animation: `$run 2s steps(5) infinite`,   
+  },
+  '@keyframes run': {
+    to: {
+       clipPath: 'inset(0 -1ch 0 0)'
+    },
+  },
+})
+
+interface IChatItem {
+  chat: TChat,
+  selectedIndex:null | number,
+  i: number,
+  handleListItemClick: (i: number, companionId: string) => void,
+  handleNewMsgs: (e: any, i: number, companionId: string) => void,
+  setSelectedIndex: (i: null | number) => void,
+}
+const  ChatItem = ({chat,selectedIndex,i,handleListItemClick,handleNewMsgs,setSelectedIndex}:IChatItem) => {
+  const classes = useStyles()
+  const [anchorEl, setAnchorEl] = useState<any>(null);
+  const open = Boolean(anchorEl);
+  const { name, lastName, avatarUrl, color, companionId, mute, seen, total, watched,
+    typing, online, lastMessage, lastMessageCreatedAt, createdAt } = chat
+
+  const handleClose = (type: string | undefined): void => {
+    if (type === 'mute') muteChat(companionId)
+    if (type === 'delete') removeChatForBoth(companionId)
+    setAnchorEl(null)
+    setSelectedIndex(null)
+  }
+  const handleContextMenu = (e: React.MouseEvent<HTMLDivElement>,i: number):void => {
+    e.preventDefault()
+    setAnchorEl(e.currentTarget)
+    setSelectedIndex(i)
+  }  
+
+  return (
+    <div>
+      <ListItemButton
+        selected={selectedIndex === i}
+        onClick={() => handleListItemClick(i, companionId)}
+        onContextMenu={(e) => handleContextMenu(e,i)}
+      >
+        <ListItemIcon className={classes.listItem_iconAvatar}>
+          <StyledBadge overlap="circular"  variant={online === 'true'?'dot':'standard'}
+             anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}>
+            <Avatar alt={name} src={avatarUrl?`http://localhost:3000/${avatarUrl}`:undefined}
+              sx={{ background: color, width: 54, height: 54 }}>
+              {!avatarUrl&&`${firstLetter(name)}${firstLetter(lastName)}`}
+            </Avatar>
+          </StyledBadge>
+          </ListItemIcon> 
+          <ListItemText primary={<div className={classes.listItemInnerText}>
+            <span>{`${firstLetter(name)}${slicedWord(name, 15, 1)} 
+             ${firstLetter(lastName)}${slicedWord(lastName, 15, 1)}`}</span>
+            {mute&&<VolumeOffIcon className={classes.listItemInnerText__icon} fontSize='small' />}</div>}
+            secondary={typing ? <span className={classes.listItem_typing}>
+            typing<span className={classes.listItem_dots}>...</span></span> :
+            lastMessage ? slicedWord(lastMessage, 35) :
+            `${firstLetter(name)}${slicedWord(name, 8, 1)} joined Telegram`}/>
+          <ListItemIcon className={classes.listItem_iconRight}>
+            <div className={classes.listItem_iconTimeChecked}>
+              {watched&& <DoneAllIcon style={{ color: '#18bd03' }} fontSize='small' />}
+              <Typography className={classes.listItem_icon_time} variant="h6" color="initial">
+                {timeStampEU(lastMessageCreatedAt?lastMessageCreatedAt:createdAt)}
+              </Typography>
+            </div>
+            {lastMessage && total > seen ? <button onClick={(e) => handleNewMsgs(e, i,companionId)}
+            className={mute?classes.listItem_iconRightBtnMute:classes.listItem_iconRightBtn}>{total-seen}</button> :
+            <button  className={classes.listItem_iconRightBtnHidden}/>}
+          </ListItemIcon>            
+        </ListItemButton>
+      <StyledMenu
+        id="demo-positioned-menu"
+        aria-labelledby="demo-positioned-button"
+        anchorEl={anchorEl}
+        open={open}
+        onClose={handleClose}
+      >
+        <MenuItem onClick={() => handleClose('mute')}>
+          {mute ? <NotificationsNoneIcon /> : <VolumeOffIcon />}
+          {mute ? 'Unmute chat':'Mute chat'}
+        </MenuItem>
+        <MenuItem style={{color:'#f02a2a'}} onClick={() => handleClose('delete')}>
+            <DeleteOutlineIcon style={{color:'#f02a2a'}}/>
+            Delete chat
+        </MenuItem>       
+       </StyledMenu>    
+    </div>
+  );
+}
+export default ChatItem

+ 0 - 72
src/components/HomePage/LeftBar/ChatsList/ContextMenuBar/index.tsx

@@ -1,72 +0,0 @@
-import { styled } from '@mui/material/styles';
-import Menu from '@mui/material/Menu';
-import MenuItem from '@mui/material/MenuItem';
-import NotificationsNoneIcon from '@mui/icons-material/NotificationsNone';
-import VolumeOffIcon from '@mui/icons-material/VolumeOff';
-import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
-import { useDispatch } from 'react-redux';
-import { muteChat,removeChatForBoth } from '../../../../../api-data';
-import { actionIsOpen } from '../../../../../redux/control/action';
-
-const StyledMenu = styled((props:any) => (
-  <Menu
-    elevation={0}
-    {...props}
-  />
-))(({ theme }:any) => ({
-  '& .MuiPaper-root': {
-    borderRadius: 10,
-    marginTop: theme.spacing(-2),
-    minWidth: 220,
-    color:
-      theme.palette.mode === 'light' ? 'rgb(55, 65, 81)' : theme.palette.grey[500],
-    boxShadow:
-      'rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px',
-    '& .MuiMenu-list': {
-      padding: '14px 14px',
-    },
-    '& .MuiMenuItem-root': {
-      marginBottom: theme.spacing(1),
-      '& .MuiSvgIcon-root': {
-        fontSize: 21,
-        color: theme.palette.text.secondary,
-        marginRight: theme.spacing(4),
-      }
-    },
-  },
-}));
-
-interface IContextMenuBar {
-  setModal: any,
-  mute: boolean,
-  companionId:string,
-}
-const ContextMenuBar = ({ setModal, mute, companionId }: IContextMenuBar) => {
-  const dispatch = useDispatch()
-  const handleClose = (i: number | undefined): void => {
-    if (i === 0) muteChat(companionId)
-    if (i === 1) {
-      removeChatForBoth(companionId)
-      dispatch(actionIsOpen(''))
-    }
-    setModal(false)
-  }
-  return (
-      <StyledMenu
-        id="demo-positioned-menu"
-        aria-labelledby="demo-positioned-button"
-        open={true}
-        onClose={handleClose}
-      >
-        <MenuItem onClick={() => handleClose(0)}>
-          {mute ? <NotificationsNoneIcon /> : <VolumeOffIcon />}
-          {mute ? 'Unmute chat':'Mute chat'}
-        </MenuItem>
-        <MenuItem style={{color:'#f02a2a'}} onClick={() => handleClose(1)}>
-            <DeleteOutlineIcon style={{color:'#f02a2a'}}/>
-            Delete chat
-         </MenuItem>        
-       </StyledMenu>    
-  );
-}
-export default ContextMenuBar

+ 13 - 195
src/components/HomePage/LeftBar/ChatsList/index.tsx

@@ -1,54 +1,18 @@
 import List from '@mui/material/List';
-import ListItemButton from '@mui/material/ListItemButton';
-import Avatar from '@mui/material/Avatar';
-import ListItemText from '@mui/material/ListItemText';
-import ListItemIcon from '@mui/material/ListItemIcon';
-import { styled } from '@mui/material/styles';
-import Badge from '@mui/material/Badge';
-import VolumeOffIcon from '@mui/icons-material/VolumeOff';
-import { makeStyles, Typography } from '@material-ui/core'
+import { makeStyles } from '@material-ui/core'
 import { useState,useEffect,useRef } from 'react';
 import { useSelector, useDispatch } from 'react-redux';
 
 import AlertInfo from '../../../reusableComponents/AlertInfo'
-import ContextMenuBar from './ContextMenuBar';
-import DoneAllIcon from '@mui/icons-material/DoneAll';
-import { firstLetter, slicedWord, timeStampEU,notification,playNotificationWithoutPermission } from '../../../../helpers'
+import ChatItem from './ChatItem';
+import { notification,playNotificationWithoutPermission } from '../../../../helpers'
 import { getState } from '../../../../redux/chats/selector'
 import { getChatMemo } from '../../../../redux/chat/selector'
 import { asyncGetChats } from '../../../../redux/chats/operations'
 import { asyncStartChatById } from '../../../../redux/chat/operations'
 import { actionRemoveChat } from '../../../../redux/chat/action'
-import { actionScroll,actionIsOpen } from '../../../../redux/control/action'
-
-const StyledBadge = styled(Badge)(({ theme }) => ({
-  '& .MuiBadge-badge': {
-    backgroundColor: '#44b700',
-    color: '#44b700',
-    boxShadow: `0 0 0 2px ${theme.palette.background.paper}`,
-    '&::after': {
-      position: 'absolute',
-      top: 0,
-      left: 0,
-      width: '100%',
-      height: '100%',
-      borderRadius: '50%',
-      animation: 'ripple 1.2s infinite ease-in-out',
-      border: '1px solid currentColor',
-      content: '""',
-    },
-  },
-  '@keyframes ripple': {
-    '0%': {
-      transform: 'scale(.8)',
-      opacity: 1,
-    },
-    '100%': {
-      transform: 'scale(2.4)',
-      opacity: 0,
-    },
-  },
-}));
+import { actionScroll, actionIsOpen } from '../../../../redux/control/action'
+import { getIsOpen } from '../../../../redux/control/selector';
 
 const useStyles = makeStyles({
   list: {
@@ -73,122 +37,21 @@ const useStyles = makeStyles({
     backgroundColor: "#959595",
     },
   },
-  listItemInnerText: {
-    display: 'flex',
-    alignContent: 'center',
-    alignItems: 'center',
-    flexWrap: 'nowrap',
-  },
-  listItemInnerText__icon: {
-    marginLeft: 5,
-    color: '#959595',
-  },
-  listItem_iconAvatar: {
-    marginRight:10
-  },
-  listItem_iconRight: {
-    marginRight: 10,
-    display: 'flex',
-    alignItems: 'center',
-    justifyContent: 'center',
-    alignContent: 'center',
-    flexDirection: 'column'
-  },
-  listItem_iconTimeChecked: {
-    display: 'flex',
-    flexWrap: 'nowrap',
-    alignItems: 'center',
-    justifyContent: 'center',
-    alignContent: 'center',
-    marginBottom:2
-  },
-  listItem_iconRightBtn: {
-    background: '#0ac40a',
-    borderRadius: '50%',
-    color: '#ffffff',
-    border: 'none',
-    height: 24,
-    width: 24,
-    textAlign: 'center',
-    display: 'flex',
-    alignItems: 'center',
-    justifyContent: 'center',
-    alignContent: 'center',
-    fontSize: 12,
-    marginLeft: 'auto',
-    '&:hover': {
-      outline: 'solid 3px #3ee415',
-    }
-  },
-    listItem_iconRightBtnMute: {
-    background: '#a7aaa7',
-    borderRadius: '50%',
-    color: '#ffffff',
-    border: 'none',
-    height: 24,
-    width: 24,
-    textAlign: 'center',
-    display: 'flex',
-    alignItems: 'center',
-    justifyContent: 'center',
-    alignContent: 'center',
-    fontSize: 12,
-    marginLeft: 'auto',
-    '&:hover': {
-      outline: 'solid 3px #cccbcb',
-    }
-  },
-  listItem_iconRightBtnHidden: {
-    background: 'inherit',
-    borderRadius: '50%',
-    border: 'none',
-    height: 24,
-    width: 24,
-    textAlign: 'center',
-    display: 'flex',
-    alignItems: 'center',
-    justifyContent: 'center',
-    alignContent: 'center',
-    fontSize: 12,
-    marginLeft: 'auto', 
-  },
-  listItem_icon_time: {
-    fontSize: 12,
-    marginLeft: 5,
-    color: '#1b1b1b'
-  },
-  listItem_typing: {
-    color: '#4d4d4d',
-    animation: 'ripple 4s infinite ease-in-out',   
-  },  
-  listItem_dots: {
-    color: '#1b1b1b',
-    fontWeight: 'bold',
-    display:'inline-block',
-    fontFamily: 'monospace',
-    clipPath: 'inset(0 3ch 0 0)',
-    animation: `$run 2s steps(5) infinite`,   
-  },
-  '@keyframes run': {
-    to: {
-       clipPath: 'inset(0 -1ch 0 0)'
-    },
-  },
 })
 
 const ChatsList = () => {
   const classes = useStyles()
   const dispatch = useDispatch()
   const ref = useRef<any>(null)
-  const [selectedIndex, setSelectedIndex] = useState<number>(1);
-  const [modal, setModal] = useState<any>(false);
+  const [selectedIndex, setSelectedIndex] = useState<null | number>(null);
   const { total, chats } = useSelector(getState)
   const chat = useSelector(getChatMemo)
+  const isOpen = useSelector(getIsOpen)
 
   const handleListItemClick = (i: number, companionId: string) => {
     dispatch(asyncStartChatById(companionId))
     dispatch(actionScroll(false))
-    dispatch(actionIsOpen(''))
+    isOpen&&dispatch(actionIsOpen(''))
     setSelectedIndex(i);
   }
 
@@ -198,12 +61,6 @@ const ChatsList = () => {
     dispatch(actionScroll(true))
   }
 
-  const handleContextMenu = (e: any) => {
-    e.preventDefault()
-    const companionId = e.currentTarget.id
-    const isChat = chats.find((el) => el.companionId === companionId)
-    if (isChat) setModal(isChat)
-  }
   useEffect(() => {
     dispatch(asyncGetChats())
     const handleReset = () => dispatch(asyncGetChats())
@@ -216,9 +73,7 @@ const ChatsList = () => {
       dispatch(asyncStartChatById(companionId))
       dispatch(actionScroll(true))
     }
-    if (chat.companionId&&!chats.find((el) => el.companionId === chat.companionId)) {
-      dispatch(actionRemoveChat())
-    }
+    if (chat.companionId&&!chats.find((el) => el.companionId === chat.companionId))dispatch(actionRemoveChat())
     if (ref.current) {
       ref.current.forEach(({total,seen}: any,i:number) => {
         const oldDifferent = total - seen
@@ -236,47 +91,10 @@ const ChatsList = () => {
 
   return total !== '0' ? (
     <List className={classes.list} component="nav"
-          aria-label="main mailbox folders">
-      {chats.map(({ name, lastName, avatarUrl, color, companionId, mute, seen, total,
-        watched, typing, number, online, lastMessage,lastMessageCreatedAt,createdAt }, i: number) =>
-          <ListItemButton
-          key={number}
-          id={companionId}
-          selected={selectedIndex === i}
-          onClick={() => handleListItemClick(i, companionId)}
-          onContextMenu={handleContextMenu}
-        >
-          {modal&&modal.companionId === companionId&&<ContextMenuBar setModal={setModal} mute={mute} companionId={companionId}/>}
-          <ListItemIcon className={classes.listItem_iconAvatar}>
-            <StyledBadge overlap="circular"  variant={online === 'true'?'dot':'standard'}
-                  anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}>
-                 <Avatar alt={name} src={avatarUrl?`http://localhost:3000/${avatarUrl}`:undefined}
-                     sx={{ background: color, width: 54, height: 54 }}>
-                     {!avatarUrl&&`${firstLetter(name)}${firstLetter(lastName)}`}
-                  </Avatar>
-              </StyledBadge>
-          </ListItemIcon> 
-          <ListItemText primary={<div className={classes.listItemInnerText}>
-            <span>{`${firstLetter(name)}${slicedWord(name, 15, 1)} 
-             ${firstLetter(lastName)}${slicedWord(lastName, 15, 1)}`}</span>
-            {mute&&<VolumeOffIcon className={classes.listItemInnerText__icon} fontSize='small' />}</div>}
-            secondary={typing ? <span className={classes.listItem_typing}>
-              typing<span className={classes.listItem_dots}>...</span></span> :
-              lastMessage ? slicedWord(lastMessage, 35) :
-              `${firstLetter(name)}${slicedWord(name, 8, 1)} joined Telegram`}/>
-          <ListItemIcon className={classes.listItem_iconRight}>
-               <div className={classes.listItem_iconTimeChecked}>
-                 {watched&& <DoneAllIcon style={{ color: '#18bd03' }} fontSize='small' />}
-              <Typography className={classes.listItem_icon_time} variant="h6" color="initial">
-                {timeStampEU(lastMessageCreatedAt?lastMessageCreatedAt:createdAt)}
-              </Typography>
-               </div>
-            {lastMessage && total > seen ? <button onClick={(e) => handleNewMsgs(e, i,companionId)}
-              className={mute?classes.listItem_iconRightBtnMute:classes.listItem_iconRightBtn}>{total-seen}</button> :
-              <button  className={classes.listItem_iconRightBtnHidden}/>}
-          </ListItemIcon>            
-        </ListItemButton>)}
-      </List>
+      aria-label="main mailbox folders">
+      {chats.map((el, i: number) => <ChatItem key={el.number} chat={el} selectedIndex={selectedIndex} i={i}
+       handleListItemClick={handleListItemClick} handleNewMsgs={handleNewMsgs} setSelectedIndex={setSelectedIndex}/>)}
+    </List>
   ):<AlertInfo name='You do not have any chats yet!' />;
 }
 

+ 129 - 0
src/components/HomePage/LeftBar/ContactsList/ContactItem/index.tsx

@@ -0,0 +1,129 @@
+import { makeStyles } from '@material-ui/core'
+import { useState } from 'react';
+import { useDispatch } from 'react-redux';
+import { styled } from '@mui/material/styles';
+import Menu from '@mui/material/Menu';
+import MenuItem from '@mui/material/MenuItem';
+import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
+import ListItemButton from '@mui/material/ListItemButton';
+import Avatar from '@mui/material/Avatar';
+import ListItemText from '@mui/material/ListItemText';
+import ListItemIcon from '@mui/material/ListItemIcon';
+import ContentCopyIcon from '@mui/icons-material/ContentCopy';
+import { CopyToClipboard } from 'react-copy-to-clipboard';
+import {removeContact } from '../../../../../api-data';
+import { actionIsOpen } from '../../../../../redux/control/action';
+import { TContact } from '../../../../../typescript/redux/contacts/types';
+import { TIsOpen } from '../../../../../typescript/redux/control/types';
+import { firstLetter,slicedWord,timeStampEU,copied } from '../../../../../helpers';
+
+const StyledMenu = styled((props:any) => (
+  <Menu
+    elevation={0}
+    anchorOrigin={{
+      vertical: 'top',
+      horizontal: 'right',
+    }}
+    transformOrigin={{
+      vertical: 'bottom',
+      horizontal: 'right',
+    }}
+    {...props}
+  />
+))(({ theme }:any) => ({
+  '& .MuiPaper-root': {
+    borderRadius: 10,
+    marginTop: theme.spacing(0),
+    minWidth: 220,
+    color:
+      theme.palette.mode === 'light' ? 'rgb(55, 65, 81)' : theme.palette.grey[500],
+    boxShadow:
+      'rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px',
+    '& .MuiMenu-list': {
+      padding: '14px 14px',
+    },
+    '& .MuiMenuItem-root': {
+      marginBottom: theme.spacing(1),
+      '& .MuiSvgIcon-root': {
+        fontSize: 21,
+        color: theme.palette.text.secondary,
+        marginRight: theme.spacing(4),
+      }
+    },
+  },
+}));
+
+const useStyles = makeStyles({
+  listItem_iconAvatar: {
+    marginRight:10
+  },
+})
+interface IContactItem {
+  contact: TContact,
+  selectedIndex: null | number,
+  setSelectedIndex: (i: null | number) => void,
+  i: number,
+  handleListItemClick: (companionId: string) => void,
+  isOpen: TIsOpen,
+}
+const  ContactItem = ({contact,selectedIndex,setSelectedIndex,i,handleListItemClick,isOpen}:IContactItem) => {
+  const classes = useStyles()
+  const dispatch = useDispatch()
+  const [anchorEl, setAnchorEl] = useState<any>(null);
+  const open = Boolean(anchorEl);
+  const { name, lastName, avatarUrl, color, companionId,createdAt, number,_id } = contact
+
+  const handleClose = (type: string | undefined): void => {
+    if (type === 'copy') copied('Number')
+    if (type === 'delete') {
+      removeContact(_id)
+      isOpen === 'edit'&&dispatch(actionIsOpen('credentials'))
+    }
+    setAnchorEl(null)
+    setSelectedIndex(null)
+  }
+  const handleContextMenu = (e: React.MouseEvent<HTMLDivElement>,i: number):void => {
+    e.preventDefault()
+    setAnchorEl(e.currentTarget)
+    setSelectedIndex(i)
+  } 
+
+  return (
+    <div>
+      <ListItemButton
+        selected={selectedIndex === i}
+        onClick={() => handleListItemClick(companionId)}
+        onContextMenu={(e) => handleContextMenu(e, i)}
+      >
+        <ListItemIcon className={classes.listItem_iconAvatar}>
+        <Avatar alt={name} src={avatarUrl?`http://localhost:3000/${avatarUrl}`:undefined}
+          sx={{ background: color, width: 54, height: 54 }}>
+          {!avatarUrl&&`${firstLetter(name)}${firstLetter(lastName)}`}
+          </Avatar>
+        </ListItemIcon> 
+        <ListItemText primary={`${firstLetter(name)}${slicedWord(name, 15, 1)}
+          ${firstLetter(lastName)}${slicedWord(lastName, 15, 1)}`}
+          secondary={`Registered since ${timeStampEU(createdAt)}`} />
+      </ListItemButton>      
+      <StyledMenu
+        id="demo-positioned-menu"
+        aria-labelledby="demo-positioned-button"
+        anchorEl={anchorEl}
+        open={open}
+        onClose={handleClose}
+      >
+        <CopyToClipboard onCopy={() => handleClose('copy')} text={number}>
+          <MenuItem>
+            <ContentCopyIcon />
+             Copy number
+          </MenuItem>
+        </CopyToClipboard>
+        <MenuItem style={{color:'#f02a2a'}} onClick={() => handleClose('delete')}>
+            <DeleteOutlineIcon style={{color:'#f02a2a'}}/>
+            Delete contact
+        </MenuItem>        
+      </StyledMenu>     
+    </div>
+  );
+}
+export default ContactItem

+ 0 - 74
src/components/HomePage/LeftBar/ContactsList/ContextMenuBar/index.tsx

@@ -1,74 +0,0 @@
-import { styled } from '@mui/material/styles';
-import Menu from '@mui/material/Menu';
-import MenuItem from '@mui/material/MenuItem';
-import ContentCopyIcon from '@mui/icons-material/ContentCopy';
-import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
-import { CopyToClipboard } from 'react-copy-to-clipboard';
-import { useDispatch } from 'react-redux';
-import { removeContact } from '../../../../../api-data';
-import { actionIsOpen } from '../../../../../redux/control/action';
-import { copied } from '../../../../../helpers';
-
-const StyledMenu = styled((props:any) => (
-  <Menu
-    elevation={0}
-    {...props}
-  />
-))(({ theme }:any) => ({
-  '& .MuiPaper-root': {
-    borderRadius: 10,
-    marginTop: theme.spacing(-2),
-    minWidth: 220,
-    color:
-      theme.palette.mode === 'light' ? 'rgb(55, 65, 81)' : theme.palette.grey[500],
-    boxShadow:
-      'rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px',
-    '& .MuiMenu-list': {
-      padding: '14px 14px',
-    },
-    '& .MuiMenuItem-root': {
-      marginBottom: theme.spacing(1),
-      '& .MuiSvgIcon-root': {
-        fontSize: 21,
-        color: theme.palette.text.secondary,
-        marginRight: theme.spacing(4),
-      }
-    },
-  },
-}));
-
-interface IContextMenuBar {
-  setModal: any,
-  number: string,
-  _id:string,
-}
-const ContextMenuBar = ({ setModal, number, _id }: IContextMenuBar) => {
-  const dispatch = useDispatch()
-  const handleClose = (i: number | undefined): void => {
-    if(i === 0 ) copied('Number')
-    if(i === 1) {
-      removeContact(_id)
-      dispatch(actionIsOpen(''))
-    }
-    setModal(false)
-  }
-  return (
-      <StyledMenu
-        id="demo-positioned-menu"
-        aria-labelledby="demo-positioned-button"
-        open={true}
-        onClose={handleClose}
-      > <CopyToClipboard onCopy={() => handleClose(0)} text={number}>
-          <MenuItem>
-             <ContentCopyIcon />
-             Copy number
-          </MenuItem>
-        </CopyToClipboard>
-        <MenuItem style={{color:'#f02a2a'}} onClick={() => handleClose(1)}>
-            <DeleteOutlineIcon style={{color:'#f02a2a'}}/>
-            Delete contact
-         </MenuItem>        
-       </StyledMenu>    
-  );
-}
-export default ContextMenuBar

+ 11 - 35
src/components/HomePage/LeftBar/ContactsList/index.tsx

@@ -1,19 +1,15 @@
 import List from '@mui/material/List';
-import ListItemButton from '@mui/material/ListItemButton';
-import Avatar from '@mui/material/Avatar';
-import ListItemText from '@mui/material/ListItemText';
-import ListItemIcon from '@mui/material/ListItemIcon';
 import { makeStyles } from '@material-ui/core'
 import { useEffect,useState } from 'react';
 import { useSelector,useDispatch } from 'react-redux';
 
 import AlertInfo from '../../../reusableComponents/AlertInfo'
-import ContextMenuBar from './ContextMenuBar';
+import ContactItem from './ContactItem';
 import { getState } from '../../../../redux/contacts/selector'
 import { asyncGetContacts } from '../../../../redux/contacts/operations'
-import { firstLetter, slicedWord, timeStampEU } from '../../../../helpers'
 import { asyncStartChatById } from '../../../../redux/chat/operations'
 import { getIsOpen } from '../../../../redux/control/selector'
+import { actionIsOpen } from '../../../../redux/control/action';
 
 const useStyles = makeStyles({
   list: {
@@ -38,9 +34,6 @@ const useStyles = makeStyles({
     backgroundColor: "#959595",
     },
   },
-  listItem_iconAvatar: {
-    marginRight:10
-  },
 })
 
 interface IContactList {
@@ -51,19 +44,13 @@ const  ContactsList = ({value,handleClick} : IContactList) => {
   const classes = useStyles()
   const dispatch = useDispatch()
   const { total, contacts } = useSelector(getState)
+  const [selectedIndex, setSelectedIndex] = useState<null | number>(null);
   const isOpen = useSelector(getIsOpen)
-  const [modal, setModal] = useState<any>(false);
-
-  const handleListItemClick = ( companionId:string) => {
-     handleClick()
-     dispatch(asyncStartChatById(companionId))
-  }
 
-  const handleContextMenu = (e: any) => {
-    e.preventDefault()
-    const _id = e.currentTarget.id
-    const isContact= contacts.find((el) => el._id === _id)
-    if (isContact) setModal(isContact)
+  const handleListItemClick = (companionId:string) => {
+    handleClick()
+    isOpen&&dispatch(actionIsOpen(''))
+    dispatch(asyncStartChatById(companionId))
   }
 
   const filteredContacts = () => contacts.filter((el) => {
@@ -73,7 +60,7 @@ const  ContactsList = ({value,handleClick} : IContactList) => {
 
   useEffect(() => {
     dispatch(asyncGetContacts())
-    const handleReset = () => isOpen === '' &&dispatch(asyncGetContacts())
+    const handleReset = () => isOpen !== 'credentials' &&dispatch(asyncGetContacts())
     const idInterval = setInterval(handleReset, 3000);
     return () => clearInterval(idInterval);
   }, [dispatch,isOpen]);
@@ -84,20 +71,9 @@ const  ContactsList = ({value,handleClick} : IContactList) => {
     <List
       className={classes.list} component="nav"
       aria-label="main mailbox folders">
-      {arr.length > 0? arr.map(({name,lastName,avatarUrl,color,createdAt,companionId,number,_id }) => 
-        <ListItemButton onClick={() => handleListItemClick(companionId)}
-          onContextMenu={handleContextMenu} id={_id} key={number}>
-          {modal&&modal._id === _id&&<ContextMenuBar setModal={setModal} number={number} _id={_id}/>}
-          <ListItemIcon className={classes.listItem_iconAvatar}>
-            <Avatar alt={name} src={avatarUrl?`http://localhost:3000/${avatarUrl}`:undefined}
-                 sx={{ background: color, width: 54, height: 54 }}>
-                 {!avatarUrl&&`${firstLetter(name)}${firstLetter(lastName)}`}
-              </Avatar>
-          </ListItemIcon> 
-          <ListItemText primary={`${firstLetter(name)}${slicedWord(name, 15, 1)}
-               ${firstLetter(lastName)}${slicedWord(lastName, 15, 1)}`}
-               secondary={`Registered since ${timeStampEU(createdAt)}`} />
-        </ListItemButton>) :
+      {arr.length > 0 ? arr.map((contact,i) => <ContactItem key={contact.number} contact={contact}
+        selectedIndex={selectedIndex} setSelectedIndex={setSelectedIndex} i={i}
+        handleListItemClick={handleListItemClick} isOpen={isOpen}/>) :
         <AlertInfo name={`Can not find contact by request : ${value}`} />}
       </List>
   ):<AlertInfo name='You do not have any contact yet!' />;

+ 25 - 22
src/components/HomePage/RightBar/ChatBar/Messages/MessageLeftText/index.tsx

@@ -1,5 +1,6 @@
 import { makeStyles } from "@material-ui/core/styles";
 import ListItemText from '@mui/material/ListItemText';
+import ContentCopyIcon from '@mui/icons-material/ContentCopy';
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 import { firstLetter,slicedWord,timeStampMessage,copied } from '../../../../../../helpers'
 
@@ -13,19 +14,20 @@ const useStyles = makeStyles({
     },
     wrapper: {
       position: 'relative',
-      maxWidth: 450
-    },
-    message: {
-      position: "relative",
-      marginLeft: 5,
+      display: 'flex',
+      alignItems: 'center',
+      alignContent: 'center',
+      maxWidth: 450,
       padding: "10px",
       paddingBottom:18,
       backgroundColor: "#ffffff",
+      border: "1px solid #f0f0f0",
+      borderRadius: 7,
+    },
+    message: {
       wordBreak:'break-word',
       textAlign: "left",
       font: "400 .9em 'Open Sans', sans-serif",
-      border: "1px solid #f0f0f0",
-      borderRadius: 7,
       "&:after": {
         content: "''",
         position: "absolute",
@@ -71,21 +73,22 @@ interface IMessageLeftText {
 
 const MessageLeftText = ({message,name,lastName,updatedAt}:IMessageLeftText) => {
   const classes = useStyles();
+
   return (
-    <div className={classes.container}>
-      <CopyToClipboard onCopy={() => copied('Text')} text={message}>
-        <div className={classes.wrapper}>
-          <ListItemText className={classes.message}
-            primary={`${firstLetter(name)}${slicedWord(name, 15, 1)} 
-            ${firstLetter(lastName)}${slicedWord(lastName, 15, 1)}`}
-            primaryTypographyProps={{color: "#0379af"}}
-            secondary={message}
-            secondaryTypographyProps={{color: "#0e0d0d"}}/>
-          <div className={classes.time}>{timeStampMessage(updatedAt)}</div>
-        </div>
-      </CopyToClipboard>
-    </div>
-  );
-};
+  <div className={classes.container}>
+     <div className={classes.wrapper}>
+        <CopyToClipboard onCopy={() => copied('Message')} text={message}>
+          <ContentCopyIcon fontSize='large' style={{ color: '#b56ff7',cursor: 'pointer',marginRight:7}} />
+        </CopyToClipboard>
+        <ListItemText className={classes.message}
+          primary={`${firstLetter(name)}${slicedWord(name, 15, 1)} 
+          ${firstLetter(lastName)}${slicedWord(lastName, 15, 1)}`}
+          primaryTypographyProps={{color: "#0379af"}}
+          secondary={message}
+          secondaryTypographyProps={{ color: "#0e0d0d" }} />
+        <div className={classes.time}>{timeStampMessage(updatedAt)}</div>
+      </div>
+  </div>    
+)};  
 
 export default MessageLeftText

+ 6 - 6
src/components/HomePage/RightBar/ChatBar/Messages/MessageRightImage/index.tsx

@@ -8,12 +8,12 @@ import { timeStampMessage, timeStampFilter,handleDownload } from '../../../../..
 
 
 const useStyles = makeStyles({
-    container: {
-      display: "flex",
-      justifyContent: "flex-end",
-      width:'auto',
-      maxWidth: '80%',
-      marginBottom:15
+  container: {
+    display: "flex",
+    justifyContent: "flex-end",
+    width:'auto',
+    maxWidth: '80%',
+    marginBottom:15
   },
   wrapper: {
     width: 400,

+ 13 - 10
src/components/HomePage/RightBar/ChatBar/Messages/MessageRightText/index.tsx

@@ -1,5 +1,6 @@
 import { makeStyles } from "@material-ui/core/styles";
 import ListItemText from '@mui/material/ListItemText';
+import ContentCopyIcon from '@mui/icons-material/ContentCopy';
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 import { firstLetter, slicedWord, timeStampMessage,copied } from '../../../../../../helpers'
 
@@ -13,19 +14,20 @@ const useStyles = makeStyles({
     },
     wrapper: {
       position: 'relative',
-      maxWidth: 450
-    },
-    message: {
-      position: "relative",
-      marginRight: 5,
+      display: 'flex',
+      alignItems: 'center',
+      alignContent: 'center',
+      maxWidth: 450,
       padding: "10px",
       paddingBottom:18,
       backgroundColor: "#deffa9",
+      border: "1px solid #d5ff91",
+      borderRadius: 7,
+    },
+    message: {
       wordBreak:'break-word',
       textAlign: "left",
       font: "400 .9em 'Open Sans', sans-serif",
-      border: "1px solid #d5ff91",
-      borderRadius: 7,
       "&:after": {
         content: "''",
         position: "absolute",
@@ -73,8 +75,10 @@ const MessageRightText = ({message,name,lastName,updatedAt}:IMessageRightText) =
 
   return (
   <div className={classes.container}>
-    <CopyToClipboard onCopy={() => copied('Text')} text={message}>
-      <div className={classes.wrapper}>
+     <div className={classes.wrapper}>
+        <CopyToClipboard onCopy={() => copied('Message')} text={message}>
+          <ContentCopyIcon fontSize='large' style={{ color: '#b56ff7',cursor: 'pointer',marginRight:7}} />
+        </CopyToClipboard>
         <ListItemText className={classes.message}
           primary={`${firstLetter(name)}${slicedWord(name, 15, 1)} 
           ${firstLetter(lastName)}${slicedWord(lastName, 15, 1)}`}
@@ -83,7 +87,6 @@ const MessageRightText = ({message,name,lastName,updatedAt}:IMessageRightText) =
           secondaryTypographyProps={{ color: "#0e0d0d" }} />
         <div className={classes.time}>{timeStampMessage(updatedAt)}</div>
       </div>
-    </CopyToClipboard>
   </div>    
 )};
 

+ 14 - 5
src/components/HomePage/RightBar/HeaderBar/RightListsAndBars/CredentialsList/ProfileLists/TextList/index.tsx

@@ -4,6 +4,7 @@ import ListItemText from '@mui/material/ListItemText';
 import ListItemAvatar from '@mui/material/ListItemAvatar';
 import Avatar from '@mui/material/Avatar';
 import Divider from '@mui/material/Divider';
+import ContentCopyIcon from '@mui/icons-material/ContentCopy';
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 import { makeStyles } from '@material-ui/core'
 
@@ -16,6 +17,13 @@ const useStyles = makeStyles({
     '&:hover': {
       backgroundColor: '#f0f0f0',
     }
+  },
+  copyIcon: {
+    color: '#54b0fc',
+    cursor: 'pointer',
+    '&:hover': {
+      color: '#016cc3'
+    },
   },  
 })
 
@@ -26,19 +34,20 @@ const TextList = ({ messagesMemo }: { messagesMemo: TMessages }) => {
     <List>
        {filteredMessagesMemo.map(({ message, createdAt, lastName, name, color, avatarUrl }) =>
       <div key={createdAt}>
-        <CopyToClipboard onCopy={() => copied('Text')} text={message}>
           <ListItem alignItems="flex-start" className={classes.listItem}>
             <ListItemAvatar>
               <Avatar alt={name} src={avatarUrl?`http://localhost:3000/${avatarUrl}`:undefined}
-                  sx={{ background: color, width: 38, height: 38,marginRight:2}}>
+                  sx={{ background: color, width: 38, height: 38}}>
                   {`${firstLetter(name)}${firstLetter(lastName)}`}
               </Avatar>
             </ListItemAvatar>
-             <ListItemText style={{ wordBreak: 'break-word' }} primary={message}
+             <ListItemText style={{ wordBreak: 'break-word',marginRight:2 }} primary={message}
                secondary={timeStampEU(createdAt)} secondaryTypographyProps={{color: '#020202',paddingTop:0.5}}
-            />
+             />
+            <CopyToClipboard onCopy={() => copied('Message')} text={message}>
+              <ContentCopyIcon className={classes.copyIcon} fontSize='large' />
+            </CopyToClipboard>             
           </ListItem>
-        </CopyToClipboard>
           <Divider variant="inset" />
       </div>)}
     </List>

+ 4 - 4
src/components/HomePage/RightBar/HeaderBar/RightListsAndBars/CredentialsList/ProfileMenu/index.tsx

@@ -47,20 +47,20 @@ const ProfileMenu = () => {
             </MenuItem>
           </CopyToClipboard>
         <Divider variant="inset"/>
-        <MenuItem>
+        <MenuItem style={{cursor:'default'}}>
           <ListItemIcon className={classes.listIcon}>
             <NotificationsIcon fontSize="medium" />
           </ListItemIcon>
           <ListItemText primary='Notification' />
-          <Switch onClick={handleMute} {...label} defaultChecked={!mute} />
+          <Switch style={{ cursor:'pointer'}} onClick={handleMute} {...label} defaultChecked={!mute} />
         </MenuItem>
         <Divider variant="inset"/>
-        <MenuItem>
+        <MenuItem style={{cursor:'default'}}>
           <ListItemIcon className={classes.listIcon}>
             <SortIcon fontSize="medium" />
           </ListItemIcon>
           <ListItemText primary={`Sort by Date`} />
-          <Switch onClick={handleSort} {...label} defaultChecked={sort} />
+          <Switch style={{ cursor:'pointer'}} onClick={handleSort} {...label} defaultChecked={sort} />
         </MenuItem>
         <Divider/>
       </MenuList>

+ 2 - 2
src/helpers/index.ts

@@ -56,9 +56,9 @@ const notification = (name: string, onClick: () => void) => {
 }
 
 const copied = (text:string) => {
-    toast.success(`${text} Copied`, {
+    toast(`${text} Copied`, {
     position: "bottom-right",
-    autoClose: 1000,
+    autoClose: 2000,
     hideProgressBar: false,
     closeOnClick: true,
     pauseOnHover: true,