Proyectos de Subversion LeadersLinked - SPA

Rev

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;