index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. import { makeStyles,Typography } from '@material-ui/core'
  2. import { useState } from 'react';
  3. import { styled } from '@mui/material/styles';
  4. import Menu from '@mui/material/Menu';
  5. import MenuItem from '@mui/material/MenuItem';
  6. import NotificationsNoneIcon from '@mui/icons-material/NotificationsNone';
  7. import VolumeOffIcon from '@mui/icons-material/VolumeOff';
  8. import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
  9. import ListItemButton from '@mui/material/ListItemButton';
  10. import Avatar from '@mui/material/Avatar';
  11. import ListItemText from '@mui/material/ListItemText';
  12. import ListItemIcon from '@mui/material/ListItemIcon';
  13. import Badge from '@mui/material/Badge';
  14. import DoneAllIcon from '@mui/icons-material/DoneAll';
  15. import DoneIcon from '@mui/icons-material/Done';
  16. import PushPinIcon from '@mui/icons-material/PushPin';
  17. import CloseIcon from '@mui/icons-material/Close';
  18. import { muteChat,pinChat } from '../../../../../api-data';
  19. import { TChat } from '../../../../../typescript/redux/chats/types';
  20. import { firstLetter, slicedWord, timeStampEU,prodAwsS3 } from '../../../../../helpers';
  21. import DeleteModal from './DeleteModal';
  22. const StyledMenu = styled((props:any) => (
  23. <Menu
  24. elevation={0}
  25. anchorOrigin={{
  26. vertical: 'top',
  27. horizontal: 'right',
  28. }}
  29. transformOrigin={{
  30. vertical: 'bottom',
  31. horizontal: 'right',
  32. }}
  33. {...props}
  34. />
  35. ))(({ theme }:any) => ({
  36. '& .MuiPaper-root': {
  37. borderRadius: 10,
  38. marginTop: theme.spacing(0),
  39. minWidth: 220,
  40. color:
  41. theme.palette.mode === 'light' ? 'rgb(55, 65, 81)' : theme.palette.grey[500],
  42. boxShadow:
  43. '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',
  44. '& .MuiMenu-list': {
  45. padding: '8px 8px',
  46. },
  47. '& .MuiMenuItem-root': {
  48. marginBottom: theme.spacing(1),
  49. '& .MuiSvgIcon-root': {
  50. fontSize: 21,
  51. color: theme.palette.text.secondary,
  52. marginRight: theme.spacing(4),
  53. }
  54. },
  55. },
  56. }));
  57. const StyledBadge = styled(Badge)(({ theme }) => ({
  58. '& .MuiBadge-badge': {
  59. backgroundColor: '#44b700',
  60. color: '#44b700',
  61. boxShadow: `0 0 0 2px ${theme.palette.background.paper}`,
  62. '&::after': {
  63. position: 'absolute',
  64. top: 0,
  65. left: 0,
  66. width: '100%',
  67. height: '100%',
  68. borderRadius: '50%',
  69. animation: 'ripple 1.2s infinite ease-in-out',
  70. border: '1px solid currentColor',
  71. content: '""',
  72. },
  73. },
  74. '@keyframes ripple': {
  75. '0%': {
  76. transform: 'scale(.8)',
  77. opacity: 1,
  78. },
  79. '100%': {
  80. transform: 'scale(2.4)',
  81. opacity: 0,
  82. },
  83. },
  84. }));
  85. const useStyles = makeStyles({
  86. listItemInnerText: {
  87. display: 'flex',
  88. alignContent: 'center',
  89. alignItems: 'center',
  90. flexWrap: 'nowrap',
  91. },
  92. listItemInnerText__icon: {
  93. marginLeft: 5,
  94. },
  95. listItem_iconAvatar: {
  96. marginRight:10
  97. },
  98. listItem_iconRight: {
  99. marginRight: 10,
  100. display: 'flex',
  101. alignItems: 'center',
  102. justifyContent: 'center',
  103. alignContent: 'center',
  104. flexDirection: 'column'
  105. },
  106. listItem_iconTimeChecked: {
  107. display: 'flex',
  108. flexWrap: 'nowrap',
  109. alignItems: 'center',
  110. justifyContent: 'center',
  111. alignContent: 'center',
  112. marginBottom:2
  113. },
  114. listItem_iconRightBtn: {
  115. cursor: 'pointer',
  116. background: '#0ac40a',
  117. borderRadius: '50%',
  118. color: '#ffffff',
  119. border: 'none',
  120. height: 24,
  121. width: 24,
  122. textAlign: 'center',
  123. display: 'flex',
  124. alignItems: 'center',
  125. justifyContent: 'center',
  126. alignContent: 'center',
  127. fontSize: 12,
  128. marginLeft: 10,
  129. '&:hover': {
  130. outline: 'solid 3px #3ee415',
  131. }
  132. },
  133. listItem_iconRightBtnMute: {
  134. cursor: 'pointer',
  135. background: '#a7aaa7',
  136. borderRadius: '50%',
  137. color: '#ffffff',
  138. border: 'none',
  139. height: 24,
  140. width: 24,
  141. textAlign: 'center',
  142. display: 'flex',
  143. alignItems: 'center',
  144. justifyContent: 'center',
  145. alignContent: 'center',
  146. fontSize: 12,
  147. marginLeft: 10,
  148. '&:hover': {
  149. outline: 'solid 3px #cccbcb',
  150. }
  151. },
  152. pinnedIcon: {
  153. transform: 'rotate(45deg)',
  154. },
  155. listIconsRightContainer: {
  156. marginLeft: 'auto',
  157. display: 'flex',
  158. alignItems: 'center',
  159. justifyContent: 'center',
  160. alignContent: 'center',
  161. },
  162. listItem_icon_time: {
  163. fontSize: 12,
  164. marginLeft: 5,
  165. },
  166. listItem_typing: {
  167. animation: 'ripple 4s infinite ease-in-out',
  168. },
  169. listItem_dots: {
  170. fontWeight: 'bold',
  171. display:'inline-block',
  172. fontFamily: 'monospace',
  173. clipPath: 'inset(0 3ch 0 0)',
  174. animation: `$run 2s steps(5) infinite`,
  175. },
  176. '@keyframes run': {
  177. to: {
  178. clipPath: 'inset(0 -1ch 0 0)'
  179. },
  180. },
  181. iconClose: {
  182. '&:hover': {
  183. transform: 'rotate(180deg)',
  184. transition: 'all 250ms ease-out ',
  185. }
  186. },
  187. })
  188. interface IChatItem {
  189. chat: TChat,
  190. handleListItemClick: (companionId: string) => void,
  191. handleNewMsgs: (e: any, companionId: string) => void,
  192. id: string,
  193. pinned: boolean,
  194. selectedCompanionId: string,
  195. }
  196. const ChatItem = ({chat,handleListItemClick,handleNewMsgs,id,pinned,selectedCompanionId}:IChatItem) => {
  197. const classes = useStyles()
  198. const [anchorEl, setAnchorEl] = useState<any>(null);
  199. const [selected, setSelected] = useState<boolean>(false);
  200. const [modal, setModal] = useState<boolean>(false);
  201. const open = Boolean(anchorEl);
  202. const { name, lastName, avatarUrl, color, companionId, mute, seen, total, watched,
  203. typing, online, lastMessage, lastMessageCreatedAt, createdAt,seenCompanion } = chat
  204. const openedChat = companionId === selectedCompanionId
  205. const handlePin = (id: string, pinned:boolean) => {
  206. pinChat(id,!pinned)
  207. handleClose(undefined)
  208. }
  209. const handleClose = (type: string | undefined): void => {
  210. if (type === 'mute') muteChat(companionId)
  211. if (type === 'delete') setModal(true)
  212. setAnchorEl(null)
  213. setSelected(false)
  214. }
  215. const handleContextMenu = (e: React.MouseEvent<HTMLDivElement>):void => {
  216. e.preventDefault()
  217. setAnchorEl(e.currentTarget)
  218. setSelected(true)
  219. }
  220. return (
  221. <>
  222. {modal&&<DeleteModal setModal={setModal} chat={chat}/>}
  223. <ListItemButton
  224. sx={{backgroundColor: openedChat ? '#26afee' : selected ? 'rgba(0, 0, 0, 0.1)' : 'transparent',
  225. '&:hover':{backgroundColor:openedChat ? '#26afee':'rgba(0, 0, 0, 0.1)'}}}
  226. onClick={() => handleListItemClick(companionId)}
  227. onContextMenu={(e) => handleContextMenu(e)}>
  228. <ListItemIcon className={classes.listItem_iconAvatar}>
  229. <StyledBadge overlap="circular" variant={online === 'true'?'dot':'standard'}
  230. anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}>
  231. <Avatar alt={name} src={avatarUrl?`${prodAwsS3}/${avatarUrl}`:undefined}
  232. sx={{ background: color, width: 54, height: 54 }}>
  233. {!avatarUrl&&`${firstLetter(name)}${firstLetter(lastName)}`}
  234. </Avatar>
  235. </StyledBadge>
  236. </ListItemIcon>
  237. <ListItemText primaryTypographyProps={{color:openedChat?'#ffffff':'#000000'}} primary={<div className={classes.listItemInnerText}>
  238. <span>{`${firstLetter(name)}${slicedWord(name, 15, 1)}
  239. ${firstLetter(lastName)}${slicedWord(lastName, 15, 1)}`}</span>
  240. {mute && <VolumeOffIcon style={{color:openedChat?'#ffffff':'#959595'}}
  241. className={classes.listItemInnerText__icon} fontSize='small' />}</div>}
  242. secondary={typing ? <span style={{color:openedChat?'#ffffff':'#4d4d4d'}}
  243. className={classes.listItem_typing}>
  244. typing<span style={{color:openedChat?'#ffffff':'#1b1b1b'}}
  245. className={classes.listItem_dots}>...</span></span> :
  246. lastMessage ? slicedWord(lastMessage, 28) :
  247. `${firstLetter(name)}${slicedWord(name, 15, 1)} joined Telegram`}
  248. secondaryTypographyProps={{color:openedChat?'#ffffff':'#000000'}}/>
  249. <ListItemIcon className={classes.listItem_iconRight}>
  250. <div className={classes.listItem_iconTimeChecked}>
  251. {watched &&<DoneAllIcon style={{ color: openedChat ? '#ffffff' : '#18bd03' }} fontSize='small' />}
  252. {!watched&&seenCompanion < total&& <DoneIcon style={{ color: openedChat ? '#ffffff' :'#18bd03' }} fontSize='small' />}
  253. <Typography className={classes.listItem_icon_time} style={{ color: openedChat ? '#ffffff' : '#1b1b1b' }}
  254. variant="h6" color="initial">
  255. {timeStampEU(lastMessageCreatedAt?lastMessageCreatedAt:createdAt)}
  256. </Typography>
  257. </div>
  258. <div className={classes.listIconsRightContainer}>
  259. {pinned && <PushPinIcon className={classes.pinnedIcon} fontSize='small'
  260. style={{color:openedChat?'#ffffff':'#959595'}}/>}
  261. {lastMessage && total > seen &&
  262. <button onClick={(e) => handleNewMsgs(e, companionId)}
  263. style={{outline:openedChat?'none':undefined,color:openedChat&&!mute?'#26afee':openedChat?'#ffffff':undefined,backgroundColor:openedChat&&!mute?'#ffffff':openedChat?'#a7aaa7':undefined}}
  264. className={mute ? classes.listItem_iconRightBtnMute : classes.listItem_iconRightBtn}>
  265. {total - seen}
  266. </button>}
  267. </div>
  268. </ListItemIcon>
  269. </ListItemButton>
  270. <StyledMenu
  271. id="demo-positioned-menu"
  272. aria-labelledby="demo-positioned-button"
  273. anchorEl={anchorEl}
  274. open={open}
  275. onClose={handleClose}
  276. >
  277. <MenuItem onClick={() => handleClose('mute')}>
  278. {mute ? <NotificationsNoneIcon /> : <VolumeOffIcon />}
  279. {mute ? 'Unmute chat':'Mute chat'}
  280. </MenuItem>
  281. <MenuItem onClick={() => handlePin(id,pinned)}>
  282. {pinned ?
  283. <CloseIcon className={classes.iconClose} /> :
  284. <PushPinIcon />}
  285. {pinned?'Unpin chat':'Pin chat'}
  286. </MenuItem>
  287. <MenuItem style={{color:'#f02a2a'}} onClick={() => handleClose('delete')}>
  288. <DeleteOutlineIcon style={{color:'#f02a2a'}}/>
  289. Delete chat
  290. </MenuItem>
  291. </StyledMenu>
  292. </>
  293. );
  294. }
  295. export default ChatItem