Rev 1682 | Rev 1684 | Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { axios } from '../../utils'
import { useForm } from 'react-hook-form'
import { useDispatch } from 'react-redux'
import { addNotification } from '../../redux/notification/notification.actions'
import styled, { css } from 'styled-components'
import SendIcon from '@mui/icons-material/Send'
import IconButton from '@mui/material/IconButton'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import AttachFileIcon from '@mui/icons-material/AttachFile'
import InsertEmoticonIcon from '@mui/icons-material/InsertEmoticon'
import { Inbox } from '@mui/icons-material'
import Options from '../UI/Option'
import Emojione from './emojione/Emojione'
import FileModal from '../modals/FileModal'
import LoaderContainer from '../UI/LoaderContainer'
import Spinner from '../UI/Spinner'
import Paraphrase from '../UI/Paraphrase'
import useNearScreen from '@app/hooks/useNearScreen'
import EmptySection from '../UI/EmptySection'
const ChatContainer = styled.div`
background-color: var(--bg-color);
border-radius: var(--border-radius);
border: 1px solid var(--border-primary);
height: 80vh;
display: flex;
flex-direction: column;
flex-grow: 1;
padding: 0px 0px !important;
`
const StyledChatHeader = styled.div`
align-items: center;
border-bottom: 1px solid var(--border-primary);
display: flex;
justify-content: center;
padding: 1rem 0.5rem;
position: relative;
& > button:first-child {
position: absolute;
left: 1rem;
top: 50%;
transform: translateY(-50%);
display: inline-flex;
@media (min-width: 768px) {
display: none;
}
}
`
const StyledTitle = styled.h2`
font-size: 16px;
font-weight: 600;
width: fit-content;
max-width: 20ch;
text-align: center;
color: #1d315c;
@media (min-width: 768px) {
max-width: 30ch;
}
`
const StyledMessageList = styled.div`
gap: 0.5rem;
display: flex;
flex-direction: column-reverse;
height: -webkit-fill-available;
padding: 0.5rem 0;
overflow: auto;
`
const StyledMessage = styled.div`
box-shadow: var(--light-shadow);
display: inline-flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 0.5rem;
max-width: 70%;
min-width: 4rem;
padding: 0.5rem;
position: relative;
& > p {
color: var(--chat-color);
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-word;
}
& > img,
& > video {
max-width: 250px;
max-height: 250px;
object-fit: contain;
}
&:first-child {
margin-top: 0.5rem;
}
.time {
color: var(--subtitle-color);
font-size: 0.8rem;
}
.emojione {
width: 1rem;
height: 1rem;
}
${(props) => {
if (props.send) {
return css`
align-self: flex-end;
background-color: #eee;
border-radius: 10px 0px 10px 10px;
margin-right: 0.5rem;
color: #393939;
`
}
return css`
align-self: flex-start;
background-color: var(--chat-received);
border-radius: 0 10px 10px 10px;
margin-left: 0.5rem;
`
}}
`
const StyledForm = styled.form`
border-top: 1px solid var(--border-primary);
padding: 0.5rem;
position: relative;
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
`
const StyledInput = styled.input`
border: none;
outline: none;
width: 100%;
padding: 0.5rem 1rem;
border-radius: 30px;
background: var(--bg-color-secondary);
&:focus {
background: var(--bg-color-secondary);
}
`
const Header = ({ children, options = [], onClose }) => {
return (
<StyledChatHeader>
<IconButton onClick={onClose}>
<ArrowBackIcon />
</IconButton>
{children}
{!!options.length && <Options options={options} right='1rem' />}
</StyledChatHeader>
)
}
const Title = ({ children, url }) => {
if (!url) {
return <StyledTitle>{children}</StyledTitle>
}
return (
<Link to={url} style={{ width: 'fit-content' }}>
<StyledTitle>{children}</StyledTitle>
</Link>
)
}
const List = ({
messages = [],
onPagination,
onReport,
scrollRef,
loading
}) => {
const [isIntercepting, ref] = useNearScreen({
once: false,
rootMargin: '0px'
})
useEffect(() => {
if (isIntercepting) {
onPagination()
}
}, [isIntercepting])
if (!messages.length) {
return (
<EmptySection
Icon={<Inbox />}
message='No hay mensajes en esta conversación'
align='center'
/>
)
}
return (
<StyledMessageList ref={scrollRef}>
{messages.map((message) => (
<List.Message onReport={onReport} message={message} key={message.id} />
))}
<p ref={ref}>Loading</p>
</StyledMessageList>
)
}
const Message = ({ message, onReport }) => {
const [options, setOptions] = useState([])
const senderName = (message) => {
if (message.type === 'group' && !message.u === 1) {
return <span className='user_name'>{message.user_name}</span>
}
}
const messagesContent = {
text: (message.m || message.message) && (
<Paraphrase>
{emojione.shortnameToImage(message.m || message.message)}
</Paraphrase>
),
image: <img src={message.m || message.filename} alt='chat_image' />,
video: (
<video src={message.m || message.filename} preload='none' controls />
),
document: (
<a href={message.m || message.filename} download>
<img className='pdf' src='/images/extension/pdf.png' alt='pdf' />
</a>
)
}
useEffect(() => {
if (message.url_abuse_report) {
const reportOption = {
action: () =>
onReport({ url: message.url_abuse_report, id: message.id }),
label: 'Reportar'
}
setOptions([...options, reportOption])
}
}, [message])
return (
<>
{senderName(message)}
<StyledMessage
send={message.u ? message.u === 1 : message.side === 'left'}
>
{messagesContent[message.mtype || message.type]}
<label className='time'>
{!message.not_received && (
<i
className='fa fa-check'
style={message.seen ? { color: 'blue' } : { color: 'gray' }}
/>
)}
{message.time || message.date}
</label>
<Options options={options} right='-2.5rem' />
</StyledMessage>
</>
)
}
const SubmitForm = ({ sendUrl, uploadUrl, onSubmit: onComplete }) => {
const [showEmojione, setShowEmojione] = useState(false)
const [isShowFileModal, setIsShowFileModal] = useState(false)
const [isSending, setIsSending] = useState(false)
const dispatch = useDispatch()
const { handleSubmit, setValue, register, reset, getValues } = useForm()
const onSubmit = handleSubmit(({ message }) => {
const formData = new FormData()
// eslint-disable-next-line no-undef
formData.append('message', emojione.toShort(message))
axios.post(sendUrl, formData).then((response) => {
const { success, data } = response.data
if (!success) {
const errorMessage =
typeof data === 'string' ? data : 'Ha ocurrido un error'
setShowEmojione(false)
dispatch(addNotification({ style: 'danger', msg: errorMessage }))
return
}
setShowEmojione(false)
onComplete()
reset()
})
})
const sendFile = (file) => {
setIsSending(true)
const formData = new FormData()
formData.append('file', file)
axios
.post(uploadUrl, formData)
.then(({ data: response }) => {
const { success, data } = response
if (!success) {
const errorMessage =
typeof data === 'string' ? data : 'Ha ocurrido un error'
dispatch(addNotification({ style: 'success', msg: errorMessage }))
return
}
toggleFileModal()
onComplete()
})
.finally(() => setIsSending(false))
}
const toggleEmojione = () => {
setShowEmojione(!showEmojione)
}
const toggleFileModal = () => {
setIsShowFileModal(!isShowFileModal)
}
const onClickEmoji = (event) => {
const shortname = event.currentTarget.dataset.shortname
const currentMessage = getValues('message')
// eslint-disable-next-line no-undef
const unicode = emojione.shortnameToUnicode(shortname)
setValue('message', `${currentMessage}${unicode}`)
}
return (
<>
<StyledForm onSubmit={onSubmit}>
{showEmojione && <Emojione onClickEmoji={onClickEmoji} />}
<IconButton onClick={toggleFileModal}>
<AttachFileIcon />
</IconButton>
<IconButton onClick={toggleEmojione}>
<InsertEmoticonIcon />
</IconButton>
<StyledInput
type='text'
name='message'
placeholder='Escribe un mensaje'
ref={register({ required: true })}
/>
<IconButton type='submit'>
<SendIcon />
</IconButton>
</StyledForm>
<FileModal
isShow={isShowFileModal}
onHide={toggleFileModal}
onComplete={sendFile}
loading={isSending}
/>
</>
)
}
ChatContainer.Header = Header
ChatContainer.Title = Title
ChatContainer.List = List
List.Message = Message
ChatContainer.SubmitForm = SubmitForm
export default ChatContainer