diff --git a/src/pages/common/components/ChatComponent/ChatComponent.tsx b/src/pages/common/components/ChatComponent/ChatComponent.tsx index 8e7f1570e..99b2bec6b 100644 --- a/src/pages/common/components/ChatComponent/ChatComponent.tsx +++ b/src/pages/common/components/ChatComponent/ChatComponent.tsx @@ -67,6 +67,7 @@ import { selectOptimisticDiscussionMessages, inboxActions, optimisticActions, + selectInstantDiscussionMessagesOrder, } from "@/store/states"; import { ChatContentContext, ChatContentData } from "../CommonContent/context"; import { @@ -89,6 +90,7 @@ import styles from "./ChatComponent.module.scss"; import { BaseTextEditorHandles } from "@/shared/ui-kit/TextEditor/BaseTextEditor"; const BASE_CHAT_INPUT_HEIGHT = 48; +const BASE_ORDER_INTERVAL = 1000; interface ChatComponentInterface { commonId: string; @@ -277,6 +279,9 @@ export default function ChatComponent({ const optimisticDiscussionMessages = useSelector( selectOptimisticDiscussionMessages, ); + const instantDiscussionMessagesOrder = useSelector(selectInstantDiscussionMessagesOrder); + + const currentChatOrder = instantDiscussionMessagesOrder.get(discussionId)?.order || 1; const isOptimisticChat = optimisticFeedItems.has(discussionId); @@ -416,8 +421,8 @@ export default function ChatComponent({ setMessages([]); } }, - 1500, - [newMessages, discussionId, dispatch], + 1500 + BASE_ORDER_INTERVAL * currentChatOrder, + [newMessages, discussionId, dispatch, currentChatOrder], ); /** @@ -584,6 +589,7 @@ export default function ChatComponent({ return [...prev, ...filePreviewPayload, payload]; }); + dispatch(optimisticActions.setInstantDiscussionMessagesOrder({discussionId})); } if (isChatChannel) { diff --git a/src/pages/common/components/FeedCard/FeedCard.module.scss b/src/pages/common/components/FeedCard/FeedCard.module.scss index 7deb66dbe..8ff002e12 100644 --- a/src/pages/common/components/FeedCard/FeedCard.module.scss +++ b/src/pages/common/components/FeedCard/FeedCard.module.scss @@ -23,6 +23,10 @@ cursor: pointer; } +.toggleCard { + outline: none; +} + .loader { align-self: center; } diff --git a/src/pages/common/components/FeedCard/FeedCard.tsx b/src/pages/common/components/FeedCard/FeedCard.tsx index 28860dc8a..1b1625819 100644 --- a/src/pages/common/components/FeedCard/FeedCard.tsx +++ b/src/pages/common/components/FeedCard/FeedCard.tsx @@ -299,8 +299,8 @@ const FeedCard = (props, ref) => { ]); return ( -
- {!isPreviewMode &&
{feedItemBaseContent}
} +
+ {!isPreviewMode &&
{feedItemBaseContent}
}
= (props) => { [], ); + const instantDiscussionMessage = useSelector(selectInstantDiscussionMessagesOrder); + const handleFeedItemUpdate = useCallback( (item: CommonFeed, isRemoved: boolean) => { - dispatch( - inboxActions.updateFeedItem({ - item, - isRemoved, - }), - ); - - if (!isRemoved && item.data.lastMessage?.ownerId === userId) { - document - .getElementById("feedLayoutWrapper") - ?.scrollIntoView({ behavior: "smooth" }); + if(!instantDiscussionMessage.has(item.data.id)) { + dispatch( + inboxActions.updateFeedItem({ + item, + isRemoved, + }), + ); + + if (!isRemoved && item.data.lastMessage?.ownerId === userId) { + document + .getElementById("feedLayoutWrapper") + ?.scrollIntoView({ behavior: "smooth" }); + } } }, - [dispatch], + [dispatch, instantDiscussionMessage], ); const handleFeedItemUnfollowed = useCallback( diff --git a/src/shared/hooks/useCases/useInboxItems.ts b/src/shared/hooks/useCases/useInboxItems.ts index 818f69db7..e9fefd046 100644 --- a/src/shared/hooks/useCases/useInboxItems.ts +++ b/src/shared/hooks/useCases/useInboxItems.ts @@ -7,17 +7,21 @@ import { checkIsFeedItemFollowLayoutItemWithFollowData, FeedItemFollowLayoutItemWithFollowData, FeedLayoutItemWithFollowData, + InboxItemBatch, InboxItemsBatch as ItemsBatch, } from "@/shared/interfaces"; import { InboxItem, Timestamp } from "@/shared/models"; import { inboxActions, InboxItems, + NewInboxItems, optimisticActions, selectFilteredInboxItems, selectInboxItems, + selectInstantDiscussionMessagesOrder, selectOptimisticInboxFeedItems, } from "@/store/states"; +import { useDeepCompareEffect } from "react-use"; interface Return extends Pick { @@ -71,6 +75,9 @@ export const useInboxItems = ( ): Return => { const dispatch = useDispatch(); const optimisticInboxItems = useSelector(selectOptimisticInboxFeedItems); + const instantDiscussionMessages = useSelector( + selectInstantDiscussionMessagesOrder, + ); const [newItemsBatches, setNewItemsBatches] = useState([]); const [lastUpdatedAt, setLastUpdatedAt] = useState(null); const inboxItems = useSelector(selectInboxItems); @@ -244,6 +251,33 @@ export const useInboxItems = ( unread, ]); + const [notListedFeedItems, setNotListedFeedItems] = useState[]>([]); + + useDeepCompareEffect(() => { + if(notListedFeedItems.length > 0 && notListedFeedItems.length === instantDiscussionMessages.size) { + const updatedFeedItems = notListedFeedItems.map((item) => { + const itemData = item?.item as FeedItemFollowLayoutItemWithFollowData; + const feedItemData = itemData.feedItem?.data; + const messageUpdatedAt = instantDiscussionMessages.get(feedItemData?.discussionId ?? "")?.timestamp || instantDiscussionMessages.get(feedItemData.id)?.timestamp; + return { + ...item, + item: { + ...itemData, + feedItem: { + ...itemData.feedItem, + updatedAt: messageUpdatedAt, + } + } + } + }) as NewInboxItems[]; + + setNotListedFeedItems([]); + dispatch(inboxActions.addNewInboxItems(updatedFeedItems)); + dispatch(optimisticActions.clearInstantDiscussionMessagesOrder()); + } + + },[notListedFeedItems, instantDiscussionMessages]); + useEffect(() => { if (!lastBatch || !userId) { return; @@ -264,16 +298,23 @@ export const useInboxItems = ( ); if (finalData.length > 0 && isMounted) { - dispatch(inboxActions.addNewInboxItems(finalData)); - finalData.forEach(({item}) => { - const itemData = (item as FeedItemFollowLayoutItemWithFollowData).feedItem?.data; - + const newItems: InboxItemBatch[] = []; + finalData.forEach((item: InboxItemBatch) => { + const itemData = (item.item as FeedItemFollowLayoutItemWithFollowData)?.feedItem?.data; + + if(instantDiscussionMessages.has(itemData?.discussionId ?? "") || instantDiscussionMessages.has(itemData?.id)) { + setNotListedFeedItems((prev) => [...prev, item]); + } else { + newItems.push(item); + } if(optimisticInboxItems.has(itemData.id)) { dispatch(optimisticActions.removeOptimisticInboxFeedItemState({id: itemData.id})); } else if (itemData?.discussionId && optimisticInboxItems.has(itemData?.discussionId)) { dispatch(optimisticActions.removeOptimisticInboxFeedItemState({id: itemData?.discussionId})); } }) + + newItems.length > 0 && dispatch(inboxActions.addNewInboxItems(newItems)); } } catch (error) { Logger.error(error); @@ -285,7 +326,8 @@ export const useInboxItems = ( return () => { isMounted = false; }; - }, [lastBatch]); + }, [lastBatch, instantDiscussionMessages]); + return { ...inboxItems, diff --git a/src/store/states/inbox/actions.ts b/src/store/states/inbox/actions.ts index 3400fe54e..004ad09be 100644 --- a/src/store/states/inbox/actions.ts +++ b/src/store/states/inbox/actions.ts @@ -2,7 +2,7 @@ import { createAsyncAction, createStandardAction } from "typesafe-actions"; import { FeedLayoutItemWithFollowData } from "@/shared/interfaces"; import { ChatChannel, CommonFeed, LastMessageContentWithMessageId } from "@/shared/models"; import { InboxActionType } from "./constants"; -import { InboxItems, InboxSearchState } from "./types"; +import { InboxItems, InboxSearchState, NewInboxItems } from "./types"; export const resetInbox = createStandardAction(InboxActionType.RESET_INBOX)<{ onlyIfUnread?: boolean; @@ -26,15 +26,7 @@ export const getInboxItems = createAsyncAction( export const addNewInboxItems = createStandardAction( InboxActionType.ADD_NEW_INBOX_ITEMS, -)< - { - item: FeedLayoutItemWithFollowData; - statuses: { - isAdded: boolean; - isRemoved: boolean; - }; - }[] ->(); +)(); export const updateInboxItem = createStandardAction( InboxActionType.UPDATE_INBOX_ITEM, diff --git a/src/store/states/inbox/types.ts b/src/store/states/inbox/types.ts index 58542ac2a..c18c75a67 100644 --- a/src/store/states/inbox/types.ts +++ b/src/store/states/inbox/types.ts @@ -20,6 +20,14 @@ export interface InboxItems { unread: boolean; } +export interface NewInboxItems { + item: FeedLayoutItemWithFollowData; + statuses: { + isAdded: boolean; + isRemoved: boolean; + }; +} + export type LastState = Pick< InboxState, | "items" @@ -39,3 +47,4 @@ export interface InboxState { lastReadState: LastState | null; lastUnreadState: LastState | null; } + diff --git a/src/store/states/optimistic/actions.ts b/src/store/states/optimistic/actions.ts index 5c741c01d..4a94826bb 100644 --- a/src/store/states/optimistic/actions.ts +++ b/src/store/states/optimistic/actions.ts @@ -41,6 +41,16 @@ export const clearOptimisticDiscussionMessages = createStandardAction( OptimisticActionType.CLEAR_OPTIMISTIC_DISCUSSION_MESSAGES, )(); +export const setInstantDiscussionMessagesOrder = createStandardAction( + OptimisticActionType.SET_INSTANT_DISCUSSION_MESSAGES_ORDER, +)<{ + discussionId: string; +}>(); + +export const clearInstantDiscussionMessagesOrder = createStandardAction( + OptimisticActionType.CLEAR_INSTANT_DISCUSSION_MESSAGES_ORDER, +)(); + export const clearCreatedOptimisticFeedItem = createStandardAction( OptimisticActionType.CLEAR_CREATED_OPTIMISTIC_FEED_ITEM, )(); diff --git a/src/store/states/optimistic/constants.ts b/src/store/states/optimistic/constants.ts index 78d33e072..9bca26b87 100644 --- a/src/store/states/optimistic/constants.ts +++ b/src/store/states/optimistic/constants.ts @@ -7,6 +7,9 @@ export enum OptimisticActionType { SET_OPTIMISTIC_DISCUSSION_MESSAGES = "@OPTIMISTIC/SET_OPTIMISTIC_DISCUSSION_MESSAGES", CLEAR_OPTIMISTIC_DISCUSSION_MESSAGES = "@OPTIMISTIC/CLEAR_OPTIMISTIC_DISCUSSION_MESSAGES", + SET_INSTANT_DISCUSSION_MESSAGES_ORDER = "@OPTIMISTIC/SET_INSTANT_DISCUSSION_MESSAGES_ORDER", + CLEAR_INSTANT_DISCUSSION_MESSAGES_ORDER = "@OPTIMISTIC/CLEAR_INSTANT_DISCUSSION_MESSAGES_ORDER", + CLEAR_CREATED_OPTIMISTIC_FEED_ITEM = "@OPTIMISTIC/CLEAR_CREATED_OPTIMISTIC_FEED_ITEM", RESET_OPTIMISTIC_STATE = "RESET_OPTIMISTIC_STATE", } diff --git a/src/store/states/optimistic/reducer.ts b/src/store/states/optimistic/reducer.ts index b29d452c2..a437af2cd 100644 --- a/src/store/states/optimistic/reducer.ts +++ b/src/store/states/optimistic/reducer.ts @@ -6,6 +6,7 @@ import { OptimisticState } from "./types"; import { generateOptimisticFeedItemFollowWithMetadata } from "@/shared/utils"; +import { Timestamp } from "@/shared/models"; type Action = ActionType; @@ -14,10 +15,53 @@ const initialState: OptimisticState = { optimisticInboxFeedItems: new Map(), optimisticDiscussionMessages: new Map(), createdOptimisticFeedItems: new Map(), + instantDiscussionMessagesOrder: new Map(), }; export const reducer = createReducer(initialState) .handleAction(actions.resetOptimisticState, () => initialState) + .handleAction(actions.setInstantDiscussionMessagesOrder, (state, { payload }) => + produce(state, (nextState) => { + const updatedMap = new Map(nextState.instantDiscussionMessagesOrder); + const { discussionId } = payload; + + if(updatedMap.size > 1 && updatedMap.has(discussionId)) { + const keys = Array.from(updatedMap.keys()); + + keys.forEach((key) => { + const orderValue = updatedMap.get(key)?.order || 2; + const timestampValue = updatedMap.get(key)?.timestamp || Timestamp.fromDate(new Date()); + updatedMap.set(key, { + order: orderValue === 1 ? 1 : orderValue - 1, + timestamp: timestampValue + }); + }); + } + + if(updatedMap.has(discussionId)) { + updatedMap.set(discussionId, { + order: updatedMap.size || 1, + timestamp: Timestamp.fromDate(new Date()) + }); + } else { + updatedMap.set(discussionId, { + order: (updatedMap.size + 1) || 1, + timestamp: Timestamp.fromDate(new Date()) + }); + } + nextState.instantDiscussionMessagesOrder = updatedMap; + }), + ) + .handleAction(actions.clearInstantDiscussionMessagesOrder, (state) => + produce(state, (nextState) => { + const updatedMap = new Map(); + + // updatedMap.delete(payload); + + // Assign the new Map back to the state + nextState.instantDiscussionMessagesOrder = updatedMap; + }), + ) .handleAction(actions.setOptimisticFeedItem, (state, { payload }) => produce(state, (nextState) => { const updatedMap = new Map(nextState.optimisticFeedItems); diff --git a/src/store/states/optimistic/selectors.ts b/src/store/states/optimistic/selectors.ts index 6bedea23d..bed310b5e 100644 --- a/src/store/states/optimistic/selectors.ts +++ b/src/store/states/optimistic/selectors.ts @@ -12,3 +12,6 @@ export const selectOptimisticDiscussionMessages = (state: AppState) => export const selectCreatedOptimisticFeedItems = (state: AppState) => state.optimistic.createdOptimisticFeedItems; + +export const selectInstantDiscussionMessagesOrder = (state: AppState) => + state.optimistic.instantDiscussionMessagesOrder; diff --git a/src/store/states/optimistic/types.ts b/src/store/states/optimistic/types.ts index 4a24fa8c3..fe11016df 100644 --- a/src/store/states/optimistic/types.ts +++ b/src/store/states/optimistic/types.ts @@ -2,10 +2,15 @@ import { FeedItemFollowLayoutItem } from "@/shared/interfaces"; import { CreateDiscussionMessageDto } from "@/shared/interfaces/api/discussionMessages"; +import { Timestamp } from "@/shared/models"; export interface OptimisticState { createdOptimisticFeedItems: Map; optimisticFeedItems: Map; optimisticInboxFeedItems: Map; optimisticDiscussionMessages: Map; + instantDiscussionMessagesOrder: Map; } diff --git a/src/store/store.tsx b/src/store/store.tsx index 623c46b39..915483972 100644 --- a/src/store/store.tsx +++ b/src/store/store.tsx @@ -33,7 +33,8 @@ const mapFields: Array = [ "createdOptimisticFeedItems", "optimisticFeedItems", "optimisticInboxFeedItems", - "optimisticDiscussionMessages" + "optimisticDiscussionMessages", + "instantDiscussionMessagesOrder" ]; const mapTransformer = createTransform(