Browse Source

call message

unknown 2 năm trước cách đây
mục cha
commit
6dbd454cb4

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1 - 1
.eslintcache


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

@@ -340,6 +340,16 @@ const sentMessageById = async <T>(id:string,message:string,caption:string): Prom
   }
 };
 
+const sentMessageCallById = async <T>(id:string,reject:boolean,duration:number): Promise<T | undefined> => {
+  try {
+    const { data: { data } } = await axios.post('/messages/call', { id, reject,duration });
+    return data
+  } catch (e) {
+    forbidden(e)
+    return undefined
+  }
+};
+
 const sentMessageEditById = async <T>(id:string,message:string,caption:string): Promise<T | undefined> => {
   try {
     const { data: { data } } = await axios.post('/messages/edit', { id, message,caption });
@@ -464,6 +474,7 @@ export {
   pinMessageById,
   unpinAllMessagesById,
   sentMessageById,
+  sentMessageCallById,
   sentMessageEditById,
   sentMessageReplyById,
   sentMessageForwardById,

+ 25 - 17
src/components/HomePage/CallBar/index.tsx

@@ -22,7 +22,7 @@ import Alert from '@mui/material/Alert';
 import { getChat } from '../../../redux/chat/selector';
 import { getAuthorizationState } from '../../../redux/authorization/selector'; 
 import { prodAwsS3,prodBaseURL,prodSocketURL, firstLetter, slicedWord,getTimeBySeconds,playNotification } from '../../../helpers'
-import { socketIdChat } from '../../../api-data';
+import { socketIdChat,sentMessageCallById } from '../../../api-data';
 
 const Peer = require('simple-peer')
 const socket = io(prodSocketURL)
@@ -159,27 +159,27 @@ const CallBar = ({callStatus,setCallStatus}:ICallBar) => {
   const { _id } = useSelector(getAuthorizationState)
   const { socketId, companionId, name:_name,lastName:_lastName,avatarUrl:_avatarUrl,color:_color,number:_number } = useSelector(getChat)
   const connectionRef = useRef<any>(null);
-  const idAudioIntervalRef = useRef<any>(null);
   const myVideoRef = useRef<any>(null);
   const companionVideoRef = useRef<any>(null);
   const companionAudioRef = useRef<any>(null);
+  const idAudioIntervalRef = useRef<any>(null);
   const [mySocket, setMySocket] = useState<string>('')
-  const [mutedMyVideo,setMutedMyVideo] = useState<boolean>(false)
-  const [mutedMyAudio,setMutedMyAudio] = useState<boolean>(false)
-  const [companionSocket, setCompanionSocket] = useState<string>('')
   const [myStream, setMyStream] = useState<any>(null)
   const [myShareStream, setMyShareStream] = useState<any>(null)
+  const [mutedMyVideo,setMutedMyVideo] = useState<boolean>(true)
+  const [mutedMyAudio,setMutedMyAudio] = useState<boolean>(false)
+  const [companionSocket, setCompanionSocket] = useState<string>('')
   const [companionSignal, setCompanionSignal] = useState<any>(null)
+  const [audioHtml, setAudioHtml] = useState<any>(null)
+  const [callLast, setCallLast] = useState<number>(0)
+  const [conversationLast, cetConversationLast] = useState<string>('')
+  const [fullScreen, setFullScreen] = useState<boolean>(false)  
+  const [alert, setAlert] = useState<string>('')
   const [name, setName] = useState<string>('')
   const [lastName, setLastName] = useState<string>('')
   const [avatarUrl, setAvatarUrl] = useState<string>('')
   const [color, setColor] = useState<string>('')
   const [number, setNumber] = useState<string>('')
-  const [callLast, setCallLast] = useState<number>(0)
-  const [conversationLast, cetConversationLast] = useState<string>('')
-  const [fullScreen, setFullScreen] = useState<boolean>(false)
-  const [alert, setAlert] = useState<string>('')
-  const [audioHtml, setAudioHtml] = useState<any>(null)
 
   const handleLeaveCall = useCallback(() => {
     setCallStatus('hanging up...')
@@ -239,11 +239,11 @@ const CallBar = ({callStatus,setCallStatus}:ICallBar) => {
         }
         replaceStream(shareTrack)
       } else {
-        setAlert(`Can not start Share before ringing`)
+        setAlert(`Can not start Share before stream`)
         setTimeout(() => setAlert(''),2000)
       }
     } catch (e: any) {
-      setAlert(`Permission fro getDisplayMedia is required!`)
+      setAlert(`Permission for getDisplayMedia is required!`)
       setTimeout(() => setAlert(''),2000)
     }
   }  
@@ -253,7 +253,7 @@ const CallBar = ({callStatus,setCallStatus}:ICallBar) => {
       setMutedMyVideo(!mutedMyVideo)
       myStream.getVideoTracks()[0].enabled = !myStream.getVideoTracks()[0].enabled
     } else {
-      setAlert(`Can not ${mutedMyVideo ? 'start' : 'stop'} Video before ringing`)
+      setAlert(`Can not ${mutedMyVideo ? 'start' : 'stop'} Video before stream`)
       setTimeout(() => setAlert(''),2000)
     }
   }
@@ -263,18 +263,20 @@ const CallBar = ({callStatus,setCallStatus}:ICallBar) => {
       setMutedMyAudio(!mutedMyAudio)
       myStream.getAudioTracks()[0].enabled = !myStream.getAudioTracks()[0].enabled
     } else {
-      setAlert(`Can not start ${mutedMyAudio ? 'start' : 'stop'} Audio before ringing`)
+      setAlert(`Can not start ${mutedMyAudio ? 'start' : 'stop'} Audio before stream`)
       setTimeout(() => setAlert(''),2000)
     }
   }
   
   const handleStartCall = useCallback(async () => {
     try {
+      sentMessageCallById(companionId,false,0)
       setCallStatus('waiting...')
       const stream = await navigator.mediaDevices.getUserMedia({
         video: true,
         audio: true
       })
+      stream.getVideoTracks()[0].enabled = false
       setMyStream(stream)
       myVideoRef.current.srcObject = stream;
       const peer = new Peer({
@@ -312,8 +314,9 @@ const CallBar = ({callStatus,setCallStatus}:ICallBar) => {
       peer.on('error', handleLeaveCall)
       socket.on("acceptedCall", ({ signal }: any) => peer.signal(signal));
       connectionRef.current = peer;
-    } catch (e:any) {
-      handleLeaveCall()
+    } catch (e: any) {
+      setAlert(`Permission for getUserMedia is required!`)
+      setTimeout(handleLeaveCall,2000)
     } 
   },[socketId,companionId,_id,mySocket,myVideoRef,setCallStatus,handleLeaveCall])
 
@@ -325,6 +328,7 @@ const CallBar = ({callStatus,setCallStatus}:ICallBar) => {
       //   video: true,
       //   audio: true
       // })
+      //stream.getVideoTracks()[0].enabled = false
       // setMyStream(stream)
       // myVideoRef.current.srcObject = stream;
       const peer = new Peer({
@@ -344,6 +348,7 @@ const CallBar = ({callStatus,setCallStatus}:ICallBar) => {
       });
       peer.on('connect', () => {
         setCallStatus('connection')
+        clearInterval(idAudioIntervalRef.current)
         setTimeout(() => setCallStatus('conversation'),500)
       })
       peer.on("close", handleLeaveCall);
@@ -351,7 +356,8 @@ const CallBar = ({callStatus,setCallStatus}:ICallBar) => {
       peer.signal(companionSignal);
       connectionRef.current = peer;
     } catch (e:any) {
-      handleLeaveCall()
+      setAlert(`Permission for getUserMedia is required!`)
+      setTimeout(handleLeaveCall,2000)
     }
   }, [companionSocket, companionSignal, setCallStatus,audioHtml,handleLeaveCall])
   
@@ -376,6 +382,8 @@ const CallBar = ({callStatus,setCallStatus}:ICallBar) => {
         audioRing.loop = true
         setAudioHtml(audioRing)
         setCallStatus('is calling you')
+        idAudioIntervalRef.current = setInterval(() =>
+          setCallLast(prevState => prevState + 1), 1000)
       } 
     })
   }, [setCallStatus,companionSocket])

+ 365 - 0
src/components/HomePage/CentralBar/ChatBar/Messages/MessageLeftCall/index.tsx

@@ -0,0 +1,365 @@
+import { makeStyles } from "@material-ui/core/styles";
+import { styled } from '@mui/material/styles';
+import { useState } from "react";
+import Typography from '@mui/material/Typography';
+import Button from '@mui/material/Button';
+import ContentCopyIcon from '@mui/icons-material/ContentCopy';
+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 VisibilityIcon from '@mui/icons-material/Visibility';
+import SouthWestIcon from '@mui/icons-material/SouthWest';
+import NorthEastIcon from '@mui/icons-material/NorthEast';
+import PhoneIcon from '@mui/icons-material/Phone';
+import Avatar from '@mui/material/Avatar';
+import { CopyToClipboard } from 'react-copy-to-clipboard';
+import { removeMessageById,updateMessageById,pinMessageById } from "../../../../../../api-data";
+import { firstLetter,slicedWord,timeStampMessage,copied,emojisArr,prodAwsS3,getTimeBySeconds } 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",
+    alignItems: 'flex-start',
+    alignContent: 'flex-start',
+    flexDirection:'column',
+    borderRadius: 7,
+    position: 'relative',
+    padding:'4px 0px 4px 22px'
+  }, 
+  wrapper: {
+    position: 'relative',
+    display: 'flex',
+    alignItems: 'start',
+    alignContent: 'start',
+    flexDirection: 'column',
+    maxWidth: 450,
+    minWidth:200,
+    padding: 5,
+    borderRadius: 7,
+    wordBreak:'break-word',
+    textAlign: "left",
+    font: "400 .9em 'Open Sans', sans-serif",
+  },
+  wrapperInner: {
+    display: 'flex',
+    alignItems: 'center',
+    alignContent: 'center',
+    justifyContent: 'space-between',
+    flexWrap: "nowrap",
+    width: '100%',
+    marginBottom:10
+  },  
+  informationWrapper: {
+    display: 'flex',
+    alignItems: 'center',
+    alignContent: 'center',
+    justifyContent: 'flex-end',
+    width: '100%',
+    paddingRight:3,
+  },
+  time: {
+    fontSize: ".65em",
+    fontWeight:600,
+    color: '#414141',
+  },     
+  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.2rem',
+      right: -40,
+  },
+  emojiCompanionTitle: {
+      position: "absolute",
+      fontSize: "1.7em",
+      fontWeight:600,
+      bottom: '2.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 ',
+    }
+  },
+  checkboxSelect: {
+    position: 'absolute',
+    left: -64,
+    top: -10,
+    pointerEvents: 'auto'
+  },
+ avatarIcon: {
+    position: 'absolute',
+    left: -54,
+    bottom: 0,
+  },
+ tongueOne: {
+  content: "''",
+  position: "absolute",
+  width: "0",
+  height: "0",
+  borderLeft: "15px solid transparent",
+  borderRight: "15px solid transparent",
+  bottom: '0px',
+  left: "-15px",
+},
+tongueTwo: {
+  content: "''",
+  position: "absolute",
+  width: "0",
+  height: "0",
+  borderLeft: "16px solid transparent",
+  borderRight: "16px solid transparent",
+  bottom: "0px",
+  left: "-17px",
+ },    
+});
+
+const label = { inputProps: { 'aria-label': 'Checkbox demo' } };
+
+interface IMessageLeftCall {
+  message: string,
+  initiator: boolean,
+  reject: boolean,
+  duration: number,
+  tongue: boolean,
+  watched: boolean,
+  edited: boolean,
+  avatarUrl: string,
+  color: string,
+  name:string,
+  lastName:string,
+  createdAt: string,
+  caption: 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,
+  handleForward: (_id: string) => void,
+}
+
+const MessageLeftCall = ({message,initiator,reject,duration,tongue,watched,edited,avatarUrl,color,name,lastName,createdAt,caption,emoji,emojiCompanion,pinned,isSomeSelected,isSelected,handleSelected,_id,nightMode,handleReply,handleForward}:IMessageLeftCall) => {
+  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('Text')
+    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)
+  } 
+  
+  return (
+    <div className={classes.container} style={{marginBottom:tongue?12:0}}>
+     <div onContextMenu={(e) => handleContextMenu(e)} className={classes.wrapper}
+       style={{backgroundColor:selected?'#babdbc':"#ffffff",pointerEvents:isSomeSelected?'none':'auto'}}>
+        <Typography style={{color: "#00b333"}} variant="h6" align="right">
+          {`${firstLetter(name)}${slicedWord(name, 15, 1)} 
+          ${firstLetter(lastName)}${slicedWord(lastName, 15, 1)}`}
+        </Typography>
+        <div className={classes.wrapperInner}>
+          <Typography style={{color: "#26afee"}} variant="h6" align="right">
+            {message}
+          </Typography>
+          <PhoneIcon style={{color:'#26afee'}} fontSize='large' />
+        </div>
+        <div className={classes.informationWrapper}>
+          {!initiator ? <SouthWestIcon style={{ color: reject?'#f02a2a':'#959595', marginRight: 5 }} fontSize='small' /> :
+            <NorthEastIcon style={{ color: reject?'#f02a2a':'#959595',marginRight:5}} fontSize='small' />}
+           <div className={classes.time} style={{ color: '#959595'}}>{`${edited?'edited ':''}${timeStampMessage(createdAt)}${duration?', '+getTimeBySeconds(duration):''}`}</div>
+           {watched&&<VisibilityIcon style={{ color: '#959595', marginLeft: 5 }} fontSize='small' />}
+        </div>
+        {tongue&&<div className={classes.avatarIcon}>
+          <Avatar alt={name} src={avatarUrl?`${prodAwsS3}/${avatarUrl}`:undefined}
+              sx={{ background: color, width: 40, height: 40 }}>
+              {!avatarUrl&&`${firstLetter(name)}${firstLetter(lastName)}`}
+          </Avatar>
+        </div>}
+        {tongue&&<span className={classes.tongueOne} style={{borderBottom: `15px solid ${selected?'#babdbc' : "#ffffff"}`}}></span>}
+        {tongue&&<span className={classes.tongueTwo} style={{borderBottom: `17px solid ${selected?'#babdbc' : "#ffffff"}`}}></span>}
+        {emojiCompanion && <div className={classes.emojiCompanionTitle}>{emojisArr[Number(emojiCompanion)]}</div>}
+        {emoji && <div className={classes.emojiTitle}>{emojisArr[Number(emoji)]}</div>}
+        {isSomeSelected && <div className={classes.checkboxSelect}><Checkbox {...label} checked={checked} sx={{ color: nightMode ? '#ffffff' : '#00ff48', '&.Mui-checked': { color: nightMode ? '#ffffff' : '#00ff48' } }}
+        onClick={() => handleSelected(_id)}/></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 onClick={() => {
+            handleForward(_id)
+            handleClose(undefined)
+        }}>
+            <ReplyIcon style={{transform :'rotateY(180deg)'}} />
+            Forward
+        </MenuItem>           
+        <CopyToClipboard onCopy={() => handleClose('copy')} text={`${message?message:''} ${caption?caption:''}`}>
+          <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 MessageLeftCall

+ 374 - 0
src/components/HomePage/CentralBar/ChatBar/Messages/MessageRightCall/index.tsx

@@ -0,0 +1,374 @@
+import { makeStyles } from "@material-ui/core/styles";
+import { styled } from '@mui/material/styles';
+import { useState } from "react";
+import Typography from '@mui/material/Typography';
+import Button from '@mui/material/Button';
+import ContentCopyIcon from '@mui/icons-material/ContentCopy';
+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 DoneAllIcon from '@mui/icons-material/DoneAll';
+import DoneIcon from '@mui/icons-material/Done';
+import EditIcon from '@mui/icons-material/Edit';
+import SouthWestIcon from '@mui/icons-material/SouthWest';
+import NorthEastIcon from '@mui/icons-material/NorthEast';
+import PhoneIcon from '@mui/icons-material/Phone';
+import Avatar from '@mui/material/Avatar';
+import { CopyToClipboard } from 'react-copy-to-clipboard';
+import { firstLetter, slicedWord, timeStampMessage, copied,emojisArr,prodAwsS3,getTimeBySeconds } 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",
+    alignItems: 'flex-end',
+    alignContent: 'flex-end',
+    flexDirection:'column',
+    borderRadius: 7,
+    position: 'relative',
+    padding:'4px 22px 4px 0px'
+  },
+  wrapper: {
+    position: 'relative',
+    display: 'flex',
+    alignItems: 'start',
+    alignContent: 'start',
+    flexDirection: 'column',
+    maxWidth: 450,
+    minWidth:200,
+    padding: 5,
+    borderRadius: 7,
+    wordBreak:'break-word',
+    textAlign: "left",
+    font: "400 .9em 'Open Sans', sans-serif",
+  },
+  wrapperInner: {
+    display: 'flex',
+    alignItems: 'center',
+    alignContent: 'center',
+    justifyContent: 'space-between',
+    flexWrap: "nowrap",
+    width: '100%',
+    marginBottom:10
+  },  
+  informationWrapper: {
+    display: 'flex',
+    alignItems: 'center',
+    alignContent: 'center',
+    justifyContent: 'flex-end',
+    width: '100%',
+    paddingRight:3,
+  },
+  time: {
+    fontSize: ".65em",
+    fontWeight:600,
+  },
+  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.2rem',
+      left: -40,
+  },
+  emojiCompanionTitle: {
+      position: "absolute",
+      fontSize: "1.7em",
+      fontWeight:600,
+      bottom: '2.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 ',
+    }
+  },
+  checkboxSelect: {
+    position: 'absolute',
+    right: -64,
+    top: -10,
+    pointerEvents: 'auto'
+  },
+ avatarIcon: {
+    position: 'absolute',
+    right: -54,
+    bottom: 0,
+  },
+ tongueOne: {
+  content: "''",
+  position: "absolute",
+  width: "0",
+  height: "0",
+  borderRight: "15px solid transparent",
+  borderLeft: "15px solid transparent",
+  bottom: '0px',
+  right: "-15px",
+},
+tongueTwo: {
+  content: "''",
+  position: "absolute",
+  width: "0",
+  height: "0",
+  borderRight: "16px solid transparent",
+  borderLeft: "16px solid transparent",
+  bottom: "0px",
+  right: "-17px",
+ },    
+});
+
+const label = { inputProps: { 'aria-label': 'Checkbox demo' } };
+
+interface IMessageRightCall {
+  message: string,
+  initiator: boolean,
+  reject: boolean,
+  duration: number,
+  tongue: boolean,
+  watched: boolean,
+  edited: boolean,
+  avatarUrl: string,
+  color: string,
+  name:string,
+  lastName:string,
+  createdAt: string,
+  caption: 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,
+  handleForward: (_id: string) => void,
+  handleEdit:(_id: string) => void,
+}
+
+const MessageRightCall = ({message,initiator,reject,duration,tongue,watched,edited,avatarUrl,color,name,lastName,createdAt,caption,emoji,emojiCompanion,pinned,isSomeSelected,isSelected,handleSelected,_id,nightMode,handleReply,handleForward,handleEdit}:IMessageRightCall) => {
+  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('Text')
+    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)
+  }  
+
+  return (
+    <div className={classes.container} style={{marginBottom:tongue?12:0}}>
+      <div onContextMenu={(e) => handleContextMenu(e)} className={classes.wrapper}
+       style={{backgroundColor:selected?'#ced8d7':"#deffa9",pointerEvents:isSomeSelected?'none':'auto'}}>
+        <Typography style={{color: "#26afee"}} variant="h6" align="right">
+          {`${firstLetter(name)}${slicedWord(name, 15, 1)} 
+          ${firstLetter(lastName)}${slicedWord(lastName, 15, 1)}`}
+        </Typography>
+        <div className={classes.wrapperInner}>
+          <Typography style={{color: "#00b333"}} variant="h6" align="right">
+            {message}
+          </Typography>
+          <PhoneIcon style={{color:'#18bd03'}} fontSize='large' />
+        </div>
+        <div className={classes.informationWrapper}>
+          {!initiator ? <SouthWestIcon style={{ color: reject?'#f02a2a':'#18bd03', marginRight: 5 }} fontSize='small' /> :
+            <NorthEastIcon style={{ color: reject?'#f02a2a':'#18bd03',marginRight:5}} fontSize='small' />}
+           <div className={classes.time} style={{ color: '#18bd03'}}>{`${edited?'edited ':''}${timeStampMessage(createdAt)}${duration?', '+getTimeBySeconds(duration):''}`}</div>
+           {watched ? <DoneAllIcon style={{ color: '#18bd03', marginLeft: 5 }} fontSize='small' /> :
+            <DoneIcon style={{ color: '#18bd03',marginLeft:5}} fontSize='small' />}
+        </div>
+        {tongue&&<div className={classes.avatarIcon}>
+          <Avatar alt={name} src={avatarUrl?`${prodAwsS3}/${avatarUrl}`:undefined}
+              sx={{ background: color, width: 40, height: 40 }}>
+              {!avatarUrl&&`${firstLetter(name)}${firstLetter(lastName)}`}
+          </Avatar>
+        </div>}
+        {tongue&&<span className={classes.tongueOne} style={{borderBottom: `15px solid ${selected?'#ced8d7':'#deffa9'}`}}></span>}
+        {tongue&&<span className={classes.tongueTwo} style={{borderBottom: `17px solid ${selected?'#ced8d7':'#deffa9'}`}}></span>}
+        {emojiCompanion && <div className={classes.emojiCompanionTitle}>{emojisArr[Number(emojiCompanion)]}</div>}
+        {emoji && <div className={classes.emojiTitle}>{emojisArr[Number(emoji)]}</div>}
+        {isSomeSelected && <div className={classes.checkboxSelect}><Checkbox {...label} checked={checked} sx={{ color: nightMode ? '#ffffff' : '#00ff48', '&.Mui-checked': { color: nightMode ? '#ffffff' : '#00ff48' } }}
+        onClick={() => handleSelected(_id)}/></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 onClick={() => {
+            handleForward(_id)
+            handleClose(undefined)
+        }}>
+            <ReplyIcon style={{transform :'rotateY(180deg)'}} />
+            Forward
+        </MenuItem>           
+        <MenuItem onClick={() => {
+            handleEdit(_id)
+            handleClose(undefined)
+        }}>
+            <EditIcon/>
+            Edit
+        </MenuItem>        
+          <CopyToClipboard onCopy={() => handleClose('copy')} text={`${message ? message : ''} ${caption ? caption : ''}`}>
+          <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  MessageRightCall

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

@@ -5,6 +5,7 @@ import { useSelector,useDispatch } from "react-redux";
 import ArrowBack from "./ArrowBack";
 import SendMessage from "./SendMessage";
 import UnpinBar from "./UnpinBar";
+import MessageLeftCall from "./Messages/MessageLeftCall";
 import MessageLeftDeleted from "./Messages/MessageLeftDeleted";
 import MessageLeftText from './Messages/MessageLeftText'
 import MessageLeftReply from "./Messages/MessageLeftReply";
@@ -13,6 +14,7 @@ import MessageLeftImage from './Messages/MessageLeftImage'
 import MessageLeftAudio from './Messages/MessageLeftAudio'
 import MessageLeftVideo from './Messages/MessageLeftVideo'
 import MessageLeftFile from "./Messages/MessageLeftFile";
+import MessageRightCall from "./Messages/MessageRightCall";
 import MessageRightDeleted from "./Messages/MessageRightDeleted";
 import MessageRightText from './Messages/MessageRightText'
 import MessageRightReply from "./Messages/MessageRightReply";
@@ -240,7 +242,8 @@ const ChatBar = ({chatDivRef,selectedArr,setSelectedArr,isSomeSelected,setIsSome
         <div className={classes.messagesBody}>
         {messagesMemo.length > 0 ? renderArr.map(({ replyMessage,message, name, lastName,avatarUrl,color,pinned,
           createdAt, number, type, fullType, replyName, replyLastName, replyCaption, caption, emoji, emojiCompanion,
-          _id, oldId, forwardName, forwardLastName, companionIdForwardToAndFrom,forwardMessage,forwardCaption,edited,deleted},i) => {
+          _id, oldId, forwardName, forwardLastName, companionIdForwardToAndFrom, forwardMessage, forwardCaption, edited,
+          deleted, initiator, reject, duration }, i) => {
           const watched = seenCompanion - (i + 1) < 0 ? false : true
           let isUnread
           let isTime
@@ -292,6 +295,34 @@ const ChatBar = ({chatDivRef,selectedArr,setSelectedArr,isSomeSelected,setIsSome
           const urlForward = `${prodAwsS3}/${forwardMessage}`
           const urlReply = `${prodAwsS3}/${replyMessage}`
           if (number !== userNumber) {
+            if (type === 'text' && duration !== null) return (<div key={createdAt} id={_id} style={{borderRadius: 7}}> 
+              {isTime&&<MessageDivider message={timeStampFilter(createdAt)}/>}
+              {isUnread&&<MessageDivider message='Unread Messages'/>}
+              <MessageLeftCall
+               message={message}
+               initiator={initiator}
+               reject={reject}
+               duration={duration}
+               tongue={isTongue}
+               watched={!unread}
+               edited={edited}
+               avatarUrl={avatarUrl}
+               color={color}
+               createdAt={createdAt}
+               name={name}
+               lastName={lastName}
+               caption={caption}
+               emoji={emoji}
+               emojiCompanion={emojiCompanion}
+               pinned={pinned}
+               isSomeSelected={isSomeSelected}
+               isSelected={isSelected}
+               handleSelected={handleSelected}
+               _id={_id}
+               nightMode={nightMode}
+               handleReply={handleReply}
+               handleForward={handleForward} 
+               /></div>)            
             if (type === 'text' && !oldId && !companionIdForwardToAndFrom && !deleted) return (<div key={createdAt} id={_id} style={{borderRadius: 7}}> 
               {isTime&&<MessageDivider message={timeStampFilter(createdAt)}/>}
               {isUnread&&<MessageDivider message='Unread Messages'/>}
@@ -519,6 +550,35 @@ const ChatBar = ({chatDivRef,selectedArr,setSelectedArr,isSomeSelected,setIsSome
                 handleForward={handleForward}
                   /></div>)             
           } else {
+            if (type === 'text' && duration !== null) return (<div key={createdAt} id={_id} style={{borderRadius: 7}}>
+              {isTime&&<MessageDivider message={timeStampFilter(createdAt)}/>}
+              {isUnread&&<MessageDivider message='Unread Messages'/>}
+                <MessageRightCall   
+                message={message}
+                initiator={initiator}
+                reject={reject}
+                duration={duration}
+                tongue={isTongue}
+                watched={watched}
+                edited={edited}
+                avatarUrl={avatarUrl}
+                color={color}
+                createdAt={createdAt}
+                name={name}
+                lastName={lastName}
+                caption={caption}
+                emoji={emoji}
+                emojiCompanion={emojiCompanion}
+                pinned={pinned}
+                isSomeSelected={isSomeSelected}
+                isSelected={isSelected}
+                handleSelected={handleSelected}                
+                _id={_id}
+                nightMode={nightMode}
+                handleReply={handleReply}
+                handleForward={handleForward}
+                handleEdit={handleEdit}
+                /></div>)            
             if (type === 'text' && !oldId && !companionIdForwardToAndFrom && !deleted) return (<div key={createdAt} id={_id} style={{borderRadius: 7}}>
               {isTime&&<MessageDivider message={timeStampFilter(createdAt)}/>}
               {isUnread&&<MessageDivider message='Unread Messages'/>}

+ 3 - 0
src/typescript/redux/allMessages/types.ts

@@ -25,6 +25,9 @@ export type TMessage = {
   pinned: boolean,
   edited: boolean,
   deleted: boolean,
+  initiator: boolean,
+  reject: boolean,
+  duration: number,
   idTime: string,
   oldId: string,
   owner: any,

+ 3 - 0
src/typescript/redux/messages/types.ts

@@ -22,6 +22,9 @@ export type TMessage = {
   pinned: boolean,
   edited: boolean,
   deleted: boolean,
+  initiator: boolean,
+  reject: boolean,
+  duration: number,
   idTime: string,
   oldId: string,
   companionId: string,

+ 3 - 0
src/typescript/redux/pinnedMessages/types.ts

@@ -22,6 +22,9 @@ export type TPinnedMessage = {
   pinned: boolean,
   edited: boolean,
   deleted: boolean,
+  initiator: boolean,
+  reject: boolean,
+  duration: number,
   idTime: string,
   oldId: string,
   companionId: string,