@droppii-org/chat-sdk 0.7.6 → 0.7.8

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 (109) hide show
  1. package/dist/components/conversation/ThreadCard.d.ts +2 -1
  2. package/dist/components/conversation/ThreadCard.d.ts.map +1 -1
  3. package/dist/components/conversation/ThreadCard.js +2 -2
  4. package/dist/components/conversation-detail/ConversationDetailHeader.d.ts +5 -1
  5. package/dist/components/conversation-detail/ConversationDetailHeader.d.ts.map +1 -1
  6. package/dist/components/conversation-detail/ConversationDetailHeader.js +3 -3
  7. package/dist/components/conversation-detail/ConversationMoreOptionPanel.d.ts.map +1 -1
  8. package/dist/components/conversation-detail/ConversationMoreOptionPanel.js +3 -1
  9. package/dist/components/conversation-detail/ThreadProfilePanel.d.ts +5 -1
  10. package/dist/components/conversation-detail/ThreadProfilePanel.d.ts.map +1 -1
  11. package/dist/components/conversation-detail/ThreadProfilePanel.js +13 -3
  12. package/dist/components/conversation-detail/profile/PinThreadLimitModal.d.ts +7 -0
  13. package/dist/components/conversation-detail/profile/PinThreadLimitModal.d.ts.map +1 -0
  14. package/dist/components/conversation-detail/profile/PinThreadLimitModal.js +14 -0
  15. package/dist/components/conversation-detail/profile/ThreadProfileActionRows.d.ts +15 -0
  16. package/dist/components/conversation-detail/profile/ThreadProfileActionRows.d.ts.map +1 -0
  17. package/dist/components/conversation-detail/profile/ThreadProfileActionRows.js +20 -0
  18. package/dist/components/conversation-detail/profile/ThreadProfileQuickActions.d.ts +11 -0
  19. package/dist/components/conversation-detail/profile/ThreadProfileQuickActions.d.ts.map +1 -0
  20. package/dist/components/conversation-detail/profile/ThreadProfileQuickActions.js +36 -0
  21. package/dist/components/conversation-detail/thread-search/ThreadSearchContent.d.ts +13 -0
  22. package/dist/components/conversation-detail/thread-search/ThreadSearchContent.d.ts.map +1 -0
  23. package/dist/components/conversation-detail/thread-search/ThreadSearchContent.js +70 -0
  24. package/dist/components/conversation-detail/thread-search/ThreadSearchPanel.d.ts +11 -0
  25. package/dist/components/conversation-detail/thread-search/ThreadSearchPanel.d.ts.map +1 -0
  26. package/dist/components/conversation-detail/thread-search/ThreadSearchPanel.js +12 -0
  27. package/dist/components/conversation-detail/thread-search/ThreadSearchResultItem.d.ts +11 -0
  28. package/dist/components/conversation-detail/thread-search/ThreadSearchResultItem.d.ts.map +1 -0
  29. package/dist/components/conversation-detail/thread-search/ThreadSearchResultItem.js +24 -0
  30. package/dist/components/conversation-detail/thread-search/utils.d.ts +8 -0
  31. package/dist/components/conversation-detail/thread-search/utils.d.ts.map +1 -0
  32. package/dist/components/conversation-detail/thread-search/utils.js +17 -0
  33. package/dist/components/conversation-inbox/ConversationInboxItem.d.ts +3 -1
  34. package/dist/components/conversation-inbox/ConversationInboxItem.d.ts.map +1 -1
  35. package/dist/components/conversation-inbox/ConversationInboxItem.js +4 -6
  36. package/dist/components/conversation-inbox/ConversationInboxList.d.ts +3 -1
  37. package/dist/components/conversation-inbox/ConversationInboxList.d.ts.map +1 -1
  38. package/dist/components/conversation-inbox/ConversationInboxList.js +2 -2
  39. package/dist/components/conversation-inbox/ConversationThreadActions.d.ts +12 -0
  40. package/dist/components/conversation-inbox/ConversationThreadActions.d.ts.map +1 -0
  41. package/dist/components/conversation-inbox/ConversationThreadActions.js +58 -0
  42. package/dist/components/conversation-inbox/contact-search/ContactSearchCommunitySection.d.ts +10 -0
  43. package/dist/components/conversation-inbox/contact-search/ContactSearchCommunitySection.d.ts.map +1 -0
  44. package/dist/components/conversation-inbox/contact-search/ContactSearchCommunitySection.js +15 -0
  45. package/dist/components/conversation-inbox/contact-search/ContactSearchPanel.d.ts.map +1 -1
  46. package/dist/components/conversation-inbox/contact-search/ContactSearchPanel.js +39 -28
  47. package/dist/components/conversation-inbox/contact-search/constants.js +2 -2
  48. package/dist/components/conversation-inbox/index.d.ts +2 -2
  49. package/dist/components/conversation-inbox/index.d.ts.map +1 -1
  50. package/dist/components/conversation-inbox/index.js +1 -1
  51. package/dist/components/group/MemberPermissionPanel.d.ts.map +1 -1
  52. package/dist/components/group/MemberPermissionPanel.js +12 -23
  53. package/dist/components/message/toolbox/MessageToolboxMenu.d.ts +7 -0
  54. package/dist/components/message/toolbox/MessageToolboxMenu.d.ts.map +1 -1
  55. package/dist/components/message/toolbox/MessageToolboxMenu.js +1 -1
  56. package/dist/constants/moreOption.d.ts +1 -0
  57. package/dist/constants/moreOption.d.ts.map +1 -1
  58. package/dist/hooks/conversation/useBotFlow.d.ts.map +1 -1
  59. package/dist/hooks/conversation/useBotFlow.js +42 -23
  60. package/dist/hooks/conversation/useConversationTarget.d.ts +4 -0
  61. package/dist/hooks/conversation/useConversationTarget.d.ts.map +1 -0
  62. package/dist/hooks/conversation/useConversationTarget.js +23 -0
  63. package/dist/hooks/conversation/usePinConversation.d.ts +10 -0
  64. package/dist/hooks/conversation/usePinConversation.d.ts.map +1 -0
  65. package/dist/hooks/conversation/usePinConversation.js +52 -0
  66. package/dist/hooks/message/holdMessageHighlight.d.ts +6 -0
  67. package/dist/hooks/message/holdMessageHighlight.d.ts.map +1 -0
  68. package/dist/hooks/message/holdMessageHighlight.js +22 -0
  69. package/dist/hooks/message/useMessageListScroll.d.ts.map +1 -1
  70. package/dist/hooks/message/useMessageListScroll.js +13 -0
  71. package/dist/hooks/message/useSendMessage.d.ts +1 -1
  72. package/dist/hooks/message/useSendMessage.d.ts.map +1 -1
  73. package/dist/hooks/message/useSendMessage.js +358 -285
  74. package/dist/hooks/search/useSearchPublicGroups.d.ts +1 -0
  75. package/dist/hooks/search/useSearchPublicGroups.d.ts.map +1 -1
  76. package/dist/hooks/search/useSearchPublicGroups.js +6 -0
  77. package/dist/hooks/search/useSearchThreadMessages.d.ts +222 -0
  78. package/dist/hooks/search/useSearchThreadMessages.d.ts.map +1 -0
  79. package/dist/hooks/search/useSearchThreadMessages.js +60 -0
  80. package/dist/locales/vi/conversation-inbox.json +31 -0
  81. package/dist/screens/conversation-detail/index.d.ts +5 -1
  82. package/dist/screens/conversation-detail/index.d.ts.map +1 -1
  83. package/dist/screens/conversation-detail/index.js +17 -2
  84. package/dist/screens/conversation-inbox/index.d.ts +6 -0
  85. package/dist/screens/conversation-inbox/index.d.ts.map +1 -1
  86. package/dist/screens/conversation-inbox/index.js +3 -3
  87. package/dist/services/query.d.ts +3 -0
  88. package/dist/services/query.d.ts.map +1 -1
  89. package/dist/services/query.js +3 -0
  90. package/dist/services/routes.d.ts +2 -0
  91. package/dist/services/routes.d.ts.map +1 -1
  92. package/dist/services/routes.js +2 -0
  93. package/dist/store/conversation.d.ts.map +1 -1
  94. package/dist/store/conversation.js +10 -6
  95. package/dist/store/type.d.ts +3 -0
  96. package/dist/store/type.d.ts.map +1 -1
  97. package/dist/styles/global.css +1 -1
  98. package/dist/types/conversation.d.ts +8 -0
  99. package/dist/types/conversation.d.ts.map +1 -1
  100. package/dist/utils/common.d.ts +7 -0
  101. package/dist/utils/common.d.ts.map +1 -1
  102. package/dist/utils/common.js +42 -13
  103. package/dist/utils/sendQueue.d.ts +2 -0
  104. package/dist/utils/sendQueue.d.ts.map +1 -0
  105. package/dist/utils/sendQueue.js +14 -0
  106. package/package.json +1 -1
  107. package/dist/components/conversation-inbox/ConversationMuteSwipeAction.d.ts +0 -8
  108. package/dist/components/conversation-inbox/ConversationMuteSwipeAction.d.ts.map +0 -1
  109. package/dist/components/conversation-inbox/ConversationMuteSwipeAction.js +0 -31
@@ -13,7 +13,7 @@ import InboxBootStatus from "./InboxBootStatus";
13
13
  import InboxListSkeleton from "./InboxListSkeleton";
14
14
  const INBOX_SLOW_AFTER_MS = 8000;
15
15
  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" })] })] }));
16
- const ConversationInboxList = ({ items, isLoading, isFetchingNextPage, hasNextPage, onLoadMore, onSelectThread, }) => {
16
+ const ConversationInboxList = ({ items, isLoading, isFetchingNextPage, hasNextPage, onLoadMore, onSelectThread, showPinThread = false, }) => {
17
17
  const { t } = useTranslation("conversation-inbox");
18
18
  const onNavigate = useChatNavigate();
19
19
  const boot = useChatBootPhase({ slowAfterMs: INBOX_SLOW_AFTER_MS });
@@ -41,6 +41,6 @@ const ConversationInboxList = ({ items, isLoading, isFetchingNextPage, hasNextPa
41
41
  return (_jsxs("div", { "data-testid": "conversation-inbox-thread-list", className: "min-h-0 w-full flex-1 overflow-hidden", children: [!isListSettled && (_jsx(InboxBootStatus, { boot: boot.phase === "ready"
42
42
  ? Object.assign(Object.assign({}, boot), { phase: "sync", syncProgress: 100 }) : boot })), boot.phase !== "error" && _jsx(InboxListSkeleton, { rows: 6 })] }));
43
43
  }
44
- return (_jsx("div", { id: "scrollableConversationThreadsDiv", "data-testid": "conversation-inbox-thread-list", "data-cp-name": "ConversationInboxList", className: "min-h-0 w-full flex-1 overflow-auto", children: _jsx(InfiniteScroll, { dataLength: items.length, next: handleEndReached, hasMore: hasNextPage, loader: isFetchingNextPage ? _jsx(FooterSkeleton, {}) : null, scrollableTarget: "scrollableConversationThreadsDiv", children: items.length === 0 ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx("div", { className: "flex justify-center", children: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-[var(--dc-color-primary-light)]" }) }), description: t("empty.no_conversations") }) })) : (items.map((item) => (_jsx(ConversationInboxItem, { item: item, onClick: handleSelectThread }, item.conversationID)))) }) }));
44
+ return (_jsx("div", { id: "scrollableConversationThreadsDiv", "data-testid": "conversation-inbox-thread-list", "data-cp-name": "ConversationInboxList", className: "min-h-0 w-full flex-1 overflow-auto", children: _jsx(InfiniteScroll, { dataLength: items.length, next: handleEndReached, hasMore: hasNextPage, loader: isFetchingNextPage ? _jsx(FooterSkeleton, {}) : null, scrollableTarget: "scrollableConversationThreadsDiv", children: items.length === 0 ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx("div", { className: "flex justify-center", children: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-[var(--dc-color-primary-light)]" }) }), description: t("empty.no_conversations") }) })) : (items.map((item) => (_jsx(ConversationInboxItem, { item: item, onClick: handleSelectThread, showPinThread: showPinThread }, item.conversationID)))) }) }));
45
45
  };
46
46
  export default ConversationInboxList;
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ConversationItem } from "@droppii-org/wasm-client-sdk";
3
+ export interface ConversationThreadActionsProps {
4
+ conversation: ConversationItem;
5
+ isMuted: boolean;
6
+ /** Adds Ghim / Bỏ ghim to the swipe actions and a right-click menu on the card. */
7
+ showPin: boolean;
8
+ children: ReactNode;
9
+ }
10
+ declare const ConversationThreadActions: ({ conversation, isMuted, showPin, children, }: ConversationThreadActionsProps) => import("react/jsx-runtime").JSX.Element;
11
+ export default ConversationThreadActions;
12
+ //# sourceMappingURL=ConversationThreadActions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationThreadActions.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-inbox/ConversationThreadActions.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAcrE,MAAM,WAAW,8BAA8B;IAC7C,YAAY,EAAE,gBAAgB,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,mFAAmF;IACnF,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,QAAA,MAAM,yBAAyB,GAAI,+CAKhC,8BAA8B,4CAkHhC,CAAC;AAEF,eAAe,yBAAyB,CAAC"}
@@ -0,0 +1,58 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { Dropdown } from "antd";
5
+ import { useTranslation } from "react-i18next";
6
+ import SwipeableRow from "../../components/conversation/SwipeableRow";
7
+ import SwipeActionButton, { SWIPE_ACTION_WIDTH, } from "../../components/conversation/SwipeActionButton";
8
+ import { MESSAGE_TOOLBOX_OVERLAY_CLASSNAME, ToolboxMenuRow, } from "../../components/message/toolbox";
9
+ import PinThreadLimitModal from "../../components/conversation-detail/profile/PinThreadLimitModal";
10
+ import { useMuteConversation } from "../../hooks/session/useMuteConversation";
11
+ import { usePinConversation } from "../../hooks/conversation/usePinConversation";
12
+ import MuteConversationConfirmModal from "./MuteConversationConfirmModal";
13
+ const ConversationThreadActions = ({ conversation, isMuted, showPin, children, }) => {
14
+ const { t } = useTranslation("conversation-inbox");
15
+ const { muteConversation, unmuteConversation, isPending: isMutePending, } = useMuteConversation();
16
+ const { togglePin, isLimitOpen, closeLimit } = usePinConversation();
17
+ const [isMuteConfirmOpen, setIsMuteConfirmOpen] = useState(false);
18
+ const { conversationID } = conversation;
19
+ const isPinned = !!conversation.isPinned;
20
+ const muteLabel = t(isMuted ? "thread_card.mute.unmute_action" : "thread_card.mute.mute_action");
21
+ const pinLabel = t(isPinned ? "thread_card.pin.unpin_action" : "thread_card.pin.pin_action");
22
+ // Muting asks first; unmuting doesn't.
23
+ const handleToggleMute = () => {
24
+ if (isMuted) {
25
+ void unmuteConversation(conversationID);
26
+ return;
27
+ }
28
+ setIsMuteConfirmOpen(true);
29
+ };
30
+ const handleConfirmMute = () => {
31
+ setIsMuteConfirmOpen(false);
32
+ void muteConversation(conversationID);
33
+ };
34
+ const handleTogglePin = () => {
35
+ void togglePin(conversation);
36
+ };
37
+ const swipeRow = (_jsx(SwipeableRow, { actionsWidth: SWIPE_ACTION_WIDTH * (showPin ? 2 : 1), actions: (closeSwipe) => (_jsxs(_Fragment, { children: [_jsx(SwipeActionButton, { testId: "conversation-inbox-mute-action", icon: isMuted ? "bell-b" : "bell-off-b", label: muteLabel, onClick: () => {
38
+ closeSwipe();
39
+ handleToggleMute();
40
+ }, className: "bg-gray-400" }), showPin && (_jsx(SwipeActionButton, { testId: "conversation-inbox-pin-action", icon: isPinned ? "thumbtack-off-b" : "thumbtack-b", label: pinLabel, onClick: () => {
41
+ closeSwipe();
42
+ handleTogglePin();
43
+ }, className: "bg-[var(--dc-color-primary)]" }))] })), children: children }));
44
+ const menuItems = [
45
+ {
46
+ key: "mute",
47
+ label: (_jsx(ToolboxMenuRow, { icon: isMuted ? "bell-o" : "bell-off-o", label: muteLabel })),
48
+ onClick: handleToggleMute,
49
+ },
50
+ {
51
+ key: "pin",
52
+ label: (_jsx(ToolboxMenuRow, { icon: isPinned ? "thumbtack-off-o" : "thumbtack-o", label: pinLabel })),
53
+ onClick: handleTogglePin,
54
+ },
55
+ ];
56
+ return (_jsxs(_Fragment, { children: [showPin ? (_jsx(Dropdown, { menu: { items: menuItems }, trigger: ["contextMenu"], overlayClassName: MESSAGE_TOOLBOX_OVERLAY_CLASSNAME, children: _jsx("div", { "data-testid": "conversation-inbox-thread-actions", children: swipeRow }) })) : (swipeRow), _jsx(MuteConversationConfirmModal, { open: isMuteConfirmOpen, loading: isMutePending, onCancel: () => setIsMuteConfirmOpen(false), onConfirm: handleConfirmMute }), showPin && _jsx(PinThreadLimitModal, { open: isLimitOpen, onClose: closeLimit })] }));
57
+ };
58
+ export default ConversationThreadActions;
@@ -0,0 +1,10 @@
1
+ import type { SearchPublicGroupInfo } from "@droppii-org/wasm-client-sdk";
2
+ interface ContactSearchCommunitySectionProps {
3
+ groups: SearchPublicGroupInfo[];
4
+ total: number;
5
+ onOpenGroup: (groupID: string) => void;
6
+ onViewAll: () => void;
7
+ }
8
+ declare const ContactSearchCommunitySection: ({ groups, total, onOpenGroup, onViewAll, }: ContactSearchCommunitySectionProps) => import("react/jsx-runtime").JSX.Element;
9
+ export default ContactSearchCommunitySection;
10
+ //# sourceMappingURL=ContactSearchCommunitySection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContactSearchCommunitySection.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/ContactSearchCommunitySection.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAM1E,UAAU,kCAAkC;IAC1C,MAAM,EAAE,qBAAqB,EAAE,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAGD,QAAA,MAAM,6BAA6B,GAAI,4CAKpC,kCAAkC,4CAwBpC,CAAC;AAEF,eAAe,6BAA6B,CAAC"}
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useTranslation } from "react-i18next";
4
+ import ContactSearchSectionHeader from "./ContactSearchSectionHeader";
5
+ import ContactSearchViewMoreBar from "./ContactSearchViewMoreBar";
6
+ import PublicGroupResultItem from "./PublicGroupResultItem";
7
+ import { CONTACT_SEARCH_PREVIEW_LIMIT } from "./constants";
8
+ // `Tất cả` preview of the `Cộng đồng` tab: same cards, capped like contacts.
9
+ const ContactSearchCommunitySection = ({ groups, total, onOpenGroup, onViewAll, }) => {
10
+ const { t } = useTranslation("conversation-inbox");
11
+ // Guards a short server total so a full page still gets its hand-off.
12
+ const matchCount = Math.max(total, groups.length);
13
+ return (_jsxs("section", { "data-cp-name": "ContactSearchCommunitySection", children: [_jsx(ContactSearchSectionHeader, { title: t("contact_search.tabs.community"), count: matchCount || undefined, onSelect: onViewAll }), groups.slice(0, CONTACT_SEARCH_PREVIEW_LIMIT).map((group) => (_jsx(PublicGroupResultItem, { group: group, onOpen: onOpenGroup }, group.groupID))), matchCount > CONTACT_SEARCH_PREVIEW_LIMIT && (_jsx(ContactSearchViewMoreBar, { onSelect: onViewAll }))] }));
14
+ };
15
+ export default ContactSearchCommunitySection;
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"ContactSearchPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/ContactSearchPanel.tsx"],"names":[],"mappings":"AAgCA,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"}
@@ -10,6 +10,7 @@ import { useChatNavigate } from "../../../hooks/conversation/useChatNavigate";
10
10
  import { useSearchContacts } from "../../../hooks/search/useSearchContacts";
11
11
  import { useSearchPublicGroups } from "../../../hooks/search/useSearchPublicGroups";
12
12
  import useConversationStore from "../../../store/conversation";
13
+ import ContactSearchCommunitySection from "./ContactSearchCommunitySection";
13
14
  import ContactSearchFilters from "./ContactSearchFilters";
14
15
  import ContactSearchResultItem from "./ContactSearchResultItem";
15
16
  import ContactSearchSectionHeader from "./ContactSearchSectionHeader";
@@ -46,9 +47,12 @@ const ContactSearchPanel = ({ open, onClose }) => {
46
47
  type: activeType,
47
48
  enabled: open,
48
49
  });
50
+ // `Tất cả` and `Cộng đồng` share one query, so switching tabs hits the cache.
49
51
  const publicGroups = useSearchPublicGroups({
50
52
  searchTerm: trimmedKeyword,
51
- enabled: open && activeTab === ContactSearchTab.Community,
53
+ enabled: open &&
54
+ (activeTab === ContactSearchTab.All ||
55
+ activeTab === ContactSearchTab.Community),
52
56
  });
53
57
  const handleOpenGroup = (groupID) => {
54
58
  const target = { groupId: groupID };
@@ -72,46 +76,53 @@ const ContactSearchPanel = ({ open, onClose }) => {
72
76
  setActiveType(undefined);
73
77
  };
74
78
  const renderContactItems = (items) => items.map((contact) => (_jsx(ContactSearchResultItem, { contact: contact, searchTerm: trimmedKeyword, onSelect: handleSelectContact }, contact.id)));
75
- // Every contact tab shares the same not-ready states before it can render
76
- // results; returns null once there is a list to show.
79
+ const renderInitialState = (testId = "contact-search-initial") => (_jsx(ContactSearchStateMessage, { testId: testId, children: t("contact_search.initial") }));
80
+ const renderLoadingState = () => (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Spin, {}) }));
81
+ const renderFailedState = (testId = "contact-search-failed") => (_jsx(ContactSearchStateMessage, { testId: testId, children: t("contact_search.failed") }));
82
+ const renderNoResultState = (testId = "contact-search-no-result") => (_jsxs(ContactSearchStateMessage, { testId: testId, children: [t("contact_search.no_result_title"), _jsx("br", {}), t("contact_search.no_result_hint")] }));
83
+ // The `Liên hệ` tab's not-ready states before it can render results;
84
+ // returns null once there is a list to show.
77
85
  const renderPlaceholderState = () => {
78
- if (!trimmedKeyword) {
79
- return (_jsx(ContactSearchStateMessage, { testId: "contact-search-initial", children: t("contact_search.initial") }));
80
- }
81
- if (isLoading) {
82
- return (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Spin, {}) }));
83
- }
84
- if (isError) {
85
- return (_jsx(ContactSearchStateMessage, { testId: "contact-search-failed", children: t("contact_search.failed") }));
86
- }
87
- if (contacts.length === 0) {
88
- return (_jsxs(ContactSearchStateMessage, { testId: "contact-search-no-result", children: [t("contact_search.no_result_title"), _jsx("br", {}), t("contact_search.no_result_hint")] }));
89
- }
86
+ if (!trimmedKeyword)
87
+ return renderInitialState();
88
+ if (isLoading)
89
+ return renderLoadingState();
90
+ if (isError)
91
+ return renderFailedState();
92
+ if (contacts.length === 0)
93
+ return renderNoResultState();
90
94
  return null;
91
95
  };
92
- // `Tất cả` only previews the contact section and hands off to `Liên hệ`;
93
- // the message section belongs to the message-search ticket.
96
+ // `Tất cả` previews contacts, then public groups; the message section
97
+ // belongs to the message-search ticket and goes between the two.
94
98
  const renderAllTab = () => {
95
- const placeholder = renderPlaceholderState();
96
- if (placeholder)
97
- return placeholder;
98
- const hasMore = contacts.length > CONTACT_SEARCH_PREVIEW_LIMIT || !!hasNextPage;
99
+ if (!trimmedKeyword)
100
+ return renderInitialState();
101
+ if (isLoading || publicGroups.isLoading)
102
+ return renderLoadingState();
103
+ if (isError && publicGroups.isError)
104
+ return renderFailedState();
105
+ // A source that failed drops its own section instead of the whole tab.
106
+ const showContacts = !isError && contacts.length > 0;
107
+ const showGroups = !publicGroups.isError && publicGroups.groups.length > 0;
108
+ if (!showContacts && !showGroups)
109
+ return renderNoResultState();
110
+ const hasMoreContacts = contacts.length > CONTACT_SEARCH_PREVIEW_LIMIT || !!hasNextPage;
99
111
  const openContactsTab = () => handleChangeTab(ContactSearchTab.Contacts);
100
- return (_jsxs("div", { className: "flex-1 overflow-y-auto", children: [_jsx(ContactSearchSectionHeader, { title: t("contact_search.tabs.contacts"), count: totalElements || undefined, onSelect: openContactsTab }), renderContactItems(contacts.slice(0, CONTACT_SEARCH_PREVIEW_LIMIT)), hasMore && _jsx(ContactSearchViewMoreBar, { onSelect: openContactsTab })] }));
112
+ return (_jsxs("div", { className: "flex-1 overflow-y-auto", children: [showContacts && (_jsxs("section", { children: [_jsx(ContactSearchSectionHeader, { title: t("contact_search.tabs.contacts"), count: totalElements || undefined, onSelect: openContactsTab }), renderContactItems(contacts.slice(0, CONTACT_SEARCH_PREVIEW_LIMIT)), hasMoreContacts && (_jsx(ContactSearchViewMoreBar, { onSelect: openContactsTab }))] })), showGroups && (_jsx(ContactSearchCommunitySection, { groups: publicGroups.groups, total: publicGroups.total, onOpenGroup: handleOpenGroup, onViewAll: () => handleChangeTab(ContactSearchTab.Community) }))] }));
101
113
  };
102
114
  const renderContactList = () => (_jsx("div", { id: SCROLL_TARGET_ID, className: "flex-1 overflow-y-auto", children: _jsx(InfiniteScroll, { dataLength: contacts.length, next: fetchNextPage, hasMore: !!hasNextPage, scrollableTarget: SCROLL_TARGET_ID, loader: isFetchingNextPage ? (_jsx("div", { className: "flex items-center justify-center py-3", children: _jsx(Spin, { size: "small" }) })) : null, children: renderContactItems(contacts) }) }));
103
115
  const renderCommunityTab = () => {
104
116
  if (!trimmedKeyword) {
105
- return (_jsx(ContactSearchStateMessage, { testId: "contact-search-community-initial", children: t("contact_search.initial") }));
106
- }
107
- if (publicGroups.isLoading) {
108
- return (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Spin, {}) }));
117
+ return renderInitialState("contact-search-community-initial");
109
118
  }
119
+ if (publicGroups.isLoading)
120
+ return renderLoadingState();
110
121
  if (publicGroups.isError) {
111
- return (_jsx(ContactSearchStateMessage, { testId: "contact-search-community-failed", children: t("contact_search.failed") }));
122
+ return renderFailedState("contact-search-community-failed");
112
123
  }
113
124
  if (publicGroups.groups.length === 0) {
114
- return (_jsxs(ContactSearchStateMessage, { testId: "contact-search-community-no-result", children: [t("contact_search.no_result_title"), _jsx("br", {}), t("contact_search.no_result_hint")] }));
125
+ return renderNoResultState("contact-search-community-no-result");
115
126
  }
116
127
  return (_jsx("div", { id: COMMUNITY_SCROLL_TARGET_ID, className: "flex-1 overflow-y-auto", children: _jsx(InfiniteScroll, { dataLength: publicGroups.groups.length, next: publicGroups.fetchNextPage, hasMore: !!publicGroups.hasNextPage, scrollableTarget: COMMUNITY_SCROLL_TARGET_ID, loader: publicGroups.isFetchingNextPage ? (_jsx("div", { className: "flex items-center justify-center py-3", children: _jsx(Spin, { size: "small" }) })) : null, children: publicGroups.groups.map((group) => (_jsx(PublicGroupResultItem, { group: group, onOpen: handleOpenGroup }, group.groupID))) }) }));
117
128
  };
@@ -2,8 +2,8 @@ import { ContactSearchType } from "../../../types/dto";
2
2
  export const CONTACT_SEARCH_DEBOUNCE_MS = 300;
3
3
  export const CONTACT_SEARCH_ICON_SIZE = 18;
4
4
  export const CONTACT_SEARCH_AVATAR_SIZE = 48;
5
- // `Tất cả` shows a capped preview of the contact section before handing the
6
- // seller over to the full `Liên hệ` tab.
5
+ // `Tất cả` caps each section's preview (contacts, public groups) before handing
6
+ // the seller over to the full `Liên hệ` / `Cộng đồng` tab.
7
7
  export const CONTACT_SEARCH_PREVIEW_LIMIT = 5;
8
8
  export var ContactSearchTab;
9
9
  (function (ContactSearchTab) {
@@ -7,8 +7,8 @@ export { default as ConversationInboxItem } from "./ConversationInboxItem";
7
7
  export type { ConversationInboxItemProps } from "./ConversationInboxItem";
8
8
  export { default as ConversationInboxList } from "./ConversationInboxList";
9
9
  export type { ConversationInboxListProps } from "./ConversationInboxList";
10
- export { default as ConversationMuteSwipeAction } from "./ConversationMuteSwipeAction";
11
- export type { ConversationMuteSwipeActionProps } from "./ConversationMuteSwipeAction";
10
+ export { default as ConversationThreadActions } from "./ConversationThreadActions";
11
+ export type { ConversationThreadActionsProps } from "./ConversationThreadActions";
12
12
  export { default as MuteConversationConfirmModal } from "./MuteConversationConfirmModal";
13
13
  export type { MuteConversationConfirmModalProps } from "./MuteConversationConfirmModal";
14
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-inbox/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAE/E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAE5E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAE5E,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAE1E,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAE1E,OAAO,EAAE,OAAO,IAAI,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACvF,YAAY,EAAE,gCAAgC,EAAE,MAAM,+BAA+B,CAAC;AAEtF,OAAO,EAAE,OAAO,IAAI,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AACzF,YAAY,EAAE,iCAAiC,EAAE,MAAM,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-inbox/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAE/E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAE5E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAE5E,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAE1E,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAE1E,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACnF,YAAY,EAAE,8BAA8B,EAAE,MAAM,6BAA6B,CAAC;AAElF,OAAO,EAAE,OAAO,IAAI,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AACzF,YAAY,EAAE,iCAAiC,EAAE,MAAM,gCAAgC,CAAC"}
@@ -3,5 +3,5 @@ export { default as ConversationPeerAvatar } from "./ConversationPeerAvatar";
3
3
  export { default as ConversationInboxTitle } from "./ConversationInboxTitle";
4
4
  export { default as ConversationInboxItem } from "./ConversationInboxItem";
5
5
  export { default as ConversationInboxList } from "./ConversationInboxList";
6
- export { default as ConversationMuteSwipeAction } from "./ConversationMuteSwipeAction";
6
+ export { default as ConversationThreadActions } from "./ConversationThreadActions";
7
7
  export { default as MuteConversationConfirmModal } from "./MuteConversationConfirmModal";
@@ -1 +1 @@
1
- {"version":3,"file":"MemberPermissionPanel.d.ts","sourceRoot":"","sources":["../../../src/components/group/MemberPermissionPanel.tsx"],"names":[],"mappings":"AAqBA,UAAU,0BAA0B;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AA8BD,QAAA,MAAM,qBAAqB,GAAI,4BAA4B,0BAA0B,mDA0IpF,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"MemberPermissionPanel.d.ts","sourceRoot":"","sources":["../../../src/components/group/MemberPermissionPanel.tsx"],"names":[],"mappings":"AAoBA,UAAU,0BAA0B;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AA6BD,QAAA,MAAM,qBAAqB,GAAI,4BAA4B,0BAA0B,mDAgHpF,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useState } from "react";
4
4
  import { ConfigProvider, Switch, message } from "antd";
5
5
  import { useTranslation } from "react-i18next";
@@ -9,24 +9,22 @@ import { useSetMemberRolePermissions } from "../../hooks/group/useGroupMemberAct
9
9
  import { Icon } from "../../components/icon";
10
10
  import useAuthStore from "../../store/auth";
11
11
  import PermissionPanelCrm from "./PermissionPanelCrm";
12
- import DisableSendPermissionConfirm from "./DisableSendPermissionConfirm";
13
12
  import { ANTD_PRIMARY_COLOR_VAR } from "../../constants";
14
13
  import { GROUP_PERMISSION_KEY } from "../../constants/im";
15
14
  import { changedSwitches, commonSwitchStates, toRolePermissionChange, } from "../../utils/groupPermissionSwitches";
15
+ // No "Gửi tin nhắn" switch: members are muted from the group send toggle or one by one,
16
+ // and saving here never touches SEND_MESSAGE.
16
17
  const SWITCH_PERMISSION_KEYS = {
17
- sendMessage: [GROUP_PERMISSION_KEY.sendMessage],
18
18
  addMember: [GROUP_PERMISSION_KEY.addMember],
19
19
  pinMessage: [GROUP_PERMISSION_KEY.pinMessage],
20
20
  viewMemberInformation: [GROUP_PERMISSION_KEY.viewMemberInformation],
21
21
  };
22
22
  const SWITCH_LABELS = {
23
- sendMessage: "permission_send_message",
24
23
  addMember: "permission_add_member",
25
24
  pinMessage: "permission_pin_message",
26
25
  viewMemberInformation: "permission_view_member_info",
27
26
  };
28
27
  const SWITCH_DESCRIPTIONS = {
29
- sendMessage: "permission_desc_send_message",
30
28
  addMember: "permission_desc_add_member",
31
29
  pinMessage: "permission_desc_pin_message",
32
30
  viewMemberInformation: "permission_desc_view_member_info",
@@ -41,7 +39,6 @@ const MemberPermissionPanel = ({ open, groupID, onClose }) => {
41
39
  const { mutateAsync: setMemberRolePermissions } = useSetMemberRolePermissions();
42
40
  const isCrm = useAuthStore((state) => state.isCrm);
43
41
  const [isSaving, setIsSaving] = useState(false);
44
- const [isConfirmOpen, setIsConfirmOpen] = useState(false);
45
42
  const [edits, setEdits] = useState({});
46
43
  useEffect(() => {
47
44
  if (open)
@@ -52,17 +49,9 @@ const MemberPermissionPanel = ({ open, groupID, onClose }) => {
52
49
  const pending = Object.assign(Object.assign({}, baseline), edits);
53
50
  const changes = changedSwitches(baseline, pending);
54
51
  const hasChanges = Object.keys(changes).length > 0;
55
- const requestSave = () => {
56
- if (isCrm && changes.sendMessage === false) {
57
- setIsConfirmOpen(true);
58
- return;
59
- }
60
- void handleSave();
61
- };
62
52
  const handleSave = async () => {
63
53
  if (!groupID || !hasChanges)
64
54
  return;
65
- setIsConfirmOpen(false);
66
55
  setIsSaving(true);
67
56
  try {
68
57
  await setMemberRolePermissions(Object.assign({ groupID }, toRolePermissionChange(SWITCH_PERMISSION_KEYS, changes)));
@@ -80,15 +69,15 @@ const MemberPermissionPanel = ({ open, groupID, onClose }) => {
80
69
  if (!open)
81
70
  return null;
82
71
  if (isCrm) {
83
- return (_jsxs(_Fragment, { children: [_jsx(PermissionPanelCrm, { title: t("member_permission_row"), rows: SWITCHES.map((name) => ({
84
- key: name,
85
- label: t(SWITCH_LABELS[name]),
86
- description: t(SWITCH_DESCRIPTIONS[name]),
87
- checked: pending[name],
88
- disabled: isLoading,
89
- onChange: (checked) => setEdits((prev) => (Object.assign(Object.assign({}, prev), { [name]: checked }))),
90
- })), canSave: hasChanges && !isLoading, isSaving: isSaving, onBack: onClose, onSave: requestSave }), _jsx(DisableSendPermissionConfirm, { open: isConfirmOpen, title: t("disable_send_permission_title_all_members"), loading: isSaving, onConfirm: handleSave, onCancel: () => setIsConfirmOpen(false) })] }));
72
+ return (_jsx(PermissionPanelCrm, { title: t("member_permission_row"), rows: SWITCHES.map((name) => ({
73
+ key: name,
74
+ label: t(SWITCH_LABELS[name]),
75
+ description: t(SWITCH_DESCRIPTIONS[name]),
76
+ checked: pending[name],
77
+ disabled: isLoading,
78
+ onChange: (checked) => setEdits((prev) => (Object.assign(Object.assign({}, prev), { [name]: checked }))),
79
+ })), canSave: hasChanges && !isLoading, isSaving: isSaving, onBack: onClose, onSave: handleSave }));
91
80
  }
92
- return (_jsxs("div", { className: "absolute inset-0 bg-white flex flex-col z-10", "data-cp-name": "MemberPermissionPanel", children: [_jsxs("div", { className: "flex w-full items-center border-b border-gray-100 p-2", children: [_jsx("button", { type: "button", className: "flex shrink-0 cursor-pointer items-center justify-center p-2 text-[var(--dc-color-nav-icon)]", onClick: onClose, children: _jsx(Icon, { icon: "arrow-left-o", size: 24 }) }), _jsx("p", { className: "min-w-0 flex-1 truncate px-2 text-[18px] font-bold leading-[1.6] text-gray-900", children: t("member_permission_settings") })] }), _jsx("div", { className: "flex-1 overflow-y-auto", children: _jsx(ConfigProvider, { theme: { token: { colorPrimary: ANTD_PRIMARY_COLOR_VAR } }, children: _jsx("div", { className: "flex flex-col py-2", children: SWITCHES.map((name) => (_jsxs("div", { className: "flex items-center justify-between px-4 py-3", children: [_jsx("span", { className: "text-sm", children: t(SWITCH_LABELS[name]) }), _jsx(Switch, { checked: pending[name], disabled: isLoading, onChange: (checked) => setEdits((prev) => (Object.assign(Object.assign({}, prev), { [name]: checked }))) })] }, name))) }) }) }), _jsxs("div", { className: "flex items-center justify-end gap-2 px-4 py-3 border-t border-gray-100", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-full border border-gray-300 text-gray-700 text-sm font-medium", onClick: onClose, children: t("cancel") }), _jsx("button", { type: "button", className: "px-4 py-1.5 rounded-full bg-[var(--dc-color-primary)] text-white text-sm font-medium disabled:bg-gray-200 disabled:text-gray-400", disabled: !hasChanges || isSaving || isLoading, onClick: requestSave, children: t("save") })] }), _jsx(DisableSendPermissionConfirm, { open: isConfirmOpen, title: t("disable_send_permission_title_all_members"), loading: isSaving, onConfirm: handleSave, onCancel: () => setIsConfirmOpen(false) })] }));
81
+ return (_jsxs("div", { className: "absolute inset-0 bg-white flex flex-col z-10", "data-cp-name": "MemberPermissionPanel", children: [_jsxs("div", { className: "flex w-full items-center border-b border-gray-100 p-2", children: [_jsx("button", { type: "button", className: "flex shrink-0 cursor-pointer items-center justify-center p-2 text-[var(--dc-color-nav-icon)]", onClick: onClose, children: _jsx(Icon, { icon: "arrow-left-o", size: 24 }) }), _jsx("p", { className: "min-w-0 flex-1 truncate px-2 text-[18px] font-bold leading-[1.6] text-gray-900", children: t("member_permission_settings") })] }), _jsx("div", { className: "flex-1 overflow-y-auto", children: _jsx(ConfigProvider, { theme: { token: { colorPrimary: ANTD_PRIMARY_COLOR_VAR } }, children: _jsx("div", { className: "flex flex-col py-2", children: SWITCHES.map((name) => (_jsxs("div", { className: "flex items-center justify-between px-4 py-3", children: [_jsx("span", { className: "text-sm", children: t(SWITCH_LABELS[name]) }), _jsx(Switch, { checked: pending[name], disabled: isLoading, onChange: (checked) => setEdits((prev) => (Object.assign(Object.assign({}, prev), { [name]: checked }))) })] }, name))) }) }) }), _jsxs("div", { className: "flex items-center justify-end gap-2 px-4 py-3 border-t border-gray-100", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-full border border-gray-300 text-gray-700 text-sm font-medium", onClick: onClose, children: t("cancel") }), _jsx("button", { type: "button", className: "px-4 py-1.5 rounded-full bg-[var(--dc-color-primary)] text-white text-sm font-medium disabled:bg-gray-200 disabled:text-gray-400", disabled: !hasChanges || isSaving || isLoading, onClick: handleSave, children: t("save") })] })] }));
93
82
  };
94
83
  export default MemberPermissionPanel;
@@ -1,5 +1,12 @@
1
1
  import type { MenuProps } from "antd";
2
2
  import { MessageToolboxAction } from "./types";
3
3
  export declare const MESSAGE_TOOLBOX_OVERLAY_CLASSNAME: string;
4
+ interface ToolboxMenuRowProps {
5
+ icon: string;
6
+ label: string;
7
+ danger?: boolean;
8
+ }
9
+ export declare const ToolboxMenuRow: ({ icon, label, danger }: ToolboxMenuRowProps) => import("react/jsx-runtime").JSX.Element;
4
10
  export declare const buildMessageToolboxMenuItems: (actions: MessageToolboxAction[]) => NonNullable<MenuProps["items"]>;
11
+ export {};
5
12
  //# sourceMappingURL=MessageToolboxMenu.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MessageToolboxMenu.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/MessageToolboxMenu.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAKtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE/C,eAAO,MAAM,iCAAiC,QAS7C,CAAC;AAgDF,eAAO,MAAM,4BAA4B,GACvC,SAAS,oBAAoB,EAAE,KAC9B,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAO5B,CAAC"}
1
+ {"version":3,"file":"MessageToolboxMenu.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/MessageToolboxMenu.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAKtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE/C,eAAO,MAAM,iCAAiC,QAS7C,CAAC;AAEF,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAYD,eAAO,MAAM,cAAc,GAAI,yBAAiC,mBAAmB,4CA4BlF,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,SAAS,oBAAoB,EAAE,KAC9B,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAO5B,CAAC"}
@@ -13,7 +13,7 @@ const CRM_ICONS = {
13
13
  "link-o": "link",
14
14
  "trash-o": "undo",
15
15
  };
16
- const ToolboxMenuRow = ({ icon, label, danger = false }) => {
16
+ export const ToolboxMenuRow = ({ icon, label, danger = false }) => {
17
17
  const isCrm = useAuthStore((state) => state.isCrm);
18
18
  const crmIcon = isCrm ? CRM_ICONS[icon] : undefined;
19
19
  return (_jsxs("div", { "data-cp-name": "ToolboxMenuRow", className: "flex items-center gap-3", children: [crmIcon ? (_jsx(StrokeIcon, { name: crmIcon, size: 18, className: danger ? "text-danger-600" : "text-gray-600" })) : (_jsx(Icon, { icon: icon, size: 24, className: danger ? "text-danger-400" : "text-gray-900" })), _jsx("span", { className: clsx("text-base font-medium", danger ? "text-danger-400" : "text-gray-900"), children: label })] }));
@@ -11,6 +11,7 @@ export interface MoreOptionConfig {
11
11
  key: string;
12
12
  icon: string;
13
13
  labelKey: string;
14
+ /** false = still in development: the composer "+" panel doesn't show it. */
14
15
  enabled: boolean;
15
16
  onSelect?: () => void;
16
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"moreOption.d.ts","sourceRoot":"","sources":["../../src/constants/moreOption.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa;;;;;;;CAOhB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAE/E,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAAgB,EAmBlD,CAAC;AAEF,wBAAgB,iBAAiB,CAC/B,YAAY,GAAE,gBAAgB,EAAO,GACpC,gBAAgB,EAAE,CAEpB;AAED,eAAO,MAAM,YAAY,EAAE,gBAAgB,EAmBzC,CAAC"}
1
+ {"version":3,"file":"moreOption.d.ts","sourceRoot":"","sources":["../../src/constants/moreOption.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa;;;;;;;CAOhB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAE/E,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAAgB,EAmBlD,CAAC;AAEF,wBAAgB,iBAAiB,CAC/B,YAAY,GAAE,gBAAgB,EAAO,GACpC,gBAAgB,EAAE,CAEpB;AAED,eAAO,MAAM,YAAY,EAAE,gBAAgB,EAmBzC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useBotFlow.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useBotFlow.ts"],"names":[],"mappings":"AAMA,OAAO,EAKL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAgB9B,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,aAAa,CAAC;AAMtD,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;CACtB;AAsBD,eAAO,MAAM,UAAU,GAAI,kCAGxB,gBAAgB;;;;;;;;;;;;6BAuJqB,MAAM;;6BA0DN,eAAe;;;CAmDtD,CAAC"}
1
+ {"version":3,"file":"useBotFlow.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useBotFlow.ts"],"names":[],"mappings":"AAMA,OAAO,EAKL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAgB9B,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,aAAa,CAAC;AAMtD,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;CACtB;AAsBD,eAAO,MAAM,UAAU,GAAI,kCAGxB,gBAAgB;;;;;;;;;;;;6BAoKqB,MAAM;;6BA+DN,eAAe;;;CAmDtD,CAAC"}
@@ -104,9 +104,22 @@ export const useBotFlow = ({ conversationId, isBotThread, }) => {
104
104
  buttons: inputButtonsBlocks.flatMap((block) => block.data.buttons),
105
105
  } }) : null;
106
106
  const isInActiveCsSession = isInCsSession({ flowCurrentStep: activeStep });
107
- const isProcessing = runMutation.isPending ||
107
+ // Also true while a chosen label waits in the send queue behind earlier uploads,
108
+ // so a second click cannot run the same step twice.
109
+ const [isChoosing, setIsChoosing] = useState(false);
110
+ const isProcessing = isChoosing ||
111
+ runMutation.isPending ||
108
112
  resetMutation.isPending ||
109
113
  closeSessionMutation.isPending;
114
+ const whileChoosing = async (action) => {
115
+ setIsChoosing(true);
116
+ try {
117
+ await action();
118
+ }
119
+ finally {
120
+ setIsChoosing(false);
121
+ }
122
+ };
110
123
  const shouldShowButtons = !!currentButtonsBlock && !isProcessing && !pendingGroupId;
111
124
  const status = (activeStep === null || activeStep === void 0 ? void 0 : activeStep.type) === BotFlowNodeType.Start ? "not_started" : "running";
112
125
  const runStep = async (step, actionId = null) => {
@@ -144,15 +157,17 @@ export const useBotFlow = ({ conversationId, isBotThread, }) => {
144
157
  actionId,
145
158
  };
146
159
  const selectedButton = currentButtonsBlock === null || currentButtonsBlock === void 0 ? void 0 : currentButtonsBlock.data.buttons.find((button) => button.id === actionId);
147
- if (selectedButton) {
148
- await sendMergeMessage({
149
- plainText: selectedButton.label,
150
- richText: "",
151
- isInternal: false,
152
- flow,
153
- });
154
- }
155
- await runStep(activeStep, actionId);
160
+ await whileChoosing(async () => {
161
+ if (selectedButton) {
162
+ await sendMergeMessage({
163
+ plainText: selectedButton.label,
164
+ richText: "",
165
+ isInternal: false,
166
+ flow,
167
+ });
168
+ }
169
+ await runStep(activeStep, actionId);
170
+ });
156
171
  };
157
172
  const performRestart = async () => {
158
173
  var _a;
@@ -161,14 +176,16 @@ export const useBotFlow = ({ conversationId, isBotThread, }) => {
161
176
  childId: null,
162
177
  actionId: BOT_DEFAULT_BUTTON_ID,
163
178
  };
164
- await sendMergeMessage({
165
- plainText: t("thread_detail.bot.restart"),
166
- richText: "",
167
- isInternal: false,
168
- flow,
179
+ await whileChoosing(async () => {
180
+ await sendMergeMessage({
181
+ plainText: t("thread_detail.bot.restart"),
182
+ richText: "",
183
+ isInternal: false,
184
+ flow,
185
+ });
186
+ const resetDetail = await resetMutation.mutateAsync(conversationId);
187
+ await runStep(resetDetail.flowCurrentStep);
169
188
  });
170
- const resetDetail = await resetMutation.mutateAsync(conversationId);
171
- await runStep(resetDetail.flowCurrentStep);
172
189
  };
173
190
  const restart = async () => {
174
191
  if (isInActiveCsSession) {
@@ -180,13 +197,15 @@ export const useBotFlow = ({ conversationId, isBotThread, }) => {
180
197
  const runMenuOption = async (item) => {
181
198
  if (!activeStep)
182
199
  return;
183
- await sendMergeMessage({
184
- plainText: item.label,
185
- richText: "",
186
- isInternal: false,
187
- flow: { groupId: activeStep.id, childId: null, actionId: item.id },
200
+ await whileChoosing(async () => {
201
+ await sendMergeMessage({
202
+ plainText: item.label,
203
+ richText: "",
204
+ isInternal: false,
205
+ flow: { groupId: activeStep.id, childId: null, actionId: item.id },
206
+ });
207
+ await runStep(activeStep, item.id);
188
208
  });
189
- await runStep(activeStep, item.id);
190
209
  };
191
210
  const selectMenuOption = async (item) => {
192
211
  if (item.id === BOT_DEFAULT_BUTTON_ID) {
@@ -0,0 +1,4 @@
1
+ export declare const useConversationTarget: (conversationId: string, enabled: boolean) => {
2
+ isTargetActive: boolean;
3
+ };
4
+ //# sourceMappingURL=useConversationTarget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConversationTarget.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useConversationTarget.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,qBAAqB,GAChC,gBAAgB,MAAM,EACtB,SAAS,OAAO;;CAkBjB,CAAC"}
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { apiInstance } from "../../services/api";
4
+ import { ENDPOINTS } from "../../services/routes";
5
+ import { QUERY_KEYS } from "../../services/query";
6
+ // Whether the peer account is still active. Fails open like biz-mobile:
7
+ // a pending or failed lookup counts as active.
8
+ export const useConversationTarget = (conversationId, enabled) => {
9
+ var _a;
10
+ const query = useQuery({
11
+ queryKey: [QUERY_KEYS.GET_CONVERSATION_TARGET, conversationId],
12
+ queryFn: async () => {
13
+ const res = await apiInstance.get(ENDPOINTS.chatService.getConversationTarget(conversationId));
14
+ return res.data.data;
15
+ },
16
+ enabled: enabled && !!conversationId,
17
+ staleTime: 0,
18
+ retry: false,
19
+ refetchOnWindowFocus: false,
20
+ });
21
+ // isError first: a failed refetch keeps the previous data in react-query.
22
+ return { isTargetActive: query.isError || ((_a = query.data) === null || _a === void 0 ? void 0 : _a.isActive) !== false };
23
+ };
@@ -0,0 +1,10 @@
1
+ import type { ConversationItem } from "@droppii-org/wasm-client-sdk";
2
+ /** Thread pin cap, checked client-side like biz-mobile. */
3
+ export declare const MAX_PINNED_THREADS = 5;
4
+ export declare const usePinConversation: () => {
5
+ togglePin: (conversation: ConversationItem) => Promise<void>;
6
+ isPending: boolean;
7
+ isLimitOpen: boolean;
8
+ closeLimit: () => void;
9
+ };
10
+ //# sourceMappingURL=usePinConversation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePinConversation.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/usePinConversation.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAKrE,2DAA2D;AAC3D,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAMpC,eAAO,MAAM,kBAAkB;8BAUU,gBAAgB;;;;CAkCxD,CAAC"}