From 05eb1380ba8abceb7f4422c42d21e42ea982679a Mon Sep 17 00:00:00 2001 From: Zishan Ahmad Date: Thu, 22 Feb 2024 10:00:07 +0100 Subject: [PATCH] feat: added a user-mention menu --- packages/api/src/EmbeddedChatApi.ts | 20 ++ .../src/components/ChatHeader/ChatHeader.js | 16 +- .../react/src/components/Icon/icons/At.js | 17 ++ .../react/src/components/Icon/icons/index.js | 2 + .../react/src/components/Menu/Menu.stories.js | 5 + .../src/components/MessageList/MessageList.js | 7 +- .../components/UserMentions/UserMentions.js | 180 ++++++++++++++++++ .../UserMentions/UserMentions.module.css | 22 +++ packages/react/src/store/index.js | 1 + packages/react/src/store/mentionsStore.js | 8 + 10 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 packages/react/src/components/Icon/icons/At.js create mode 100644 packages/react/src/components/UserMentions/UserMentions.js create mode 100644 packages/react/src/components/UserMentions/UserMentions.module.css create mode 100644 packages/react/src/store/mentionsStore.js diff --git a/packages/api/src/EmbeddedChatApi.ts b/packages/api/src/EmbeddedChatApi.ts index 6d3cc6987..7e0ff5eca 100644 --- a/packages/api/src/EmbeddedChatApi.ts +++ b/packages/api/src/EmbeddedChatApi.ts @@ -703,6 +703,26 @@ export default class EmbeddedChatApi { } } + async getMentionedMessages() { + try { + const { userId, authToken } = (await this.auth.getCurrentUser()) || {}; + const response = await fetch( + `${this.host}/api/v1/chat.getMentionedMessages?roomId=${this.rid}`, + { + headers: { + "Content-Type": "application/json", + "X-Auth-Token": authToken, + "X-User-Id": userId, + }, + method: "GET", + } + ); + return await response.json(); + } catch (err) { + console.error(err); + } + } + async pinMessage(mid: string) { try { const { userId, authToken } = (await this.auth.getCurrentUser()) || {}; diff --git a/packages/react/src/components/ChatHeader/ChatHeader.js b/packages/react/src/components/ChatHeader/ChatHeader.js index 0ec1a8183..08cfdc8bd 100644 --- a/packages/react/src/components/ChatHeader/ChatHeader.js +++ b/packages/react/src/components/ChatHeader/ChatHeader.js @@ -10,6 +10,8 @@ import { useSearchMessageStore, useChannelStore, useToastStore, + useThreadsMessageStore, + useMentionsStore, } from '../../store'; import { DynamicHeader } from '../DynamicHeader'; import { Tooltip } from '../Tooltip'; @@ -18,7 +20,6 @@ import useComponentOverrides from '../../theme/useComponentOverrides'; import { Icon } from '../Icon'; import { ActionButton } from '../ActionButton'; import { Menu } from '../Menu'; -import useThreadsMessageStore from '../../store/threadsMessageStore'; import { useToastBarDispatch } from '../../hooks/useToastBarDispatch'; import useFetchChatData from '../../hooks/useFetchChatData'; @@ -73,6 +74,7 @@ const ChatHeader = ({ const setShowAllThreads = useThreadsMessageStore( (state) => state.setShowAllThreads ); + const setShowMentions = useMentionsStore((state) => state.setShowMentions); const toastPosition = useToastStore((state) => state.position); const handleGoBack = async () => { @@ -141,6 +143,11 @@ const ChatHeader = ({ setShowSearch(false); }, [setShowAllThreads, setShowSearch]); + const showMentions = useCallback(async () => { + setShowMentions(true); + setShowSearch(false); + }, [setShowMentions, setShowSearch]); + useEffect(() => { const setMessageAllowed = async () => { const permissionRes = await RCInstance.permissionInfo(); @@ -223,6 +230,12 @@ const ChatHeader = ({ label: 'Threads', icon: 'thread', }, + { + id: 'mentions', + action: showMentions, + label: 'Mentions', + icon: 'at', + }, { id: 'members', action: showChannelMembers, @@ -273,6 +286,7 @@ const ChatHeader = ({ moreOpts, setFullScreen, showAllThreads, + showMentions, showChannelMembers, showChannelinformation, showPinnedMessage, diff --git a/packages/react/src/components/Icon/icons/At.js b/packages/react/src/components/Icon/icons/At.js new file mode 100644 index 000000000..aeb9061fd --- /dev/null +++ b/packages/react/src/components/Icon/icons/At.js @@ -0,0 +1,17 @@ +import React from 'react'; + +const At = (props) => ( + + + +); + +export default At; diff --git a/packages/react/src/components/Icon/icons/index.js b/packages/react/src/components/Icon/icons/index.js index 2ea3fa05a..e2911aa4b 100644 --- a/packages/react/src/components/Icon/icons/index.js +++ b/packages/react/src/components/Icon/icons/index.js @@ -38,6 +38,7 @@ import ArrowDown from './ArrowDown'; import PinFilled from './PinFilled'; import VideoRecorder from './VideoRecoder'; import DisabledRecorder from './DisableRecorder'; +import At from './At'; const icons = { file: File, @@ -80,6 +81,7 @@ const icons = { 'error-circle': ErrorCircle, 'arrow-down': ArrowDown, 'pin-filled': PinFilled, + at: At, }; export default icons; diff --git a/packages/react/src/components/Menu/Menu.stories.js b/packages/react/src/components/Menu/Menu.stories.js index 6a5235714..92da52d89 100644 --- a/packages/react/src/components/Menu/Menu.stories.js +++ b/packages/react/src/components/Menu/Menu.stories.js @@ -18,6 +18,11 @@ export const Menu = { label: 'Threads', icon: 'thread', }, + { + id: 'mentions', + label: 'Mentions', + icon: 'at', + }, { id: 'members', label: 'Members', diff --git a/packages/react/src/components/MessageList/MessageList.js b/packages/react/src/components/MessageList/MessageList.js index 9a9203a89..2cad03c4d 100644 --- a/packages/react/src/components/MessageList/MessageList.js +++ b/packages/react/src/components/MessageList/MessageList.js @@ -7,6 +7,8 @@ import { useSearchMessageStore, useChannelStore, useUserStore, + useMentionsStore, + useThreadsMessageStore, } from '../../store'; import RoomMembers from '../RoomMembers/RoomMember'; import MessageReportWindow from '../ReportMessage/MessageReportWindow'; @@ -14,10 +16,9 @@ import isMessageSequential from '../../lib/isMessageSequential'; import SearchMessage from '../SearchMessage/SearchMessage'; import Roominfo from '../RoomInformation/RoomInformation'; import AllThreads from '../AllThreads/AllThreads'; +import UserMentions from '../UserMentions/UserMentions'; import { Message } from '../Message'; -import useThreadsMessageStore from '../../store/threadsMessageStore'; - const MessageList = ({ messages }) => { const showSearch = useSearchMessageStore((state) => state.showSearch); const showChannelinfo = useChannelStore((state) => state.showChannelinfo); @@ -29,6 +30,7 @@ const MessageList = ({ messages }) => { const showAllThreads = useThreadsMessageStore( (state) => state.showAllThreads ); + const showMentions = useMentionsStore((state) => state.showMentions); const isMessageNewDay = (current, previous) => !previous || !isSameDay(new Date(current.ts), new Date(previous.ts)); @@ -59,6 +61,7 @@ const MessageList = ({ messages }) => { {showSearch && } {showChannelinfo && } {showAllThreads && } + {showMentions && } ); }; diff --git a/packages/react/src/components/UserMentions/UserMentions.js b/packages/react/src/components/UserMentions/UserMentions.js new file mode 100644 index 000000000..1bd655aa6 --- /dev/null +++ b/packages/react/src/components/UserMentions/UserMentions.js @@ -0,0 +1,180 @@ +import React, { useState, useEffect } from 'react'; +import { css } from '@emotion/react'; +import { isSameDay, format } from 'date-fns'; +import classes from './UserMentions.module.css'; +import { Icon } from '../Icon'; +import { Box } from '../Box'; +import { Attachments } from '../Attachments'; +import { ActionButton } from '../ActionButton'; +import { useMessageStore, useUserStore, useMentionsStore } from '../../store'; +import { MessageBody } from '../Message/MessageBody'; +import { MessageMetrics } from '../Message/MessageMetrics'; +import { useRCContext } from '../../context/RCInstance'; +import { Markdown } from '../Markdown'; +import { MessageDivider } from '../Message/MessageDivider'; +import MessageAvatarContainer from '../Message/MessageAvatarContainer'; +import MessageBodyContainer from '../Message/MessageBodyContainer'; +import MessageHeader from '../Message/MessageHeader'; + +const MessageCss = css` + display: flex; + flex-direction: row; + align-items: flex-start; + padding-top: 0.5rem; + -webkit-padding-before: 0.5rem; + padding-block-start: 0.5rem; + padding-bottom: 0.25rem; + -webkit-padding-after: 0.25rem; + padding-block-end: 0.25rem; + padding-left: 1.25rem; + padding-right: 1.25rem; + padding-inline: 1.25rem; + &:hover { + background: #f2f3f5; + } +`; + +const UserMentions = () => { + const showAvatar = useUserStore((state) => state.showAvatar); + const setShowMentions = useMentionsStore((state) => state.setShowMentions); + const { RCInstance } = useRCContext(); + const [mentionedMessages, setMentionedMessages] = useState([]); + const [isLoaded, setIsLoaded] = useState(false); + + const openThread = useMessageStore((state) => state.openThread); + + const toggleShowMentions = () => { + setShowMentions(false); + }; + const handleOpenThread = (msg) => () => { + openThread(msg); + toggleShowMentions(false); + }; + const isMessageNewDay = (current, previous) => + !previous || !isSameDay(new Date(current.ts), new Date(previous.ts)); + + useEffect(() => { + const fetchMentionedMsgs = async () => { + const response = await RCInstance.getMentionedMessages(); + if (response && response.messages) { + setMentionedMessages(response.messages); + setIsLoaded(true); + } + }; + fetchMentionedMsgs(); + }, [RCInstance, setMentionedMessages]); + + return ( + + + + +

+ + + Mentions + + + + +

+
+
+ + {isLoaded && ( + + {mentionedMessages.length === 0 ? ( + + + + No mentions found + + + ) : ( + mentionedMessages.map((message, index, arr) => { + const newDay = + index === 0 || isMessageNewDay(message, arr[index - 1]); + return ( + + {newDay ? ( + + {format(new Date(message.ts), 'MMMM d, yyyy')} + + ) : null} + + {showAvatar && ( + + )} + + + + + {message.attachments && + message.attachments.length > 0 ? ( + <> + + + + ) : ( + + )} + + + {!message.t && message.tcount && ( + + )} + + + + ); + }) + )} + + )} +
+
+ ); +}; + +export default UserMentions; diff --git a/packages/react/src/components/UserMentions/UserMentions.module.css b/packages/react/src/components/UserMentions/UserMentions.module.css new file mode 100644 index 000000000..cca051f27 --- /dev/null +++ b/packages/react/src/components/UserMentions/UserMentions.module.css @@ -0,0 +1,22 @@ +.component { + position: fixed; + right: 0; + top: 0; + width: 350px; + height: 100%; + overflow: hidden; + background-color: white; + box-shadow: -1px 0px 5px rgb(0 0 0 / 25%); + z-index: 100; +} +.wrapContainer { + height: 100%; + display: flex; + flex-direction: column; +} + +@media (max-width: 550px) { + .component { + width: 100vw; + } +} diff --git a/packages/react/src/store/index.js b/packages/react/src/store/index.js index 1d734fbbe..78da56784 100644 --- a/packages/react/src/store/index.js +++ b/packages/react/src/store/index.js @@ -7,3 +7,4 @@ export { default as useSearchMessageStore } from './searchMessageStore'; export { default as loginModalStore } from './loginmodalStore'; export { default as useChannelStore } from './channelStore'; export { default as useThreadsMessageStore } from './threadsMessageStore'; +export { default as useMentionsStore } from './mentionsStore'; diff --git a/packages/react/src/store/mentionsStore.js b/packages/react/src/store/mentionsStore.js new file mode 100644 index 000000000..240f0602a --- /dev/null +++ b/packages/react/src/store/mentionsStore.js @@ -0,0 +1,8 @@ +import { create } from 'zustand'; + +const useMentionsStore = create((set) => ({ + showMentions: false, + setShowMentions: (showMentions) => set(() => ({ showMentions })), +})); + +export default useMentionsStore;