Rev 439 | Rev 442 | Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
import React, { memo, useEffect, useRef, useState } from "react";
import { axios } from "../../utils";
import { useForm } from "react-hook-form";
import { useDispatch } from "react-redux";
import { addNotification } from "../../redux/notification/notification.actions";
import parse from "html-react-parser";
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 Options from "../UI/Option";
import Emojione from "./emojione/Emojione";
import FileModal from "../modals/FileModal";
import LoaderContainer from "../UI/LoaderContainer";
import Spinner from "../UI/Spinner";
const StyledChatContainer = 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: 1.5rem;
font-weight: 500;
width: fit-content;
max-width: 20ch;
text-align: center;
@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: var(--chat-send);
border-radius: 10px 0 10px 10px;
margin-right: 0.5rem;
`;
}
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 StyledLoader = styled(LoaderContainer)`
max-height: 50px;
max-width: 50px;
`;
function messageAreEqual(oldProps, newProps) {
return oldProps.message.id
? oldProps.message.id === newProps.message.id
: oldProps.message.uuid === newProps.message.uuid;
}
const Chat = ({ children }) => {
return <StyledChatContainer>{children}</StyledChatContainer>;
};
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 (
<a href={url} style={{ width: "fit-content" }}>
<StyledTitle>{children}</StyledTitle>
</a>
);
};
const List = ({ messages = [], onPagination, scrollRef, loading }) => {
const loadMoreEl = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(onPagination);
if (loadMoreEl.current) {
observer.observe(loadMoreEl.current);
}
return () => {
observer.disconnect();
};
}, [messages]);
return (
<StyledMessageList ref={scrollRef}>
{messages.map((message) => (
<List.Message message={message} key={message.id} />
))}
<span ref={loadMoreEl}>.</span>
{loading && (
<StyledLoader>
<Spinner />
</StyledLoader>
)}
</StyledMessageList>
);
};
// eslint-disable-next-line react/display-name
const Message = memo(({ message }) => {
const senderName = (message) => {
if (message.type === "group" && !message.u === 1) {
return <span className="user_name">{message.user_name}</span>;
}
};
const messagesContent = {
text: (
// eslint-disable-next-line no-undef
<p>{parse(emojione.shortnameToImage(message.m || message.message))}</p>
),
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>
),
};
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>
</StyledMessage>
</>
);
}, messageAreEqual);
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}
/>
</>
);
};
Chat.Header = Header;
Chat.Title = Title;
Chat.List = List;
List.Message = Message;
Chat.SubmitForm = SubmitForm;
export default Chat;