Browse Source

almost finished reply message

unknown 1 year ago
parent
commit
4bc3fa48c7

File diff suppressed because it is too large
+ 1 - 1
.eslintcache


+ 11 - 0
src/api-data/index.ts

@@ -330,6 +330,16 @@ const sentMessageById = async <T>(id:string,message:string,caption:string): Prom
   }
 };
 
+const sentMessageReplyById = async <T>(id:string,newMessage:string,newCaption:string): Promise<T | undefined> => {
+  try {
+    const { data: { data } } = await axios.post('/messages/reply', { id, newMessage,newCaption });
+    return data
+  } catch (e) {
+    forbidden(e)
+    return undefined
+  }
+};
+
 const sentImgMessageById = async <T>(id:string,formData: object,caption:string): Promise<T | undefined> => {
   try {
     const { data: { data } } = await axios.post(`/messages/image/${id} ${caption}}`, formData);
@@ -423,6 +433,7 @@ export {
   pinMessageById,
   unpinAllMessagesById,
   sentMessageById,
+  sentMessageReplyById,
   sentImgMessageById,
   sentAudioMessageById,
   sentVideoMessageById,

+ 1 - 1
src/components/HomePage/CentralBar/ChatBar/Messages/MessageLeftText/index.tsx

@@ -177,7 +177,7 @@ const useStyles = makeStyles({
         borderBottom: "17px solid #deffa9",
         borderLeft: "16px solid transparent",
         borderRight: "16px solid transparent",
-        bottom: "-1px",
+        bottom: "0px",
         left: "-17px"
       }      
   },   

+ 438 - 0
src/components/HomePage/CentralBar/ChatBar/Messages/MessageReplyLeft/index.tsx

@@ -0,0 +1,438 @@
+import { makeStyles } from "@material-ui/core/styles";
+import { styled } from '@mui/material/styles';
+import { useState } from "react";
+import ListItemText from '@mui/material/ListItemText';
+import Button from '@mui/material/Button';
+import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
+import Menu from '@mui/material/Menu';
+import MenuItem from '@mui/material/MenuItem';
+import Divider from '@mui/material/Divider';
+import CheckBoxIcon from '@mui/icons-material/CheckBox';
+import Checkbox from '@mui/material/Checkbox';
+import PushPinIcon from '@mui/icons-material/PushPin';
+import CloseIcon from '@mui/icons-material/Close';
+import ReplyIcon from '@mui/icons-material/Reply';
+import LibraryMusicIcon from '@mui/icons-material/LibraryMusic';
+import FolderIcon from '@mui/icons-material/Folder';
+import ImageIcon from '@mui/icons-material/Image';
+import ContentCopyIcon from '@mui/icons-material/ContentCopy';
+import VideoLibraryIcon from '@mui/icons-material/VideoLibrary';
+import { CopyToClipboard } from 'react-copy-to-clipboard';
+import { removeMessageById,updateMessageById,pinMessageById } from "../../../../../../api-data";
+import { firstLetter,slicedWord,timeStampMessage,copied,emojisArr,handleDownload } 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: '4px 4px',
+    },
+    '& .MuiMenuItem-root': {
+      marginBottom: theme.spacing(1),
+      '& .MuiSvgIcon-root': {
+        fontSize: 21,
+        color: theme.palette.text.secondary,
+        marginRight: theme.spacing(2),
+      }
+    },
+  },
+}));
+
+const useStyles = makeStyles({
+    container: {
+      display: "flex",
+      justifyContent: "flex-start",
+      borderRadius: 7,
+    },
+  wrapper: {
+    position: 'relative',
+    display: 'flex',
+    alignItems: 'center',
+    alignContent: 'center',
+    flexWrap: 'wrap',
+    maxWidth: 450,
+    minWidth:200,
+    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",
+      "&:after": {
+        content: "''",
+        position: "absolute",
+        width: "0",
+        height: "0",
+        borderBottom: "15px solid #ffffff",
+        borderLeft: "15px solid transparent",
+        borderRight: "15px solid transparent",
+        bottom: '0px',
+        left: "-15px"
+      },
+      "&:before": {
+        content: "''",
+        position: "absolute",
+        width: "0",
+        height: "0",
+        borderBottom: "17px solid #ffffff",
+        borderLeft: "16px solid transparent",
+        borderRight: "16px solid transparent",
+        bottom: "-1px",
+        left: "-17px"
+      }      
+  },
+    messageActive: {
+      wordBreak:'break-word',
+      textAlign: "left",
+      font: "400 .9em 'Open Sans', sans-serif",
+      "&:after": {
+        content: "''",
+        position: "absolute",
+        width: "0",
+        height: "0",
+        borderBottom: "15px solid #babdbc",
+        borderLeft: "15px solid transparent",
+        borderRight: "15px solid transparent",
+        bottom: '0px',
+        left: "-15px"
+      },
+      "&:before": {
+        content: "''",
+        position: "absolute",
+        width: "0",
+        height: "0",
+        borderBottom: "17px solid #babdbc",
+        borderLeft: "16px solid transparent",
+        borderRight: "16px solid transparent",
+        bottom: "-1px",
+        left: "-17px"
+      }      
+  },    
+  copyIcon: {
+    color: '#b56ff7',
+    cursor: 'pointer',
+    marginRight: 7,
+    '&:hover': {
+      color: '#9c3bf8',
+    },
+  },  
+  time: {
+      position: "absolute",
+      fontSize: ".65em",
+      fontWeight:600,
+      bottom: 6,
+      right: 6,
+      color: '#414141',
+      padding: 3,
+      borderRadius: 5,
+  },
+  captionWrapper: {
+      position: "absolute",
+      fontSize: ".85em",
+      color: '#414141',
+      maxWidth:'80%',
+      fontWeight:600,
+      bottom: -30,
+      left: 6,
+      borderRadius: 5,
+      wordBreak:'break-word',
+      textAlign: "left",
+      font: "400 .9em 'Open Sans', sans-serif",
+      backgroundColor: '#deffa9',
+      padding:10,
+      "&:after": {
+        content: "''",
+        position: "absolute",
+        width: "0",
+        height: "0",
+        borderBottom: "15px solid #deffa9",
+        borderLeft: "15px solid transparent",
+        borderRight: "15px solid transparent",
+        bottom: '0px',
+        left: "-15px"
+      },
+      "&:before": {
+        content: "''",
+        position: "absolute",
+        width: "0",
+        height: "0",
+        borderBottom: "17px solid #deffa9",
+        borderLeft: "16px solid transparent",
+        borderRight: "16px solid transparent",
+        bottom: "0px",
+        left: "-17px"
+      }      
+  },   
+  modalDelete: {
+    background: '#ffffff',
+    position: 'absolute',
+    content:'',
+    width: '20%',
+    height:'auto',
+    left: '40%',
+    bottom: '48.5%',
+    borderRadius: 10,
+    padding: 10,
+    display: 'flex',
+    flexDirection:'column'
+  },  
+  overlay: {
+    position: 'fixed',
+    top: 0,
+    left: 0,
+    width: '100vw',
+    height: '100vh',
+    zIndex: 100,
+    backgroundColor: 'rgba(104, 105, 104, 0.6)',
+    overflowY: 'hidden',
+  },
+  emojiTitle: {
+      position: "absolute",
+      fontSize: "1.7em",
+      fontWeight:600,
+      bottom: 0,
+      right: -40,
+  },
+  emojiCompanionTitle: {
+      position: "absolute",
+      fontSize: "1.7em",
+      fontWeight:600,
+      bottom: '2rem',
+      right: -40,
+  },  
+  emoji: {
+		cursor: 'pointer',
+		fontSize: '1.7rem',
+    transition: 'all 0.3s',
+    '&:hover': {
+      transform: 'scale(1.5)'
+    }
+  },
+  emojiActive: {
+    cursor: 'pointer',
+    fontSize: '1.2rem',
+    animation: `$emoji 0.6s ease-out`,
+		animationDirection: 'forwards',
+		animationIterationCount: 1,
+  },  
+  '@keyframes emoji': {
+	  '5%': { transform: 'translateY(1rem)'},
+	  '10%': { transform: 'translateY(0) scale(1)',opacity: 1},
+	  '50%': { transform: 'translateY(-4rem) scale(1.5) rotateY(90deg)'},
+	  '80%': {opacity: 0},
+	  '100%': {transform: 'translateY(-8rem) scale(2) rotateY(180deg)',opacity: 0},
+  },
+  iconClose: {
+    '&:hover': {
+      transform: 'rotate(180deg)',
+      transition: 'all 250ms ease-out ',
+    }
+  },
+  folderIcon: {
+    color: '#00b333',
+    cursor: 'pointer',
+    '&:hover': {
+      color: '#00e040'
+    },
+  },
+  replyColumn: {
+    height: 50,
+    width: 2,
+    backgroundColor: '#00b333',
+    margin:'0px 10px'
+  },
+  replyListItem: {
+    display: 'flex',
+    width: '100%',
+    flexWrap: 'nowrap',
+    alignContent: 'center',
+    alignItems: 'center',
+  },  
+});
+
+const label = { inputProps: { 'aria-label': 'Checkbox demo' } };
+
+interface IMessageReplyLeft {
+  url:string,
+  newMessage: string,
+  message:string,
+  name:string,
+  lastName:string,
+  createdAt: string,
+  caption: string,
+  newCaption: string,
+  emoji: string,
+  emojiCompanion: string,
+  pinned: boolean,
+  isSomeSelected: boolean,
+  isSelected:(_id:string) => boolean,
+  handleSelected: (_id:string) => void,  
+  _id: string,
+  nightMode: boolean,
+  handleReply: (_id: string) => void,
+  fullType: string,
+  handleScrollToTheMessage: (_id: string) => void,
+  oldId: string
+}
+
+const MessageReplyLeft = ({url,newMessage,message,name,lastName,createdAt,caption,newCaption,emoji,emojiCompanion,pinned,isSomeSelected,isSelected,handleSelected,_id,nightMode,handleReply,fullType,handleScrollToTheMessage,oldId}:IMessageReplyLeft) => {
+  const classes = useStyles();
+  const [anchorEl, setAnchorEl] = useState<any>(null);
+  const [selected, setSelected] = useState<boolean>(false);
+  const [modal,setModal] = useState<boolean>(false)
+  const open = Boolean(anchorEl);
+  const checked = isSelected(_id)
+  
+  const handleClose = (type: string | undefined): void => {
+    if (type === 'copy') copied('Message')
+    if (type === 'delete') setModal(true)
+    setAnchorEl(null)
+    setSelected(false)
+  }
+
+  const handleDeleteModal = (e: any) => {
+    const id = e.target.id
+    if (id === 'overlay' || id === 'cancel') return setModal(false)
+    if (id === 'delete') {
+      removeMessageById(_id)
+      setModal(false)
+    }
+  }  
+  const handleContextMenu = (e: React.MouseEvent<HTMLDivElement>):void => {
+    e.preventDefault()
+    setAnchorEl(e.currentTarget)
+    setSelected(true)
+  }     
+
+  const handleEmojiMenu = ({ target }: any): void => {
+    const idEmoji = target.id
+    if (idEmoji === emoji) {updateMessageById(_id,'')  
+    } else updateMessageById(_id,idEmoji)
+  }
+  
+  const handleClickIcon = (e: any) => {
+    e.stopPropagation()
+    handleDownload(url, fullType)
+  }  
+  
+  return (
+    <div className={classes.container} style={{ marginBottom: newCaption ? 45 : 15}}>
+      {isSomeSelected&&<Checkbox {...label} checked={checked} sx={{color:nightMode?'#ffffff':'#00ff48','&.Mui-checked': {color: nightMode?'#ffffff':'#00ff48'}}} onClick={() => handleSelected(_id)}/>}
+     <div onContextMenu={(e) => handleContextMenu(e)} className={classes.wrapper}
+        style={{backgroundColor:selected?'#babdbc':undefined,
+        border:selected?'#babdbc':undefined,pointerEvents:isSomeSelected?'none':'auto'}}>
+        <ul style={{ backgroundColor: selected?'#babdbc':undefined,width:'100%'}}>
+          <MenuItem className={classes.replyListItem}>
+            {fullType === null &&<CopyToClipboard onCopy={() => copied('Message')} text={message}>
+            <ContentCopyIcon className={classes.folderIcon} fontSize='large'/>
+            </CopyToClipboard>}
+            {fullType&&fullType.includes('audio') &&<LibraryMusicIcon onClick={handleClickIcon}
+            className={classes.folderIcon} fontSize='large' />}
+            {fullType&&fullType.includes('video') &&<VideoLibraryIcon onClick={handleClickIcon}
+            className={classes.folderIcon} fontSize='large' />}
+            {fullType&&fullType.includes('image') &&<ImageIcon onClick={handleClickIcon}
+            className={classes.folderIcon} fontSize='large' />}
+            {fullType&&fullType.includes('application') && <FolderIcon onClick={handleClickIcon}
+            className={classes.folderIcon} fontSize='large' />} 
+            <div className={classes.replyColumn}></div>
+            <ListItemText onClick={() => handleScrollToTheMessage(oldId)}
+              primary={`${firstLetter(name)}${slicedWord(name, 15, 1)}
+              ${firstLetter(lastName)}${slicedWord(lastName, 15, 1)}`}
+              primaryTypographyProps={{color: "#00b333"}}
+              secondary={slicedWord(message, 40, 0)}
+              secondaryTypographyProps={{ color: "#626262" }} />          
+          </MenuItem>
+        </ul>
+        <ListItemText className={selected?classes.messageActive:classes.message}  
+          primary={slicedWord(newMessage, 40, 0)}
+          primaryTypographyProps={{color: "#0e0d0d"}}/>        
+        <div className={classes.time}>{timeStampMessage(createdAt)}</div>
+        {emojiCompanion && <div className={classes.emojiCompanionTitle}>{emojisArr[Number(emojiCompanion)]}</div>}
+        {emoji && <div className={classes.emojiTitle}>{emojisArr[Number(emoji)]}</div>}
+        {newCaption&&<div className={classes.captionWrapper}>{newCaption}</div>}
+      <StyledMenu id="demo-positioned-menu" aria-labelledby="demo-positioned-button"
+          anchorEl={anchorEl} open={open} onClose={handleClose}>
+          <MenuItem onClick={handleEmojiMenu} style={{ cursor: 'none' }} >
+            {emojisArr.map((el:string, i:number) =>
+              <div key={el} className={emoji === String(i)?classes.emojiActive:classes.emoji} id={String(i)}>{el}</div>)}
+          </MenuItem>
+        <Divider />
+        <MenuItem onClick={() => {
+          handleReply(_id)
+          handleClose(undefined)
+        }}>
+          <ReplyIcon />
+          Reply
+        </MenuItem>
+        <MenuItem>
+            <ReplyIcon style={{transform :'rotateY(180deg)'}} />
+            Forward
+        </MenuItem>           
+        <CopyToClipboard onCopy={() => handleClose('copy')} text={newMessage?newMessage:message}>
+          <MenuItem>
+            <ContentCopyIcon />
+             Copy Text
+          </MenuItem>
+        </CopyToClipboard>
+        <MenuItem onClick={() => {
+          pinMessageById(_id, !pinned)
+          handleClose(undefined)
+        }}>
+            {pinned ?
+              <CloseIcon className={classes.iconClose} /> :
+              <PushPinIcon />}
+             {pinned?'Unpin':'Pin'}
+        </MenuItem>          
+        <MenuItem onClick={() => {
+            handleSelected(_id)
+            handleClose(undefined)
+          }}>
+            <CheckBoxIcon />
+            Select
+        </MenuItem>  
+        <MenuItem style={{color:'#f02a2a'}} onClick={() => handleClose('delete')}>
+            <DeleteOutlineIcon style={{color:'#f02a2a'}}/>
+            Delete
+        </MenuItem>        
+      </StyledMenu>
+      {modal &&
+      <div onClick={handleDeleteModal} className={classes.overlay} id='overlay'>
+        <div className={classes.modalDelete}>
+          <h3 style={{color: '#2c2c2c'}}>Delete message</h3>
+          <p style={{ color: '#050505' }}>Are you sure you want to delete message?</p>
+          <Button id='delete' variant="text" color="error" style={{fontWeight:500,fontSize:22}}>
+            DELETE MESSAGE
+          </Button>         
+          <Button id='cancel' variant="text" style={{fontWeight:500,fontSize:22}}>
+             CANCEL
+          </Button>
+        </div>  
+      </div>}         
+      </div>
+  </div>    
+)};  
+
+export default MessageReplyLeft

+ 429 - 0
src/components/HomePage/CentralBar/ChatBar/Messages/MessageReplyRight/index.tsx

@@ -0,0 +1,429 @@
+import { makeStyles } from "@material-ui/core/styles";
+import { styled } from '@mui/material/styles';
+import { useState } from "react";
+import ListItemText from '@mui/material/ListItemText';
+import Button from '@mui/material/Button';
+import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
+import Menu from '@mui/material/Menu';
+import MenuItem from '@mui/material/MenuItem';
+import Divider from '@mui/material/Divider';
+import CheckBoxIcon from '@mui/icons-material/CheckBox';
+import Checkbox from '@mui/material/Checkbox';
+import PushPinIcon from '@mui/icons-material/PushPin';
+import CloseIcon from '@mui/icons-material/Close';
+import ReplyIcon from '@mui/icons-material/Reply';
+import LibraryMusicIcon from '@mui/icons-material/LibraryMusic';
+import FolderIcon from '@mui/icons-material/Folder';
+import ImageIcon from '@mui/icons-material/Image';
+import ContentCopyIcon from '@mui/icons-material/ContentCopy';
+import VideoLibraryIcon from '@mui/icons-material/VideoLibrary';
+import { CopyToClipboard } from 'react-copy-to-clipboard';
+import { firstLetter, slicedWord, timeStampMessage, copied,emojisArr,handleDownload } from '../../../../../../helpers'
+import { removeMessageById,updateMessageById,pinMessageById } from "../../../../../../api-data";
+
+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: '4px 4px',
+    },
+    '& .MuiMenuItem-root': {
+      marginBottom: theme.spacing(1),
+      '& .MuiSvgIcon-root': {
+        fontSize: 21,
+        color: theme.palette.text.secondary,
+        marginRight: theme.spacing(2),
+      }
+    },
+  },
+}));
+
+const useStyles = makeStyles({
+  container: {
+    display: "flex",
+    justifyContent: "flex-end",
+    borderRadius: 7,
+  },
+  wrapper: {
+    position: 'relative',
+    display: 'flex',
+    alignItems: 'center',
+    alignContent: 'center',
+    flexWrap: 'wrap',
+    maxWidth: 450,
+    minWidth:200,
+    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",
+    "&:after": {
+      content: "''",
+      position: "absolute",
+      width: "0",
+      height: "0",
+      borderBottom: "15px solid #deffa9",
+      borderLeft: "15px solid transparent",
+      borderRight: "15px solid transparent",
+      bottom: "0",
+      right: "-15px"
+    },
+    "&:before": {
+      content: "''",
+      position: "absolute",
+      width: "0",
+      height: "0",
+      borderBottom: "17px solid #deffa9",
+      borderLeft: "16px solid transparent",
+      borderRight: "16px solid transparent",
+      bottom: "-1px",
+      right: "-17px"
+    },
+  },
+  messageActive: {
+    wordBreak:'break-word',
+    textAlign: "left",
+    font: "400 .9em 'Open Sans', sans-serif",
+    "&:after": {
+      content: "''",
+      position: "absolute",
+      width: "0",
+      height: "0",
+      borderBottom: "15px solid #ced8d7",
+      borderLeft: "15px solid transparent",
+      borderRight: "15px solid transparent",
+      bottom: "0",
+      right: "-15px"
+    },
+    "&:before": {
+      content: "''",
+      position: "absolute",
+      width: "0",
+      height: "0",
+      borderBottom: "17px solid #ced8d7",
+      borderLeft: "16px solid transparent",
+      borderRight: "16px solid transparent",
+      bottom: "-1px",
+      right: "-17px"
+    }
+  },    
+  time: {
+      position: "absolute",
+      fontSize: ".65em",
+      fontWeight:600,
+      bottom: 6,
+      right: 6,
+      color: '#414141',
+      padding: 3,
+      borderRadius: 5,
+  },
+  captionWrapper: {
+      position: "absolute",
+      fontSize: ".85em",
+      color: '#626262',
+      maxWidth:'76%',
+      fontWeight:600,
+      bottom: -30,
+      right: 40,
+      borderRadius: 5,
+      wordBreak:'break-word',
+      textAlign: "left",
+      font: "400 .9em 'Open Sans', sans-serif",
+      backgroundColor: '#ffffff',
+      padding:10,
+      "&:after": {
+        content: "''",
+        position: "absolute",
+        width: "0",
+        height: "0",
+        borderBottom: "15px solid #ffffff",
+        borderLeft: "15px solid transparent",
+        borderRight: "15px solid transparent",
+        bottom: "0",
+        right: "-15px"
+      },
+      "&:before": {
+        content: "''",
+        position: "absolute",
+        width: "0",
+        height: "0",
+        borderBottom: "17px solid #ffffff",
+        borderLeft: "16px solid transparent",
+        borderRight: "16px solid transparent",
+        bottom: "0px",
+        right: "-17px"
+      }     
+  },  
+  modalDelete: {
+    background: '#ffffff',
+    position: 'absolute',
+    content:'',
+    width: '20%',
+    height:'auto',
+    left: '40%',
+    bottom: '48.5%',
+    borderRadius: 10,
+    padding: 10,
+    display: 'flex',
+    flexDirection:'column'
+  },  
+  overlay: {
+    position: 'fixed',
+    top: 0,
+    left: 0,
+    width: '100vw',
+    height: '100vh',
+    zIndex: 100,
+    backgroundColor: 'rgba(104, 105, 104, 0.6)',
+    overflowY: 'hidden',
+  },
+  emojiTitle: {
+      position: "absolute",
+      fontSize: "1.7em",
+      fontWeight:600,
+      bottom: 0,
+      left: -40,
+  },
+  emojiCompanionTitle: {
+      position: "absolute",
+      fontSize: "1.7em",
+      fontWeight:600,
+      bottom: '2rem',
+      left: -40,
+  },   
+  emoji: {
+		cursor: 'pointer',
+		fontSize: '1.7rem',
+    transition: 'all 0.3s',
+    '&:hover': {
+      transform: 'scale(1.5)'
+    }
+  },
+  emojiActive: {
+    cursor: 'pointer',
+    fontSize: '1.2rem',
+    animation: `$emoji 0.6s ease-out`,
+		animationDirection: 'forwards',
+		animationIterationCount: 1,
+  },  
+  '@keyframes emoji': {
+	  '5%': { transform: 'translateY(1rem)'},
+	  '10%': { transform: 'translateY(0) scale(1)',opacity: 1},
+	  '50%': { transform: 'translateY(-4rem) scale(1.5) rotateY(90deg)'},
+	  '80%': {opacity: 0},
+	  '100%': {transform: 'translateY(-8rem) scale(2) rotateY(180deg)',opacity: 0},
+  },
+  iconClose: {
+    '&:hover': {
+      transform: 'rotate(180deg)',
+      transition: 'all 250ms ease-out ',
+    }
+  },
+  folderIcon: {
+    color: '#00b333',
+    cursor: 'pointer',
+    '&:hover': {
+      color: '#00e040'
+    },
+  },
+  replyColumn: {
+    height: 50,
+    width: 2,
+    backgroundColor: '#00b333',
+    margin:'0px 10px'
+  },
+  replyListItem: {
+    display: 'flex',
+    width: '100%',
+    flexWrap: 'nowrap',
+    alignContent: 'center',
+    alignItems: 'center',
+  },
+});
+
+const label = { inputProps: { 'aria-label': 'Checkbox demo' } };
+
+interface IMessageReplyRight {
+  url:string,
+  newMessage: string,
+  message:string,
+  name:string,
+  lastName:string,
+  createdAt: string,
+  caption: string,
+  newCaption: string,
+  emoji: string,
+  emojiCompanion: string,
+  pinned: boolean,
+  isSomeSelected: boolean,
+  isSelected:(_id:string) => boolean,
+  handleSelected: (_id:string) => void,  
+  _id: string,
+  nightMode: boolean,
+  handleReply: (_id: string) => void,
+  fullType: string,
+  handleScrollToTheMessage: (_id: string) => void,
+  oldId: string
+}
+
+const MessageReplyRight = ({url,newMessage,message,name,lastName,createdAt,caption,newCaption,emoji,emojiCompanion,pinned,isSomeSelected,isSelected,handleSelected,_id,nightMode,handleReply,fullType,handleScrollToTheMessage,oldId}:IMessageReplyRight) => {
+  const classes = useStyles();
+  const [anchorEl, setAnchorEl] = useState<any>(null);
+  const [selected, setSelected] = useState<boolean>(false);
+  const [modal,setModal] = useState<boolean>(false)
+  const open = Boolean(anchorEl);
+  const checked = isSelected(_id)
+  const handleClose = (type: string | undefined): void => {
+    if (type === 'copy') copied('Message')
+    if (type === 'delete') setModal(true)
+      setAnchorEl(null)
+      setSelected(false)
+  }
+
+  const handleDeleteModal = (e: any) => {
+    const id = e.target.id
+    if (id === 'overlay' || id === 'cancel') return setModal(false)
+    if (id === 'delete') {
+      removeMessageById(_id)
+      setModal(false)
+    }
+  }  
+  const handleContextMenu = (e: React.MouseEvent<HTMLDivElement>):void => {
+    e.preventDefault()
+    setAnchorEl(e.currentTarget)
+    setSelected(true)
+  }
+  
+  const handleEmojiMenu = ({ target }: any): void => {
+    const idEmoji = target.id
+    if (idEmoji === emoji) {updateMessageById(_id,'')  
+    } else updateMessageById(_id,idEmoji)
+  }
+  
+  const handleClickIcon = (e: any) => {
+    e.stopPropagation()
+    handleDownload(url, fullType)
+  }
+
+  return (
+    <div className={classes.container} style={{ marginBottom: newCaption ? 45 : 15}}>
+      <div onContextMenu={(e) => handleContextMenu(e)} className={classes.wrapper}
+       style={{backgroundColor:selected?'#ced8d7':undefined,
+        border:selected?'#ced8d7':undefined,pointerEvents:isSomeSelected?'none':'auto'}}>
+        <ul style={{ backgroundColor: selected?'#ced8d7':undefined,width:'100%'}}>
+          <MenuItem className={classes.replyListItem}>
+            {fullType === null &&<CopyToClipboard onCopy={() => copied('Message')} text={message}>
+            <ContentCopyIcon className={classes.folderIcon} fontSize='large'/>
+            </CopyToClipboard>}
+            {fullType&&fullType.includes('audio') &&<LibraryMusicIcon onClick={handleClickIcon}
+            className={classes.folderIcon} fontSize='large' />}
+            {fullType&&fullType.includes('video') &&<VideoLibraryIcon onClick={handleClickIcon}
+            className={classes.folderIcon} fontSize='large' />}
+            {fullType&&fullType.includes('image') &&<ImageIcon onClick={handleClickIcon}
+            className={classes.folderIcon} fontSize='large' />}
+            {fullType&&fullType.includes('application') && <FolderIcon onClick={handleClickIcon}
+            className={classes.folderIcon} fontSize='large' />}         
+            <div className={classes.replyColumn}></div>
+            <ListItemText onClick={() => handleScrollToTheMessage(oldId)}
+              primary={`${firstLetter(name)}${slicedWord(name, 15, 1)}
+              ${firstLetter(lastName)}${slicedWord(lastName, 15, 1)}`}
+              primaryTypographyProps={{color: "#00b333"}}
+              secondary={slicedWord(message, 40, 0)}
+              secondaryTypographyProps={{ color: "#626262" }} />          
+          </MenuItem>
+        </ul>
+        <ListItemText className={selected?classes.messageActive:classes.message}  
+          primary={slicedWord(newMessage, 40, 0)}
+          primaryTypographyProps={{color: "#0e0d0d"}}/>         
+        <div className={classes.time}>{timeStampMessage(createdAt)}</div>
+        {emojiCompanion && <div className={classes.emojiCompanionTitle}>{emojisArr[Number(emojiCompanion)]}</div>}
+        {emoji && <div className={classes.emojiTitle}>{emojisArr[Number(emoji)]}</div>}
+        {newCaption&&<div className={classes.captionWrapper}>{newCaption}</div>}
+      <StyledMenu id="demo-positioned-menu" aria-labelledby="demo-positioned-button"
+          anchorEl={anchorEl} open={open} onClose={handleClose}>
+          <MenuItem onClick={handleEmojiMenu} style={{ cursor: 'none' }} >
+            {emojisArr.map((el:string, i:number) =>
+              <div key={el} className={emoji === String(i)?classes.emojiActive:classes.emoji} id={String(i)}>{el}</div>)}
+          </MenuItem>
+        <Divider />
+        <MenuItem onClick={() => {
+          handleReply(_id)
+          handleClose(undefined)
+        }}>
+          <ReplyIcon />
+          Reply
+        </MenuItem>
+        <MenuItem>
+            <ReplyIcon style={{transform :'rotateY(180deg)'}} />
+            Forward
+        </MenuItem>           
+        <CopyToClipboard onCopy={() => handleClose('copy')} text={newMessage?newMessage:message}>
+          <MenuItem>
+            <ContentCopyIcon />
+             Copy Text
+          </MenuItem>
+        </CopyToClipboard>
+        <MenuItem onClick={() => {
+          pinMessageById(_id, !pinned)
+          handleClose(undefined)
+        }}>
+            {pinned ?
+              <CloseIcon className={classes.iconClose} /> :
+              <PushPinIcon />}
+             {pinned?'Unpin':'Pin'}
+        </MenuItem>           
+        <MenuItem onClick={() => {
+            handleSelected(_id)
+            handleClose(undefined)
+          }}>
+            <CheckBoxIcon />
+            Select
+        </MenuItem>
+        <MenuItem style={{color:'#f02a2a'}} onClick={() => handleClose('delete')}>
+            <DeleteOutlineIcon style={{color:'#f02a2a'}}/>
+            Delete
+        </MenuItem>        
+      </StyledMenu>
+      {modal &&
+      <div onClick={handleDeleteModal} className={classes.overlay} id='overlay'>
+        <div className={classes.modalDelete}>
+          <h3 style={{color: '#2c2c2c'}}>Delete message</h3>
+          <p style={{ color: '#050505' }}>Are you sure you want to delete message?</p>
+          <Button id='delete' variant="text" color="error" style={{fontWeight:500,fontSize:22}}>
+            DELETE MESSAGE
+          </Button>         
+          <Button id='cancel' variant="text" style={{fontWeight:500,fontSize:22}}>
+             CANCEL
+          </Button>
+        </div>  
+      </div>}        
+      </div>
+   {isSomeSelected&&<Checkbox {...label} checked={checked} sx={{color:nightMode?'#ffffff':'#00ff48','&.Mui-checked': {color: nightMode?'#ffffff':'#00ff48'}}} onClick={() => handleSelected(_id)}/>}      
+   </div>    
+)};
+
+export  default  MessageReplyRight

+ 1 - 1
src/components/HomePage/CentralBar/ChatBar/Messages/MessageRightText/index.tsx

@@ -177,7 +177,7 @@ const useStyles = makeStyles({
         borderBottom: "17px solid #ffffff",
         borderLeft: "16px solid transparent",
         borderRight: "16px solid transparent",
-        bottom: "-1px",
+        bottom: "0px",
         right: "-17px"
       }     
   },  

+ 32 - 28
src/components/HomePage/CentralBar/ChatBar/SendMessage/index.tsx

@@ -17,13 +17,13 @@ import ListItemText from '@mui/material/ListItemText';
 import Picker from 'emoji-picker-react';
 
 import { useReactMediaRecorder } from "react-media-recorder";
-import { useState } from "react";
+import { useState,useEffect } from "react";
 import { useSelector } from "react-redux";
 
 import FilesMenu from "../FilesMenu";
 import {
     sentMessageById, sentImgMessageById, sentAudioMessageById,
-    sentVideoMessageById,sentFileMessageById
+    sentVideoMessageById,sentFileMessageById,sentMessageReplyById
 } from '../../../../../api-data'
 import { getChat } from '../../../../../redux/chat/selector'
 import { getRightIsOpen } from '../../../../../redux/control/selector'
@@ -31,6 +31,7 @@ import { playNotification,prodBaseURL,firstLetter,slicedWord } from "../../../..
 import { typingChat } from "../../../../../api-data";
 import { TMessage } from "../../../../../typescript/redux/messages/types";
 
+
 const useStyles = makeStyles({   
     container: {
         width: '35vw',
@@ -310,9 +311,10 @@ interface ISendMessage{
   isReply:TMessage | undefined,
   setIsReply: React.Dispatch<React.SetStateAction<TMessage | undefined>>,
   chatDivRef: any | null,
+  handleScrollToTheMessage: (_id:string) => void
 }
 
-const SendMessage = ({isArrow,silentMode,isReply,setIsReply,chatDivRef }:ISendMessage) => {
+const SendMessage = ({isArrow,silentMode,isReply,setIsReply,chatDivRef,handleScrollToTheMessage }:ISendMessage) => {
     const classes = useStyles();
     const { companionId } = useSelector(getChat)
     const rightIsOpen = useSelector(getRightIsOpen)
@@ -330,10 +332,10 @@ const SendMessage = ({isArrow,silentMode,isReply,setIsReply,chatDivRef }:ISendMe
     const { status: _status, startRecording: _startRecording, stopRecording: _stopRecording,
       mediaBlobUrl: _mediaBlobUrl, clearBlobUrl: _clearBlobUrl } = useReactMediaRecorder({ video: true, blobPropertyBag: { type: "video/mp4" } });
     const videoConstraints = {
-    width: 1280,
-    height: 720,
-    facingMode: "user"
-  };
+      width: 1280,
+      height: 720,
+      facingMode: "user"
+    };
     const onEmojiClick = (_e:any, emojiObject:any) => {
         setValue(prevValue => prevValue + emojiObject.emoji)
         setIsOpenEmoji(false)
@@ -350,12 +352,24 @@ const SendMessage = ({isArrow,silentMode,isReply,setIsReply,chatDivRef }:ISendMe
       isOpenMenu && setIsOpenMenu(false)
       isOpenEmoji && setIsOpenEmoji(false)
       isOpenCaption && setIsOpenCaption(false)
-    }
-  const sentMessage = async () => {
+      isReply&&setIsReply(undefined)
+   }
+  const defaultState = () => {
+      clearBlobUrl()
+      _clearBlobUrl()
+      setIsRecording(false)
+      setIsFilming(false)     
+      setFile(false)
+      setValue('')
+      setCaption('')
+      setType('')
+      setIsOpenMenu(false)
+      setIsOpenEmoji(false)
+      setIsOpenCaption(false)
+    }  
+    const sentMessage = async () => {
         if (value && !isReply) sentMessageById(companionId, value, caption.trim())
-        if (value && isReply) {
-          console.log('sended reply')
-        }
+        if (value && isReply) sentMessageReplyById(isReply._id, value, caption.trim())
         if (mediaBlobUrl && type === 'recording') {
             const audio = new XMLHttpRequest();
             audio.open('GET', mediaBlobUrl, true);
@@ -467,20 +481,10 @@ const SendMessage = ({isArrow,silentMode,isReply,setIsReply,chatDivRef }:ISendMe
     setIsReply(undefined)
     clearMessage()
   }
-
-  const handleScrollToTheMessage = () => {
-    if(!isReply) return
-    const childNodes = chatDivRef.current.childNodes[0].childNodes
-    let toScrollNode = [...childNodes].find((el: any) => el.id === isReply._id)
-    if (toScrollNode) {
-      toScrollNode = [...toScrollNode.childNodes].slice(-1)[0]
-      toScrollNode.style.boxShadow = '0px 0px 6px 0px #ffffff'
-      toScrollNode.scrollIntoView({ behavior: 'smooth' })
-      setTimeout(() => {
-        toScrollNode.style.boxShadow = 'unset'
-      }, 2000)
-    }
-  }  
+  
+  useEffect(() => {
+    if (isReply) defaultState()
+  },[isReply])
 
     return (
         <div className={value || file || status === 'stopped' || _status === 'stopped' ?classes.containerActive:classes.container}>
@@ -489,7 +493,7 @@ const SendMessage = ({isArrow,silentMode,isReply,setIsReply,chatDivRef }:ISendMe
                 <CloseIcon onClick={handleCloseReply} className={classes.replyIconClose} />
                 <div className={classes.replyColumn}></div>
                 <ul className={classes.replyListWrapper}>
-                  <MenuItem onClick={handleScrollToTheMessage}>
+                  <MenuItem onClick={() => handleScrollToTheMessage(isReply._id)}>
                     <ListItemText
                       primary={`${firstLetter(isReply.name)}${slicedWord(isReply.name, 15, 1)} 
                        ${firstLetter(isReply.lastName)}${slicedWord(isReply.lastName, 15, 1)}`}
@@ -527,7 +531,7 @@ const SendMessage = ({isArrow,silentMode,isReply,setIsReply,chatDivRef }:ISendMe
                 </>}            
                 <CloseIcon onClick={clearMessage} fontSize="small" className={classes.iconCancel}
                     sx={{width: 56, height: 56, display: file || value || status === 'stopped'
-                    || _status === 'stopped' || !isReply ? 'inline-block' : 'none'}} />
+                    || _status === 'stopped' ? 'inline-block' : 'none'}} />
                 <VideocamIcon onClick={handleFilming} className={classes.avatarCamera}
                     sx={{backgroundColor: '#ffffff', color: '#6b6b6b', width: 56, height: 56}}
                     style={{ display: status !== 'idle' || _status === 'stopped' || file || value || isFilming || isReply ? 'none' : 'block' }} />                           

+ 71 - 8
src/components/HomePage/CentralBar/ChatBar/index.tsx

@@ -6,11 +6,13 @@ import ArrowBack from "./ArrowBack";
 import SendMessage from "./SendMessage";
 import UnpinBar from "./UnpinBar";
 import MessageLeftText from './Messages/MessageLeftText'
+import MessageReplyLeft from "./Messages/MessageReplyLeft";
 import MessageLeftImage from './Messages/MessageLeftImage'
 import MessageLeftAudio from './Messages/MessageLeftAudio'
 import MessageLeftVideo from './Messages/MessageLeftVideo'
 import MessageLeftFile from "./Messages/MessageLeftFile";
 import MessageRightText from './Messages/MessageRightText'
+import MessageReplyRight from "./Messages/MessageReplyRight";
 import MessageRightImage from './Messages/MessageRightImage'
 import MessageRightAudio from './Messages/MessageRightAudio'
 import MessageRightVideo from './Messages/MessageRightVideo'
@@ -129,7 +131,20 @@ const ChatBar = ({chatDivRef,selectedArr,setSelectedArr,isSomeSelected,setIsSome
   
   const handleReply = (_id: string) => {
     setIsReply(renderArr.find((el) => el._id ===_id))
-  }    
+  }
+  
+  const handleScrollToTheMessage = (_id:string) => {
+    const childNodes = chatDivRef.current.childNodes[0].childNodes
+    let toScrollNode = [...childNodes].find((el: any) => el.id === _id)
+    if (toScrollNode) {
+      toScrollNode = [...toScrollNode.childNodes].slice(-1)[0]
+      toScrollNode.style.boxShadow = '0px 0px 6px 0px #ffffff'
+      toScrollNode.scrollIntoView({ behavior: 'smooth' })
+      setTimeout(() => {
+        toScrollNode.style.boxShadow = 'unset'
+      }, 2000)
+    }
+  }  
   
   useEffect(() => {
     if (scrollChat) {
@@ -176,8 +191,8 @@ const ChatBar = ({chatDivRef,selectedArr,setSelectedArr,isSomeSelected,setIsSome
       <div id={companionId} ref={chatDivRef} onScroll={debouncedHandleScroll}
         className={messagesMemo.length > 0 ? classes.messagesScroll : classes.messagesEmpty}>
         <div className={classes.messagesBody}>
-        {messagesMemo.length > 0 ? renderArr.map(({ message, name, lastName, color,pinned,
-           createdAt,number, type,fullType,caption,emoji,emojiCompanion,_id}) => {
+        {messagesMemo.length > 0 ? renderArr.map(({ newMessage,message, name, lastName, color,pinned,
+           createdAt,number, type,fullType,newCaption,caption,emoji,emojiCompanion,_id,oldId,}) => {
           let isTime
           if (!time) {
             isTime = true
@@ -188,7 +203,7 @@ const ChatBar = ({chatDivRef,selectedArr,setSelectedArr,isSomeSelected,setIsSome
           }
           const url = `${prodAwsS3}/${message}`
           if (number !== userNumber) {
-            if (type === 'text') return (<div key={createdAt} id={_id} style={{borderRadius: 7}}> 
+            if (type === 'text' && !oldId) return (<div key={createdAt} id={_id} style={{borderRadius: 7}}> 
               {isTime&&<MessageTime  message={timeStampFilter(createdAt)}/>}
                <MessageLeftText
                message={message}
@@ -205,7 +220,31 @@ const ChatBar = ({chatDivRef,selectedArr,setSelectedArr,isSomeSelected,setIsSome
                _id={_id}
                nightMode={nightMode}
                handleReply={handleReply}
-                 /></div>)
+               /></div>)
+            if (type === 'text' && oldId) return (<div key={createdAt} id={_id} style={{borderRadius: 7}}> 
+              {isTime&&<MessageTime  message={timeStampFilter(createdAt)}/>}
+               <MessageReplyLeft
+                url={url}
+                newMessage={newMessage}
+                message={message}
+                createdAt={createdAt}
+                name={name}
+                lastName={lastName}
+                caption={caption}
+                newCaption={newCaption}
+                emoji={emoji}
+                emojiCompanion={emojiCompanion}
+                pinned={pinned}
+                isSomeSelected={isSomeSelected}
+                isSelected={isSelected}
+                handleSelected={handleSelected}                
+                _id={_id}
+                nightMode={nightMode}
+                handleReply={handleReply}
+                fullType={fullType}
+                handleScrollToTheMessage={handleScrollToTheMessage}
+                oldId={oldId}
+                 /></div>)            
             if (type === 'image') return (<div key={createdAt} id={_id} style={{borderRadius: 7}}>
               {isTime&&<MessageTime message={timeStampFilter(createdAt)}/>}
                 <MessageLeftImage   
@@ -278,7 +317,7 @@ const ChatBar = ({chatDivRef,selectedArr,setSelectedArr,isSomeSelected,setIsSome
                 handleReply={handleReply}
                   /></div>)             
           } else {
-            if (type === 'text') return (<div key={createdAt} id={_id} style={{borderRadius: 7}}>
+            if (type === 'text' && !oldId) return (<div key={createdAt} id={_id} style={{borderRadius: 7}}>
               {isTime&&<MessageTime message={timeStampFilter(createdAt)}/>}
                 <MessageRightText   
                 message={message}
@@ -295,7 +334,31 @@ const ChatBar = ({chatDivRef,selectedArr,setSelectedArr,isSomeSelected,setIsSome
                 _id={_id}
                 nightMode={nightMode}
                 handleReply={handleReply}
-                  /></div>)
+                /></div>)
+            if (type === 'text' && oldId) return (<div key={createdAt} id={_id} style={{borderRadius: 7}}>
+              {isTime&&<MessageTime message={timeStampFilter(createdAt)}/>}
+              <MessageReplyRight
+                url={url}
+                newMessage={newMessage}
+                message={message}
+                createdAt={createdAt}
+                name={name}
+                lastName={lastName}
+                caption={caption}
+                newCaption={newCaption}
+                emoji={emoji}
+                emojiCompanion={emojiCompanion}
+                pinned={pinned}
+                isSomeSelected={isSomeSelected}
+                isSelected={isSelected}
+                handleSelected={handleSelected}                
+                _id={_id}
+                nightMode={nightMode}
+                handleReply={handleReply}
+                fullType={fullType}
+                handleScrollToTheMessage={handleScrollToTheMessage}
+                oldId={oldId}
+                  /></div>)            
             if (type === 'image') return (<div key={createdAt} id={_id} style={{borderRadius: 7}}>
               {isTime&&<MessageTime message={timeStampFilter(createdAt)}/>}
                 <MessageRightImage   
@@ -371,7 +434,7 @@ const ChatBar = ({chatDivRef,selectedArr,setSelectedArr,isSomeSelected,setIsSome
         }) : <AlertInfo name='You do not have messages yet!' />}
         </div>
       </div>
-      {!openPinned&&<SendMessage isArrow={isArrow} silentMode={silentMode} isReply={isReply} setIsReply={setIsReply} chatDivRef={chatDivRef}/>}
+      {!openPinned&&<SendMessage isArrow={isArrow} silentMode={silentMode} isReply={isReply} setIsReply={setIsReply} chatDivRef={chatDivRef} handleScrollToTheMessage={handleScrollToTheMessage}/>}
       {openPinned&&!isSomeSelected &&<UnpinBar pinnedMessagesMemo={pinnedMessagesMemo} handleUnpinAll={handleUnpinAll} />}
     </div>
   );

+ 4 - 2
src/typescript/redux/allMessages/types.ts

@@ -1,5 +1,6 @@
 export type TMessage = {
-  message:string,
+  message: string,
+  newMessage:string,
   name: string,
   lastName: string,
   avatarUrl:string,
@@ -11,11 +12,12 @@ export type TMessage = {
   companionId: string,
   companionIdFlow: string,
   caption: string,
+  newCaption: string,
   emoji: string,
   emojiCompanion: string,
   pinned: boolean,
-  reply: boolean,
   idTime: string,
+  oldId: string,
   owner: any,
   createdAt: string,
   updatedAt: string,

+ 4 - 2
src/typescript/redux/messages/types.ts

@@ -1,5 +1,6 @@
 export type TMessage = {
-  message:string,
+  message: string,
+  newMessage:string,
   name: string,
   lastName: string,
   avatarUrl:string,
@@ -9,11 +10,12 @@ export type TMessage = {
   type: string,
   fullType: string,
   caption: string,
+  newCaption: string,
   emoji: string,
   emojiCompanion: string,
   pinned: boolean,
-  reply: boolean,
   idTime: string,
+  oldId: string,
   companionId: string,
   companionIdFlow: string,
   owner: any,

+ 4 - 2
src/typescript/redux/pinnedMessages/types.ts

@@ -1,5 +1,6 @@
 export type TPinnedMessage = {
-  message:string,
+  message: string,
+  newMessage:string,
   name: string,
   lastName: string,
   avatarUrl:string,
@@ -9,11 +10,12 @@ export type TPinnedMessage = {
   type: string,
   fullType: string,
   caption: string,
+  newCaption: string,
   emoji: string,
   emojiCompanion: string,
   pinned: boolean,
-  reply: boolean,
   idTime: string,
+  oldId: string,
   companionId: string,
   companionIdFlow: string,
   owner: any,