From 2a05fb7138480ed3a6f65f361b4e3bea8ea9262f Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 28 Aug 2023 17:04:50 +0300 Subject: [PATCH 01/29] add handler for "feed item created" system message --- .../Chat/ChatMessage/ChatMessage.module.scss | 1 + .../components/Chat/ChatMessage/types.ts | 6 +-- .../utils/getFeedItemTitleByDataIdAndType.ts | 20 ++++++++ .../utils/getTextFromSystemMessage.tsx | 49 +++++++++++++++++++ .../Chat/ChatMessage/utils/index.ts | 1 + src/shared/models/DiscussionMessage.tsx | 7 ++- 6 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 src/shared/components/Chat/ChatMessage/utils/getFeedItemTitleByDataIdAndType.ts diff --git a/src/shared/components/Chat/ChatMessage/ChatMessage.module.scss b/src/shared/components/Chat/ChatMessage/ChatMessage.module.scss index 6a6f612c2b..343fd950c7 100644 --- a/src/shared/components/Chat/ChatMessage/ChatMessage.module.scss +++ b/src/shared/components/Chat/ChatMessage/ChatMessage.module.scss @@ -128,6 +128,7 @@ .systemMessageCommonLink { color: $c-pink-mention; text-decoration: none; + cursor: pointer; &:hover { text-decoration: underline; diff --git a/src/shared/components/Chat/ChatMessage/types.ts b/src/shared/components/Chat/ChatMessage/types.ts index 1fd5d07b5c..da5577a581 100644 --- a/src/shared/components/Chat/ChatMessage/types.ts +++ b/src/shared/components/Chat/ChatMessage/types.ts @@ -9,12 +9,10 @@ export interface TextData { mentionTextClassName?: string; emojiTextClassName?: string; commonId?: string; - systemMessage?: Pick< - SystemDiscussionMessage, - "systemMessageType" | "systemMessageData" - >; + systemMessage?: SystemDiscussionMessage; getCommonPagePath?: GetCommonPagePath; getCommonPageAboutTabPath?: GetCommonPageAboutTabPath; directParent?: DirectParent | null; onUserClick?: (userId: string) => void; + onFeedItemClick?: (feedItemId: string) => void; } diff --git a/src/shared/components/Chat/ChatMessage/utils/getFeedItemTitleByDataIdAndType.ts b/src/shared/components/Chat/ChatMessage/utils/getFeedItemTitleByDataIdAndType.ts new file mode 100644 index 0000000000..52d12827cc --- /dev/null +++ b/src/shared/components/Chat/ChatMessage/utils/getFeedItemTitleByDataIdAndType.ts @@ -0,0 +1,20 @@ +import { DiscussionService, ProposalService } from "@/services"; +import { CommonFeedType } from "@/shared/models"; + +export const getFeedItemTitleByDataIdAndType = async ( + feedItemDataId: string, + feedItemType: CommonFeedType, +): Promise => { + if (feedItemType === CommonFeedType.Discussion) { + const discussion = await DiscussionService.getDiscussionById( + feedItemDataId, + ); + return discussion?.title || ""; + } + if (feedItemType === CommonFeedType.Proposal) { + const discussion = await ProposalService.getProposalById(feedItemDataId); + return discussion?.data.args.title || ""; + } + + return ""; +}; diff --git a/src/shared/components/Chat/ChatMessage/utils/getTextFromSystemMessage.tsx b/src/shared/components/Chat/ChatMessage/utils/getTextFromSystemMessage.tsx index dcb810292c..7ea5abf6d3 100644 --- a/src/shared/components/Chat/ChatMessage/utils/getTextFromSystemMessage.tsx +++ b/src/shared/components/Chat/ChatMessage/utils/getTextFromSystemMessage.tsx @@ -6,6 +6,8 @@ import { Common, CommonCreatedSystemMessage, CommonEditedSystemMessage, + CommonFeedItemCreatedSystemMessage, + CommonFeedType, CommonMemberAddedSystemMessage, SystemMessageCommonType, User, @@ -17,6 +19,7 @@ import { } from "@/shared/utils"; import { UserMention } from "../components"; import { Text, TextData } from "../types"; +import { getFeedItemTitleByDataIdAndType } from "./getFeedItemTitleByDataIdAndType"; import styles from "../ChatMessage.module.scss"; const getUser = async (userId: string, users: User[]): Promise => @@ -54,6 +57,12 @@ const renderLink = (to: string, name: string): Text => ( ); +const renderClickableText = (text: string, onClick: () => void): Text => ( + + {text} + +); + const getCommonCreatedSystemMessageText = async ( systemMessageData: CommonCreatedSystemMessage["systemMessageData"], data: TextData, @@ -117,6 +126,40 @@ const getCommonMemberAddedSystemMessageText = async ( ]; }; +const getFeedItemCreatedSystemMessageText = async ( + systemMessageData: CommonFeedItemCreatedSystemMessage["systemMessageData"], + data: TextData, +): Promise => { + const [user, title] = await Promise.all([ + getUser(systemMessageData.userId, data.users), + getFeedItemTitleByDataIdAndType( + systemMessageData.feedItemDataId, + systemMessageData.feedItemType, + ), + ]); + const userEl = renderUserMention(user, data); + const titleEl = title ? ( + <> + {" "} + {renderClickableText(title, () => { + console.log(123); + })} + + ) : ( + "" + ); + + return [ + userEl, + ` created the ${ + systemMessageData.feedItemType === CommonFeedType.Discussion + ? "discussion" + : "proposal" + }`, + titleEl, + ].filter(Boolean); +}; + export const getTextFromSystemMessage = async ( data: TextData, ): Promise => { @@ -146,6 +189,12 @@ export const getTextFromSystemMessage = async ( data, ); break; + case SystemDiscussionMessageType.FeedItemCreated: + text = await getFeedItemCreatedSystemMessageText( + systemMessage.systemMessageData, + data, + ); + break; default: return null; } diff --git a/src/shared/components/Chat/ChatMessage/utils/index.ts b/src/shared/components/Chat/ChatMessage/utils/index.ts index c56d993a16..09a42a51db 100644 --- a/src/shared/components/Chat/ChatMessage/utils/index.ts +++ b/src/shared/components/Chat/ChatMessage/utils/index.ts @@ -1 +1,2 @@ +export * from "./getFeedItemTitleByDataIdAndType"; export * from "./getTextFromTextEditorString"; diff --git a/src/shared/models/DiscussionMessage.tsx b/src/shared/models/DiscussionMessage.tsx index 8e4a2e81a0..e6f98b6fd5 100644 --- a/src/shared/models/DiscussionMessage.tsx +++ b/src/shared/models/DiscussionMessage.tsx @@ -5,6 +5,7 @@ import { } from "@/shared/constants"; import { Moderation } from "@/shared/interfaces/Moderation"; import { BaseEntity } from "./BaseEntity"; +import { CommonFeedType } from "./CommonFeed"; import { Link } from "./Link"; import { User } from "./User"; @@ -98,9 +99,11 @@ export interface CommonFeedItemCreatedSystemMessage extends BaseSystemDiscussionMessage { systemMessageType: SystemDiscussionMessageType.FeedItemCreated; systemMessageData: { - commonType: SystemMessageCommonType; - commonId: string; userId: string; + commonId: string; + feedItemId: string; + feedItemType: CommonFeedType; + feedItemDataId: string; }; } From d4ae193bde339da9f39f693042aadeaee0428fd5 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 29 Aug 2023 11:25:04 +0300 Subject: [PATCH 02/29] allow discussion to be undefined in chat item --- .../common/components/ChatComponent/ChatComponent.tsx | 8 ++++---- src/pages/common/components/ChatComponent/context.ts | 2 +- .../CommonTabPanels/components/FeedTab/FeedTab.tsx | 10 +++++----- .../DiscussionFeedCard/DiscussionFeedCard.tsx | 7 +++++++ .../commonFeed/components/FeedLayout/FeedLayout.tsx | 2 +- .../FeedLayout/components/DesktopChat/DesktopChat.tsx | 2 +- .../FeedLayout/components/MobileChat/MobileChat.tsx | 4 ++-- src/shared/utils/shared.tsx | 6 +++++- 8 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/pages/common/components/ChatComponent/ChatComponent.tsx b/src/pages/common/components/ChatComponent/ChatComponent.tsx index 1343ffddcd..7aa36dd10f 100644 --- a/src/pages/common/components/ChatComponent/ChatComponent.tsx +++ b/src/pages/common/components/ChatComponent/ChatComponent.tsx @@ -78,7 +78,7 @@ interface ChatComponentInterface { governanceCircles?: Circles; commonMember: CommonMember | null; hasAccess?: boolean; - discussion: Discussion; + discussion?: Discussion; chatChannel?: ChatChannel; lastSeenItem?: CommonFeedObjectUserUnique["lastSeen"]; feedItemId: string; @@ -136,7 +136,7 @@ export default function ChatComponent({ ); const user = useSelector(selectUser()); const userId = user?.uid; - const discussionId = discussion.id; + const discussionId = discussion?.id || ""; const isChatChannel = Boolean(chatChannel); const hasPermissionToHide = @@ -198,9 +198,9 @@ export default function ChatComponent({ useEffect(() => { if (commonId && !isChatChannel) { - fetchDiscussionUsers(commonId, discussion.circleVisibility); + fetchDiscussionUsers(commonId, discussion?.circleVisibility); } - }, [commonId, discussion.circleVisibility]); + }, [commonId, discussion?.circleVisibility]); useEffect(() => { if (chatChannel?.id) { diff --git a/src/pages/common/components/ChatComponent/context.ts b/src/pages/common/components/ChatComponent/context.ts index befe8c2fd0..518c6ba7b4 100644 --- a/src/pages/common/components/ChatComponent/context.ts +++ b/src/pages/common/components/ChatComponent/context.ts @@ -9,7 +9,7 @@ import { export interface ChatItem { feedItemId: string; proposal?: Proposal; - discussion: Discussion; + discussion?: Discussion; chatChannel?: ChatChannel; circleVisibility: string[]; lastSeenItem?: CommonFeedObjectUserUnique["lastSeen"]; diff --git a/src/pages/common/components/CommonTabPanels/components/FeedTab/FeedTab.tsx b/src/pages/common/components/CommonTabPanels/components/FeedTab/FeedTab.tsx index f8c1ecffaa..ddc2b5404a 100644 --- a/src/pages/common/components/CommonTabPanels/components/FeedTab/FeedTab.tsx +++ b/src/pages/common/components/CommonTabPanels/components/FeedTab/FeedTab.tsx @@ -114,11 +114,11 @@ export const FeedTab: FC = (props) => {

- {chatItem.discussion.title} + {chatItem.discussion?.title}

= (props) => { }} commonName={common.name} commonImage={common.image} - title={chatItem?.discussion.title} + title={chatItem?.discussion?.title} > {chatItem && ( = (props) => { const contextValue = useMemo( () => ({ setChatItem, - activeItemDiscussionId: chatItem?.discussion.id, + activeItemDiscussionId: chatItem?.discussion?.id, }), - [setChatItem, chatItem?.discussion.id], + [setChatItem, chatItem?.discussion?.id], ); return ( diff --git a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx index 8381d712a2..3ebbae796b 100644 --- a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx +++ b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx @@ -199,6 +199,7 @@ const DiscussionFeedCard: FC = (props) => { useEffect(() => { if ( + !isActive && isDiscussionFetched && isFeedItemUserMetadataFetched && item.id === feedItemIdForAutoChatOpen && @@ -213,6 +214,12 @@ const DiscussionFeedCard: FC = (props) => { shouldAllowChatAutoOpen, ]); + useEffect(() => { + if (isActive) { + handleOpenChat(); + } + }, [isActive, handleOpenChat]); + useEffect(() => { if (isActive && cardTitle) { onActiveItemDataChange?.({ diff --git a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx index c267ebeb1e..20d0437b9f 100644 --- a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx +++ b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx @@ -598,7 +598,7 @@ const FeedLayout: ForwardRefRenderFunction = ( })} {!isTabletView && - (chatItem ? ( + (chatItem?.discussion ? ( = (props) => { const dmUserId = chatItem.chatChannel?.participants.filter( (participant) => participant !== userId, )[0]; - const title = getUserName(dmUser) || chatItem.discussion.title; + const title = getUserName(dmUser) || chatItem.discussion?.title || ""; const hasAccessToChat = useMemo( () => checkHasAccessToChat(userCircleIds, chatItem), diff --git a/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx b/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx index c8d4e8482e..63b34a0a69 100644 --- a/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx +++ b/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx @@ -69,9 +69,9 @@ const MobileChat: FC = (props) => { )[0]; const title = getUserName(dmUser) || - (chatItem?.discussion.predefinedType === PredefinedTypes.General + (chatItem?.discussion?.predefinedType === PredefinedTypes.General ? commonName - : chatItem?.discussion.title || ""); + : chatItem?.discussion?.title || ""); const hasAccessToChat = useMemo( () => checkHasAccessToChat(userCircleIds, chatItem), diff --git a/src/shared/utils/shared.tsx b/src/shared/utils/shared.tsx index 2c035d1568..aa32f97fdc 100644 --- a/src/shared/utils/shared.tsx +++ b/src/shared/utils/shared.tsx @@ -186,7 +186,11 @@ export const containsHebrew = (str: string) => { return /[\u0590-\u05FF]/.test(str); }; -export const isRTL = (text: string): boolean => { +export const isRTL = (text = ""): boolean => { + if (!text) { + return false; + } + const ltrChars = "A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF" + "\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF"; From 8109005bcf3f3da023d4f4501aec8492caeb6289 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 29 Aug 2023 12:09:56 +0300 Subject: [PATCH 03/29] add feed item click logic in system message on inbox --- .../components/ChatComponent/ChatComponent.tsx | 3 +++ .../components/ChatContent/ChatContent.tsx | 3 +++ .../components/FeedLayout/FeedLayout.tsx | 17 +++++++++++++++++ .../components/DesktopChat/DesktopChat.tsx | 3 +++ .../components/MobileChat/MobileChat.tsx | 3 +++ src/pages/inbox/BaseInbox.tsx | 12 ++++++++++++ .../components/Chat/ChatMessage/ChatMessage.tsx | 4 ++++ .../utils/getTextFromSystemMessage.tsx | 6 +++--- 8 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/pages/common/components/ChatComponent/ChatComponent.tsx b/src/pages/common/components/ChatComponent/ChatComponent.tsx index 7aa36dd10f..e6e2b8af7d 100644 --- a/src/pages/common/components/ChatComponent/ChatComponent.tsx +++ b/src/pages/common/components/ChatComponent/ChatComponent.tsx @@ -87,6 +87,7 @@ interface ChatComponentInterface { onMessagesAmountChange?: (newMessagesAmount: number) => void; directParent?: DirectParent | null; onUserClick?: (userId: string) => void; + onFeedItemClick?: (feedItemId: string) => void; } interface Messages { @@ -125,6 +126,7 @@ export default function ChatComponent({ onMessagesAmountChange, directParent, onUserClick, + onFeedItemClick, }: ChatComponentInterface) { const dispatch = useDispatch(); useZoomDisabling(); @@ -584,6 +586,7 @@ export default function ChatComponent({ onMessageDelete={handleMessageDelete} directParent={directParent} onUserClick={onUserClick} + onFeedItemClick={onFeedItemClick} /> {isAuthorized && ( diff --git a/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx b/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx index babe51dd78..fc2c955afd 100644 --- a/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx +++ b/src/pages/common/components/ChatComponent/components/ChatContent/ChatContent.tsx @@ -54,6 +54,7 @@ interface ChatContentInterface { onMessageDelete?: (messageId: string) => void; directParent?: DirectParent | null; onUserClick?: (userId: string) => void; + onFeedItemClick?: (feedItemId: string) => void; } const isToday = (someDate: Date) => { @@ -89,6 +90,7 @@ const ChatContent: ForwardRefRenderFunction< onMessageDelete, directParent, onUserClick, + onFeedItemClick, }, chatContentRef, ) => { @@ -266,6 +268,7 @@ const ChatContent: ForwardRefRenderFunction< onMessageDelete={onMessageDelete} directParent={directParent} onUserClick={onUserClick} + onFeedItemClick={onFeedItemClick} /> ); diff --git a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx index 20d0437b9f..f8ef3bfc10 100644 --- a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx +++ b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx @@ -115,6 +115,7 @@ interface FeedLayoutProps { feedItem: FeedLayoutItem, becameEmpty: boolean, ) => void; + onFeedItemSelect?: (commonId: string, feedItemId: string) => void; outerStyles?: FeedLayoutOuterStyles; settings?: FeedLayoutSettings; } @@ -145,6 +146,7 @@ const FeedLayout: ForwardRefRenderFunction = ( onActiveItemChange, onActiveItemDataChange, onMessagesAmountEmptinessToggle, + onFeedItemSelect, outerStyles, settings, } = props; @@ -299,6 +301,19 @@ const FeedLayout: ForwardRefRenderFunction = ( [handleUserWithCommonClick, selectedItemCommonData?.id], ); + const handleFeedItemClickExternal = useCallback( + (feedItemId: string) => { + if (selectedItemCommonData?.id) { + onFeedItemSelect?.(selectedItemCommonData.id, feedItemId); + } + }, + [selectedItemCommonData?.id, onFeedItemSelect], + ); + + const handleFeedItemClick = onFeedItemSelect + ? handleFeedItemClickExternal + : undefined; + // We should try to set here only the data which rarely can be changed, // so we will not have extra re-renders of ALL rendered items const feedItemContextValue = useMemo( @@ -610,6 +625,7 @@ const FeedLayout: ForwardRefRenderFunction = ( onMessagesAmountChange={handleMessagesAmountChange} directParent={outerCommon?.directParent} onUserClick={handleUserClick} + onFeedItemClick={handleFeedItemClick} /> ) : ( = ( directParent={outerCommon?.directParent} onClose={handleMobileChatClose} onUserClick={handleUserClick} + onFeedItemClick={handleFeedItemClick} > {selectedItemCommonData && checkIsFeedItemFollowLayoutItem(selectedFeedItem) && ( diff --git a/src/pages/commonFeed/components/FeedLayout/components/DesktopChat/DesktopChat.tsx b/src/pages/commonFeed/components/FeedLayout/components/DesktopChat/DesktopChat.tsx index ee9d39bec3..df5695a323 100644 --- a/src/pages/commonFeed/components/FeedLayout/components/DesktopChat/DesktopChat.tsx +++ b/src/pages/commonFeed/components/FeedLayout/components/DesktopChat/DesktopChat.tsx @@ -31,6 +31,7 @@ interface ChatProps { onMessagesAmountChange?: (newMessagesAmount: number) => void; directParent?: DirectParent | null; onUserClick?: (userId: string) => void; + onFeedItemClick?: (feedItemId: string) => void; } const DesktopChat: FC = (props) => { @@ -45,6 +46,7 @@ const DesktopChat: FC = (props) => { onMessagesAmountChange, directParent, onUserClick, + onFeedItemClick, } = props; const { fetchUser: fetchDMUser, @@ -116,6 +118,7 @@ const DesktopChat: FC = (props) => { onMessagesAmountChange={onMessagesAmountChange} directParent={directParent} onUserClick={onUserClick} + onFeedItemClick={onFeedItemClick} /> ); diff --git a/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx b/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx index 63b34a0a69..7083723500 100644 --- a/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx +++ b/src/pages/commonFeed/components/FeedLayout/components/MobileChat/MobileChat.tsx @@ -34,6 +34,7 @@ interface ChatProps { directParent?: DirectParent | null; onClose: () => void; onUserClick?: (userId: string) => void; + onFeedItemClick?: (feedItemId: string) => void; } const MobileChat: FC = (props) => { @@ -51,6 +52,7 @@ const MobileChat: FC = (props) => { directParent, onClose, onUserClick, + onFeedItemClick, } = props; const { setIsShowFeedItemDetailsModal } = useChatContext(); const { @@ -146,6 +148,7 @@ const MobileChat: FC = (props) => { onMessagesAmountChange={onMessagesAmountChange} directParent={directParent} onUserClick={onUserClick} + onFeedItemClick={onFeedItemClick} /> )} diff --git a/src/pages/inbox/BaseInbox.tsx b/src/pages/inbox/BaseInbox.tsx index 6da8afd5fa..f4eb2f7bd5 100644 --- a/src/pages/inbox/BaseInbox.tsx +++ b/src/pages/inbox/BaseInbox.tsx @@ -8,6 +8,7 @@ import React, { useState, } from "react"; import { useDispatch, useSelector } from "react-redux"; +import { useHistory } from "react-router-dom"; import { selectUser } from "@/pages/Auth/store/selectors"; import { FeedItemBaseContentProps } from "@/pages/common"; import { @@ -16,6 +17,7 @@ import { FeedLayoutSettings, } from "@/pages/commonFeed"; import { QueryParamKey } from "@/shared/constants"; +import { useRoutesContext } from "@/shared/contexts"; import { ChatChannelToDiscussionConverter } from "@/shared/converters"; import { useQueryParams } from "@/shared/hooks"; import { useInboxItems } from "@/shared/hooks/useCases"; @@ -60,6 +62,8 @@ const InboxPage: FC = (props) => { } = props; const queryParams = useQueryParams(); const dispatch = useDispatch(); + const history = useHistory(); + const { getCommonPagePath } = useRoutesContext(); const [feedLayoutRef, setFeedLayoutRef] = useState( null, ); @@ -156,6 +160,13 @@ const InboxPage: FC = (props) => { [dispatch], ); + const handleFeedItemSelect = useCallback( + (commonId: string, feedItemId: string) => { + history.push(getCommonPagePath(commonId, { item: feedItemId })); + }, + [history.push, getCommonPagePath], + ); + useEffect(() => { dispatch(inboxActions.setSharedFeedItemId(sharedFeedItemId)); @@ -250,6 +261,7 @@ const InboxPage: FC = (props) => { onActiveItemChange={handleActiveItemChange} onActiveItemDataChange={onActiveItemDataChange} onMessagesAmountEmptinessToggle={handleMessagesAmountEmptinessToggle} + onFeedItemSelect={handleFeedItemSelect} outerStyles={feedLayoutOuterStyles} settings={feedLayoutSettings} /> diff --git a/src/shared/components/Chat/ChatMessage/ChatMessage.tsx b/src/shared/components/Chat/ChatMessage/ChatMessage.tsx index 0bbd90d800..7c328cf589 100644 --- a/src/shared/components/Chat/ChatMessage/ChatMessage.tsx +++ b/src/shared/components/Chat/ChatMessage/ChatMessage.tsx @@ -63,6 +63,7 @@ interface ChatMessageProps { onMessageDelete?: (messageId: string) => void; directParent?: DirectParent | null; onUserClick?: (userId: string) => void; + onFeedItemClick?: (feedItemId: string) => void; } const getStaticLinkByChatType = (chatType: ChatType): StaticLinkType => { @@ -92,6 +93,7 @@ export default function ChatMessage({ onMessageDelete, directParent, onUserClick, + onFeedItemClick, }: ChatMessageProps) { const messageRef = useRef(null); const { getCommonPagePath, getCommonPageAboutTabPath } = useRoutesContext(); @@ -168,6 +170,7 @@ export default function ChatMessage({ getCommonPageAboutTabPath, directParent, onUserClick, + onFeedItemClick, }); setMessageText(parsedText); @@ -195,6 +198,7 @@ export default function ChatMessage({ commonId: discussionMessage.commonId, directParent, onUserClick, + onFeedItemClick, }); setReplyMessageText(parsedText); diff --git a/src/shared/components/Chat/ChatMessage/utils/getTextFromSystemMessage.tsx b/src/shared/components/Chat/ChatMessage/utils/getTextFromSystemMessage.tsx index 7ea5abf6d3..b49443cebb 100644 --- a/src/shared/components/Chat/ChatMessage/utils/getTextFromSystemMessage.tsx +++ b/src/shared/components/Chat/ChatMessage/utils/getTextFromSystemMessage.tsx @@ -141,9 +141,9 @@ const getFeedItemCreatedSystemMessageText = async ( const titleEl = title ? ( <> {" "} - {renderClickableText(title, () => { - console.log(123); - })} + {renderClickableText(title, () => + data.onFeedItemClick?.(systemMessageData.feedItemId), + )} ) : ( "" From 13f48d622b0f7ae44ae07fb9fb39be2860f19551 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 29 Aug 2023 12:24:22 +0300 Subject: [PATCH 04/29] add feed item click logic in system message on feed --- .../ChatComponent/ChatComponent.tsx | 2 +- .../components/FeedLayout/FeedLayout.tsx | 40 ++++++++++++------- .../utils/checkShouldAutoOpenPreview.ts | 2 +- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/pages/common/components/ChatComponent/ChatComponent.tsx b/src/pages/common/components/ChatComponent/ChatComponent.tsx index e6e2b8af7d..ebcad2f9f1 100644 --- a/src/pages/common/components/ChatComponent/ChatComponent.tsx +++ b/src/pages/common/components/ChatComponent/ChatComponent.tsx @@ -582,7 +582,7 @@ export default function ChatComponent({ users={users} discussionId={discussionId} feedItemId={feedItemId} - isLoading={isLoadingDiscussionMessages} + isLoading={!discussion || isLoadingDiscussionMessages} onMessageDelete={handleMessageDelete} directParent={directParent} onUserClick={onUserClick} diff --git a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx index f8ef3bfc10..420345bae7 100644 --- a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx +++ b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx @@ -301,19 +301,6 @@ const FeedLayout: ForwardRefRenderFunction = ( [handleUserWithCommonClick, selectedItemCommonData?.id], ); - const handleFeedItemClickExternal = useCallback( - (feedItemId: string) => { - if (selectedItemCommonData?.id) { - onFeedItemSelect?.(selectedItemCommonData.id, feedItemId); - } - }, - [selectedItemCommonData?.id, onFeedItemSelect], - ); - - const handleFeedItemClick = onFeedItemSelect - ? handleFeedItemClickExternal - : undefined; - // We should try to set here only the data which rarely can be changed, // so we will not have extra re-renders of ALL rendered items const feedItemContextValue = useMemo( @@ -448,6 +435,29 @@ const FeedLayout: ForwardRefRenderFunction = ( ? handleDMClick : undefined; + const handleFeedItemClickExternal = useCallback( + (feedItemId: string) => { + if (selectedItemCommonData?.id) { + onFeedItemSelect?.(selectedItemCommonData.id, feedItemId); + } + }, + [selectedItemCommonData?.id, onFeedItemSelect], + ); + + const handleFeedItemClickInternal = useCallback( + (feedItemId: string) => { + setActiveChatItem({ + feedItemId, + circleVisibility: [], + }); + }, + [setActiveChatItem], + ); + + const handleFeedItemClick = onFeedItemSelect + ? handleFeedItemClickExternal + : handleFeedItemClickInternal; + useEffect(() => { if (!outerGovernance && selectedItemCommonData?.id) { fetchGovernance(selectedItemCommonData.id); @@ -475,7 +485,7 @@ const FeedLayout: ForwardRefRenderFunction = ( }, [activeFeedItemId]); useEffect(() => { - if (selectedFeedItem?.itemId) { + if (selectedFeedItem?.itemId || (chatItem && !chatItem.discussion)) { return; } @@ -630,7 +640,7 @@ const FeedLayout: ForwardRefRenderFunction = ( ) : ( ))} diff --git a/src/pages/commonFeed/components/FeedLayout/utils/checkShouldAutoOpenPreview.ts b/src/pages/commonFeed/components/FeedLayout/utils/checkShouldAutoOpenPreview.ts index 91619c9ed6..fd4bdb0678 100644 --- a/src/pages/commonFeed/components/FeedLayout/utils/checkShouldAutoOpenPreview.ts +++ b/src/pages/commonFeed/components/FeedLayout/utils/checkShouldAutoOpenPreview.ts @@ -5,7 +5,7 @@ import { checkIsCountdownState } from "@/shared/utils"; export const checkShouldAutoOpenPreview = ( chatItem?: ChatItem | null, ): boolean => { - if (!chatItem) { + if (!chatItem || !chatItem.discussion) { return false; } if (!chatItem.seenOnce || chatItem.proposal?.state === ProposalState.VOTING) { From 2c85fa8a2e7188e7b9dcf6ac3b4908477ed7ca22 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 29 Aug 2023 13:45:01 +0300 Subject: [PATCH 05/29] add resource id to fetch items till the provided one --- src/pages/commonFeed/CommonFeed.tsx | 4 +-- .../components/FeedLayout/FeedLayout.tsx | 27 ++++++++++++------- .../hooks/useCases/useCommonFeedItems.ts | 5 ++-- src/store/states/common/actions.ts | 1 + 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/pages/commonFeed/CommonFeed.tsx b/src/pages/commonFeed/CommonFeed.tsx index e07834c93d..7c899d50e5 100644 --- a/src/pages/commonFeed/CommonFeed.tsx +++ b/src/pages/commonFeed/CommonFeed.tsx @@ -168,9 +168,9 @@ const CommonFeedComponent: FC = (props) => { fetchUserRelatedData(); }; - const fetchMoreCommonFeedItems = () => { + const fetchMoreCommonFeedItems = (resourceId?: string) => { if (hasMoreCommonFeedItems) { - fetchCommonFeedItems(); + fetchCommonFeedItems(resourceId); } }; diff --git a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx index 420345bae7..ce2fa44b22 100644 --- a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx +++ b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx @@ -101,7 +101,7 @@ interface FeedLayoutProps { topFeedItems?: FeedLayoutItem[]; loading: boolean; shouldHideContent?: boolean; - onFetchNext: () => void; + onFetchNext: (resourceId?: string) => void; renderFeedItemBaseContent: (props: FeedItemBaseContentProps) => ReactNode; renderChatChannelItem?: (props: ChatChannelFeedLayoutItemProps) => ReactNode; onFeedItemUpdate?: (item: CommonFeed, isRemoved: boolean) => void; @@ -444,15 +444,24 @@ const FeedLayout: ForwardRefRenderFunction = ( [selectedItemCommonData?.id, onFeedItemSelect], ); - const handleFeedItemClickInternal = useCallback( - (feedItemId: string) => { - setActiveChatItem({ - feedItemId, - circleVisibility: [], + const handleFeedItemClickInternal = (feedItemId: string) => { + setActiveChatItem({ + feedItemId, + circleVisibility: [], + }); + + const itemExists = allFeedItems.some((item) => item.itemId === feedItemId); + + if (itemExists) { + // scroll to item + } else { + onFetchNext(feedItemId); + window.scrollTo({ + top: document.body.scrollHeight, + behavior: "smooth", }); - }, - [setActiveChatItem], - ); + } + }; const handleFeedItemClick = onFeedItemSelect ? handleFeedItemClickExternal diff --git a/src/shared/hooks/useCases/useCommonFeedItems.ts b/src/shared/hooks/useCases/useCommonFeedItems.ts index 1daf5d750f..b49069601d 100644 --- a/src/shared/hooks/useCases/useCommonFeedItems.ts +++ b/src/shared/hooks/useCases/useCommonFeedItems.ts @@ -4,7 +4,7 @@ import { CommonFeedService } from "@/services"; import { commonActions, FeedItems, selectFeedItems } from "@/store/states"; interface Return extends Pick { - fetch: () => void; + fetch: (resourceId?: string) => void; } export const useCommonFeedItems = ( @@ -15,10 +15,11 @@ export const useCommonFeedItems = ( const feedItems = useSelector(selectFeedItems); const idsForNotListeningRef = useRef(idsForNotListening || []); - const fetch = () => { + const fetch = (resourceId?: string) => { dispatch( commonActions.getFeedItems.request({ commonId, + resourceId, limit: 15, }), ); diff --git a/src/store/states/common/actions.ts b/src/store/states/common/actions.ts index b8e4097d0b..1ec252fd47 100644 --- a/src/store/states/common/actions.ts +++ b/src/store/states/common/actions.ts @@ -116,6 +116,7 @@ export const getFeedItems = createAsyncAction( )< { commonId: string; + resourceId?: string; limit?: number; }, Omit, From 620c263a574cab404059bc60d37cf5ffca0acc1d Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 29 Aug 2023 14:28:03 +0300 Subject: [PATCH 06/29] add item refs and scroll to item functionality --- .../DiscussionFeedCard/DiscussionFeedCard.tsx | 556 ++++++++------- .../common/components/FeedCard/FeedCard.tsx | 22 +- src/pages/common/components/FeedCard/types.ts | 4 + .../common/components/FeedItem/FeedItem.tsx | 9 +- src/pages/common/components/FeedItem/types.ts | 4 + .../ProposalFeedCard/ProposalFeedCard.tsx | 668 +++++++++--------- .../components/FeedLayout/FeedLayout.tsx | 8 +- 7 files changed, 661 insertions(+), 610 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx index 3ebbae796b..2f5deade12 100644 --- a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx +++ b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx @@ -1,4 +1,10 @@ -import React, { FC, ReactNode, useCallback, useEffect, useState } from "react"; +import React, { + forwardRef, + ReactNode, + useCallback, + useEffect, + useState, +} from "react"; import { useSelector } from "react-redux"; import { selectUser } from "@/pages/Auth/store/selectors"; import { DiscussionService } from "@/services"; @@ -16,7 +22,6 @@ import { FeedLayoutItemChangeData } from "@/shared/interfaces"; import { Common, CommonFeed, - CommonLink, CommonMember, DirectParent, Governance, @@ -33,7 +38,11 @@ import { } from "../FeedCard"; import { getVisibilityString } from "../FeedCard"; import { FeedCardShare } from "../FeedCard"; -import { GetLastMessageOptions, GetNonAllowedItemsOptions } from "../FeedItem"; +import { + FeedItemRef, + GetLastMessageOptions, + GetNonAllowedItemsOptions, +} from "../FeedItem"; import { useMenuItems } from "./hooks"; interface DiscussionFeedCardProps { @@ -57,297 +66,300 @@ interface DiscussionFeedCardProps { onUserSelect?: (userId: string, commonId?: string) => void; } -const DiscussionFeedCard: FC = (props) => { - const { setChatItem, feedItemIdForAutoChatOpen, shouldAllowChatAutoOpen } = - useChatContext(); - const { notify } = useNotification(); - const { - item, - governanceCircles, - isMobileVersion = false, - commonId, - commonName, - commonImage, - pinnedFeedItems, - commonMember, - isProject, - isPinned, - isPreviewMode, - isActive, - isExpanded, - getLastMessage, - getNonAllowedItems, - onActiveItemDataChange, - directParent, - onUserSelect, - } = props; - const { - isShowing: isReportModalOpen, - onOpen: onReportModalOpen, - onClose: onReportModalClose, - } = useModal(false); - const { - isShowing: isShareModalOpen, - onOpen: onShareModalOpen, - onClose: onShareModalClose, - } = useModal(false); - const { - isShowing: isDeleteModalOpen, - onOpen: onDeleteModalOpen, - onClose: onDeleteModalClose, - } = useModal(false); - const [isDeletingInProgress, setDeletingInProgress] = useState(false); - const { - fetchUser: fetchDiscussionCreator, - data: discussionCreator, - fetched: isDiscussionCreatorFetched, - } = useUserById(); - const { - fetchDiscussion, - data: discussion, - fetched: isDiscussionFetched, - } = useDiscussionById(); - const isHome = discussion?.predefinedType === PredefinedTypes.General; - const { - data: feedItemUserMetadata, - fetched: isFeedItemUserMetadataFetched, - fetchFeedItemUserMetadata, - } = useFeedItemUserMetadata(); - const { data: common } = useCommon(isHome ? commonId : ""); - const feedItemFollow = useFeedItemFollow(item.id, commonId); - const menuItems = useMenuItems( - { +const DiscussionFeedCard = forwardRef( + (props, ref) => { + const { setChatItem, feedItemIdForAutoChatOpen, shouldAllowChatAutoOpen } = + useChatContext(); + const { notify } = useNotification(); + const { + item, + governanceCircles, + isMobileVersion = false, commonId, + commonName, + commonImage, pinnedFeedItems, - feedItem: item, - discussion, - governanceCircles, commonMember, - feedItemFollow, + isProject, + isPinned, + isPreviewMode, + isActive, + isExpanded, + getLastMessage, getNonAllowedItems, - }, - { - report: onReportModalOpen, - share: () => onShareModalOpen(), - remove: onDeleteModalOpen, - }, - ); - const user = useSelector(selectUser()); - const [isHovering, setHovering] = useState(false); - const onHover = (isMouseEnter: boolean): void => { - setHovering(isMouseEnter); - }; - const userId = user?.uid; - const isLoading = - !isDiscussionCreatorFetched || - !isDiscussionFetched || - !isFeedItemUserMetadataFetched || - !commonId; - const cardTitle = discussion?.title; - - const handleOpenChat = useCallback(() => { - if (discussion) { - setChatItem({ - feedItemId: item.id, + onActiveItemDataChange, + directParent, + onUserSelect, + } = props; + const { + isShowing: isReportModalOpen, + onOpen: onReportModalOpen, + onClose: onReportModalClose, + } = useModal(false); + const { + isShowing: isShareModalOpen, + onOpen: onShareModalOpen, + onClose: onShareModalClose, + } = useModal(false); + const { + isShowing: isDeleteModalOpen, + onOpen: onDeleteModalOpen, + onClose: onDeleteModalClose, + } = useModal(false); + const [isDeletingInProgress, setDeletingInProgress] = useState(false); + const { + fetchUser: fetchDiscussionCreator, + data: discussionCreator, + fetched: isDiscussionCreatorFetched, + } = useUserById(); + const { + fetchDiscussion, + data: discussion, + fetched: isDiscussionFetched, + } = useDiscussionById(); + const isHome = discussion?.predefinedType === PredefinedTypes.General; + const { + data: feedItemUserMetadata, + fetched: isFeedItemUserMetadataFetched, + fetchFeedItemUserMetadata, + } = useFeedItemUserMetadata(); + const { data: common } = useCommon(isHome ? commonId : ""); + const feedItemFollow = useFeedItemFollow(item.id, commonId); + const menuItems = useMenuItems( + { + commonId, + pinnedFeedItems, + feedItem: item, discussion, - circleVisibility: item.circleVisibility, - lastSeenItem: feedItemUserMetadata?.lastSeen, - lastSeenAt: feedItemUserMetadata?.lastSeenAt, - seenOnce: feedItemUserMetadata?.seenOnce, - }); - } - }, [ - discussion, - item.id, - item.circleVisibility, - feedItemUserMetadata?.lastSeen, - feedItemUserMetadata?.lastSeenAt, - feedItemUserMetadata?.seenOnce, - ]); + governanceCircles, + commonMember, + feedItemFollow, + getNonAllowedItems, + }, + { + report: onReportModalOpen, + share: () => onShareModalOpen(), + remove: onDeleteModalOpen, + }, + ); + const user = useSelector(selectUser()); + const [isHovering, setHovering] = useState(false); + const onHover = (isMouseEnter: boolean): void => { + setHovering(isMouseEnter); + }; + const userId = user?.uid; + const isLoading = + !isDiscussionCreatorFetched || + !isDiscussionFetched || + !isFeedItemUserMetadataFetched || + !commonId; + const cardTitle = discussion?.title; - const onDiscussionDelete = useCallback(async () => { - try { + const handleOpenChat = useCallback(() => { if (discussion) { - setDeletingInProgress(true); - await DiscussionService.deleteDiscussion(discussion.id); - onDeleteModalClose(); + setChatItem({ + feedItemId: item.id, + discussion, + circleVisibility: item.circleVisibility, + lastSeenItem: feedItemUserMetadata?.lastSeen, + lastSeenAt: feedItemUserMetadata?.lastSeenAt, + seenOnce: feedItemUserMetadata?.seenOnce, + }); } - } catch { - notify("Something went wrong"); - } finally { - setDeletingInProgress(false); - } - }, [discussion]); + }, [ + discussion, + item.id, + item.circleVisibility, + feedItemUserMetadata?.lastSeen, + feedItemUserMetadata?.lastSeenAt, + feedItemUserMetadata?.seenOnce, + ]); - useEffect(() => { - fetchDiscussionCreator(item.userId); - }, [item.userId]); + const onDiscussionDelete = useCallback(async () => { + try { + if (discussion) { + setDeletingInProgress(true); + await DiscussionService.deleteDiscussion(discussion.id); + onDeleteModalClose(); + } + } catch { + notify("Something went wrong"); + } finally { + setDeletingInProgress(false); + } + }, [discussion]); - useEffect(() => { - fetchDiscussion(item.data.id); - }, [item.data.id]); + useEffect(() => { + fetchDiscussionCreator(item.userId); + }, [item.userId]); - useEffect(() => { - if (commonId) { - fetchFeedItemUserMetadata({ - userId: userId || "", - commonId, - feedObjectId: item.id, - }); - } - }, [userId, commonId, item.id]); + useEffect(() => { + fetchDiscussion(item.data.id); + }, [item.data.id]); - useEffect(() => { - if ( - !isActive && - isDiscussionFetched && - isFeedItemUserMetadataFetched && - item.id === feedItemIdForAutoChatOpen && - !isMobileVersion && - shouldAllowChatAutoOpen !== false - ) { - handleOpenChat(); - } - }, [ - isDiscussionFetched, - isFeedItemUserMetadataFetched, - shouldAllowChatAutoOpen, - ]); + useEffect(() => { + if (commonId) { + fetchFeedItemUserMetadata({ + userId: userId || "", + commonId, + feedObjectId: item.id, + }); + } + }, [userId, commonId, item.id]); - useEffect(() => { - if (isActive) { - handleOpenChat(); - } - }, [isActive, handleOpenChat]); + useEffect(() => { + if ( + !isActive && + isDiscussionFetched && + isFeedItemUserMetadataFetched && + item.id === feedItemIdForAutoChatOpen && + !isMobileVersion && + shouldAllowChatAutoOpen !== false + ) { + handleOpenChat(); + } + }, [ + isDiscussionFetched, + isFeedItemUserMetadataFetched, + shouldAllowChatAutoOpen, + ]); - useEffect(() => { - if (isActive && cardTitle) { - onActiveItemDataChange?.({ - itemId: item.id, - title: cardTitle, - }); - } - }, [isActive, cardTitle]); + useEffect(() => { + if (isActive) { + handleOpenChat(); + } + }, [isActive, handleOpenChat]); - const renderContent = (): ReactNode => { - if (isLoading) { - return null; - } + useEffect(() => { + if (isActive && cardTitle) { + onActiveItemDataChange?.({ + itemId: item.id, + title: cardTitle, + }); + } + }, [isActive, cardTitle]); - const circleVisibility = governanceCircles - ? getVisibilityString(governanceCircles, discussion?.circleVisibility) - : undefined; + const renderContent = (): ReactNode => { + if (isLoading) { + return null; + } + + const circleVisibility = governanceCircles + ? getVisibilityString(governanceCircles, discussion?.circleVisibility) + : undefined; + + return ( + <> + {!isHome && ( + + Created:{" "} + + + } + type="Discussion" + circleVisibility={circleVisibility} + menuItems={menuItems} + isMobileVersion={isMobileVersion} + commonId={commonId} + userId={item.userId} + directParent={directParent} + onUserSelect={ + onUserSelect && (() => onUserSelect(item.userId, commonId)) + } + /> + )} + { + onHover(true); + }} + onMouseLeave={() => { + onHover(false); + }} + /> + + ); + }; return ( <> - {!isHome && ( - - Created:{" "} - - - } - type="Discussion" - circleVisibility={circleVisibility} - menuItems={menuItems} - isMobileVersion={isMobileVersion} - commonId={commonId} - userId={item.userId} - directParent={directParent} - onUserSelect={ - onUserSelect && (() => onUserSelect(item.userId, commonId)) - } + + {renderContent()} + + {userId && discussion && ( + )} - { - onHover(true); - }} - onMouseLeave={() => { - onHover(false); - }} - /> + {discussion && ( + + )} + {isDeleteModalOpen && ( + + + + )} ); - }; - - return ( - <> - - {renderContent()} - - {userId && discussion && ( - - )} - {discussion && ( - - )} - {isDeleteModalOpen && ( - - - - )} - - ); -}; + }, +); export default DiscussionFeedCard; diff --git a/src/pages/common/components/FeedCard/FeedCard.tsx b/src/pages/common/components/FeedCard/FeedCard.tsx index 25e3cc3d16..ea660ca610 100644 --- a/src/pages/common/components/FeedCard/FeedCard.tsx +++ b/src/pages/common/components/FeedCard/FeedCard.tsx @@ -1,4 +1,11 @@ -import React, { FC, useEffect, useRef, MouseEventHandler } from "react"; +import React, { + useEffect, + useRef, + MouseEventHandler, + forwardRef, + useImperativeHandle, + PropsWithChildren, +} from "react"; import { useCollapse } from "react-collapsed"; import classNames from "classnames"; import { useFeedItemContext } from "@/pages/common"; @@ -7,9 +14,10 @@ import { ContextMenuItem } from "@/shared/interfaces"; import { CommonFeedType, PredefinedTypes } from "@/shared/models"; import { Loader, TextEditorValue } from "@/shared/ui-kit"; import { CommonCard } from "../CommonCard"; +import { FeedCardRef } from "./types"; import styles from "./FeedCard.module.scss"; -interface FeedCardProps { +type FeedCardProps = PropsWithChildren<{ className?: string; feedItemId: string; isHovering?: boolean; @@ -36,7 +44,7 @@ interface FeedCardProps { discussionPredefinedType?: PredefinedTypes; hasFiles?: boolean; hasImages?: boolean; -} +}>; const MOBILE_HEADER_HEIGHT = 52; const DESKTOP_HEADER_HEIGHT = 72; @@ -45,7 +53,7 @@ const COLLAPSE_DURATION = 300; const OFFSET_FROM_BOTTOM_FOR_SCROLLING = 10; const EXTRA_WAITING_TIME_FOR_TIMEOUT = 10; -export const FeedCard: FC = (props) => { +export const FeedCard = forwardRef((props, ref) => { const { className, feedItemId, @@ -161,6 +169,10 @@ export const FeedCard: FC = (props) => { toggleExpanding(); }; + useImperativeHandle(ref, () => ({ + scrollToItem: scrollToTargetAdjusted, + })); + return (
{!isPreviewMode && ( @@ -210,4 +222,4 @@ export const FeedCard: FC = (props) => {
); -}; +}); diff --git a/src/pages/common/components/FeedCard/types.ts b/src/pages/common/components/FeedCard/types.ts index b1d147db22..28487dd1b3 100644 --- a/src/pages/common/components/FeedCard/types.ts +++ b/src/pages/common/components/FeedCard/types.ts @@ -3,3 +3,7 @@ export interface FeedCardSettings { shouldHideCardStyles?: boolean; withHovering?: boolean; } + +export interface FeedCardRef { + scrollToItem: () => void; +} diff --git a/src/pages/common/components/FeedItem/FeedItem.tsx b/src/pages/common/components/FeedItem/FeedItem.tsx index e22d6d3c13..96b79f19fb 100644 --- a/src/pages/common/components/FeedItem/FeedItem.tsx +++ b/src/pages/common/components/FeedItem/FeedItem.tsx @@ -1,4 +1,4 @@ -import React, { FC, memo } from "react"; +import React, { forwardRef, memo } from "react"; import { FeedLayoutItemChangeData } from "@/shared/interfaces"; import { Circles, @@ -6,7 +6,6 @@ import { Common, CommonFeed, CommonFeedType, - CommonLink, CommonMember, DirectParent, } from "@/shared/models"; @@ -16,6 +15,7 @@ import { DiscussionFeedCard } from "../DiscussionFeedCard"; import { ProposalFeedCard } from "../ProposalFeedCard"; import { ProjectFeedItem } from "./components"; import { useFeedItemContext } from "./context"; +import { FeedItemRef } from "./types"; interface FeedItemProps { commonId?: string; @@ -42,7 +42,7 @@ interface FeedItemProps { directParent?: DirectParent | null; } -const FeedItem: FC = (props) => { +const FeedItem = forwardRef((props, ref) => { const { commonId, commonName, @@ -85,6 +85,7 @@ const FeedItem: FC = (props) => { }; const generalProps = { + ref, item, commonId, commonName, @@ -118,6 +119,6 @@ const FeedItem: FC = (props) => { } return null; -}; +}); export default memo(FeedItem); diff --git a/src/pages/common/components/FeedItem/types.ts b/src/pages/common/components/FeedItem/types.ts index 6f76108c99..dea8ade8a1 100644 --- a/src/pages/common/components/FeedItem/types.ts +++ b/src/pages/common/components/FeedItem/types.ts @@ -28,3 +28,7 @@ export interface GetAllowedItemsOptions { } export type MenuItemOptions = Omit; + +export interface FeedItemRef { + scrollToItem: () => void; +} diff --git a/src/pages/common/components/ProposalFeedCard/ProposalFeedCard.tsx b/src/pages/common/components/ProposalFeedCard/ProposalFeedCard.tsx index 8d55b190e3..2fa1a91edc 100644 --- a/src/pages/common/components/ProposalFeedCard/ProposalFeedCard.tsx +++ b/src/pages/common/components/ProposalFeedCard/ProposalFeedCard.tsx @@ -1,4 +1,10 @@ -import React, { ReactNode, useCallback, useEffect, useState } from "react"; +import React, { + forwardRef, + ReactNode, + useCallback, + useEffect, + useState, +} from "react"; import { useSelector } from "react-redux"; import { selectUser } from "@/pages/Auth/store/selectors"; import { useCommonMember, useProposalUserVote } from "@/pages/OldCommon/hooks"; @@ -17,7 +23,6 @@ import { CommonFeed, Governance, PredefinedTypes, - ProposalState, ResolutionType, } from "@/shared/models"; import { TextEditorValue } from "@/shared/ui-kit"; @@ -36,7 +41,11 @@ import { FeedCountdown, FeedCardShare, } from "../FeedCard"; -import { GetLastMessageOptions, GetNonAllowedItemsOptions } from "../FeedItem"; +import { + FeedItemRef, + GetLastMessageOptions, + GetNonAllowedItemsOptions, +} from "../FeedItem"; import { ProposalFeedVotingInfo, ProposalFeedButtonContainer, @@ -73,356 +82,359 @@ interface ProposalFeedCardProps { onUserSelect?: (userId: string, commonId?: string) => void; } -const ProposalFeedCard: React.FC = (props) => { - const { - commonId, - commonName, - commonImage, - pinnedFeedItems, - isProject, - isPinned, - item, - governanceCircles, - isPreviewMode, - isActive, - isExpanded, - getLastMessage, - getNonAllowedItems, - isMobileVersion, - onActiveItemDataChange, - onUserSelect, - } = props; - const user = useSelector(selectUser()); - const userId = user?.uid; - const { setChatItem, feedItemIdForAutoChatOpen, shouldAllowChatAutoOpen } = - useChatContext(); - const forceUpdate = useForceUpdate(); - const { getCommonPagePath } = useRoutesContext(); - const { - fetchUser: fetchFeedItemUser, - data: feedItemUser, - fetched: isFeedItemUserFetched, - } = useUserById(); - const { - fetchDiscussion, - data: discussion, - fetched: isDiscussionFetched, - } = useDiscussionById(); - const { - fetchProposal, - data: proposal, - fetched: isProposalFetched, - } = useProposalById(); - const { - fetched: isCommonMemberFetched, - data: commonMember, - fetchCommonMember, - } = useCommonMember(); - const { - data: userVote, - loading: isUserVoteLoading, - fetchProposalVote, - setVote, - } = useProposalUserVote(); - const { - data: proposalSpecificData, - fetched: isProposalSpecificDataFetched, - fetchData: fetchProposalSpecificData, - } = useProposalSpecificData(); - const { - data: feedItemUserMetadata, - fetched: isFeedItemUserMetadataFetched, - fetchFeedItemUserMetadata, - } = useFeedItemUserMetadata(); - const isLoading = - !isFeedItemUserFetched || - !isDiscussionFetched || - !isProposalFetched || - !proposal || - isUserVoteLoading || - !isCommonMemberFetched || - !isProposalSpecificDataFetched || - !isFeedItemUserMetadataFetched || - !commonId || - !governanceCircles; - const [isHovering, setHovering] = useState(false); - const onHover = (isMouseEnter: boolean): void => { - setHovering(isMouseEnter); - }; - const proposalId = item.data.id; - const { - isShowing: isShareModalOpen, - onOpen: onShareModalOpen, - onClose: onShareModalClose, - } = useModal(false); - const feedItemFollow = useFeedItemFollow(item.id, commonId); - const menuItems = useMenuItems( - { +const ProposalFeedCard = forwardRef( + (props, ref) => { + const { commonId, + commonName, + commonImage, pinnedFeedItems, - feedItem: item, - discussion, + isProject, + isPinned, + item, governanceCircles, - commonMember, - feedItemFollow, + isPreviewMode, + isActive, + isExpanded, + getLastMessage, getNonAllowedItems, - }, - { - report: () => {}, - share: () => onShareModalOpen(), - }, - ); - const cardTitle = discussion?.title; + isMobileVersion, + onActiveItemDataChange, + onUserSelect, + } = props; + const user = useSelector(selectUser()); + const userId = user?.uid; + const { setChatItem, feedItemIdForAutoChatOpen, shouldAllowChatAutoOpen } = + useChatContext(); + const forceUpdate = useForceUpdate(); + const { getCommonPagePath } = useRoutesContext(); + const { + fetchUser: fetchFeedItemUser, + data: feedItemUser, + fetched: isFeedItemUserFetched, + } = useUserById(); + const { + fetchDiscussion, + data: discussion, + fetched: isDiscussionFetched, + } = useDiscussionById(); + const { + fetchProposal, + data: proposal, + fetched: isProposalFetched, + } = useProposalById(); + const { + fetched: isCommonMemberFetched, + data: commonMember, + fetchCommonMember, + } = useCommonMember(); + const { + data: userVote, + loading: isUserVoteLoading, + fetchProposalVote, + setVote, + } = useProposalUserVote(); + const { + data: proposalSpecificData, + fetched: isProposalSpecificDataFetched, + fetchData: fetchProposalSpecificData, + } = useProposalSpecificData(); + const { + data: feedItemUserMetadata, + fetched: isFeedItemUserMetadataFetched, + fetchFeedItemUserMetadata, + } = useFeedItemUserMetadata(); + const isLoading = + !isFeedItemUserFetched || + !isDiscussionFetched || + !isProposalFetched || + !proposal || + isUserVoteLoading || + !isCommonMemberFetched || + !isProposalSpecificDataFetched || + !isFeedItemUserMetadataFetched || + !commonId || + !governanceCircles; + const [isHovering, setHovering] = useState(false); + const onHover = (isMouseEnter: boolean): void => { + setHovering(isMouseEnter); + }; + const proposalId = item.data.id; + const { + isShowing: isShareModalOpen, + onOpen: onShareModalOpen, + onClose: onShareModalClose, + } = useModal(false); + const feedItemFollow = useFeedItemFollow(item.id, commonId); + const menuItems = useMenuItems( + { + commonId, + pinnedFeedItems, + feedItem: item, + discussion, + governanceCircles, + commonMember, + feedItemFollow, + getNonAllowedItems, + }, + { + report: () => {}, + share: () => onShareModalOpen(), + }, + ); + const cardTitle = discussion?.title; - useEffect(() => { - fetchFeedItemUser(item.userId); - }, [item.userId]); + useEffect(() => { + fetchFeedItemUser(item.userId); + }, [item.userId]); - useEffect(() => { - if (item.data.discussionId) { - fetchDiscussion(item.data.discussionId); - } - }, [item.data.discussionId]); + useEffect(() => { + if (item.data.discussionId) { + fetchDiscussion(item.data.discussionId); + } + }, [item.data.discussionId]); - useEffect(() => { - fetchProposal(item.data.id); - }, [item.data.id]); + useEffect(() => { + fetchProposal(item.data.id); + }, [item.data.id]); - useEffect(() => { - fetchProposalVote(proposalId); - }, [fetchProposalVote, proposalId]); + useEffect(() => { + fetchProposalVote(proposalId); + }, [fetchProposalVote, proposalId]); - useEffect(() => { - if (commonId) { - fetchCommonMember(commonId, {}); - } - }, [fetchCommonMember, commonId]); + useEffect(() => { + if (commonId) { + fetchCommonMember(commonId, {}); + } + }, [fetchCommonMember, commonId]); - useEffect(() => { - if (commonId) { - fetchFeedItemUserMetadata({ - userId: userId || "", - commonId, - feedObjectId: item.id, - }); - } - }, [userId, commonId, item.id]); + useEffect(() => { + if (commonId) { + fetchFeedItemUserMetadata({ + userId: userId || "", + commonId, + feedObjectId: item.id, + }); + } + }, [userId, commonId, item.id]); - useEffect(() => { - if (proposal) { - fetchProposalSpecificData(proposal, true); - } - }, [proposal?.id]); + useEffect(() => { + if (proposal) { + fetchProposalSpecificData(proposal, true); + } + }, [proposal?.id]); - useEffect(() => { - if (isActive && cardTitle) { - onActiveItemDataChange?.({ - itemId: item.id, - title: cardTitle, - }); - } - }, [isActive, cardTitle]); + useEffect(() => { + if (isActive && cardTitle) { + onActiveItemDataChange?.({ + itemId: item.id, + title: cardTitle, + }); + } + }, [isActive, cardTitle]); - const handleOpenChat = useCallback(() => { - if (discussion && proposal) { - setChatItem({ - feedItemId: item.id, - discussion, - proposal, - circleVisibility: item.circleVisibility, - lastSeenItem: feedItemUserMetadata?.lastSeen, - lastSeenAt: feedItemUserMetadata?.lastSeenAt, - seenOnce: feedItemUserMetadata?.seenOnce, - }); - } - }, [ - item.id, - proposal, - discussion, - setChatItem, - item.circleVisibility, - feedItemUserMetadata?.lastSeen, - feedItemUserMetadata?.lastSeenAt, - feedItemUserMetadata?.seenOnce, - ]); + const handleOpenChat = useCallback(() => { + if (discussion && proposal) { + setChatItem({ + feedItemId: item.id, + discussion, + proposal, + circleVisibility: item.circleVisibility, + lastSeenItem: feedItemUserMetadata?.lastSeen, + lastSeenAt: feedItemUserMetadata?.lastSeenAt, + seenOnce: feedItemUserMetadata?.seenOnce, + }); + } + }, [ + item.id, + proposal, + discussion, + setChatItem, + item.circleVisibility, + feedItemUserMetadata?.lastSeen, + feedItemUserMetadata?.lastSeenAt, + feedItemUserMetadata?.seenOnce, + ]); - useEffect(() => { - if ( - isDiscussionFetched && - isProposalFetched && - isFeedItemUserMetadataFetched && - item.id === feedItemIdForAutoChatOpen && - !isMobileVersion && - shouldAllowChatAutoOpen !== false - ) { - handleOpenChat(); - } - }, [ - isDiscussionFetched, - isProposalFetched, - isFeedItemUserMetadataFetched, - shouldAllowChatAutoOpen, - ]); + useEffect(() => { + if ( + isDiscussionFetched && + isProposalFetched && + isFeedItemUserMetadataFetched && + item.id === feedItemIdForAutoChatOpen && + !isMobileVersion && + shouldAllowChatAutoOpen !== false + ) { + handleOpenChat(); + } + }, [ + isDiscussionFetched, + isProposalFetched, + isFeedItemUserMetadataFetched, + shouldAllowChatAutoOpen, + ]); - useEffect(() => { - if (isExpanded) { - forceUpdate(); - } - }, [isExpanded]); + useEffect(() => { + if (isExpanded) { + forceUpdate(); + } + }, [isExpanded]); - const renderContent = (): ReactNode => { - if (isLoading) { - return null; - } + const renderContent = (): ReactNode => { + if (isLoading) { + return null; + } - const isCountdownState = checkIsCountdownState(proposal); - const userHasPermissionsToVote = checkUserPermissionsToVote({ - proposal, - commonMember, - }); - const isVotingAllowed = - userHasPermissionsToVote && - checkIsVotingAllowed({ - userVote, + const isCountdownState = checkIsCountdownState(proposal); + const userHasPermissionsToVote = checkUserPermissionsToVote({ proposal, + commonMember, }); - const circleVisibility = getVisibilityString( - governanceCircles, - item.circleVisibility, - proposal?.type, - getUserName(feedItemUser), - ); + const isVotingAllowed = + userHasPermissionsToVote && + checkIsVotingAllowed({ + userVote, + proposal, + }); + const circleVisibility = getVisibilityString( + governanceCircles, + item.circleVisibility, + proposal?.type, + getUserName(feedItemUser), + ); + + return ( + <> + + Created:{" "} + + + } + type={getProposalTypeString(proposal.type)} + circleVisibility={circleVisibility} + commonId={commonId} + userId={item.userId} + menuItems={menuItems} + onUserSelect={ + onUserSelect && (() => onUserSelect(item.userId, commonId)) + } + /> + { + onHover(true); + }} + onMouseLeave={() => { + onHover(false); + }} + > + {proposal.resolutionType === ResolutionType.WAIT_FOR_EXPIRATION && ( + <> + + + + )} + + {proposal.resolutionType === ResolutionType.IMMEDIATE && ( + <> + + + + )} + + {isVotingAllowed && ( + + )} + + + ); + }; return ( <> - - Created:{" "} - - - } - type={getProposalTypeString(proposal.type)} - circleVisibility={circleVisibility} - commonId={commonId} - userId={item.userId} - menuItems={menuItems} - onUserSelect={ - onUserSelect && (() => onUserSelect(item.userId, commonId)) - } - /> - { - onHover(true); - }} - onMouseLeave={() => { - onHover(false); - }} + lastActivity={item.updatedAt.seconds * 1000} + isActive={isActive} + isExpanded={isExpanded} + unreadMessages={feedItemUserMetadata?.count || 0} + title={cardTitle} + lastMessage={getLastMessage({ + commonFeedType: item.data.type, + lastMessage: item.data.lastMessage, + discussion, + currentUserId: userId, + feedItemCreatorName: getUserName(feedItemUser), + commonName, + isProject, + hasFiles: item.data.hasFiles, + hasImages: item.data.hasImages, + })} + canBeExpanded={discussion?.predefinedType !== PredefinedTypes.General} + isPreviewMode={isPreviewMode} + image={commonImage} + imageAlt={`${commonName}'s image`} + isProject={isProject} + isPinned={isPinned} + isFollowing={feedItemFollow.isFollowing} + isLoading={isLoading} + type={item.data.type} + seenOnce={feedItemUserMetadata?.seenOnce} + menuItems={menuItems} + ownerId={item.userId} > - {proposal.resolutionType === ResolutionType.WAIT_FOR_EXPIRATION && ( - <> - - - - )} - - {proposal.resolutionType === ResolutionType.IMMEDIATE && ( - <> - - - - )} - - {isVotingAllowed && ( - - )} - + {renderContent()} + + {discussion && ( + + )} ); - }; - - return ( - <> - - {renderContent()} - - {discussion && ( - - )} - - ); -}; + }, +); export default ProposalFeedCard; diff --git a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx index ce2fa44b22..8223ac41d4 100644 --- a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx +++ b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx @@ -7,6 +7,7 @@ import React, { useEffect, useImperativeHandle, useMemo, + useRef, useState, } from "react"; import { useDispatch, useSelector } from "react-redux"; @@ -20,6 +21,7 @@ import { FeedItemBaseContentProps, FeedItemContext, FeedItemContextValue, + FeedItemRef, GetLastMessageOptions, GetNonAllowedItemsOptions, } from "@/pages/common"; @@ -151,6 +153,7 @@ const FeedLayout: ForwardRefRenderFunction = ( settings, } = props; const dispatch = useDispatch(); + const refsByItemId = useRef>({}); const { width: windowWidth } = useWindowSize(); const history = useHistory(); const queryParams = useQueryParams(); @@ -453,7 +456,7 @@ const FeedLayout: ForwardRefRenderFunction = ( const itemExists = allFeedItems.some((item) => item.itemId === feedItemId); if (itemExists) { - // scroll to item + refsByItemId.current[feedItemId]?.scrollToItem(); } else { onFetchNext(feedItemId); window.scrollTo({ @@ -589,6 +592,9 @@ const FeedLayout: ForwardRefRenderFunction = ( return ( { + refsByItemId.current[item.itemId] = ref; + }} key={item.feedItem.id} commonMember={commonMember} commonId={commonData?.id} From 5989484e2ecab076c4fc0dc9dfff4256ae98e185 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 29 Aug 2023 15:12:09 +0300 Subject: [PATCH 07/29] add scroll to the item on id change --- src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx index 8223ac41d4..beb1b13ee2 100644 --- a/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx +++ b/src/pages/commonFeed/components/FeedLayout/FeedLayout.tsx @@ -497,6 +497,9 @@ const FeedLayout: ForwardRefRenderFunction = ( }, [activeFeedItemId]); useEffect(() => { + if (selectedFeedItem?.itemId && !isTabletView) { + refsByItemId.current[selectedFeedItem.itemId]?.scrollToItem(); + } if (selectedFeedItem?.itemId || (chatItem && !chatItem.discussion)) { return; } From eb3c4c79d1ee247674411b00621333fab162747c Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 29 Aug 2023 15:34:52 +0300 Subject: [PATCH 08/29] disable chat opening when item is active and shouldAllowChatAutoOpen is null --- .../DiscussionFeedCard/DiscussionFeedCard.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx index 2f5deade12..cc7951603e 100644 --- a/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx +++ b/src/pages/common/components/DiscussionFeedCard/DiscussionFeedCard.tsx @@ -209,12 +209,13 @@ const DiscussionFeedCard = forwardRef( useEffect(() => { if ( - !isActive && + (!isActive || + shouldAllowChatAutoOpen === null || + shouldAllowChatAutoOpen) && isDiscussionFetched && isFeedItemUserMetadataFetched && item.id === feedItemIdForAutoChatOpen && - !isMobileVersion && - shouldAllowChatAutoOpen !== false + !isMobileVersion ) { handleOpenChat(); } @@ -225,10 +226,10 @@ const DiscussionFeedCard = forwardRef( ]); useEffect(() => { - if (isActive) { + if (isActive && shouldAllowChatAutoOpen !== null) { handleOpenChat(); } - }, [isActive, handleOpenChat]); + }, [isActive, shouldAllowChatAutoOpen, handleOpenChat]); useEffect(() => { if (isActive && cardTitle) { From 09905606b5019c04a656e5ebd53179e3b7a471fe Mon Sep 17 00:00:00 2001 From: Kiryl Budnik Date: Sun, 3 Sep 2023 23:56:30 +0300 Subject: [PATCH 09/29] subscribe to common member changes to display counters in real time --- .../ProjectFeedItem/ProjectFeedItem.tsx | 21 ++++--------- .../common/components/FeedItem/hooks/index.ts | 1 + .../FeedItem/hooks/useFeedItemCounters.ts | 30 +++++++++++++++++++ src/services/CommonFeed.ts | 5 +++- src/shared/models/Common.tsx | 4 +-- 5 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 src/pages/common/components/FeedItem/hooks/index.ts create mode 100644 src/pages/common/components/FeedItem/hooks/useFeedItemCounters.ts diff --git a/src/pages/common/components/FeedItem/components/ProjectFeedItem/ProjectFeedItem.tsx b/src/pages/common/components/FeedItem/components/ProjectFeedItem/ProjectFeedItem.tsx index e1b1ac96f0..18f571f87e 100644 --- a/src/pages/common/components/FeedItem/components/ProjectFeedItem/ProjectFeedItem.tsx +++ b/src/pages/common/components/FeedItem/components/ProjectFeedItem/ProjectFeedItem.tsx @@ -1,7 +1,6 @@ import React, { FC, ReactNode, useEffect } from "react"; import { useHistory } from "react-router-dom"; import classNames from "classnames"; -import { useCommonMember } from "@/pages/OldCommon/hooks"; import { useFeedItemContext } from "@/pages/common"; import { useRoutesContext } from "@/shared/contexts"; import { useCommon } from "@/shared/hooks/useCases"; @@ -9,6 +8,7 @@ import { OpenIcon } from "@/shared/icons"; import { CommonFeed } from "@/shared/models"; import { CommonAvatar, parseStringToTextEditorValue } from "@/shared/ui-kit"; import { checkIsProject } from "@/shared/utils"; +import { useFeedItemCounters } from "../../hooks"; import styles from "./ProjectFeedItem.module.scss"; interface ProjectFeedItemProps { @@ -22,16 +22,10 @@ export const ProjectFeedItem: FC = (props) => { const { getCommonPagePath } = useRoutesContext(); const { renderFeedItemBaseContent } = useFeedItemContext(); const { data: common, fetched: isCommonFetched, fetchCommon } = useCommon(); - const { - fetched: isCommonMemberFetched, - data: commonMember, - fetchCommonMember, - } = useCommonMember(); + const { unreadStreamsCount, unreadMessages } = useFeedItemCounters(item); const commonId = item.data.id; - const unreadStreamsCount = - commonMember?.streamsUnreadCountByProjectStream[item.id] ?? null; const lastMessage = parseStringToTextEditorValue( - unreadStreamsCount !== null + Number.isInteger(unreadStreamsCount) ? `${unreadStreamsCount} updated stream${ unreadStreamsCount === 1 ? "" : "s" }` @@ -65,10 +59,6 @@ export const ProjectFeedItem: FC = (props) => { fetchCommon(commonId); }, [commonId]); - useEffect(() => { - fetchCommonMember(item.commonId); - }, [fetchCommonMember, item.commonId]); - return ( ( <> @@ -76,13 +66,12 @@ export const ProjectFeedItem: FC = (props) => { className: styles.container, titleWrapperClassName: styles.titleWrapper, lastActivity: item.updatedAt.seconds * 1000, - unreadMessages: - commonMember?.unreadCountByProjectStream[item.id] ?? 0, isMobileView: isMobileVersion, title: titleEl, onClick: handleClick, seenOnce: true, - isLoading: !isCommonFetched || !isCommonMemberFetched, + isLoading: !isCommonFetched, + unreadMessages, lastMessage, renderLeftContent, shouldHideBottomContent: !lastMessage, diff --git a/src/pages/common/components/FeedItem/hooks/index.ts b/src/pages/common/components/FeedItem/hooks/index.ts new file mode 100644 index 0000000000..269de5d6a0 --- /dev/null +++ b/src/pages/common/components/FeedItem/hooks/index.ts @@ -0,0 +1 @@ +export * from "./useFeedItemCounters"; diff --git a/src/pages/common/components/FeedItem/hooks/useFeedItemCounters.ts b/src/pages/common/components/FeedItem/hooks/useFeedItemCounters.ts new file mode 100644 index 0000000000..b2c32d62cb --- /dev/null +++ b/src/pages/common/components/FeedItem/hooks/useFeedItemCounters.ts @@ -0,0 +1,30 @@ +import { useEffect } from "react"; +import { useCommonMember } from "@/pages/OldCommon/hooks"; +import { useCommonData } from "@/pages/commonFeed/hooks"; +import { CommonFeed } from "@/shared/models"; + +interface Return { + unreadStreamsCount?: number; + unreadMessages?: number; +} + +export const useFeedItemCounters = (item: CommonFeed): Return => { + const { data: commonData, fetchCommonData } = useCommonData(); + const { data: commonMember } = useCommonMember({ + shouldAutoReset: false, + withSubscription: true, + commonId: item.commonId, + governanceCircles: commonData?.governance.circles, + }); + const { streamsUnreadCountByProjectStream, unreadCountByProjectStream } = + commonMember || {}; + + useEffect(() => { + fetchCommonData({ commonId: item.commonId }); + }, [fetchCommonData, item.commonId]); + + return { + unreadStreamsCount: streamsUnreadCountByProjectStream?.[item.id], + unreadMessages: unreadCountByProjectStream?.[item.id], + }; +}; diff --git a/src/services/CommonFeed.ts b/src/services/CommonFeed.ts index 37fbcf7848..638642e7cc 100644 --- a/src/services/CommonFeed.ts +++ b/src/services/CommonFeed.ts @@ -194,7 +194,10 @@ class CommonFeedService { return query.onSnapshot((snapshot) => { const data = snapshot.docChanges().map((docChange) => ({ - commonFeedItem: docChange.doc.data(), + commonFeedItem: { + ...docChange.doc.data(), + commonId, + }, statuses: { isAdded: docChange.type === "added", isRemoved: docChange.type === "removed", diff --git a/src/shared/models/Common.tsx b/src/shared/models/Common.tsx index 80a69ec68c..14a04fea5a 100644 --- a/src/shared/models/Common.tsx +++ b/src/shared/models/Common.tsx @@ -144,8 +144,8 @@ export interface CommonMember { rulesAccepted?: boolean; joinedAt: firebase.firestore.Timestamp; circleIds: string[]; - streamsUnreadCountByProjectStream: Record; - unreadCountByProjectStream: Record; + streamsUnreadCountByProjectStream?: Record; + unreadCountByProjectStream?: Record; } export interface CirclesPermissions { From 7f1cad791306d08e848e5331052378a6b457799b Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 4 Sep 2023 10:23:10 +0300 Subject: [PATCH 10/29] fix proposal chat item update --- .../components/ProposalFeedCard/ProposalFeedCard.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pages/common/components/ProposalFeedCard/ProposalFeedCard.tsx b/src/pages/common/components/ProposalFeedCard/ProposalFeedCard.tsx index 2fa1a91edc..106f2bccc2 100644 --- a/src/pages/common/components/ProposalFeedCard/ProposalFeedCard.tsx +++ b/src/pages/common/components/ProposalFeedCard/ProposalFeedCard.tsx @@ -258,12 +258,14 @@ const ProposalFeedCard = forwardRef( useEffect(() => { if ( + (!isActive || + shouldAllowChatAutoOpen === null || + shouldAllowChatAutoOpen) && isDiscussionFetched && isProposalFetched && isFeedItemUserMetadataFetched && item.id === feedItemIdForAutoChatOpen && - !isMobileVersion && - shouldAllowChatAutoOpen !== false + !isMobileVersion ) { handleOpenChat(); } @@ -274,6 +276,12 @@ const ProposalFeedCard = forwardRef( shouldAllowChatAutoOpen, ]); + useEffect(() => { + if (isActive && shouldAllowChatAutoOpen !== null) { + handleOpenChat(); + } + }, [isActive, shouldAllowChatAutoOpen, handleOpenChat]); + useEffect(() => { if (isExpanded) { forceUpdate(); From b43c20ae51af10c28b4149740d7eff7f1c961bf6 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 4 Sep 2023 10:27:35 +0300 Subject: [PATCH 11/29] fix checkShouldAutoOpenPreview util for empty proposal and seenOnce --- .../FeedLayout/utils/checkShouldAutoOpenPreview.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/commonFeed/components/FeedLayout/utils/checkShouldAutoOpenPreview.ts b/src/pages/commonFeed/components/FeedLayout/utils/checkShouldAutoOpenPreview.ts index fd4bdb0678..71fc5c2de6 100644 --- a/src/pages/commonFeed/components/FeedLayout/utils/checkShouldAutoOpenPreview.ts +++ b/src/pages/commonFeed/components/FeedLayout/utils/checkShouldAutoOpenPreview.ts @@ -8,7 +8,13 @@ export const checkShouldAutoOpenPreview = ( if (!chatItem || !chatItem.discussion) { return false; } - if (!chatItem.seenOnce || chatItem.proposal?.state === ProposalState.VOTING) { + if ( + chatItem.proposal && + (!chatItem.seenOnce || chatItem.proposal.state === ProposalState.VOTING) + ) { + return true; + } + if (chatItem.discussion && !chatItem.seenOnce) { return true; } const expirationTimestamp = From 026f4dfb3feace0d62923de12921ad732e3152bf Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 4 Sep 2023 13:29:35 +0300 Subject: [PATCH 12/29] remove "Edit" and "Delete" options from inbox items --- src/pages/inbox/utils/getNonAllowedItems.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/pages/inbox/utils/getNonAllowedItems.ts b/src/pages/inbox/utils/getNonAllowedItems.ts index 0dcba5f370..8fb89fd88f 100644 --- a/src/pages/inbox/utils/getNonAllowedItems.ts +++ b/src/pages/inbox/utils/getNonAllowedItems.ts @@ -1,15 +1,8 @@ import { FeedItemMenuItem, GetNonAllowedItemsOptions } from "@/pages/common"; -import { CommonFeedType } from "@/shared/models"; -export const getNonAllowedItems: GetNonAllowedItemsOptions = (type) => { - const items: FeedItemMenuItem[] = [ - FeedItemMenuItem.Pin, - FeedItemMenuItem.Unpin, - ]; - - if (type !== CommonFeedType.Discussion) { - items.push(FeedItemMenuItem.Remove); - } - - return items; -}; +export const getNonAllowedItems: GetNonAllowedItemsOptions = () => [ + FeedItemMenuItem.Pin, + FeedItemMenuItem.Unpin, + FeedItemMenuItem.Edit, + FeedItemMenuItem.Remove, +]; From 5f2cf666e902a4163288a88d03da49a0b525a8d6 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 4 Sep 2023 15:44:52 +0300 Subject: [PATCH 13/29] skip hide action for messages --- src/shared/components/ElementDropdown/ElementDropdown.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shared/components/ElementDropdown/ElementDropdown.tsx b/src/shared/components/ElementDropdown/ElementDropdown.tsx index bdf6ccd1df..a9860c34f6 100644 --- a/src/shared/components/ElementDropdown/ElementDropdown.tsx +++ b/src/shared/components/ElementDropdown/ElementDropdown.tsx @@ -181,6 +181,9 @@ const ElementDropdown: FC = ({ } if ( + ![EntityTypes.DiscussionMessage, EntityTypes.ProposalMessage].includes( + entityType, + ) && governanceCircles && commonMember && hasPermission({ From 6434b7259073e84fe7f78c6439caa0232eced24d Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 4 Sep 2023 16:40:23 +0300 Subject: [PATCH 14/29] add word bread style to chat text input --- .../common/components/ChatComponent/ChatComponent.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/common/components/ChatComponent/ChatComponent.module.scss b/src/pages/common/components/ChatComponent/ChatComponent.module.scss index 54bcb33c17..f072d86ded 100644 --- a/src/pages/common/components/ChatComponent/ChatComponent.module.scss +++ b/src/pages/common/components/ChatComponent/ChatComponent.module.scss @@ -97,6 +97,7 @@ flex-direction: column; justify-content: center; padding: 0.125rem 1.5rem 0.125rem 0.25rem; + word-break: break-word; } .messageInputEmpty { From 196c260fded75bcad31ecefaec027dcaf41041c2 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 4 Sep 2023 16:44:33 +0300 Subject: [PATCH 15/29] add word bread style to chat message --- src/shared/components/Chat/ChatMessage/ChatMessage.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shared/components/Chat/ChatMessage/ChatMessage.module.scss b/src/shared/components/Chat/ChatMessage/ChatMessage.module.scss index 6a6f612c2b..7f4d6ea3fc 100644 --- a/src/shared/components/Chat/ChatMessage/ChatMessage.module.scss +++ b/src/shared/components/Chat/ChatMessage/ChatMessage.module.scss @@ -108,6 +108,7 @@ white-space: pre-line; margin-right: 1rem; margin-left: 1rem; + word-break: break-word; } .messageContentCurrentUser { From 5e3adc43e784e6a956612cf9b410f6a00753fd62 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 4 Sep 2023 17:01:38 +0300 Subject: [PATCH 16/29] create chat message linkify component --- .../Chat/ChatMessage/ChatMessage.tsx | 11 ++++-- .../ChatMessageLinkify.module.scss | 9 +++++ .../ChatMessageLinkify/ChatMessageLinkify.tsx | 38 +++++++++++++++++++ .../components/ChatMessageLinkify/index.ts | 1 + .../Chat/ChatMessage/components/index.ts | 2 + 5 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 src/shared/components/Chat/ChatMessage/components/ChatMessageLinkify/ChatMessageLinkify.module.scss create mode 100644 src/shared/components/Chat/ChatMessage/components/ChatMessageLinkify/ChatMessageLinkify.tsx create mode 100644 src/shared/components/Chat/ChatMessage/components/ChatMessageLinkify/index.ts diff --git a/src/shared/components/Chat/ChatMessage/ChatMessage.tsx b/src/shared/components/Chat/ChatMessage/ChatMessage.tsx index 0bbd90d800..40a9e9111a 100644 --- a/src/shared/components/Chat/ChatMessage/ChatMessage.tsx +++ b/src/shared/components/Chat/ChatMessage/ChatMessage.tsx @@ -8,7 +8,6 @@ import React, { } from "react"; import classNames from "classnames"; import { - Linkify, ElementDropdown, UserAvatar, UserInfoPopup, @@ -40,7 +39,7 @@ import { StaticLinkType, isRTL } from "@/shared/utils"; import { getUserName } from "@/shared/utils"; import { convertBytes } from "@/shared/utils/convertBytes"; import { EditMessageInput } from "../EditMessageInput"; -import { Time } from "./components/Time"; +import { ChatMessageLinkify, Time } from "./components"; import { getTextFromTextEditorString } from "./utils"; import styles from "./ChatMessage.module.scss"; @@ -295,7 +294,9 @@ export default function ChatMessage({ )} ) : ( - {replyMessageText.map((text) => text)} + + {replyMessageText.map((text) => text)} + )} @@ -385,7 +386,9 @@ export default function ChatMessage({ /> )} - {messageText.map((text) => text)} + + {messageText.map((text) => text)} + {!isSystemMessage && (