From 15e573116753d5f4808b46f5adc5b7e0ce61f910 Mon Sep 17 00:00:00 2001
From: Szymon Szlachtowicz <38212223+Szymx95@users.noreply.github.com>
Date: Tue, 14 Dec 2021 00:55:09 +0100
Subject: [PATCH] Refactor chat messages (#156)
---
.../src/components/ActivityCenter.tsx | 22 +-
.../src/components/Chat/ChatBody.tsx | 4 +-
.../src/components/Chat/ChatMessages.tsx | 470 ------------------
.../src/components/Form/ContactMenu.tsx | 2 +-
.../src/components/Messages/MessageQuote.tsx | 81 +++
.../src/components/Messages/MessagesList.tsx | 95 ++++
.../src/components/Messages/Styles.tsx | 117 +++++
.../src/components/Messages/UiMessage.tsx | 197 ++++++++
8 files changed, 504 insertions(+), 484 deletions(-)
delete mode 100644 packages/react-chat/src/components/Chat/ChatMessages.tsx
create mode 100644 packages/react-chat/src/components/Messages/MessageQuote.tsx
create mode 100644 packages/react-chat/src/components/Messages/MessagesList.tsx
create mode 100644 packages/react-chat/src/components/Messages/Styles.tsx
create mode 100644 packages/react-chat/src/components/Messages/UiMessage.tsx
diff --git a/packages/react-chat/src/components/ActivityCenter.tsx b/packages/react-chat/src/components/ActivityCenter.tsx
index ba2be3cd..2a133056 100644
--- a/packages/react-chat/src/components/ActivityCenter.tsx
+++ b/packages/react-chat/src/components/ActivityCenter.tsx
@@ -11,17 +11,6 @@ import { equalDate } from "../utils/equalDate";
import { DownloadButton } from "./Buttons/DownloadButton";
import { buttonStyles } from "./Buttons/buttonStyle";
import { Mention } from "./Chat/ChatMessageContent";
-import {
- ContentWrapper,
- DateSeparator,
- MessageHeaderWrapper,
- MessageOuterWrapper,
- MessageText,
- TimeWrapper,
- UserAddress,
- UserName,
- UserNameWrapper,
-} from "./Chat/ChatMessages";
import { Logo } from "./CommunityIdentity";
import { ContactMenu } from "./Form/ContactMenu";
import { Tooltip } from "./Form/Tooltip";
@@ -37,6 +26,17 @@ import { ReplyIcon } from "./Icons/ReplyActivityIcon";
import { ShowIcon } from "./Icons/ShowIcon";
import { UntrustworthIcon } from "./Icons/UntrustworthIcon";
import { UserIcon } from "./Icons/UserIcon";
+import {
+ ContentWrapper,
+ DateSeparator,
+ MessageHeaderWrapper,
+ MessageOuterWrapper,
+ MessageText,
+ TimeWrapper,
+ UserAddress,
+ UserName,
+ UserNameWrapper,
+} from "./Messages/Styles";
import { ProfileModalName } from "./Modals/ProfileModal";
import { textMediumStyles, textSmallStyles } from "./Text";
diff --git a/packages/react-chat/src/components/Chat/ChatBody.tsx b/packages/react-chat/src/components/Chat/ChatBody.tsx
index d3749ca4..e958d782 100644
--- a/packages/react-chat/src/components/Chat/ChatBody.tsx
+++ b/packages/react-chat/src/components/Chat/ChatBody.tsx
@@ -4,13 +4,13 @@ import styled from "styled-components";
import { useMessengerContext } from "../../contexts/messengerProvider";
import { useNarrow } from "../../contexts/narrowProvider";
import { Reply } from "../../hooks/useReply";
+import { MessagesList } from "../Messages/MessagesList";
import { NarrowChannels } from "../NarrowMode/NarrowChannels";
import { NarrowMembers } from "../NarrowMode/NarrowMembers";
import { LoadingSkeleton } from "../Skeleton/LoadingSkeleton";
import { ChatCreation } from "./ChatCreation";
import { ChatInput } from "./ChatInput";
-import { ChatMessages } from "./ChatMessages";
import { ChatTopbar } from "./ChatTopbar";
export enum ChatBodyState {
@@ -71,7 +71,7 @@ export function ChatBody({ onClick, showMembers }: ChatBodyProps) {
{showState === ChatBodyState.Chat && (
<>
{messenger && communityData ? (
-
+
) : (
)}
diff --git a/packages/react-chat/src/components/Chat/ChatMessages.tsx b/packages/react-chat/src/components/Chat/ChatMessages.tsx
deleted file mode 100644
index 16c69614..00000000
--- a/packages/react-chat/src/components/Chat/ChatMessages.tsx
+++ /dev/null
@@ -1,470 +0,0 @@
-import React, {
- useCallback,
- useEffect,
- useMemo,
- useRef,
- useState,
-} from "react";
-import { utils } from "status-communities/dist/cjs";
-import styled from "styled-components";
-
-import { useActivities } from "../../contexts/activityProvider";
-import { useIdentity } from "../../contexts/identityProvider";
-import { useMessengerContext } from "../../contexts/messengerProvider";
-import { useModal } from "../../contexts/modalProvider";
-import { useChatScrollHandle } from "../../hooks/useChatScrollHandle";
-import { Reply } from "../../hooks/useReply";
-import { ChannelData } from "../../models/ChannelData";
-import { ChatMessage } from "../../models/ChatMessage";
-import { equalDate } from "../../utils";
-import { EmptyChannel } from "../Channels/EmptyChannel";
-import { ContactMenu } from "../Form/ContactMenu";
-import { Icon } from "../Icons/Icon";
-import { LoadingIcon } from "../Icons/LoadingIcon";
-import { QuoteSvg } from "../Icons/QuoteIcon";
-import { ReactionSvg } from "../Icons/ReactionIcon";
-import { ReplySvg } from "../Icons/ReplyIcon";
-import { UntrustworthIcon } from "../Icons/UntrustworthIcon";
-import { UserIcon } from "../Icons/UserIcon";
-import { LinkModal, LinkModalName } from "../Modals/LinkModal";
-import { PictureModal, PictureModalName } from "../Modals/PictureModal";
-import { textMediumStyles, textSmallStyles } from "../Text";
-
-import { ReplyOn, ReplyTo } from "./ChatInput";
-import { ChatMessageContent } from "./ChatMessageContent";
-
-const today = new Date();
-
-function calcHeight(quote: ChatMessage) {
- if (quote.image && quote.content) {
- return 88;
- } else if (quote.image && !quote.content) {
- return 68;
- } else {
- return 25;
- }
-}
-
-type MessageQuoteProps = {
- quote: ChatMessage | undefined;
-};
-
-function MessageQuote({ quote }: MessageQuoteProps) {
- const quoteClick = useCallback(() => {
- if (quote) {
- const quoteDiv = document.getElementById(quote.id);
- if (quoteDiv) {
- quoteDiv.scrollIntoView({
- behavior: "smooth",
- block: "center",
- inline: "center",
- });
- quoteDiv.style.background = "lightblue";
- quoteDiv.style.transition = "background-color 1000ms linear";
- window.setTimeout(() => {
- quoteDiv.style.background = "";
- window.setTimeout(() => {
- quoteDiv.style.transition = "";
- }, 1000);
- }, 1000);
- }
- }
- }, [quote]);
-
- if (quote) {
- return (
-
-
-
- {" "}
- {quote.sender}
-
- {quote.content}
- {quote.image && }
-
- );
- }
- return null;
-}
-
-type ChatUiMessageProps = {
- idx: number;
- message: ChatMessage;
- channel: ChannelData;
- prevMessage: ChatMessage;
- setImage: (img: string) => void;
- setLink: (link: string) => void;
- setReply: (val: Reply | undefined) => void;
- quote?: ChatMessage;
-};
-
-function ChatUiMessage({
- message,
- channel,
- idx,
- prevMessage,
- setImage,
- setLink,
- setReply,
- quote,
-}: ChatUiMessageProps) {
- const { contacts } = useMessengerContext();
- const { setActivities } = useActivities();
- const identity = useIdentity();
-
- const contact = useMemo(
- () => contacts[message.sender],
- [message.sender, contacts]
- );
- const [showMenu, setShowMenu] = useState(false);
- const [mentioned, setMentioned] = useState(false);
-
- useEffect(() => {
- if (mentioned)
- setActivities((prev) => [
- ...prev,
- {
- id: message.date.getTime().toString() + message.content,
- type: "mention",
- date: message.date,
- user: message.sender,
- message: message,
- channel: channel,
- },
- ]);
- if (quote && quote.sender === utils.bufToHex(identity.publicKey))
- setActivities((prev) => [
- ...prev,
- {
- id: message.date.getTime().toString() + message.content,
- type: "reply",
- date: message.date,
- user: message.sender,
- message: message,
- channel: channel,
- quote: quote,
- },
- ]);
- }, [mentioned, message, quote]);
-
- return (
-
- {(idx === 0 || !equalDate(prevMessage.date, message.date)) && (
-
- {equalDate(message.date, today)
- ? "Today"
- : message.date.toLocaleDateString()}
-
- )}
-
-
-
- {
- setShowMenu((e) => !e);
- }}
- >
- {showMenu && (
-
- )}
-
-
-
-
-
-
- {" "}
- {contact.customName ?? message.sender.slice(0, 10)}
-
- {contact.customName && (
-
- {message.sender.slice(0, 5)}...{message.sender.slice(-3)}
-
- )}
- {contact.isUntrustworthy && }
-
- {message.date.toLocaleString()}
-
-
-
-
-
-
-
-
-
-
-
- setReply({
- sender: message.sender,
- content: message.content,
- image: message.image,
- id: message.id,
- })
- }
- >
-
-
-
-
-
- );
-}
-
-interface ChatMessagesProps {
- setReply: (val: Reply | undefined) => void;
-}
-
-export function ChatMessages({ setReply }: ChatMessagesProps) {
- const { messages, activeChannel, contacts } = useMessengerContext();
- const ref = useRef(null);
- const loadingMessages = useChatScrollHandle(messages, ref, activeChannel);
-
- const shownMessages = useMemo(
- () =>
- messages.filter(
- (message) => !contacts?.[message.sender]?.blocked ?? true
- ),
- [contacts, messages, messages.length]
- );
-
- const [image, setImage] = useState("");
- const [link, setLink] = useState("");
-
- const { setModal: setPictureModal, isVisible: showPictureModal } =
- useModal(PictureModalName);
- const { setModal: setLinkModal, isVisible: showLinkModal } =
- useModal(LinkModalName);
-
- useEffect(() => (!image ? undefined : setPictureModal(true)), [image]);
- useEffect(() => (!link ? undefined : setLinkModal(true)), [link]);
-
- useEffect(
- () => (!showPictureModal ? setImage("") : undefined),
- [showPictureModal]
- );
- useEffect(() => (!showLinkModal ? setLink("") : undefined), [showLinkModal]);
-
- return (
-
-
-
-
- {loadingMessages && (
-
-
-
- )}
- {shownMessages.map((message, idx) => (
- msg.id == message?.responseTo)}
- />
- ))}
-
- );
-}
-
-const MessagesWrapper = styled.div`
- display: flex;
- flex-direction: column;
- height: calc(100% - 44px);
- overflow: auto;
- padding: 8px 0;
-
- &::-webkit-scrollbar {
- width: 0;
- }
-`;
-
-export const MessageWrapper = styled.div`
- width: 100%;
- display: flex;
- flex-direction: column;
- padding: 8px 16px;
- border-left: 2px solid ${({ theme }) => theme.bodyBackgroundColor};
- position: relative;
-
- &:hover {
- background: ${({ theme }) => theme.inputColor};
- border-color: ${({ theme }) => theme.inputColor};
- }
-
- &:hover > div {
- visibility: visible;
- }
-
- &.mention {
- background: ${({ theme }) => theme.mentionBg};
- border-color: ${({ theme }) => theme.mentionColor};
- }
-
- &.mention:hover {
- background: ${({ theme }) => theme.mentionBgHover};
- border-color: ${({ theme }) => theme.mentionColor};
- }
-`;
-
-export const MessageOuterWrapper = styled.div`
- width: 100%;
- display: flex;
- flex-direction: column;
- position: relative;
-`;
-
-const UserMessageWrapper = styled.div`
- width: 100%;
- display: flex;
-`;
-
-export const DateSeparator = styled.div`
- width: 100%;
- display: flex;
- flex: 1;
- height: 100%;
- text-align: center;
- justify-content: center;
- align-items: center;
- font-family: "Inter";
- font-style: normal;
- font-weight: 500;
- color: #939ba1;
- margin-top: 16px;
- margin-bottom: 16px;
-
- ${textSmallStyles}
-`;
-
-export const ContentWrapper = styled.div`
- display: flex;
- flex-direction: column;
- margin-left: 8px;
-`;
-
-export const MessageHeaderWrapper = styled.div`
- display: flex;
- align-items: center;
-`;
-
-export const UserNameWrapper = styled.div`
- display: flex;
- align-items: center;
-`;
-
-export const UserName = styled.p`
- font-weight: 500;
- color: ${({ theme }) => theme.tertiary};
- margin-right: 4px;
-
- ${textMediumStyles}
-`;
-
-export const UserAddress = styled.p`
- font-size: 10px;
- line-height: 14px;
- letter-spacing: 0.2px;
- color: ${({ theme }) => theme.secondary};
- position: relative;
- padding-right: 8px;
-
- &:after {
- content: "";
- position: absolute;
- right: 0;
- top: 50%;
- transform: translateY(-50%);
- width: 4px;
- height: 4px;
- border-radius: 50%;
- background: ${({ theme }) => theme.secondary};
- }
-`;
-
-export const TimeWrapper = styled.div`
- font-size: 10px;
- line-height: 14px;
- letter-spacing: 0.2px;
- text-transform: uppercase;
- color: ${({ theme }) => theme.secondary};
- margin-left: 4px;
-`;
-
-export const MessageText = styled.div`
- overflow-wrap: anywhere;
- width: 100%;
- white-space: pre-wrap;
- color: ${({ theme }) => theme.primary};
-`;
-
-const LoadingWrapper = styled.div`
- display: flex;
- align-self: center;
- align-items: center;
- justify-content: center;
- background: ${({ theme }) => theme.bodyBackgroundColor};
- position: relative;
-`;
-
-const Reactions = styled.div`
- display: flex;
- position: absolute;
- right: 20px;
- top: -18px;
- box-shadow: 0px 4px 12px rgba(0, 34, 51, 0.08);
- border-radius: 8px;
- background: ${({ theme }) => theme.bodyBackgroundColor};
- visibility: hidden;
-`;
-
-const ReactionBtn = styled.button`
- width: 32px;
- height: 32px;
- display: flex;
- justify-content: center;
- align-items: center;
- border-radius: 8px;
- align-self: center;
-
- &:hover {
- background: ${({ theme }) => theme.buttonBgHover};
- }
-
- &:hover > svg {
- fill: ${({ theme }) => theme.tertiary};
- }
-`;
-
-const QuoteWrapper = styled.div`
- display: flex;
- flex-direction: column;
- padding-left: 48px;
- position: relative;
-`;
-
-const QuoteSender = styled(ReplyTo)`
- color: ${({ theme }) => theme.secondary};
-`;
-
-const Quote = styled(ReplyOn)`
- color: ${({ theme }) => theme.secondary};
-`;
-
-const QuoteImage = styled.img`
- width: 56px;
- height: 56px;
- border-radius: 4px;
- margin-top: 4px;
-`;
diff --git a/packages/react-chat/src/components/Form/ContactMenu.tsx b/packages/react-chat/src/components/Form/ContactMenu.tsx
index 8d36f5c2..af541a4b 100644
--- a/packages/react-chat/src/components/Form/ContactMenu.tsx
+++ b/packages/react-chat/src/components/Form/ContactMenu.tsx
@@ -5,7 +5,6 @@ import styled from "styled-components";
import { useIdentity } from "../../contexts/identityProvider";
import { useModal } from "../../contexts/modalProvider";
import { useManageContact } from "../../hooks/useManageContact";
-import { UserAddress } from "../Chat/ChatMessages";
import { AddContactSvg } from "../Icons/AddContactIcon";
import { BlockSvg } from "../Icons/BlockIcon";
import { ChatSvg } from "../Icons/ChatIcon";
@@ -14,6 +13,7 @@ import { ProfileSvg } from "../Icons/ProfileIcon";
import { UntrustworthIcon } from "../Icons/UntrustworthIcon";
import { UserIcon } from "../Icons/UserIcon";
import { WarningSvg } from "../Icons/WarningIcon";
+import { UserAddress } from "../Messages/Styles";
import { ProfileModalName } from "../Modals/ProfileModal";
import { textMediumStyles } from "../Text";
diff --git a/packages/react-chat/src/components/Messages/MessageQuote.tsx b/packages/react-chat/src/components/Messages/MessageQuote.tsx
new file mode 100644
index 00000000..54dbaa6c
--- /dev/null
+++ b/packages/react-chat/src/components/Messages/MessageQuote.tsx
@@ -0,0 +1,81 @@
+import React, { useCallback } from "react";
+import styled from "styled-components";
+
+import { ChatMessage } from "../../models/ChatMessage";
+import { ReplyOn, ReplyTo } from "../Chat/ChatInput";
+import { QuoteSvg } from "../Icons/QuoteIcon";
+import { UserIcon } from "../Icons/UserIcon";
+
+function calcHeight(quote: ChatMessage) {
+ if (quote.image && quote.content) {
+ return 88;
+ } else if (quote.image && !quote.content) {
+ return 68;
+ } else {
+ return 25;
+ }
+}
+
+type MessageQuoteProps = {
+ quote: ChatMessage | undefined;
+};
+
+export function MessageQuote({ quote }: MessageQuoteProps) {
+ const quoteClick = useCallback(() => {
+ if (quote) {
+ const quoteDiv = document.getElementById(quote.id);
+ if (quoteDiv) {
+ quoteDiv.scrollIntoView({
+ behavior: "smooth",
+ block: "center",
+ inline: "center",
+ });
+ quoteDiv.style.background = "lightblue";
+ quoteDiv.style.transition = "background-color 1000ms linear";
+ window.setTimeout(() => {
+ quoteDiv.style.background = "";
+ window.setTimeout(() => {
+ quoteDiv.style.transition = "";
+ }, 1000);
+ }, 1000);
+ }
+ }
+ }, [quote]);
+
+ if (quote) {
+ return (
+
+
+
+ {" "}
+ {quote.sender}
+
+ {quote.content}
+ {quote.image && }
+
+ );
+ }
+ return null;
+}
+
+const QuoteWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ padding-left: 48px;
+ position: relative;
+`;
+
+const QuoteSender = styled(ReplyTo)`
+ color: ${({ theme }) => theme.secondary};
+`;
+
+const Quote = styled(ReplyOn)`
+ color: ${({ theme }) => theme.secondary};
+`;
+
+const QuoteImage = styled.img`
+ width: 56px;
+ height: 56px;
+ border-radius: 4px;
+ margin-top: 4px;
+`;
diff --git a/packages/react-chat/src/components/Messages/MessagesList.tsx b/packages/react-chat/src/components/Messages/MessagesList.tsx
new file mode 100644
index 00000000..8fce8819
--- /dev/null
+++ b/packages/react-chat/src/components/Messages/MessagesList.tsx
@@ -0,0 +1,95 @@
+import React, { useEffect, useMemo, useRef, useState } from "react";
+import styled from "styled-components";
+
+import { useMessengerContext } from "../../contexts/messengerProvider";
+import { useModal } from "../../contexts/modalProvider";
+import { useChatScrollHandle } from "../../hooks/useChatScrollHandle";
+import { Reply } from "../../hooks/useReply";
+import { EmptyChannel } from "../Channels/EmptyChannel";
+import { LoadingIcon } from "../Icons/LoadingIcon";
+import { LinkModal, LinkModalName } from "../Modals/LinkModal";
+import { PictureModal, PictureModalName } from "../Modals/PictureModal";
+
+import { UiMessage } from "./UiMessage";
+
+interface MessagesListProps {
+ setReply: (val: Reply | undefined) => void;
+}
+
+export function MessagesList({ setReply }: MessagesListProps) {
+ const { messages, activeChannel, contacts } = useMessengerContext();
+ const ref = useRef(null);
+ const loadingMessages = useChatScrollHandle(messages, ref, activeChannel);
+
+ const shownMessages = useMemo(
+ () =>
+ messages.filter(
+ (message) => !contacts?.[message.sender]?.blocked ?? true
+ ),
+ [contacts, messages, messages.length]
+ );
+
+ const [image, setImage] = useState("");
+ const [link, setLink] = useState("");
+
+ const { setModal: setPictureModal, isVisible: showPictureModal } =
+ useModal(PictureModalName);
+ const { setModal: setLinkModal, isVisible: showLinkModal } =
+ useModal(LinkModalName);
+
+ useEffect(() => (!image ? undefined : setPictureModal(true)), [image]);
+ useEffect(() => (!link ? undefined : setLinkModal(true)), [link]);
+
+ useEffect(
+ () => (!showPictureModal ? setImage("") : undefined),
+ [showPictureModal]
+ );
+ useEffect(() => (!showLinkModal ? setLink("") : undefined), [showLinkModal]);
+
+ return (
+
+
+
+
+ {loadingMessages && (
+
+
+
+ )}
+ {shownMessages.map((message, idx) => (
+ msg.id == message?.responseTo)}
+ />
+ ))}
+
+ );
+}
+
+const LoadingWrapper = styled.div`
+ display: flex;
+ align-self: center;
+ align-items: center;
+ justify-content: center;
+ background: ${({ theme }) => theme.bodyBackgroundColor};
+ position: relative;
+`;
+
+const MessagesWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ height: calc(100% - 44px);
+ overflow: auto;
+ padding: 8px 0;
+
+ &::-webkit-scrollbar {
+ width: 0;
+ }
+`;
diff --git a/packages/react-chat/src/components/Messages/Styles.tsx b/packages/react-chat/src/components/Messages/Styles.tsx
new file mode 100644
index 00000000..f2d24b9a
--- /dev/null
+++ b/packages/react-chat/src/components/Messages/Styles.tsx
@@ -0,0 +1,117 @@
+import styled from "styled-components";
+
+import { textMediumStyles, textSmallStyles } from "../Text";
+
+export const MessageWrapper = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ padding: 8px 16px;
+ border-left: 2px solid ${({ theme }) => theme.bodyBackgroundColor};
+ position: relative;
+
+ &:hover {
+ background: ${({ theme }) => theme.inputColor};
+ border-color: ${({ theme }) => theme.inputColor};
+ }
+
+ &:hover > div {
+ visibility: visible;
+ }
+
+ &.mention {
+ background: ${({ theme }) => theme.mentionBg};
+ border-color: ${({ theme }) => theme.mentionColor};
+ }
+
+ &.mention:hover {
+ background: ${({ theme }) => theme.mentionBgHover};
+ border-color: ${({ theme }) => theme.mentionColor};
+ }
+`;
+
+export const MessageOuterWrapper = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+`;
+
+export const DateSeparator = styled.div`
+ width: 100%;
+ display: flex;
+ flex: 1;
+ height: 100%;
+ text-align: center;
+ justify-content: center;
+ align-items: center;
+ font-family: "Inter";
+ font-style: normal;
+ font-weight: 500;
+ color: #939ba1;
+ margin-top: 16px;
+ margin-bottom: 16px;
+
+ ${textSmallStyles}
+`;
+
+export const ContentWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ margin-left: 8px;
+`;
+
+export const MessageHeaderWrapper = styled.div`
+ display: flex;
+ align-items: center;
+`;
+
+export const UserNameWrapper = styled.div`
+ display: flex;
+ align-items: center;
+`;
+
+export const UserName = styled.p`
+ font-weight: 500;
+ color: ${({ theme }) => theme.tertiary};
+ margin-right: 4px;
+
+ ${textMediumStyles}
+`;
+
+export const UserAddress = styled.p`
+ font-size: 10px;
+ line-height: 14px;
+ letter-spacing: 0.2px;
+ color: ${({ theme }) => theme.secondary};
+ position: relative;
+ padding-right: 8px;
+
+ &:after {
+ content: "";
+ position: absolute;
+ right: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 4px;
+ height: 4px;
+ border-radius: 50%;
+ background: ${({ theme }) => theme.secondary};
+ }
+`;
+
+export const TimeWrapper = styled.div`
+ font-size: 10px;
+ line-height: 14px;
+ letter-spacing: 0.2px;
+ text-transform: uppercase;
+ color: ${({ theme }) => theme.secondary};
+ margin-left: 4px;
+`;
+
+export const MessageText = styled.div`
+ overflow-wrap: anywhere;
+ width: 100%;
+ white-space: pre-wrap;
+ color: ${({ theme }) => theme.primary};
+`;
diff --git a/packages/react-chat/src/components/Messages/UiMessage.tsx b/packages/react-chat/src/components/Messages/UiMessage.tsx
new file mode 100644
index 00000000..da0e947d
--- /dev/null
+++ b/packages/react-chat/src/components/Messages/UiMessage.tsx
@@ -0,0 +1,197 @@
+import React, { useEffect, useMemo, useState } from "react";
+import { utils } from "status-communities/dist/cjs";
+import styled from "styled-components";
+
+import { useActivities } from "../../contexts/activityProvider";
+import { useIdentity } from "../../contexts/identityProvider";
+import { useMessengerContext } from "../../contexts/messengerProvider";
+import { Reply } from "../../hooks/useReply";
+import { ChannelData } from "../../models/ChannelData";
+import { ChatMessage } from "../../models/ChatMessage";
+import { equalDate } from "../../utils";
+import { ChatMessageContent } from "../Chat/ChatMessageContent";
+import { ContactMenu } from "../Form/ContactMenu";
+import { Icon } from "../Icons/Icon";
+import { ReactionSvg } from "../Icons/ReactionIcon";
+import { ReplySvg } from "../Icons/ReplyIcon";
+import { UntrustworthIcon } from "../Icons/UntrustworthIcon";
+import { UserIcon } from "../Icons/UserIcon";
+
+import { MessageQuote } from "./MessageQuote";
+import {
+ ContentWrapper,
+ DateSeparator,
+ MessageHeaderWrapper,
+ MessageOuterWrapper,
+ MessageText,
+ MessageWrapper,
+ TimeWrapper,
+ UserAddress,
+ UserName,
+ UserNameWrapper,
+} from "./Styles";
+
+type UiMessageProps = {
+ idx: number;
+ message: ChatMessage;
+ channel: ChannelData;
+ prevMessage: ChatMessage;
+ setImage: (img: string) => void;
+ setLink: (link: string) => void;
+ setReply: (val: Reply | undefined) => void;
+ quote?: ChatMessage;
+};
+
+export function UiMessage({
+ message,
+ channel,
+ idx,
+ prevMessage,
+ setImage,
+ setLink,
+ setReply,
+ quote,
+}: UiMessageProps) {
+ const today = new Date();
+ const { contacts } = useMessengerContext();
+ const { setActivities } = useActivities();
+ const identity = useIdentity();
+
+ const contact = useMemo(
+ () => contacts[message.sender],
+ [message.sender, contacts]
+ );
+ const [showMenu, setShowMenu] = useState(false);
+ const [mentioned, setMentioned] = useState(false);
+
+ useEffect(() => {
+ if (mentioned)
+ setActivities((prev) => [
+ ...prev,
+ {
+ id: message.date.getTime().toString() + message.content,
+ type: "mention",
+ date: message.date,
+ user: message.sender,
+ message: message,
+ channel: channel,
+ },
+ ]);
+ if (quote && quote.sender === utils.bufToHex(identity.publicKey))
+ setActivities((prev) => [
+ ...prev,
+ {
+ id: message.date.getTime().toString() + message.content,
+ type: "reply",
+ date: message.date,
+ user: message.sender,
+ message: message,
+ channel: channel,
+ quote: quote,
+ },
+ ]);
+ }, [mentioned, message, quote]);
+
+ return (
+
+ {(idx === 0 || !equalDate(prevMessage.date, message.date)) && (
+
+ {equalDate(message.date, today)
+ ? "Today"
+ : message.date.toLocaleDateString()}
+
+ )}
+
+
+
+ {
+ setShowMenu((e) => !e);
+ }}
+ >
+ {showMenu && (
+
+ )}
+
+
+
+
+
+
+ {" "}
+ {contact.customName ?? message.sender.slice(0, 10)}
+
+ {contact.customName && (
+
+ {message.sender.slice(0, 5)}...{message.sender.slice(-3)}
+
+ )}
+ {contact.isUntrustworthy && }
+
+ {message.date.toLocaleString()}
+
+
+
+
+
+
+
+
+
+
+
+ setReply({
+ sender: message.sender,
+ content: message.content,
+ image: message.image,
+ id: message.id,
+ })
+ }
+ >
+
+
+
+
+
+ );
+}
+
+const UserMessageWrapper = styled.div`
+ width: 100%;
+ display: flex;
+`;
+
+const Reactions = styled.div`
+ display: flex;
+ position: absolute;
+ right: 20px;
+ top: -18px;
+ box-shadow: 0px 4px 12px rgba(0, 34, 51, 0.08);
+ border-radius: 8px;
+ background: ${({ theme }) => theme.bodyBackgroundColor};
+ visibility: hidden;
+`;
+
+const ReactionBtn = styled.button`
+ width: 32px;
+ height: 32px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-radius: 8px;
+ align-self: center;
+
+ &:hover {
+ background: ${({ theme }) => theme.buttonBgHover};
+ }
+
+ &:hover > svg {
+ fill: ${({ theme }) => theme.tertiary};
+ }
+`;