@droppii-org/chat-sdk 0.5.5 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/components/compose/ComposeNewMessageModal.js +3 -3
  2. package/dist/components/conversation/ConversationByGroupItem.d.ts.map +1 -1
  3. package/dist/components/conversation/ConversationByGroupItem.js +4 -2
  4. package/dist/components/conversation/ConversationBySessionItem.d.ts.map +1 -1
  5. package/dist/components/conversation/ConversationBySessionItem.js +4 -2
  6. package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
  7. package/dist/components/conversation/DeskConversationList.js +15 -5
  8. package/dist/components/conversation-inbox/ConversationInboxList.js +3 -3
  9. package/dist/components/conversation-inbox/contact-search/ContactSearchPanel.d.ts.map +1 -1
  10. package/dist/components/conversation-inbox/contact-search/ContactSearchPanel.js +13 -11
  11. package/dist/components/conversation-inbox/contact-search/utils.d.ts +7 -1
  12. package/dist/components/conversation-inbox/contact-search/utils.d.ts.map +1 -1
  13. package/dist/components/conversation-inbox/contact-search/utils.js +19 -15
  14. package/dist/components/message/GroupMembersDrawer.d.ts.map +1 -1
  15. package/dist/components/message/GroupMembersDrawer.js +3 -3
  16. package/dist/components/message/MessageList.d.ts.map +1 -1
  17. package/dist/components/message/MessageList.js +27 -11
  18. package/dist/components/search-conversation/item/SearchItemAsGroup.js +3 -3
  19. package/dist/context/ChatContext.d.ts +1 -1
  20. package/dist/context/ChatContext.d.ts.map +1 -1
  21. package/dist/context/ChatContext.js +10 -1
  22. package/dist/hooks/conversation/useChatNavigate.d.ts +2 -0
  23. package/dist/hooks/conversation/useChatNavigate.d.ts.map +1 -0
  24. package/dist/hooks/conversation/useChatNavigate.js +14 -0
  25. package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
  26. package/dist/hooks/global/useGlobalEvent.js +11 -4
  27. package/dist/hooks/group/useCreateGroup.js +3 -3
  28. package/dist/hooks/group/useDismissGroup.js +3 -3
  29. package/dist/hooks/group/useQuitGroup.js +3 -3
  30. package/dist/hooks/session/useConversationSessionState.d.ts +2 -1
  31. package/dist/hooks/session/useConversationSessionState.d.ts.map +1 -1
  32. package/dist/hooks/session/useConversationSessionState.js +14 -5
  33. package/dist/hooks/session/useGetSession.d.ts +6 -0
  34. package/dist/hooks/session/useGetSession.d.ts.map +1 -1
  35. package/dist/hooks/session/useGetSession.js +17 -6
  36. package/dist/screens/conversation-detail/index.d.ts.map +1 -1
  37. package/dist/screens/conversation-detail/index.js +15 -2
  38. package/dist/screens/conversation-inbox/index.d.ts +1 -1
  39. package/dist/screens/conversation-inbox/index.d.ts.map +1 -1
  40. package/dist/screens/conversation-inbox/index.js +48 -14
  41. package/dist/store/conversation.d.ts +1 -0
  42. package/dist/store/conversation.d.ts.map +1 -1
  43. package/dist/store/conversation.js +26 -18
  44. package/dist/store/type.d.ts +10 -0
  45. package/dist/store/type.d.ts.map +1 -1
  46. package/dist/types/chat.d.ts +2 -0
  47. package/dist/types/chat.d.ts.map +1 -1
  48. package/package.json +1 -1
@@ -10,7 +10,7 @@ import { useEligibleAccounts } from "../../hooks/group/useEligibleAccounts";
10
10
  import { DChatSDK } from "../../constants/sdk";
11
11
  import useConversationStore from "../../store/conversation";
12
12
  import CreateGroupForm from "../../components/group/CreateGroupForm";
13
- import { useChatContext } from "../../context/ChatContext";
13
+ import { useChatNavigate } from "../../hooks/conversation/useChatNavigate";
14
14
  const ComposeNewMessageModal = ({ open, onClose, }) => {
15
15
  const { t } = useTranslation("conversation-inbox");
16
16
  const [step, setStep] = useState("compose");
@@ -18,7 +18,7 @@ const ComposeNewMessageModal = ({ open, onClose, }) => {
18
18
  const [openingUserID, setOpeningUserID] = useState(null);
19
19
  const debouncedSearch = useDebounce(search, { wait: 300 });
20
20
  const { accounts, isLoading } = useEligibleAccounts(debouncedSearch, open);
21
- const { onNavigate } = useChatContext();
21
+ const onNavigate = useChatNavigate();
22
22
  const setConversationData = useConversationStore((state) => state.setConversationData);
23
23
  const setSelectedConversationId = useConversationStore((state) => state.setSelectedConversationId);
24
24
  const handleClose = () => {
@@ -47,7 +47,7 @@ const ComposeNewMessageModal = ({ open, onClose, }) => {
47
47
  newSearchParams.delete("userId");
48
48
  newSearchParams.delete("groupId");
49
49
  newSearchParams.delete("botId");
50
- onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(`${window.location.pathname}?${newSearchParams.toString()}`);
50
+ onNavigate(`${window.location.pathname}?${newSearchParams.toString()}`);
51
51
  handleClose();
52
52
  }
53
53
  finally {
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationByGroupItem.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/ConversationByGroupItem.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AAQxF,UAAU,4BAA4B;IACpC,KAAK,EAAE,4BAA4B,CAAC;IACpC,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,QAAA,MAAM,uBAAuB,GAAI,2BAG9B,4BAA4B,4CA+D9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"ConversationByGroupItem.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/ConversationByGroupItem.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AASxF,UAAU,4BAA4B;IACpC,KAAK,EAAE,4BAA4B,CAAC;IACpC,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,QAAA,MAAM,uBAAuB,GAAI,2BAG9B,4BAA4B,4CAgE9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
@@ -3,13 +3,15 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import useConversationStore from "../../store/conversation";
5
5
  import { useChatContext } from "../../context/ChatContext";
6
+ import { useChatNavigate } from "../../hooks/conversation/useChatNavigate";
6
7
  import { formatTimestamp, parseLatestMessage } from "../../utils/common";
7
8
  import GroupCompositeAvatar from "../../components/group/GroupCompositeAvatar";
8
9
  import ThreadCard from "./ThreadCard";
9
10
  import MentionIndicatorIcon from "./MentionIndicatorIcon";
10
11
  const ConversationByGroupItem = ({ group, latestPreview, }) => {
11
12
  const { t } = useTranslation();
12
- const { user, onNavigate } = useChatContext();
13
+ const { user } = useChatContext();
14
+ const onNavigate = useChatNavigate();
13
15
  const setConversationData = useConversationStore((state) => state.setConversationData);
14
16
  const setSelectedConversationId = useConversationStore((state) => state.setSelectedConversationId);
15
17
  const isSelected = useConversationStore((state) => state.selectedConversationId === group.conversationId);
@@ -22,7 +24,7 @@ const ConversationByGroupItem = ({ group, latestPreview, }) => {
22
24
  newSearchParams.delete("userId");
23
25
  newSearchParams.delete("groupId");
24
26
  newSearchParams.delete("botId");
25
- onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(`${window.location.pathname}?${newSearchParams.toString()}`);
27
+ onNavigate(`${window.location.pathname}?${newSearchParams.toString()}`);
26
28
  };
27
29
  const latestMsg = (latestPreview === null || latestPreview === void 0 ? void 0 : latestPreview.latestMsg) || "";
28
30
  const latestMsgSendTime = (latestPreview === null || latestPreview === void 0 ? void 0 : latestPreview.latestMsgSendTime) || 0;
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationBySessionItem.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/ConversationBySessionItem.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAE7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAWhD,UAAU,8BAA8B;IACtC,WAAW,EAAE,gBAAgB,CAAC;IAC9B,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,QAAA,MAAM,yBAAyB,GAAI,iCAGhC,8BAA8B,mDA8EhC,CAAC;AAEF,eAAe,yBAAyB,CAAC"}
1
+ {"version":3,"file":"ConversationBySessionItem.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/ConversationBySessionItem.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAE7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAYhD,UAAU,8BAA8B;IACtC,WAAW,EAAE,gBAAgB,CAAC;IAC9B,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,QAAA,MAAM,yBAAyB,GAAI,iCAGhC,8BAA8B,mDA+EhC,CAAC;AAEF,eAAe,yBAAyB,CAAC"}
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
4
4
  import useConversationStore from "../../store/conversation";
5
5
  import { Avatar } from "antd";
6
6
  import { useChatContext } from "../../context/ChatContext";
7
+ import { useChatNavigate } from "../../hooks/conversation/useChatNavigate";
7
8
  import { useConversationDisplayData } from "../../hooks/conversation/useConversation";
8
9
  import { formatTimestamp, parseLatestMessage } from "../../utils/common";
9
10
  import { isGroupSession } from "../../utils/imCommon";
@@ -13,7 +14,8 @@ import MentionIndicatorIcon from "./MentionIndicatorIcon";
13
14
  const ConversationBySessionItem = ({ sessionItem, latestPreview, }) => {
14
15
  var _a;
15
16
  const { t } = useTranslation();
16
- const { user, onNavigate } = useChatContext();
17
+ const { user } = useChatContext();
18
+ const onNavigate = useChatNavigate();
17
19
  const isSelected = useConversationStore((state) => {
18
20
  var _a;
19
21
  return state.selectedConversationId ===
@@ -29,7 +31,7 @@ const ConversationBySessionItem = ({ sessionItem, latestPreview, }) => {
29
31
  newSearchParams.delete("userId");
30
32
  newSearchParams.delete("groupId");
31
33
  newSearchParams.delete("botId");
32
- onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(`${window.location.pathname}?${newSearchParams.toString()}`);
34
+ onNavigate(`${window.location.pathname}?${newSearchParams.toString()}`);
33
35
  setSelectedConversationId(conversation.conversationID);
34
36
  };
35
37
  const { avatar, displayName = "" } = useConversationDisplayData(conversation || null);
@@ -1 +1 @@
1
- {"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"AA4BA,QAAA,MAAM,oBAAoB,+CA2UzB,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"AA4BA,QAAA,MAAM,oBAAoB,+CAuVzB,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -31,13 +31,12 @@ const DeskConversationList = () => {
31
31
  const searchParams = useSearchParams();
32
32
  const setConversationData = useConversationStore((state) => state.setConversationData);
33
33
  const setSelectedConversationId = useConversationStore((state) => state.setSelectedConversationId);
34
- const conversationList = useConversationStore((state) => state.conversationList);
35
34
  const updateConversationList = useConversationStore((state) => state.updateConversationList);
36
35
  const filterSummary = useSessionStore((state) => state.filterSummary);
37
36
  const viewMode = useSessionStore((state) => state.viewMode);
38
37
  const isAllGroupsView = viewMode === "allGroups";
39
38
  const { data: teamSummaries } = useGetTeamSessionSummary();
40
- const { dataFlatten: rawSessions, hasNextPage, fetchNextPage, refetch, } = useGetSession({
39
+ const { dataFlatten: rawSessions, hasNextPage, fetchNextPage, refetch, isSyncing, } = useGetSession({
41
40
  filter: filterSummary,
42
41
  options: { isEnabled: !isAllGroupsView },
43
42
  });
@@ -89,7 +88,13 @@ const DeskConversationList = () => {
89
88
  return;
90
89
  }
91
90
  setSelectedConversationId(threadId);
92
- const selectedConversation = conversationList.find((conv) => conv.conversationID === threadId);
91
+ // Read the snapshot instead of subscribing — this effect must only
92
+ // re-run on threadId change. Depending on conversationList re-fired it
93
+ // on every unrelated mutation (including its own updateConversationList
94
+ // call below), looping the sidebar between loaded and loading.
95
+ const selectedConversation = useConversationStore
96
+ .getState()
97
+ .conversationList.find((conv) => conv.conversationID === threadId);
93
98
  // threadId can come from a notification/stale search click — force a
94
99
  // fresh membership check instead of trusting the groupID cache.
95
100
  if (selectedConversation) {
@@ -110,7 +115,7 @@ const DeskConversationList = () => {
110
115
  }
111
116
  });
112
117
  }
113
- }, [searchParams, conversationList]);
118
+ }, [searchParams]);
114
119
  useEffect(() => {
115
120
  const handler = (sessionUpdated) => {
116
121
  if (sessionUpdated.status !== filterSummary.status ||
@@ -131,7 +136,12 @@ const DeskConversationList = () => {
131
136
  }, className: "rounded-lg text-sm flex-1 h-[36px]", size: "large", allowClear: true, onClick: setShowSearchTrue, value: search, autoFocus: false, onClear: onCloseSearch }), showSearch ? (_jsx(Button, { onClick: onCloseSearch, variant: "outlined", className: "p-0 w-[36px] h-[36px] text-gray-500", children: _jsx(Icon, { icon: "close-b", size: 22 }) })) : (_jsx(Button, { "data-testid": "conversation-list-compose-icon", onClick: () => setIsComposeOpen(true), variant: "outlined", className: "p-0 w-[36px] h-[36px] shrink-0 text-gray-500", children: _jsx(Icon, { icon: "edit-b", size: 20 }) }))] }), _jsxs("div", { className: "flex items-center justify-between mt-2", children: [_jsx("span", { className: "text-sm font-semibold text-blue-500 truncate", children: pathName }), isAllGroupsView ? (_jsxs("span", { className: "flex items-center gap-1.5 shrink-0", children: [_jsx(Icon, { icon: "user-three-o", size: 16, className: "text-gray-500" }), _jsx("span", { className: "text-sm font-semibold text-gray-800", children: t("all_groups") })] })) : (filterConfig && (_jsxs("span", { className: "flex items-center gap-1.5 shrink-0", children: [_jsx(Icon, { icon: filterConfig.iconName, size: 16, className: "text-gray-500" }), _jsx("span", { className: "text-sm font-semibold text-gray-800", children: t(filterConfig.labelKey) })] })))] })] }), _jsxs("div", { className: "relative flex-1 min-h-0", children: [_jsx("div", { id: "scrollableConversationsDiv", style: {
132
137
  height: "100%",
133
138
  overflow: showSearch ? "hidden" : "auto",
134
- }, children: isAllGroupsView ? (isConversationGroupsLoading ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Spin, {}) })) : !(conversationGroups === null || conversationGroups === void 0 ? void 0 : conversationGroups.length) ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-gray-300" }), description: t("no_conversation") }) })) : (_jsx(InfiniteScroll, { dataLength: conversationGroups.length, next: fetchNextGroupsPage, hasMore: !!hasNextGroupsPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableConversationsDiv", children: conversationGroups.map((group) => (_jsx(ConversationByGroupItem, { group: group, latestPreview: groupLatestPreviewMap.get(group.conversationId) }, group.groupId))) }))) : (_jsx(InfiniteScroll, { dataLength: (sessions === null || sessions === void 0 ? void 0 : sessions.length) || 0, next: fetchNextPage, hasMore: hasNextPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableConversationsDiv", children: (sessions === null || sessions === void 0 ? void 0 : sessions.length) === 0 ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-gray-300" }), description: t("no_conversation") }) })) : ((_b = sessions === null || sessions === void 0 ? void 0 : sessions.map) === null || _b === void 0 ? void 0 : _b.call(sessions, (session) => (_jsx(ConversationBySessionItem, { sessionItem: session, latestPreview: latestPreviewMap.get(session.conversationId) }, session.conversationId)))) })) }), _jsx(Drawer, { open: showSearch, mask: false, closeIcon: false, styles: {
139
+ }, children: isAllGroupsView ? (isConversationGroupsLoading ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Spin, {}) })) : !(conversationGroups === null || conversationGroups === void 0 ? void 0 : conversationGroups.length) ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-gray-300" }), description: t("no_conversation") }) })) : (_jsx(InfiniteScroll, { dataLength: conversationGroups.length, next: fetchNextGroupsPage, hasMore: !!hasNextGroupsPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableConversationsDiv", children: conversationGroups.map((group) => (_jsx(ConversationByGroupItem, { group: group, latestPreview: groupLatestPreviewMap.get(group.conversationId) }, group.groupId))) }))) : (_jsx(InfiniteScroll, { dataLength: (sessions === null || sessions === void 0 ? void 0 : sessions.length) || 0, next: fetchNextPage, hasMore: hasNextPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableConversationsDiv", children: isSyncing ? (
140
+ // conversationList (the client cache dataFlatten joins
141
+ // against) hasn't caught up with this filter's BE response
142
+ // yet — showing the list now would flash a wrong, too-low
143
+ // count that corrects itself a moment later.
144
+ _jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Spin, {}) })) : (sessions === null || sessions === void 0 ? void 0 : sessions.length) === 0 ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-gray-300" }), description: t("no_conversation") }) })) : ((_b = sessions === null || sessions === void 0 ? void 0 : sessions.map) === null || _b === void 0 ? void 0 : _b.call(sessions, (session) => (_jsx(ConversationBySessionItem, { sessionItem: session, latestPreview: latestPreviewMap.get(session.conversationId) }, session.conversationId)))) })) }), _jsx(Drawer, { open: showSearch, mask: false, closeIcon: false, styles: {
135
145
  body: {
136
146
  padding: 0,
137
147
  },
@@ -4,13 +4,13 @@ import { useTranslation } from "react-i18next";
4
4
  import { Empty, Spin } from "antd";
5
5
  import InfiniteScroll from "react-infinite-scroll-component";
6
6
  import useConversationStore from "../../store/conversation";
7
- import { useChatContext } from "../../context/ChatContext";
7
+ import { useChatNavigate } from "../../hooks/conversation/useChatNavigate";
8
8
  import { Icon } from "../../components/icon";
9
9
  import ConversationInboxItem from "./ConversationInboxItem";
10
10
  const FooterSkeleton = () => (_jsxs("div", { className: "flex items-center gap-3 p-3", children: [_jsx("div", { className: "h-10 w-10 animate-pulse rounded-full bg-neutral-200" }), _jsxs("div", { className: "flex flex-1 flex-col gap-2", children: [_jsx("div", { className: "h-3 w-1/2 animate-pulse rounded bg-neutral-200" }), _jsx("div", { className: "h-2.5 w-1/3 animate-pulse rounded bg-neutral-200" })] })] }));
11
11
  const ConversationInboxList = ({ items, isLoading, isFetchingNextPage, hasNextPage, onLoadMore, onSelectThread, }) => {
12
12
  const { t } = useTranslation("conversation-inbox");
13
- const { onNavigate } = useChatContext();
13
+ const onNavigate = useChatNavigate();
14
14
  const setConversationData = useConversationStore((state) => state.setConversationData);
15
15
  const setSelectedConversationId = useConversationStore((state) => state.setSelectedConversationId);
16
16
  const handleSelectThread = (item) => {
@@ -19,7 +19,7 @@ const ConversationInboxList = ({ items, isLoading, isFetchingNextPage, hasNextPa
19
19
  newSearchParams.delete("userId");
20
20
  newSearchParams.delete("groupId");
21
21
  newSearchParams.delete("botId");
22
- onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(`${window.location.pathname}?${newSearchParams.toString()}`);
22
+ onNavigate(`${window.location.pathname}?${newSearchParams.toString()}`);
23
23
  setSelectedConversationId(item.conversationID);
24
24
  setConversationData(item);
25
25
  onSelectThread === null || onSelectThread === void 0 ? void 0 : onSelectThread(item);
@@ -1 +1 @@
1
- {"version":3,"file":"ContactSearchPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/ContactSearchPanel.tsx"],"names":[],"mappings":"AA8BA,UAAU,uBAAuB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,QAAA,MAAM,kBAAkB,GAAI,mBAAmB,uBAAuB,4CAuTrE,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"ContactSearchPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/ContactSearchPanel.tsx"],"names":[],"mappings":"AA+BA,UAAU,uBAAuB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,QAAA,MAAM,kBAAkB,GAAI,mBAAmB,uBAAuB,4CA2TrE,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -6,9 +6,10 @@ import { useDebounce } from "ahooks";
6
6
  import InfiniteScroll from "react-infinite-scroll-component";
7
7
  import { useTranslation } from "react-i18next";
8
8
  import { Icon } from "../../../components/icon";
9
- import { useChatContext } from "../../../context/ChatContext";
9
+ import { useChatNavigate } from "../../../hooks/conversation/useChatNavigate";
10
10
  import { useSearchContacts } from "../../../hooks/search/useSearchContacts";
11
11
  import { useSearchPublicGroups } from "../../../hooks/search/useSearchPublicGroups";
12
+ import useConversationStore from "../../../store/conversation";
12
13
  import ContactSearchFilters from "./ContactSearchFilters";
13
14
  import ContactSearchResultItem from "./ContactSearchResultItem";
14
15
  import ContactSearchSectionHeader from "./ContactSearchSectionHeader";
@@ -17,12 +18,13 @@ import ContactSearchTabs from "./ContactSearchTabs";
17
18
  import ContactSearchViewMoreBar from "./ContactSearchViewMoreBar";
18
19
  import PublicGroupResultItem from "./PublicGroupResultItem";
19
20
  import { CONTACT_SEARCH_DEBOUNCE_MS, CONTACT_SEARCH_ICON_SIZE, CONTACT_SEARCH_PREVIEW_LIMIT, ContactSearchTab, } from "./constants";
20
- import { buildContactSearchParams } from "./utils";
21
+ import { buildContactSearchParams, resolveContactSearchTarget } from "./utils";
21
22
  const SCROLL_TARGET_ID = "contact-search-scroll";
22
23
  const COMMUNITY_SCROLL_TARGET_ID = "contact-search-community-scroll";
23
24
  const ContactSearchPanel = ({ open, onClose }) => {
24
25
  const { t } = useTranslation("conversation-inbox");
25
- const { onNavigate } = useChatContext();
26
+ const onNavigate = useChatNavigate();
27
+ const setPendingResolveTarget = useConversationStore((state) => state.setPendingResolveTarget);
26
28
  const [keyword, setKeyword] = useState("");
27
29
  const [activeType, setActiveType] = useState();
28
30
  const [activeTab, setActiveTab] = useState(ContactSearchTab.All);
@@ -49,18 +51,18 @@ const ContactSearchPanel = ({ open, onClose }) => {
49
51
  enabled: open && activeTab === ContactSearchTab.Community,
50
52
  });
51
53
  const handleOpenGroup = (groupID) => {
52
- const params = new URLSearchParams(window.location.search);
53
- params.delete("threadId");
54
- params.delete("userId");
55
- params.delete("botId");
56
- params.set("groupId", groupID);
54
+ const target = { groupId: groupID };
55
+ setPendingResolveTarget(target);
56
+ const params = buildContactSearchParams(target);
57
57
  onClose();
58
- onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(`${window.location.pathname}?${params.toString()}`);
58
+ onNavigate(`${window.location.pathname}?${params.toString()}`);
59
59
  };
60
60
  const handleSelectContact = (contact) => {
61
- const params = buildContactSearchParams(contact);
61
+ const target = resolveContactSearchTarget(contact);
62
+ setPendingResolveTarget(target);
63
+ const params = buildContactSearchParams(target);
62
64
  onClose();
63
- onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(`${window.location.pathname}?${params.toString()}`);
65
+ onNavigate(`${window.location.pathname}?${params.toString()}`);
64
66
  };
65
67
  // Filters belong to the `Liên hệ` tab only, so leaving it drops the active
66
68
  // type — `Tất cả` always previews every contact type.
@@ -2,5 +2,11 @@ import { ContactSearchItem, ContactSearchType } from "../../../types/dto";
2
2
  import { PeerType } from "../../../types/conversation";
3
3
  export declare const resolveContactPeerType: (type: ContactSearchType) => PeerType;
4
4
  export declare const formatLastOnlineTime: (lastOnlineTime: string | null) => string;
5
- export declare const buildContactSearchParams: (contact: ContactSearchItem) => URLSearchParams;
5
+ export interface ContactSearchTarget {
6
+ userId?: string;
7
+ groupId?: string;
8
+ botId?: string;
9
+ }
10
+ export declare const resolveContactSearchTarget: (contact: ContactSearchItem) => ContactSearchTarget;
11
+ export declare const buildContactSearchParams: (target: ContactSearchTarget) => URLSearchParams;
6
12
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAahD,eAAO,MAAM,sBAAsB,GAAI,MAAM,iBAAiB,KAAG,QAChC,CAAC;AAIlC,eAAO,MAAM,oBAAoB,GAAI,gBAAgB,MAAM,GAAG,IAAI,KAAG,MACd,CAAC;AAIxD,eAAO,MAAM,wBAAwB,GACnC,SAAS,iBAAiB,KACzB,eA0BF,CAAC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAahD,eAAO,MAAM,sBAAsB,GAAI,MAAM,iBAAiB,KAAG,QAChC,CAAC;AAIlC,eAAO,MAAM,oBAAoB,GAAI,gBAAgB,MAAM,GAAG,IAAI,KAAG,MACd,CAAC;AAExD,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAKD,eAAO,MAAM,0BAA0B,GACrC,SAAS,iBAAiB,KACzB,mBAcF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,mBAAmB,KAC1B,eAYF,CAAC"}
@@ -15,30 +15,34 @@ export const resolveContactPeerType = (type) => CONTACT_TYPE_TO_PEER_TYPE[type];
15
15
  // The Vietnamese dayjs locale is already registered in the SDK layout, so
16
16
  // fromNow() renders "1 giờ trước" without extra setup here.
17
17
  export const formatLastOnlineTime = (lastOnlineTime) => lastOnlineTime ? dayjs(lastOnlineTime).fromNow() : "";
18
- // Each contact type resolves to a different route param; the conversation
18
+ // Each contact type resolves to a different target field; the conversation
19
19
  // detail screen turns it into an existing thread or the new-chat flow.
20
- export const buildContactSearchParams = (contact) => {
20
+ // Shared by both the URL-synced and bubble-mode (store-only) navigation paths.
21
+ export const resolveContactSearchTarget = (contact) => {
21
22
  var _a;
22
- const params = new URLSearchParams(window.location.search);
23
- params.delete("threadId");
24
- params.delete("userId");
25
- params.delete("groupId");
26
- params.delete("botId");
27
23
  if (contact.type === ContactSearchType.Group) {
28
- if (contact.targetGroupId)
29
- params.set("groupId", contact.targetGroupId);
30
- return params;
24
+ return contact.targetGroupId ? { groupId: contact.targetGroupId } : {};
31
25
  }
32
26
  // PDP bots carry their id in targetBotId; targetUserId is null for them.
33
27
  if (contact.type === ContactSearchType.PdpBot) {
34
- if (contact.targetBotId)
35
- params.set("botId", contact.targetBotId);
36
- return params;
28
+ return contact.targetBotId ? { botId: contact.targetBotId } : {};
37
29
  }
38
30
  // Individual contacts: sellers carry targetUserId, customers carry
39
31
  // targetCustomerId — the other field is null in each case.
40
32
  const peerId = (_a = contact.targetUserId) !== null && _a !== void 0 ? _a : contact.targetCustomerId;
41
- if (peerId)
42
- params.set("userId", peerId);
33
+ return peerId ? { userId: peerId } : {};
34
+ };
35
+ export const buildContactSearchParams = (target) => {
36
+ const params = new URLSearchParams(window.location.search);
37
+ params.delete("threadId");
38
+ params.delete("userId");
39
+ params.delete("groupId");
40
+ params.delete("botId");
41
+ if (target.groupId)
42
+ params.set("groupId", target.groupId);
43
+ if (target.botId)
44
+ params.set("botId", target.botId);
45
+ if (target.userId)
46
+ params.set("userId", target.userId);
43
47
  return params;
44
48
  };
@@ -1 +1 @@
1
- {"version":3,"file":"GroupMembersDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/message/GroupMembersDrawer.tsx"],"names":[],"mappings":"AAUA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAiB/C,UAAU,uBAAuB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA0CD,QAAA,MAAM,kBAAkB,GAAI,wDAKzB,uBAAuB,4CAwjBzB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"GroupMembersDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/message/GroupMembersDrawer.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAiB/C,UAAU,uBAAuB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA0CD,QAAA,MAAM,kBAAkB,GAAI,wDAKzB,uBAAuB,4CAwjBzB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -5,7 +5,7 @@ import ConfirmModal from "../../components/common/ConfirmModal";
5
5
  import { useEffect, useMemo, useState } from "react";
6
6
  import { useDebounce } from "ahooks";
7
7
  import { useTranslation } from "react-i18next";
8
- import { useChatContext } from "../../context/ChatContext";
8
+ import { useChatNavigate } from "../../hooks/conversation/useChatNavigate";
9
9
  import useAuthStore from "../../store/auth";
10
10
  import useConversationStore from "../../store/conversation";
11
11
  import { useGroupMembers } from "../../hooks/group/useGroupMembers";
@@ -34,7 +34,7 @@ const getMemberRoleLabel = ({ member, customerUserID, t, }) => {
34
34
  const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled = false, }) => {
35
35
  var _a, _b;
36
36
  const { t } = useTranslation();
37
- const { onNavigate } = useChatContext();
37
+ const onNavigate = useChatNavigate();
38
38
  const [isOpen, setIsOpen] = useState(false);
39
39
  const [showLeaveConfirm, setShowLeaveConfirm] = useState(false);
40
40
  const [showAddMember, setShowAddMember] = useState(false);
@@ -244,7 +244,7 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
244
244
  try {
245
245
  await leaveConversation({ conversationId });
246
246
  message.success(t("leave_group_success"));
247
- onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(window.location.pathname);
247
+ onNavigate(window.location.pathname);
248
248
  setShowLeaveConfirm(false);
249
249
  setIsOpen(false);
250
250
  }
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AAgCA,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAgU3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AAiCA,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAkV3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -24,6 +24,7 @@ import { useJoinGroupFlow } from "../../hooks/session/useJoinGroupFlow";
24
24
  import { useGroupPermission } from "../../hooks/group/useGroupPermission";
25
25
  import { isGroupConversation } from "../../utils/imCommon";
26
26
  import { ConnectStatus, SessionStatus } from "../../types/chat";
27
+ import { PeerType } from "../../types/conversation";
27
28
  import PinLimitModal from "../../components/conversation-detail/PinLimitModal";
28
29
  import ConfirmModal from "../../components/common/ConfirmModal";
29
30
  dayjs.extend(isToday);
@@ -34,29 +35,36 @@ const MessageList = (props) => {
34
35
  const conversationData = useConversationStore((state) => state.conversationData);
35
36
  const permissions = useGroupPermission(conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID);
36
37
  const isGroupChat = isGroupConversation(conversationData !== null && conversationData !== void 0 ? conversationData : undefined);
38
+ // A CX/support session can ride a Group session with peerType missing on
39
+ // the instant response (see isGroupConversation) — non-member/mute checks
40
+ // must not fire off that loose signal, or a support thread with no group
41
+ // membership record gets wrongly treated as a blocked group member.
42
+ const isConfirmedGroup = (conversationData === null || conversationData === void 0 ? void 0 : conversationData.peerType) === PeerType.GROUP;
37
43
  const groupID = conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID;
38
44
  const currentGroupInfo = useConversationStore((state) => groupID ? state.currentGroupInfo.get(groupID) : undefined);
39
45
  const currentMemberInGroup = useConversationStore((state) => groupID ? state.currentMemberInGroup.get(groupID) : undefined);
40
46
  const isViewOnlyMember = useConversationStore((state) => state.isViewOnlyMember);
47
+ const setIsViewOnlyMember = useConversationStore((state) => state.setIsViewOnlyMember);
41
48
  // "Fresh" now means "we have a cache entry for this groupID" — populated
42
49
  // either by a fetch just now or a cache hit from earlier in the session,
43
50
  // not necessarily a response that JUST arrived (that's the point of the
44
51
  // groupID cache: no spinner on reopening a previously-viewed group).
45
52
  const isGroupInfoFresh = currentGroupInfo !== undefined;
46
- const isGroupStateFresh = !isGroupChat ||
53
+ const isGroupStateFresh = !isConfirmedGroup ||
47
54
  (isGroupInfoFresh &&
48
55
  (!currentMemberInGroup || currentMemberInGroup.roleLevel !== undefined));
49
56
  // Group-wide mute (GroupThreadInfo.tsx) restricts Normal members only.
50
- const isRestrictedByGroupMute = isGroupChat &&
57
+ const isRestrictedByGroupMute = isConfirmedGroup &&
51
58
  (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.status) === GroupStatus.Muted &&
52
59
  (currentMemberInGroup === null || currentMemberInGroup === void 0 ? void 0 : currentMemberInGroup.roleLevel) === GroupMemberRole.Normal;
53
- // useGroupPermission defaults to Normal-role (sendMessage: true) when
54
- // currentMemberInGroup is undefined — wrong once we know it's absent
55
- // for good, not just not-loaded-yet.
56
- const isConfirmedNonMember = isGroupChat && isGroupInfoFresh && !currentMemberInGroup;
57
- const canSendMessage = !isGroupChat ||
60
+ // useGroupPermission defaults to Normal-role when currentMemberInGroup
61
+ // is undefined — wrong once absent for good, not just not-loaded-yet.
62
+ // Gated on isConfirmedGroup: a support thread has no membership record
63
+ // by design, not because it's a removed member.
64
+ const isConfirmedNonMember = isConfirmedGroup && isGroupInfoFresh && !currentMemberInGroup;
65
+ const canSendMessage = !isConfirmedGroup ||
58
66
  (!isConfirmedNonMember && permissions.sendMessage && !isRestrictedByGroupMute);
59
- const canPinMessage = !isGroupChat || (!isConfirmedNonMember && permissions.pinMessage);
67
+ const canPinMessage = !isConfirmedGroup || (!isConfirmedNonMember && permissions.pinMessage);
60
68
  const setQuotedMessage = useConversationStore((state) => state.setQuotedMessage);
61
69
  const setSearchClientMsgID = useConversationStore((state) => state.setSearchClientMsgID);
62
70
  const [openMenuId, setOpenMenuId] = useState(null);
@@ -71,16 +79,24 @@ const MessageList = (props) => {
71
79
  const pinnedTotalCount = useConversationStore((state) => state.pinnedTotalCount);
72
80
  const pinnedRemainingCount = useConversationStore((state) => state.pinnedRemainingCount);
73
81
  const [pinLimitModalOpen, setPinLimitModalOpen] = useState(false);
74
- const { groupId, latestConversationSession, refetchConversationSessions, } = useConversationSessionState({
82
+ const { rawGroupId, latestConversationSession, refetchConversationSessions, } = useConversationSessionState({
75
83
  conversationId,
76
84
  conversationType: conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationType,
77
85
  peerType: conversationData === null || conversationData === void 0 ? void 0 : conversationData.peerType,
78
86
  groupId: conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID,
79
87
  });
80
88
  const { isJoined, isCheckingMembership, shouldRestrictToLastSession, joinGroup, isJoining, } = useJoinGroupFlow({
81
- groupId,
89
+ groupId: rawGroupId,
82
90
  sessionId: latestConversationSession === null || latestConversationSession === void 0 ? void 0 : latestConversationSession.id,
83
91
  });
92
+ // A stale isViewOnlyMember=true only ever clears via the WASM join-group
93
+ // path or a WS event — neither fires for a support thread joined via
94
+ // useJoinSession's REST call. Sync it off once isJoined confirms true.
95
+ useEffect(() => {
96
+ if (!isConfirmedGroup && isJoined && isViewOnlyMember) {
97
+ setIsViewOnlyMember(false);
98
+ }
99
+ }, [isConfirmedGroup, isJoined, isViewOnlyMember, setIsViewOnlyMember]);
84
100
  const messageController = useMessage(conversationId, searchClientMsgID);
85
101
  const { loadState, moreOldLoading, moreNewLoading } = messageController;
86
102
  const { scrollRef, loadMoreOldMessage, handleInfiniteScroll, scrollToBottom, scrollToMessage, } = useMessageListScroll(Object.assign({ conversationId, userId: user === null || user === void 0 ? void 0 : user.userID, isGroupChat }, messageController));
@@ -164,7 +180,7 @@ const MessageList = (props) => {
164
180
  if (!conversationData) {
165
181
  return (_jsx("div", { className: "flex flex-1 items-center justify-center h-full", children: connectStatus !== ConnectStatus.Connected ? (_jsx(Spin, {})) : (_jsx(Empty, { description: t("no_conversation_data") })) }));
166
182
  }
167
- return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full min-w-0 overflow-hidden bg-white", "data-cp-name": "MessageList", children: [_jsx(MessageHeader, { onClose: onClose, currentSession: latestConversationSession, isJoined: isJoined, onOpenThreadInfo: onOpenThreadInfo }), _jsx(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, children: loadState.messageList.map((message, _, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat, contextMenuOpen: openMenuId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenMenuId(open ? message.clientMsgID : null), onRevokeMessage: handleOpenRevoke, onQuoteMessage: setQuotedMessage, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined, timeRevealed: revealedTimeMsgId === message.clientMsgID, onToggleTimeReveal: handleToggleTimeReveal }, message.clientMsgID))) }), moreNewLoading && (_jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) })), (isGroupChat && isCheckingMembership) || !isGroupStateFresh ? (_jsx("div", { className: "border-t bg-white py-4 flex items-center justify-center", children: _jsx(Spin, {}) })) : (latestConversationSession === null || latestConversationSession === void 0 ? void 0 : latestConversationSession.status) === SessionStatus.UNASSIGNED ? (_jsx(UnassignedSessionFooter, { sessionId: latestConversationSession.id, teamId: latestConversationSession.teamId })) : !isGroupChat && shouldRestrictToLastSession ? (_jsxs("div", { className: "border-t bg-white py-4 flex flex-col items-center gap-2", children: [_jsx("p", { className: "text-sm text-gray-500", children: t("join_group_required") }), _jsx(Button, { type: "primary", onClick: joinGroup, loading: isJoining, children: t("join_group") })] })) : isViewOnlyMember ? (
183
+ return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full min-w-0 overflow-hidden bg-white", "data-cp-name": "MessageList", children: [_jsx(MessageHeader, { onClose: onClose, currentSession: latestConversationSession, isJoined: isJoined, onOpenThreadInfo: onOpenThreadInfo }), _jsx(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, children: loadState.messageList.map((message, _, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat, contextMenuOpen: openMenuId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenMenuId(open ? message.clientMsgID : null), onRevokeMessage: handleOpenRevoke, onQuoteMessage: setQuotedMessage, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined, timeRevealed: revealedTimeMsgId === message.clientMsgID, onToggleTimeReveal: handleToggleTimeReveal }, message.clientMsgID))) }), moreNewLoading && (_jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) })), (Boolean(rawGroupId) && isCheckingMembership) || !isGroupStateFresh ? (_jsx("div", { className: "border-t bg-white py-4 flex items-center justify-center", children: _jsx(Spin, {}) })) : (latestConversationSession === null || latestConversationSession === void 0 ? void 0 : latestConversationSession.status) === SessionStatus.UNASSIGNED ? (_jsx(UnassignedSessionFooter, { sessionId: latestConversationSession.id, teamId: latestConversationSession.teamId })) : !isConfirmedGroup && shouldRestrictToLastSession ? (_jsxs("div", { className: "border-t bg-white py-4 flex flex-col items-center gap-2", "data-cp-name": "JoinGroupRequired", children: [_jsx("p", { className: "text-sm text-gray-500", children: t("join_group_required") }), _jsx(Button, { type: "primary", onClick: joinGroup, loading: isJoining, children: t("join_group") })] })) : isViewOnlyMember ? (
168
184
  // Brief fallback while isJoined catches up after the kick event.
169
185
  _jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })) : !canSendMessage ? (_jsx("div", { className: "border-t bg-[#f5f5f5] py-3 flex items-center justify-center", children: _jsx("p", { className: "text-sm text-gray-500", children: t(isRestrictedByGroupMute ? "send_message_disabled_group_mute" : "send_message_disabled") }) })) : (_jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })), _jsx(ConfirmModal, { open: showConfirmRevoke, onOk: onRevokeMessage, onCancel: handleCloseRevoke, title: t("revoke_message_confirm_title"), okText: t("revoke"), cancelText: t("cancel"), okType: "danger", confirmLoading: isRevoking, children: _jsx("p", { children: t("revoke_message_confirm_message") }) }), _jsx(PinLimitModal, { open: pinLimitModalOpen, max: pinnedTotalCount, onClose: () => setPinLimitModalOpen(false) })] }));
170
186
  };
@@ -3,14 +3,14 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useTranslation } from "react-i18next";
4
4
  import { GroupVisibility } from "@droppii-org/wasm-client-sdk";
5
5
  import useConversationStore from "../../../store/conversation";
6
- import { useChatContext } from "../../../context/ChatContext";
6
+ import { useChatNavigate } from "../../../hooks/conversation/useChatNavigate";
7
7
  import { highlightSearch, sanitizeHtml } from "../../../utils/common";
8
8
  import { Icon } from "../../../components/icon";
9
9
  import GroupCompositeAvatar from "../../../components/group/GroupCompositeAvatar";
10
10
  const SearchItemAsGroup = (props) => {
11
11
  const { group, searchTerm = "" } = props;
12
12
  const { t } = useTranslation();
13
- const { onNavigate } = useChatContext();
13
+ const onNavigate = useChatNavigate();
14
14
  const setConversationData = useConversationStore((state) => state.setConversationData);
15
15
  const setSelectedConversationId = useConversationStore((state) => state.setSelectedConversationId);
16
16
  const { conversation } = group;
@@ -24,7 +24,7 @@ const SearchItemAsGroup = (props) => {
24
24
  newSearchParams.delete("userId");
25
25
  newSearchParams.delete("groupId");
26
26
  newSearchParams.delete("botId");
27
- onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(`${window.location.pathname}?${newSearchParams.toString()}`);
27
+ onNavigate(`${window.location.pathname}?${newSearchParams.toString()}`);
28
28
  };
29
29
  return (_jsxs("div", { className: "py-3 px-2 flex items-center gap-3 hover:bg-gray-100 hover:rounded-sm cursor-pointer border-b mx-1", onClick: onPressItem, "data-cp-name": "SearchItemAsGroup", children: [_jsx(GroupCompositeAvatar, { groupID: group.groupId, faceURL: conversation.faceURL, groupName: conversation.showName, size: 48 }), _jsxs("div", { className: "flex flex-col flex-1 min-w-0 gap-0.5", children: [_jsx("p", { className: "text-sm font-semibold truncate", dangerouslySetInnerHTML: {
30
30
  __html: sanitizeHtml(highlightSearch(conversation.showName || "", searchTerm)),
@@ -2,5 +2,5 @@ import "../styles/global.css";
2
2
  import { ChatContextType, ChatProviderProps } from "../types/chat";
3
3
  export declare const ChatContext: import("react").Context<ChatContextType>;
4
4
  export declare const useChatContext: () => ChatContextType;
5
- export declare const ChatProvider: ({ children, config, renderCustomMessage, onNavigate, renderGroupAccessModal, theme, }: ChatProviderProps) => import("react/jsx-runtime").JSX.Element;
5
+ export declare const ChatProvider: ({ children, config, renderCustomMessage, onNavigate, useUrlState, renderGroupAccessModal, theme, }: ChatProviderProps) => import("react/jsx-runtime").JSX.Element;
6
6
  //# sourceMappingURL=ChatContext.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatContext.tsx"],"names":[],"mappings":"AAEA,OAAO,sBAAsB,CAAC;AAG9B,OAAO,EACL,eAAe,EACf,iBAAiB,EAGlB,MAAM,cAAc,CAAC;AAStB,eAAO,MAAM,WAAW,0CAOtB,CAAC;AAEH,eAAO,MAAM,cAAc,uBAAgC,CAAC;AAS5D,eAAO,MAAM,YAAY,GAAI,uFAO1B,iBAAiB,4CAyHnB,CAAC"}
1
+ {"version":3,"file":"ChatContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatContext.tsx"],"names":[],"mappings":"AAEA,OAAO,sBAAsB,CAAC;AAG9B,OAAO,EACL,eAAe,EACf,iBAAiB,EAGlB,MAAM,cAAc,CAAC;AAUtB,eAAO,MAAM,WAAW,0CAQtB,CAAC;AAEH,eAAO,MAAM,cAAc,uBAAgC,CAAC;AAS5D,eAAO,MAAM,YAAY,GAAI,oGAQ1B,iBAAiB,4CAiInB,CAAC"}
@@ -6,6 +6,7 @@ import { ConnectStatus, SyncStatus, } from "../types/chat";
6
6
  import { DChatSDK } from "../constants/sdk";
7
7
  import MainLayout from "../layout";
8
8
  import useAuthStore from "../store/auth";
9
+ import { setUrlStateEnabled } from "../store/conversation";
9
10
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
10
11
  import { ConfigProvider } from "antd";
11
12
  const queryClient = new QueryClient();
@@ -16,6 +17,7 @@ export const ChatContext = createContext({
16
17
  getSelfUserInfo: () => { },
17
18
  updateConnectStatus: () => { },
18
19
  updateSyncStatus: () => { },
20
+ useUrlState: true,
19
21
  });
20
22
  export const useChatContext = () => useContext(ChatContext);
21
23
  const DEFAULT_THEME = {
@@ -24,8 +26,14 @@ const DEFAULT_THEME = {
24
26
  "--dc-color-primary-light": "#E6E9FF",
25
27
  "--dc-color-primary-focus": "#607CFB",
26
28
  };
27
- export const ChatProvider = ({ children, config, renderCustomMessage, onNavigate, renderGroupAccessModal, theme, }) => {
29
+ export const ChatProvider = ({ children, config, renderCustomMessage, onNavigate, useUrlState = true, renderGroupAccessModal, theme, }) => {
28
30
  var _a, _b, _c, _d;
31
+ // The store's clearSelectedConversation touches window.history directly
32
+ // (it has no React context access) — mirror the flag there so bubble mode
33
+ // doesn't clobber the host page's own URL.
34
+ useEffect(() => {
35
+ setUrlStateEnabled(useUrlState);
36
+ }, [useUrlState]);
29
37
  const themeStyle = {
30
38
  "--dc-color-primary": (_a = theme === null || theme === void 0 ? void 0 : theme.primary) !== null && _a !== void 0 ? _a : DEFAULT_THEME["--dc-color-primary"],
31
39
  "--dc-color-primary-hover": (_b = theme === null || theme === void 0 ? void 0 : theme.primaryHover) !== null && _b !== void 0 ? _b : DEFAULT_THEME["--dc-color-primary-hover"],
@@ -109,6 +117,7 @@ export const ChatProvider = ({ children, config, renderCustomMessage, onNavigate
109
117
  updateSyncStatus,
110
118
  renderCustomMessage,
111
119
  onNavigate,
120
+ useUrlState,
112
121
  renderGroupAccessModal,
113
122
  }, children: _jsx(ConfigProvider, { getPopupContainer: () => { var _a; return (_a = rootRef.current) !== null && _a !== void 0 ? _a : document.body; }, children: _jsx(QueryClientProvider, { client: queryClient, children: _jsx("div", { ref: rootRef, style: themeStyle, className: "contents", children: _jsx(MainLayout, { children: children }) }) }) }) }));
114
123
  };
@@ -0,0 +1,2 @@
1
+ export declare const useChatNavigate: () => (url: string) => void;
2
+ //# sourceMappingURL=useChatNavigate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useChatNavigate.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useChatNavigate.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,eAAe,cAIlB,MAAM,SAMf,CAAC"}
@@ -0,0 +1,14 @@
1
+ "use client";
2
+ import { useCallback } from "react";
3
+ import { useChatContext } from "../../context/ChatContext";
4
+ // Every call site builds a URL and hands it to onNavigate — wrapping that
5
+ // here means bubble-mode hosts (useUrlState: false) get a single place that
6
+ // no-ops instead of needing every call site to check the flag itself.
7
+ export const useChatNavigate = () => {
8
+ const { onNavigate, useUrlState = true } = useChatContext();
9
+ return useCallback((url) => {
10
+ if (!useUrlState)
11
+ return;
12
+ onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(url);
13
+ }, [onNavigate, useUrlState]);
14
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AAyCA,eAAO,MAAM,cAAc,YAsiB1B,CAAC"}
1
+ {"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AA0CA,eAAO,MAAM,cAAc,YA8iB1B,CAAC"}