dappros/ethora

View on GitHub
client-web/src/components/Chat/Messages/Message.tsx

Summary

Maintainability
D
1 day
Test Coverage
import React, { useEffect, useMemo, useState } from "react";
import {
  Message as KitMessage,
  MessageModel,
  MessageSeparator,
} from "@chatscope/chat-ui-kit-react";
import { differenceInHours, format, formatDistance, subDays } from "date-fns";
import { TMessageHistory } from "../../../store";
import { useHistory } from "react-router";
import {
  Card,
  CardActionArea,
  CardMedia,
  Button,
  IconButton,
  Typography,
  Divider,
} from "@mui/material";
import MoreVertIcon from "@mui/icons-material/MoreVert";
import coin from "../../../assets/images/coin.png";
import { Box } from "@mui/system";
import {
  audioMimetypes,
  imageMimetypes,
  videoMimetypes,
} from "../../../constants";
import xmpp from "../../../xmpp";
import { IButtons } from "../../../pages/ChatInRoom/Chat";

const avatarPreviewUrl = "https://icotar.com/initials/";

export interface IMessage {
  message: TMessageHistory;
  position: {
    type: string;
    position: MessageModel["position"];
    separator?: string;
  };
  is?: string;
  onMessageButtonClick: (button: IButtons) => void;
  toggleTransferDialog: (value: boolean, message: TMessageHistory) => void;
  onMediaMessageClick: (value: boolean, message: TMessageHistory) => void;

  onThreadClick?: () => void;
  isThread?: boolean;
}

type IDirection = "outgoing" | "incoming";

export const Message: React.FC<IMessage> = ({
  message,
  position,
  onMessageButtonClick,
  toggleTransferDialog,
  onMediaMessageClick,
  onThreadClick,
  isThread,
}) => {
  const firstName = message.data.senderFirstName;
  const lastName = message.data.senderLastName;
  const messageJid = message.data.senderJID.split("/")[0];
  const userJid = useMemo(() => xmpp.client?.jid?.toString().split("/")[0], []);
  const isSameUser = userJid === messageJid;
  const history = useHistory();
  const [buttons, setButtons] = useState<IButtons[]>();

  const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
  const openMenu = Boolean(anchorEl);

  const [messageDirection, setMessageDirection] =
    useState<IDirection>("incoming");

  const openDialogMenu = () => {
    setAnchorEl(null);
    toggleTransferDialog(true, message);
  };

  const openThreadView = () => {
    onThreadClick();
  };

  const fullViewImage = () => {
    onMediaMessageClick(true, message);
  };

  const rightClick = (event: React.SyntheticEvent<HTMLElement>) => {
    event.preventDefault();
    openDialogMenu();
  };

  const ReplyComponent = () => {
    return (
      <Button
        variant="text"
        style={{
          flexDirection: "row",
          display: "flex",
          textTransform: "none",
          textAlign: "left",
        }}
      >
        <Divider
          style={{
            borderWidth: "3px",
            borderRadius: "5px",
            marginRight: "5px",
          }}
          variant="middle"
          orientation="vertical"
          flexItem
        />

        <div>
          <strong style={{ cursor: "pointer" }}>
            {message.data.mainMessage?.userName || "N/A"}
            <br />
          </strong>
          {message.data.mainMessage?.imageLocation &&
            imageMimetypes[message.data.mainMessage.mimeType] && (
              <Card sx={{ maxWidth: 200 }}>
                <CardActionArea onClick={fullViewImage}>
                  <CardMedia
                    style={{
                      height: 150,
                      objectFit: "cover",
                      objectPosition: "left",
                    }}
                    component="img"
                    height="150"
                    image={message.data.mainMessage.imageLocation}
                    alt={message.data.mainMessage?.originalName}
                  />
                </CardActionArea>
              </Card>
            )}

          {/* {message.data.mainMessage.ImageLocation &&
          message.data.mainMessage.MimeType.split("/")[0] === "application" ? (
            <a target="_blank" href={message.data.location}>
              <KitMessage.ImageContent
                src={message.data.mainMessage.ImageLocation}
                alt={message.data.mainMessage.OriginalName}
                width={150}
              />
              {message.data.mainMessage.MimeType.split("/")[1]}
            </a>
          ) : null} */}

          {message.data.mainMessage?.imageLocation &&
            videoMimetypes[message.data.mainMessage?.mimeType] && (
              <video controls width="200px">
                <source
                  src={message.data.mainMessage.imageLocation}
                  type={message.data.mainMessage.mimeType}
                  title={message.data.mainMessage.originalName}
                />
                Sorry, your browser doesn't support videos.
              </video>
            )}

          {message.data.mainMessage?.imageLocation &&
            audioMimetypes[message.data.mainMessage?.mimeType] && (
              <audio controls>
                <source
                  src={message.data.mainMessage.imageLocation}
                  type={message.data.mainMessage.mimeType}
                />
                Your browser does not support the audio element.
              </audio>
            )}

          <span
            dangerouslySetInnerHTML={{
              __html: message.data?.mainMessage?.text.replace(
                /\b(https?\:\/\/\S+)/gm,
                '<a href="$1">$1</a>'
              ),
            }}
          ></span>
        </div>
      </Button>
    );
  };

  useEffect(() => {
    if (message.data.quickReplies) {
      setButtons(JSON.parse(message.data.quickReplies));
    }
    setMessageDirection(isSameUser ? "outgoing" : "incoming");
  }, []);

  return (
    <div is={"Message"}>
      {!!position.separator && (
        <MessageSeparator>{position.separator}</MessageSeparator>
      )}
      <KitMessage
        onContextMenu={
          !message.data.isReply && !isThread ? rightClick : () => {}
        }
        style={{
          marginBottom:
            position.type === "last" || position.type === "single" ? 15 : null,
        }}
        model={{
          sender: firstName + " " + lastName,
          direction: messageDirection,
          position: position.position,
        }}
        avatarPosition={isSameUser ? "tr" : "tl"}
        avatarSpacer={position.type !== "first" && position.type !== "single"}
      >
        {(position.type === "first" || position.type === "single") && (
          <img
            style={{
              borderRadius: "50%",
              boxSizing: "border-box",
              width: "42px",
              height: "42px",
              cursor: "pointer",
            }}
            onClick={() =>
              history.push("/profile/" + message.data.senderWalletAddress)
            }
            is={"Avatar"}
            src={
              message.data.photoURL
                ? message.data.photoURL
                : +firstName + " " + lastName
            }
            onError={({ currentTarget }) => {
              currentTarget.onerror = null;
              currentTarget.src = avatarPreviewUrl + firstName + " " + lastName;
            }}
            alt={firstName}
          />
        )}

        <KitMessage.CustomContent>
          {/* Main Message */}
          {message.data.isReply && !isThread && <ReplyComponent />}
          {(position.type === "first" || position.type === "single") && (
            <span
              style={{
                display: "flex",
                justifyContent: "space-between",
                alignItems: "center",
              }}
            >
              <strong
                style={{ cursor: "pointer" }}
                onClick={() =>
                  history.push("/profile/" + message.data.senderWalletAddress)
                }
              >
                {firstName} {lastName}
                <br />
              </strong>
              {!isThread && !message.data.isReply && (
                <IconButton
                  aria-label="more"
                  id="long-button"
                  aria-controls={openMenu ? "long-menu" : undefined}
                  aria-expanded={openMenu ? "true" : undefined}
                  aria-haspopup="true"
                  onClick={openDialogMenu}
                >
                  <MoreVertIcon />
                </IconButton>
              )}
            </span>
          )}

          {message.data.isMediafile &&
            !!imageMimetypes[message.data.mimetype] && (
              <Card sx={{ maxWidth: 200 }}>
                <CardActionArea onClick={fullViewImage}>
                  <CardMedia
                    style={{
                      height: 150,
                      objectFit: "cover",
                      objectPosition: "left",
                    }}
                    component="img"
                    height="150"
                    image={message.data.location}
                    alt={message.data.originalName}
                  />
                </CardActionArea>
              </Card>
            )}

          {message.data.isMediafile &&
            !!videoMimetypes[message.data.mimetype] && (
              <video controls width="200px">
                <source
                  src={message.data.location}
                  type={message.data.mimetype}
                  title={message.data.originalName}
                />
                Sorry, your browser doesn't support videos.
              </video>
            )}

          {message.data.isMediafile &&
            !!audioMimetypes[message.data.mimetype] && (
              <audio controls>
                <source
                  src={message.data.location}
                  type={message.data.mimetype}
                />
                Your browser does not support the audio element.
              </audio>
            )}

          {!message.data.isMediafile && (
            <div>
              <span
                dangerouslySetInnerHTML={{
                  __html: message.body.replace(
                    /\b(https?\:\/\/\S+)/gm,
                    '<a href="$1">$1</a>'
                  ),
                }}
              />
              {/*FOOTER */}
              <div
                style={{
                  display: "flex",
                  justifyContent: "space-between",
                  alignItems: "center",
                  marginTop: 5,
                  minWidth: 200,
                  color:
                    messageDirection === "incoming"
                      ? "rgb(110, 169, 215)"
                      : "#c6e3fa",
                  flexDirection:
                    messageDirection === "incoming" ? "row" : "row-reverse",
                }}
              >
                <div
                  style={{
                    fontSize: 12,
                  }}
                >
                  {differenceInHours(new Date(), new Date(message.date)) > 5
                    ? format(new Date(message.date), "h:mm a")
                    : formatDistance(
                        subDays(new Date(message.date), 0),
                        new Date(),
                        {
                          addSuffix: true,
                        }
                      )}
                </div>
                {message.coinsInMessage > 0 && (
                  <div style={{ display: "flex", alignItems: "center" }}>
                    <div style={{ fontSize: 12 }}>
                      {message?.coinsInMessage}
                    </div>
                    <img
                      src={coin}
                      style={{ width: 25, height: 25 }}
                      alt={"coin"}
                    />
                  </div>
                )}
                {message.data.isEdited && (
                  <div
                    style={{
                      display: "flex",
                      alignItems: "flex-end",
                      marginLeft: 3,
                      marginRight: 3,
                    }}
                  >
                    <Typography fontSize={12}>edited</Typography>
                  </div>
                )}
              </div>
            </div>
          )}
        </KitMessage.CustomContent>

        {/*{(position.type === "last" || position.type === "single") && (*/}
        {/*  <KitMessage.Footer*/}
        {/*    sentTime={*/}
        {/*      differenceInHours(new Date(), new Date(message.date)) > 5*/}
        {/*        ? format(new Date(message.date), "h:mm a")*/}
        {/*        : formatDistance(*/}
        {/*            subDays(new Date(message.date), 0),*/}
        {/*            new Date(),*/}
        {/*            {*/}
        {/*              addSuffix: true,*/}
        {/*            }*/}
        {/*          )*/}
        {/*    }*/}
        {/*  />*/}
        {/*)}*/}
        <KitMessage.Footer>
          {message.numberOfReplies > 0 &&
            messageDirection === "incoming" &&
            !isThread && (
              <Button onClick={() => openThreadView()} variant="text">
                <Typography fontSize={"12px"} textTransform={"none"}>
                  {message.numberOfReplies}{" "}
                  {message.numberOfReplies === 1 ? "Reply" : "Replies"} (tap to
                  review)
                </Typography>
              </Button>
            )}
        </KitMessage.Footer>
      </KitMessage>
      {!!buttons && (
        <Box sx={{ "& button": { m: 0.5 } }}>
          <div
            style={{
              display: "flex",
              flexDirection: "column",
              width: "max-content",
              marginLeft: "45px",
            }}
          >
            {buttons.map((button, index) => {
              return (
                <Button
                  variant="outlined"
                  size="small"
                  onClick={() => onMessageButtonClick(button)}
                  key={index}
                >
                  {button.name}
                </Button>
              );
            })}
          </div>
        </Box>
      )}
    </div>
  );
};