index.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. import { makeStyles } from "@material-ui/core/styles";
  2. import SendIcon from '@mui/icons-material/Send';
  3. import MicNoneIcon from '@mui/icons-material/MicNone';
  4. import VideocamIcon from '@mui/icons-material/Videocam';
  5. import AttachFileIcon from '@mui/icons-material/AttachFile';
  6. import SentimentSatisfiedAltIcon from '@mui/icons-material/SentimentSatisfiedAlt';
  7. import CloseIcon from '@mui/icons-material/Close';
  8. import Picker from 'emoji-picker-react';
  9. import { useReactMediaRecorder } from "react-media-recorder";
  10. import { useState } from "react";
  11. import { useSelector } from "react-redux";
  12. import FilesMenu from "../FilesMenu";
  13. import {
  14. sentMessageById, sentImgMessageById, sentAudioMessageById,
  15. sentVideoMessageById,sentFileMessageById
  16. } from '../../../../../api-data'
  17. import { getChat } from '../../../../../redux/chat/selector'
  18. import { getIsOpen } from '../../../../../redux/control/selector'
  19. import { playNotification,prodBaseURL } from "../../../../../helpers";
  20. import { typingChat } from "../../../../../api-data";
  21. const useStyles = makeStyles({
  22. container: {
  23. width: '35vw',
  24. height:'6vh',
  25. position: 'fixed',
  26. bottom: '2vh',
  27. borderRadius: 8,
  28. padding: 10,
  29. display: 'flex',
  30. flexWrap: 'nowrap',
  31. alignContent: 'start',
  32. alignItems: 'start',
  33. color: '#6b6b6b',
  34. backgroundColor: '#ffffff',
  35. },
  36. textarea: {
  37. width: '100%',
  38. height: '100%',
  39. outline: 'none',
  40. border:'none',
  41. padding: '0px 10px',
  42. marginLeft: 8,
  43. marginRight: 8,
  44. overflowY:'auto',
  45. resize: 'none',
  46. '&::placeholder': {
  47. color: 'rgb(82, 82, 82)',
  48. fontWeight: 600
  49. }
  50. },
  51. attachIcon: {
  52. transform:'rotate(30deg)',
  53. },
  54. borderTop: {
  55. position: 'absolute',
  56. left: 0,
  57. top: '-2vh',
  58. width: '100%',
  59. height: 1,
  60. background:'#ffffff',
  61. },
  62. filesMenu: {
  63. background: '#fdfdfd',
  64. position: 'absolute',
  65. width: '15vw',
  66. maxWidth: '100%',
  67. left: '61%',
  68. bottom:'10vh',
  69. zIndex: 10,
  70. visibility: 'visible',
  71. borderRadius: 10,
  72. padding: '4px 6px',
  73. },
  74. emoji: {
  75. position: 'absolute',
  76. bottom:'10vh',
  77. zIndex: 10,
  78. visibility: 'visible',
  79. },
  80. iconCancel: {
  81. position: 'absolute',
  82. left: -72,
  83. bottom:-1,
  84. display:'flex',
  85. backgroundColor: '#ffffff',
  86. color: 'rgb(243, 69, 69)',
  87. border:'solid 4px rgb(243, 69, 69)',
  88. borderRadius: '50%',
  89. '&:hover': {
  90. backgroundColor: 'rgb(243, 69, 69)',
  91. color: '#ffffff',
  92. }
  93. },
  94. avatarCamera: {
  95. position: 'absolute',
  96. left: -72,
  97. bottom:-1,
  98. display: 'flex',
  99. borderRadius: '50%',
  100. zIndex: 10,
  101. border: 'solid 14px #ffffff',
  102. '&:hover': {
  103. backgroundColor: 'rgb(41, 139, 231)',
  104. border:'solid 14px rgb(41, 139, 231)',
  105. color: '#ffffff',
  106. }
  107. },
  108. avatarRight: {
  109. position: 'absolute',
  110. right: -72,
  111. bottom:-1,
  112. display: 'flex',
  113. borderRadius: '50%',
  114. zIndex: 10,
  115. border: 'solid 14px #ffffff',
  116. '&:hover': {
  117. backgroundColor: 'rgb(41, 139, 231)',
  118. border:'solid 14px rgb(41, 139, 231)',
  119. color: '#ffffff'
  120. }
  121. },
  122. overlay: {
  123. position: 'fixed',
  124. top: 0,
  125. left: 0,
  126. width: '100vw',
  127. height: '100vh',
  128. zIndex:100
  129. },
  130. });
  131. interface ISendMessage{
  132. isArrow: boolean,
  133. handleScrollTo:() => void
  134. }
  135. const SendMessage = ({isArrow,handleScrollTo}:ISendMessage) => {
  136. const classes = useStyles();
  137. const { companionId } = useSelector(getChat)
  138. const isOpen = useSelector(getIsOpen)
  139. const [value, setValue] = useState<string>('')
  140. const [file, setFile] = useState<any>(false)
  141. const [isOpenMenu, setIsOpenMenu] = useState<boolean>(false)
  142. const [isOpenEmoji, setIsOpenEmoji] = useState<boolean>(false)
  143. const [isRecording, setIsRecording] = useState<boolean>(false)
  144. const [isFilming, setIsFilming] = useState<boolean>(false)
  145. const [type, setType] = useState<string>('')
  146. const { status, startRecording, stopRecording, mediaBlobUrl, clearBlobUrl } = useReactMediaRecorder({ audio: true });
  147. const { status: _status, startRecording: _startRecording, stopRecording: _stopRecording,
  148. mediaBlobUrl: _mediaBlobUrl, clearBlobUrl: _clearBlobUrl } = useReactMediaRecorder({ video: true });
  149. const onEmojiClick = (_e:any, emojiObject:any) => {
  150. setValue(prevValue => prevValue + emojiObject.emoji)
  151. setIsOpenEmoji(false)
  152. };
  153. const clearMessage = () => {
  154. file &&setFile(false)
  155. isRecording && setIsRecording(false)
  156. isFilming && setIsFilming(false)
  157. value && setValue('')
  158. type && setType('')
  159. mediaBlobUrl && clearBlobUrl()
  160. _mediaBlobUrl && _clearBlobUrl()
  161. isOpenMenu && setIsOpenMenu(false)
  162. isOpenEmoji && setIsOpenEmoji(false)
  163. }
  164. const sentMessage = async () => {
  165. if (value) sentMessageById(companionId, value)
  166. if (mediaBlobUrl && type === 'recording') {
  167. const audio = new XMLHttpRequest();
  168. audio.open('GET', mediaBlobUrl, true);
  169. audio.responseType = 'blob';
  170. audio.onload = () => {
  171. if (audio.status === 200) {
  172. const blob = audio.response
  173. const file = new File([blob], 'audio.mp3', {
  174. type: 'audio/mpeg'
  175. })
  176. const formData: any = new FormData()
  177. formData.append("audio", file)
  178. sentAudioMessageById(companionId, formData)
  179. clearBlobUrl()
  180. }
  181. }
  182. audio.send();
  183. }
  184. if (_mediaBlobUrl && type === 'filming') {
  185. const video = new XMLHttpRequest();
  186. video.open('GET', _mediaBlobUrl, true);
  187. video.responseType = 'blob';
  188. video.onload = () => {
  189. if (video.status === 200) {
  190. const blob = video.response
  191. const file = new File([blob], 'video.mp4', {
  192. type: 'video/mp4'
  193. })
  194. const formData: any = new FormData()
  195. formData.append("video", file)
  196. sentVideoMessageById(companionId, formData)
  197. _clearBlobUrl()
  198. }
  199. }
  200. video.send();
  201. }
  202. if (file && type) {
  203. if (file.type.includes('image') && type === 'content') {
  204. const formData: any = new FormData()
  205. formData.append("image", file);
  206. await sentImgMessageById(companionId, formData)
  207. }
  208. if (file.type.includes('audio') && type === 'content') {
  209. const formData: any = new FormData()
  210. formData.append("audio", file);
  211. sentAudioMessageById(companionId, formData)
  212. }
  213. if (file.type.includes('video') && type === 'content') {
  214. const formData: any = new FormData()
  215. formData.append("video", file);
  216. sentVideoMessageById(companionId, formData)
  217. }
  218. if (file.type.includes('application') && type === 'application') {
  219. const formData: any = new FormData()
  220. formData.append("file", file);
  221. sentFileMessageById(companionId, formData)
  222. }
  223. }
  224. clearMessage()
  225. playNotification(`${prodBaseURL}/notifications/send.mp3`)
  226. setTimeout(handleScrollTo, 4000);
  227. }
  228. const handleTextarea = (e: React.ChangeEvent<HTMLTextAreaElement>) => setValue(e.target.value)
  229. const handleFocusTextarea = async () => await typingChat(companionId,true)
  230. const handleBlurTextarea = async () => await typingChat(companionId,false)
  231. const handleOpenFileMenu = () => !isOpenMenu&&setIsOpenMenu(true)
  232. const handleCloseFileMenu = (e:any) => e.target.id === 'overlay'&&isOpenMenu&&setIsOpenMenu(false)
  233. const handleOpenEmoji = () => !isOpenEmoji&&setIsOpenEmoji(true)
  234. const handleCloseEmoji = (e: any) => e.target.id === 'overlay'&&isOpenEmoji&&setIsOpenEmoji(false)
  235. const handleRecording = () => {
  236. if (isRecording) return stopRecording()
  237. startRecording()
  238. setType('recording')
  239. setIsRecording(true)
  240. }
  241. const handleFilming = () => {
  242. if (isFilming) return _stopRecording()
  243. _startRecording()
  244. setType('filming')
  245. setIsFilming(true)
  246. }
  247. return (
  248. <div className={classes.container}>
  249. {isArrow&&<div className={classes.borderTop}></div>}
  250. <CloseIcon onClick={clearMessage} fontSize="small" className={classes.iconCancel}
  251. sx={{width: 56, height: 56, display: file || value || status === 'stopped'
  252. || _status === 'stopped' ? 'inline-block' : 'none'}} />
  253. <VideocamIcon onClick={handleFilming} className={classes.avatarCamera}
  254. sx={{backgroundColor: '#ffffff', color: '#7c7c7c', width: 56, height: 56}}
  255. style={{display: status !== 'idle' || _status === 'stopped' || file || value ? 'none' : 'block',
  256. animation: isFilming ? 'ripple 1.2s infinite ease-in-out' : 'none'
  257. }} />
  258. <SendIcon onClick={sentMessage} className={classes.avatarRight}
  259. sx={{backgroundColor: '#ffffff',color: 'rgb(41, 139, 231)', width: 56, height: 56}}
  260. style={{display: value || file || status === 'stopped' || _status === 'stopped' ? 'block':'none' }}/>
  261. <MicNoneIcon onClick={handleRecording} className={classes.avatarRight}
  262. sx={{backgroundColor: isRecording ? 'rgb(41, 139, 231)' : '#ffffff',
  263. color: isRecording ? '#ffffff' : '#6b6b6b', width: 56, height: 56}}
  264. style={{display: !value && !file && status !== 'stopped' && _status === 'idle' ? 'block' : 'none',
  265. animation:isRecording ? 'ripple 1.2s infinite ease-in-out': 'none'}}/>
  266. <SentimentSatisfiedAltIcon onClick={handleOpenEmoji}
  267. fontSize='medium' sx={{color: isOpenEmoji ? 'rgb(41, 139, 231)' : '#6b6b6b', cursor: 'pointer',
  268. pointerEvents: file || status !== 'idle' || _status !== 'idle' ? 'none' : "auto",
  269. '&:hover': { color: 'rgb(41, 139, 231)'}}} />
  270. <div onClick={handleCloseEmoji} className={classes.overlay} id='overlay'
  271. style={{ display: isOpenEmoji ? 'block':'none'}}>
  272. <div className={classes.emoji} style={{left: isOpen&&isOpen !== 'menu'?'32.5vw':'45vw'}}>
  273. <Picker onEmojiClick={onEmojiClick} />
  274. </div>
  275. </div>
  276. <textarea disabled={file || status !== 'idle' || _status !== 'idle' ? true : false} value={value} onBlur={handleBlurTextarea}
  277. onFocus={handleFocusTextarea} onChange={handleTextarea} className={classes.textarea}
  278. placeholder={file ? 'The File is ready to send' : status === 'idle' && _status === 'idle' ? 'Message ' :
  279. `${status === 'stopped' || _status === 'stopped' ?'Recorded':'Recording in progress'}`} rows={1}>
  280. </textarea>
  281. <AttachFileIcon onClick={handleOpenFileMenu} className={classes.attachIcon}
  282. fontSize='medium' sx={{color: isOpenMenu ? 'rgb(41, 139, 231)' : '#6b6b6b', cursor: 'pointer',
  283. pointerEvents: value || status !== 'idle' || _status !== 'idle' ? 'none' : "auto",'&:hover': { color: 'rgb(41, 139, 231)'}}} />
  284. <div onClick={handleCloseFileMenu} className={classes.overlay} id='overlay'
  285. style={{ display: isOpenMenu ? 'block':'none'}}>
  286. <div className={classes.filesMenu} style={{left: isOpen&&isOpen !== 'menu'?'52.5vw':'65vw'}}>
  287. <FilesMenu setFile={setFile} setValue={setValue} setIsOpenMenu={setIsOpenMenu} setType={setType}/>
  288. </div>
  289. </div>
  290. </div>
  291. )
  292. }
  293. export default SendMessage