@droppii-org/chat-sdk 0.7.10 → 0.7.12

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 (51) hide show
  1. package/dist/components/conversation-detail/ConversationDetailInput.d.ts.map +1 -1
  2. package/dist/components/conversation-detail/ConversationDetailInput.js +19 -3
  3. package/dist/components/conversation-detail/ThreadProfilePanel.d.ts.map +1 -1
  4. package/dist/components/conversation-detail/ThreadProfilePanel.js +11 -2
  5. package/dist/components/conversation-detail/profile/GroupDescriptionCard.d.ts +7 -0
  6. package/dist/components/conversation-detail/profile/GroupDescriptionCard.d.ts.map +1 -0
  7. package/dist/components/conversation-detail/profile/GroupDescriptionCard.js +47 -0
  8. package/dist/components/conversation-detail/profile/GroupDescriptionPanel.d.ts +9 -0
  9. package/dist/components/conversation-detail/profile/GroupDescriptionPanel.d.ts.map +1 -0
  10. package/dist/components/conversation-detail/profile/GroupDescriptionPanel.js +11 -0
  11. package/dist/components/conversation-detail/profile/ThreadProfileActionRows.d.ts +3 -1
  12. package/dist/components/conversation-detail/profile/ThreadProfileActionRows.d.ts.map +1 -1
  13. package/dist/components/conversation-detail/profile/ThreadProfileActionRows.js +3 -2
  14. package/dist/components/conversation-detail/thread-search/ThreadSearchContent.d.ts +3 -1
  15. package/dist/components/conversation-detail/thread-search/ThreadSearchContent.d.ts.map +1 -1
  16. package/dist/components/conversation-detail/thread-search/ThreadSearchContent.js +2 -1
  17. package/dist/components/conversation-detail/thread-search/ThreadSearchPanel.d.ts +3 -1
  18. package/dist/components/conversation-detail/thread-search/ThreadSearchPanel.d.ts.map +1 -1
  19. package/dist/components/conversation-detail/thread-search/ThreadSearchPanel.js +2 -2
  20. package/dist/components/conversation-inbox/ConversationInboxHeader.d.ts +8 -1
  21. package/dist/components/conversation-inbox/ConversationInboxHeader.d.ts.map +1 -1
  22. package/dist/components/conversation-inbox/ConversationInboxHeader.js +20 -3
  23. package/dist/components/conversation-inbox/PublicGroupList.d.ts +3 -0
  24. package/dist/components/conversation-inbox/PublicGroupList.d.ts.map +1 -0
  25. package/dist/components/conversation-inbox/PublicGroupList.js +43 -0
  26. package/dist/components/conversation-inbox/contact-search/PublicGroupResultItem.d.ts +3 -1
  27. package/dist/components/conversation-inbox/contact-search/PublicGroupResultItem.d.ts.map +1 -1
  28. package/dist/components/conversation-inbox/contact-search/PublicGroupResultItem.js +12 -4
  29. package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
  30. package/dist/hooks/global/useGlobalEvent.js +5 -0
  31. package/dist/hooks/group/useGroupSendRestriction.d.ts +4 -0
  32. package/dist/hooks/group/useGroupSendRestriction.d.ts.map +1 -0
  33. package/dist/hooks/group/useGroupSendRestriction.js +31 -0
  34. package/dist/hooks/group/useJoinPublicGroup.d.ts +2 -0
  35. package/dist/hooks/group/useJoinPublicGroup.d.ts.map +1 -1
  36. package/dist/hooks/group/useJoinPublicGroup.js +44 -2
  37. package/dist/hooks/search/useSearchPublicGroups.d.ts +5 -1
  38. package/dist/hooks/search/useSearchPublicGroups.d.ts.map +1 -1
  39. package/dist/hooks/search/useSearchPublicGroups.js +5 -3
  40. package/dist/locales/vi/common.json +2 -1
  41. package/dist/locales/vi/conversation-inbox.json +8 -1
  42. package/dist/screens/conversation-detail/index.d.ts.map +1 -1
  43. package/dist/screens/conversation-detail/index.js +3 -5
  44. package/dist/screens/conversation-inbox/index.d.ts +2 -0
  45. package/dist/screens/conversation-inbox/index.d.ts.map +1 -1
  46. package/dist/screens/conversation-inbox/index.js +9 -2
  47. package/dist/store/conversation.d.ts.map +1 -1
  48. package/dist/store/conversation.js +15 -11
  49. package/dist/styles/global.css +1 -1
  50. package/dist/utils/messageLog.js +1 -1
  51. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationDetailInput.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ConversationDetailInput.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAoB/D,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,wEAAwE;IACxE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACpE;AAED,QAAA,MAAM,uBAAuB,GAAI,8GAQ9B,4BAA4B,4CAkxB9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"ConversationDetailInput.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ConversationDetailInput.tsx"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAoB/D,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,wEAAwE;IACxE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACpE;AAED,QAAA,MAAM,uBAAuB,GAAI,8GAQ9B,4BAA4B,4CA4yB9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
@@ -9,6 +9,7 @@ import { Icon } from "../../components/icon";
9
9
  import { useSendMessage } from "../../hooks/message/useSendMessage";
10
10
  import { useReportTypingState } from "../../hooks/message/useReportTypingState";
11
11
  import { useJoinPublicGroup } from "../../hooks/group/useJoinPublicGroup";
12
+ import { useGroupSendRestriction } from "../../hooks/group/useGroupSendRestriction";
12
13
  import { useAgentSupportThreadJoin } from "../../hooks/session/useAgentSupportThreadJoin";
13
14
  import useAuthStore from "../../store/auth";
14
15
  import useConversationStore from "../../store/conversation";
@@ -48,13 +49,14 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
48
49
  const getSelfGroupPermissionByReq = useConversationStore((state) => state.getSelfGroupPermissionByReq);
49
50
  const { mutate: joinPublicGroup, isPending: isJoiningPublicGroup } = useJoinPublicGroup();
50
51
  const supportThreadJoin = useAgentSupportThreadJoin(conversationData, onJoinSupportThread);
52
+ const groupSendState = useGroupSendRestriction(conversationData);
51
53
  const handleJoinPublicGroup = useCallback(() => {
52
54
  const groupID = conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID;
53
55
  if (!groupID)
54
56
  return;
55
57
  joinPublicGroup({ groupID }, {
56
58
  onSuccess: async () => {
57
- toastMessage.success(tCommon("join_group_success"));
59
+ toastMessage.success(tCommon("public_group_joined"));
58
60
  // updateCurrentConversation's revalidation only runs on a
59
61
  // conversationID change — this is the same conversation the user
60
62
  // was already viewing, so refresh membership/permissions directly
@@ -232,6 +234,17 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
232
234
  useEffect(() => {
233
235
  adjustTextareaHeight();
234
236
  }, [value, adjustTextareaHeight]);
237
+ // The textarea mounts only once a spinner/notice branch gives way, after the
238
+ // draft was restored; size it again so a multi-line draft isn't one row tall.
239
+ useEffect(() => {
240
+ requestAnimationFrame(adjustTextareaHeight);
241
+ }, [
242
+ groupSendState,
243
+ isViewOnlyMember,
244
+ supportThreadJoin.isCheckingMembership,
245
+ supportThreadJoin.needsJoin,
246
+ adjustTextareaHeight,
247
+ ]);
235
248
  useEffect(() => {
236
249
  if (!isFocused)
237
250
  return;
@@ -489,8 +502,8 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
489
502
  event.preventDefault();
490
503
  void handleSend();
491
504
  };
492
- // Don't flash a composer the agent may not be allowed to use.
493
- if (supportThreadJoin.isCheckingMembership) {
505
+ // Don't flash a composer the agent or member may not be allowed to use.
506
+ if (supportThreadJoin.isCheckingMembership || groupSendState === "checking") {
494
507
  return (_jsx("div", { "data-testid": "conversation-detail-input-checking", className: "flex w-full shrink-0 items-center justify-center bg-chat-bubble-neutral/[0.05] px-4 py-3", children: _jsx(Spin, { size: "small" }) }));
495
508
  }
496
509
  if (isViewOnlyMember || supportThreadJoin.needsJoin) {
@@ -500,6 +513,9 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
500
513
  ? supportThreadJoin.join
501
514
  : handleJoinPublicGroup, children: tCommon("join_group") })] }));
502
515
  }
516
+ if (groupSendState === "restricted") {
517
+ return (_jsx("div", { "data-testid": "conversation-detail-input-send-restricted", className: "flex w-full shrink-0 items-center justify-center bg-chat-bubble-neutral/[0.05] px-4 py-3", children: _jsx("span", { className: "text-sm text-gray-500", children: t("thread_detail.cannot_send_notice") }) }));
518
+ }
503
519
  return (_jsxs("div", { ref: containerRef, "data-testid": "conversation-detail-input", "data-cp-name": "ConversationDetailInput", className: "flex w-full shrink-0 flex-col bg-chat-bubble-neutral/[0.05]", children: [_jsx("input", { ref: mediaInputRef, type: "file", accept: "image/jpeg,image/png,image/jpg,video/*", multiple: true, className: "hidden", onChange: handleMediaInputChange }), _jsx("input", { ref: documentInputRef, type: "file", accept: ".pdf,.doc,.docx", multiple: true, className: "hidden", onChange: handleDocumentInputChange }), _jsx(ReplyPreviewBar, {}), listUploadFiles.length > 0 && (_jsx(FilePreviewList, { files: listUploadFiles, onRemove: (file) => handleRemoveFile(file.uid), onVideoReady: (uid) => setPendingVideoUids((prev) => prev.filter((p) => p !== uid)) })), showLinkPreview && previewUrl && (_jsx(LinkInputPreview, { url: previewUrl, onRemove: () => setPreviewDismissed(true) })), _jsxs("div", { className: "flex items-end gap-3 px-3 py-2", children: [_jsx("div", { className: "flex shrink-0 items-center", children: _jsx("button", { type: "button", onClick: handleToggleMoreOption, "aria-label": t("thread_detail.more_option.toggle"), "aria-expanded": isMoreOptionOpen, "data-testid": "conversation-detail-more-option-toggle", className: "flex py-3 items-center justify-center rounded-full", children: isMoreOptionOpen ? (_jsx(Icon, { icon: "close-circle-b", size: 24, className: "text-[var(--dc-color-primary)]" })) : (_jsx(Icon, { icon: "plus-circle-o", size: 24, className: "text-gray-900" })) }) }), _jsx("div", { className: clsx("flex min-w-0 flex-1 overflow-hidden rounded-xl border bg-white transition-shadow", isFocused
504
520
  ? "border-[var(--dc-color-primary-focus)] shadow-[0px_0px_2px_var(--dc-color-primary-focus)]"
505
521
  : "border-gray-50"), style: { minHeight: CONTAINER_MIN_HEIGHT }, children: _jsxs("div", { className: "flex min-w-0 flex-[1_0_0] items-center gap-2 self-stretch px-3 py-1", children: [onOpenBotMenu && (_jsx("button", { type: "button", onClick: onOpenBotMenu, "aria-label": t("thread_detail.bot.menu.open_menu"), "data-testid": "conversation-detail-open-bot-menu", className: "flex shrink-0 items-center justify-center", children: _jsx(Icon, { icon: "chat-suggest-o", size: 24, className: "text-gray-600" }) })), _jsx("textarea", { ref: textareaRef, value: value, rows: 1, onChange: (event) => {
@@ -1 +1 @@
1
- {"version":3,"file":"ThreadProfilePanel.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ThreadProfilePanel.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAGrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAc7E,UAAU,uBAAuB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gBAAgB,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACxD,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,mFAAmF;IACnF,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAID,QAAA,MAAM,kBAAkB,GAAI,uFAQzB,uBAAuB,4CAoJzB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"ThreadProfilePanel.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ThreadProfilePanel.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAGrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAe7E,UAAU,uBAAuB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gBAAgB,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACxD,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,mFAAmF;IACnF,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAID,QAAA,MAAM,kBAAkB,GAAI,uFAQzB,uBAAuB,4CAwKzB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -15,6 +15,7 @@ import LeaveGroupBlockedModal from "./LeaveGroupBlockedModal";
15
15
  import LeaveGroupConfirmModal from "./LeaveGroupConfirmModal";
16
16
  import ThreadProfileQuickActions from "./profile/ThreadProfileQuickActions";
17
17
  import ThreadProfileActionRows from "./profile/ThreadProfileActionRows";
18
+ import GroupDescriptionCard from "./profile/GroupDescriptionCard";
18
19
  const PROFILE_AVATAR_SIZE = 96;
19
20
  const PERSON_PEER_TYPES = [PeerType.USER, PeerType.CUSTOMER];
20
21
  const ThreadProfilePanel = ({ open, conversation, onClose, onSelectResource, showActions = false, onSearch, hideSearch = false, }) => {
@@ -23,6 +24,12 @@ const ThreadProfilePanel = ({ open, conversation, onClose, onSelectResource, sho
23
24
  const { displayName, avatarUrl, peerType, badgeLabel } = useConversationPeerIdentity(conversation);
24
25
  const isGroupThread = conversation.peerType === PeerType.GROUP;
25
26
  const isViewOnly = useConversationStore((state) => isGroupThread && state.isViewOnlyMember);
27
+ const groupDescription = useConversationStore((state) => {
28
+ var _a, _b, _c;
29
+ return isGroupThread && conversation.groupID
30
+ ? (_c = (_b = (_a = state.currentGroupInfo.get(conversation.groupID)) === null || _a === void 0 ? void 0 : _a.introduction) === null || _b === void 0 ? void 0 : _b.trim()) !== null && _c !== void 0 ? _c : ""
31
+ : "";
32
+ });
26
33
  const showPersonRows = PERSON_PEER_TYPES.includes((_a = conversation.peerType) !== null && _a !== void 0 ? _a : "");
27
34
  const { isTargetActive } = useConversationTarget(conversation.conversationID, open && showActions && showPersonRows);
28
35
  const { canLeaveGroup, isPublicGroup, isLeaving, isConfirmOpen, isBlockedOpen, openLeaveGroup, confirmLeaveGroup, closeConfirm, closeBlocked, } = useLeaveGroupAction({
@@ -31,8 +38,10 @@ const ThreadProfilePanel = ({ open, conversation, onClose, onSelectResource, sho
31
38
  isGroupThread,
32
39
  onLeft: onClose,
33
40
  });
34
- return (_jsxs(Drawer, { open: open, onClose: onClose, placement: "right", closeIcon: false, width: "100%", "data-cp-name": "ThreadProfilePanel", styles: {
41
+ return (_jsxs(Drawer, { open: open, onClose: onClose, placement: "right", closeIcon: false, width: "100%",
42
+ // antd slides a drawer 180px aside when one opens inside it ("Mô tả nhóm").
43
+ push: false, "data-cp-name": "ThreadProfilePanel", styles: {
35
44
  body: { padding: 0 },
36
- }, getContainer: false, forceRender: true, destroyOnHidden: true, children: [_jsxs("div", { className: clsx("flex h-full flex-col bg-gray-25", showActions && "overflow-y-auto"), children: [_jsxs("div", { className: clsx("flex w-full items-center bg-white p-2", showActions && "sticky top-0 z-10 shrink-0"), children: [_jsx("button", { type: "button", "data-testid": "thread-profile-back", 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("thread_detail.options_title") })] }), _jsxs("div", { className: "flex flex-col items-center gap-2 bg-white px-0 py-2", children: [_jsxs("div", { className: "relative h-24 w-24 shrink-0", children: [_jsx(Avatar, { size: PROFILE_AVATAR_SIZE, src: avatarUrl || undefined, alt: displayName, className: "uppercase", children: (displayName === null || displayName === void 0 ? void 0 : displayName.charAt(0)) || "A" }), badgeLabel && (_jsx("span", { className: clsx("absolute bottom-1 right-0 flex h-[17px] items-center justify-center rounded-md border border-white px-1 text-[10px] font-semibold leading-3 text-white shadow-badge-inset", resolvePeerBadgeColor(peerType)), children: badgeLabel }))] }), _jsx("p", { className: "w-full truncate text-center text-[18px] font-bold leading-[1.6] text-gray-900", children: displayName }), showActions && (_jsx(ThreadProfileQuickActions, { conversation: conversation, isViewOnly: isViewOnly, onSearch: onSearch, hideSearch: hideSearch }, conversation.conversationID))] }), _jsx("div", { className: "flex flex-col px-3 py-1.5", children: _jsx(MediaEntryCard, { onSelect: onSelectResource }) }), showActions && (_jsx(ThreadProfileActionRows, { conversation: conversation, displayName: displayName, showPersonRows: showPersonRows, isTargetActive: isTargetActive, isViewOnly: isViewOnly, canLeaveGroup: canLeaveGroup, onLeaveGroup: openLeaveGroup }, conversation.conversationID)), !showActions && canLeaveGroup && (_jsx("div", { className: "flex flex-col px-3 py-1.5", children: _jsx("div", { className: "overflow-hidden rounded-xl bg-white", children: _jsxs("button", { type: "button", "data-testid": "thread-profile-leave-group", className: "flex w-full cursor-pointer items-center gap-2 p-4 text-danger-400", onClick: openLeaveGroup, children: [_jsx(Icon, { icon: "logout-o", size: 24 }), _jsx("span", { className: "text-[16px] font-medium leading-[1.6]", children: t("thread_detail.leave_group.action") })] }) }) }))] }), _jsx(LeaveGroupConfirmModal, { open: isConfirmOpen, loading: isLeaving, isPublicGroup: isPublicGroup, onCancel: closeConfirm, onConfirm: confirmLeaveGroup }), _jsx(LeaveGroupBlockedModal, { open: isBlockedOpen, onClose: closeBlocked })] }));
45
+ }, getContainer: false, forceRender: true, destroyOnHidden: true, children: [_jsxs("div", { className: clsx("flex h-full flex-col bg-gray-25", showActions && "overflow-y-auto"), children: [_jsxs("div", { className: clsx("flex w-full items-center bg-white p-2", showActions && "sticky top-0 z-10 shrink-0"), children: [_jsx("button", { type: "button", "data-testid": "thread-profile-back", 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("thread_detail.options_title") })] }), _jsxs("div", { className: "flex flex-col items-center gap-2 bg-white px-0 py-2", children: [_jsxs("div", { className: "relative h-24 w-24 shrink-0", children: [_jsx(Avatar, { size: PROFILE_AVATAR_SIZE, src: avatarUrl || undefined, alt: displayName, className: "uppercase", children: (displayName === null || displayName === void 0 ? void 0 : displayName.charAt(0)) || "A" }), badgeLabel && (_jsx("span", { className: clsx("absolute bottom-1 right-0 flex h-[17px] items-center justify-center rounded-md border border-white px-1 text-[10px] font-semibold leading-3 text-white shadow-badge-inset", resolvePeerBadgeColor(peerType)), children: badgeLabel }))] }), _jsx("p", { className: clsx("w-full text-center text-[18px] font-bold leading-[1.6] text-gray-900", showActions && isGroupThread ? "line-clamp-2 break-words px-3" : "truncate"), children: displayName }), showActions && (_jsx(ThreadProfileQuickActions, { conversation: conversation, isViewOnly: isViewOnly, onSearch: onSearch, hideSearch: hideSearch }, conversation.conversationID))] }), _jsx("div", { className: "flex flex-col px-3 py-1.5", children: _jsx(MediaEntryCard, { onSelect: onSelectResource }) }), showActions && groupDescription && (_jsx(GroupDescriptionCard, { groupName: displayName, description: groupDescription }, `description-${conversation.conversationID}`)), showActions && (_jsx(ThreadProfileActionRows, { conversation: conversation, displayName: displayName, showPersonRows: showPersonRows, isTargetActive: isTargetActive, isViewOnly: isViewOnly, isGroupThread: isGroupThread, canLeaveGroup: canLeaveGroup, onLeaveGroup: openLeaveGroup }, conversation.conversationID)), !showActions && canLeaveGroup && (_jsx("div", { className: "flex flex-col px-3 py-1.5", children: _jsx("div", { className: "overflow-hidden rounded-xl bg-white", children: _jsxs("button", { type: "button", "data-testid": "thread-profile-leave-group", className: "flex w-full cursor-pointer items-center gap-2 p-4 text-danger-400", onClick: openLeaveGroup, children: [_jsx(Icon, { icon: "logout-o", size: 24 }), _jsx("span", { className: "text-[16px] font-medium leading-[1.6]", children: t("thread_detail.leave_group.action") })] }) }) }))] }), _jsx(LeaveGroupConfirmModal, { open: isConfirmOpen, loading: isLeaving, isPublicGroup: isPublicGroup, onCancel: closeConfirm, onConfirm: confirmLeaveGroup }), _jsx(LeaveGroupBlockedModal, { open: isBlockedOpen, onClose: closeBlocked })] }));
37
46
  };
38
47
  export default ThreadProfilePanel;
@@ -0,0 +1,7 @@
1
+ interface GroupDescriptionCardProps {
2
+ groupName: string;
3
+ description: string;
4
+ }
5
+ declare const GroupDescriptionCard: ({ groupName, description, }: GroupDescriptionCardProps) => import("react/jsx-runtime").JSX.Element;
6
+ export default GroupDescriptionCard;
7
+ //# sourceMappingURL=GroupDescriptionCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GroupDescriptionCard.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/profile/GroupDescriptionCard.tsx"],"names":[],"mappings":"AA6BA,UAAU,yBAAyB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,QAAA,MAAM,oBAAoB,GAAI,6BAG3B,yBAAyB,4CAkE3B,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -0,0 +1,47 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useLayoutEffect, useRef, useState } from "react";
4
+ import clsx from "clsx";
5
+ import { useTranslation } from "react-i18next";
6
+ import GroupDescriptionPanel from "./GroupDescriptionPanel";
7
+ const LINE_HEIGHT_PX = 25.6;
8
+ const MAX_LINES = 2;
9
+ const CLAMPED_HEIGHT_PX = MAX_LINES * LINE_HEIGHT_PX;
10
+ // Same clamp as CollapsibleText: fade the end of the last line under
11
+ // "…Xem thêm" without touching layout, so it never feeds the overflow check.
12
+ const LAST_LINE_FADE = "linear-gradient(#000, #000), linear-gradient(to right, #000 calc(100% - 7em), transparent calc(100% - 5.5em))";
13
+ const LAST_LINE_FADE_SIZE = `100% calc(100% - ${LINE_HEIGHT_PX}px), 100% ${LINE_HEIGHT_PX}px`;
14
+ const CLAMPED_STYLE = {
15
+ maxHeight: CLAMPED_HEIGHT_PX,
16
+ overflow: "clip",
17
+ maskImage: LAST_LINE_FADE,
18
+ maskSize: LAST_LINE_FADE_SIZE,
19
+ maskPosition: "top, bottom",
20
+ maskRepeat: "no-repeat",
21
+ WebkitMaskImage: LAST_LINE_FADE,
22
+ WebkitMaskSize: LAST_LINE_FADE_SIZE,
23
+ WebkitMaskPosition: "top, bottom",
24
+ WebkitMaskRepeat: "no-repeat",
25
+ };
26
+ const GroupDescriptionCard = ({ groupName, description, }) => {
27
+ const { t } = useTranslation("conversation-inbox");
28
+ const contentRef = useRef(null);
29
+ const [isOverflowing, setIsOverflowing] = useState(false);
30
+ const [isPanelOpen, setIsPanelOpen] = useState(false);
31
+ useLayoutEffect(() => {
32
+ const content = contentRef.current;
33
+ if (!content)
34
+ return;
35
+ // Half-line slack: subpixel line boxes can push an exact 2-line block past 51.2px.
36
+ const measure = () => setIsOverflowing(content.getBoundingClientRect().height >
37
+ CLAMPED_HEIGHT_PX + LINE_HEIGHT_PX / 2);
38
+ measure();
39
+ if (typeof ResizeObserver === "undefined")
40
+ return;
41
+ const observer = new ResizeObserver(measure);
42
+ observer.observe(content);
43
+ return () => observer.disconnect();
44
+ }, []);
45
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "flex flex-col px-3 py-1.5", children: _jsxs("div", { "data-testid": "group-description-card", className: "relative rounded-xl bg-white p-3", children: [_jsx("div", { className: clsx(isOverflowing && "overflow-hidden"), style: isOverflowing ? CLAMPED_STYLE : undefined, children: _jsx("p", { ref: contentRef, "data-testid": "group-description-text", className: "whitespace-pre-line break-words text-[16px] font-medium leading-[1.6] text-gray-900", children: description }) }), isOverflowing && (_jsxs("button", { type: "button", "data-testid": "group-description-see-more", className: "absolute bottom-3 right-3 cursor-pointer whitespace-nowrap rounded text-[16px] font-medium leading-[1.6] text-gray-900 focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--dc-color-link)]", onClick: () => setIsPanelOpen(true), children: [_jsx("span", { "aria-hidden": true, children: "\u2026" }), _jsx("span", { className: "text-[var(--dc-color-link)]", children: t("see_more", { ns: "common" }) })] }))] }) }), _jsx(GroupDescriptionPanel, { open: isPanelOpen, groupName: groupName, description: description, onClose: () => setIsPanelOpen(false) })] }));
46
+ };
47
+ export default GroupDescriptionCard;
@@ -0,0 +1,9 @@
1
+ interface GroupDescriptionPanelProps {
2
+ open: boolean;
3
+ groupName: string;
4
+ description: string;
5
+ onClose: () => void;
6
+ }
7
+ declare const GroupDescriptionPanel: ({ open, groupName, description, onClose, }: GroupDescriptionPanelProps) => import("react/jsx-runtime").JSX.Element;
8
+ export default GroupDescriptionPanel;
9
+ //# sourceMappingURL=GroupDescriptionPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GroupDescriptionPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/profile/GroupDescriptionPanel.tsx"],"names":[],"mappings":"AAMA,UAAU,0BAA0B;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,QAAA,MAAM,qBAAqB,GAAI,4CAK5B,0BAA0B,4CAiD5B,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Drawer } from "antd";
4
+ import { useTranslation } from "react-i18next";
5
+ import { Icon } from "../../../components/icon";
6
+ const GroupDescriptionPanel = ({ open, groupName, description, onClose, }) => {
7
+ const { t } = useTranslation("conversation-inbox");
8
+ const title = t("thread_detail.profile.group_description_title");
9
+ return (_jsx(Drawer, { open: open, onClose: onClose, placement: "right", closeIcon: false, width: "100%", "aria-label": title, "data-cp-name": "GroupDescriptionPanel", styles: { body: { padding: 0 } }, getContainer: false, destroyOnHidden: true, children: _jsxs("div", { className: "flex h-full flex-col bg-white", children: [_jsxs("div", { className: "flex w-full shrink-0 items-center border-b border-gray-25 bg-white p-2", children: [_jsx("button", { type: "button", "data-testid": "group-description-close", "aria-label": t("thread_detail.profile.group_description_close"), className: "flex shrink-0 cursor-pointer items-center justify-center p-2 text-[var(--dc-color-nav-icon)]", onClick: onClose, children: _jsx(Icon, { icon: "close-o", size: 24 }) }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col px-2", children: [_jsx("p", { className: "truncate text-[18px] font-bold leading-[1.6] text-gray-900", children: title }), _jsx("p", { "data-testid": "group-description-group-name", className: "truncate text-[13px] font-medium leading-[1.6] text-gray-900", children: groupName })] })] }), _jsx("p", { "data-testid": "group-description-content", className: "min-h-0 flex-1 overflow-y-auto whitespace-pre-line break-words px-4 pb-4 pt-3 text-[16px] leading-[1.6] text-gray-900", children: description })] }) }));
10
+ };
11
+ export default GroupDescriptionPanel;
@@ -7,9 +7,11 @@ export interface ThreadProfileActionRowsProps {
7
7
  isTargetActive: boolean;
8
8
  /** A non-member viewing a public group: no pin, delete or leave. */
9
9
  isViewOnly: boolean;
10
+ /** Group info drops delete history (spec: out of scope for groups). */
11
+ isGroupThread: boolean;
10
12
  canLeaveGroup: boolean;
11
13
  onLeaveGroup: () => void;
12
14
  }
13
- declare const ThreadProfileActionRows: ({ conversation, displayName, showPersonRows, isTargetActive, isViewOnly, canLeaveGroup, onLeaveGroup, }: ThreadProfileActionRowsProps) => import("react/jsx-runtime").JSX.Element;
15
+ declare const ThreadProfileActionRows: ({ conversation, displayName, showPersonRows, isTargetActive, isViewOnly, isGroupThread, canLeaveGroup, onLeaveGroup, }: ThreadProfileActionRowsProps) => import("react/jsx-runtime").JSX.Element;
14
16
  export default ThreadProfileActionRows;
15
17
  //# sourceMappingURL=ThreadProfileActionRows.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ThreadProfileActionRows.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/profile/ThreadProfileActionRows.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAiDrE,MAAM,WAAW,4BAA4B;IAC3C,YAAY,EAAE,gBAAgB,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,oEAAoE;IACpE,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B;AAID,QAAA,MAAM,uBAAuB,GAAI,yGAQ9B,4BAA4B,4CAqE9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"ThreadProfileActionRows.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/profile/ThreadProfileActionRows.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAiDrE,MAAM,WAAW,4BAA4B;IAC3C,YAAY,EAAE,gBAAgB,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,oEAAoE;IACpE,UAAU,EAAE,OAAO,CAAC;IACpB,uEAAuE;IACvE,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B;AAID,QAAA,MAAM,uBAAuB,GAAI,wHAS9B,4BAA4B,4CAsE9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
@@ -9,12 +9,13 @@ const ProfileActionRow = ({ icon, label, testId, danger, chevron, onClick, }) =>
9
9
  const Card = ({ children }) => (_jsx("div", { className: "flex flex-col px-3 py-1.5", children: _jsx("div", { className: "overflow-hidden rounded-xl bg-white", children: children }) }));
10
10
  // Orders, create group, add to group and delete history are UI-only until
11
11
  // their own tickets land (spec: "UI ready requirement").
12
- const ThreadProfileActionRows = ({ conversation, displayName, showPersonRows, isTargetActive, isViewOnly, canLeaveGroup, onLeaveGroup, }) => {
12
+ const ThreadProfileActionRows = ({ conversation, displayName, showPersonRows, isTargetActive, isViewOnly, isGroupThread, canLeaveGroup, onLeaveGroup, }) => {
13
13
  const { t } = useTranslation("conversation-inbox");
14
14
  const { togglePin, isLimitOpen, closeLimit } = usePinConversation();
15
15
  const isPinned = !!conversation.isPinned;
16
16
  const showGroupRows = showPersonRows && isTargetActive;
17
17
  const canEditThread = !isViewOnly;
18
- return (_jsxs(_Fragment, { children: [(showPersonRows || canEditThread) && (_jsxs(Card, { children: [showPersonRows && (_jsx(ProfileActionRow, { icon: "receipt-o", label: t("thread_detail.profile.orders", { name: displayName }), testId: "thread-profile-orders", chevron: true })), showGroupRows && (_jsx(ProfileActionRow, { icon: "user-three-o", label: t("thread_detail.profile.create_group", { name: displayName }), testId: "thread-profile-create-group", chevron: true })), showGroupRows && (_jsx(ProfileActionRow, { icon: "user-add-o", label: t("thread_detail.profile.add_to_group"), testId: "thread-profile-add-to-group", chevron: true })), canEditThread && (_jsx(ProfileActionRow, { icon: isPinned ? "thumbtack-off-o" : "thumbtack-o", label: t(isPinned ? "thread_detail.profile.unpin" : "thread_detail.profile.pin"), testId: "thread-profile-pin", onClick: () => togglePin(conversation) }))] })), (canLeaveGroup || canEditThread) && (_jsxs(Card, { children: [canLeaveGroup && (_jsx(ProfileActionRow, { icon: "logout-o", label: t("thread_detail.leave_group.action"), testId: "thread-profile-leave-group", danger: true, onClick: onLeaveGroup })), canEditThread && (_jsx(ProfileActionRow, { icon: "trash-alt-o", label: t("thread_detail.profile.delete_history"), testId: "thread-profile-delete-history", danger: true }))] })), _jsx(PinThreadLimitModal, { open: isLimitOpen, onClose: closeLimit })] }));
18
+ const showDeleteHistory = canEditThread && !isGroupThread;
19
+ return (_jsxs(_Fragment, { children: [(showPersonRows || canEditThread) && (_jsxs(Card, { children: [showPersonRows && (_jsx(ProfileActionRow, { icon: "receipt-o", label: t("thread_detail.profile.orders", { name: displayName }), testId: "thread-profile-orders", chevron: true })), showGroupRows && (_jsx(ProfileActionRow, { icon: "user-three-o", label: t("thread_detail.profile.create_group", { name: displayName }), testId: "thread-profile-create-group", chevron: true })), showGroupRows && (_jsx(ProfileActionRow, { icon: "user-add-o", label: t("thread_detail.profile.add_to_group"), testId: "thread-profile-add-to-group", chevron: true })), canEditThread && (_jsx(ProfileActionRow, { icon: isPinned ? "thumbtack-off-o" : "thumbtack-o", label: t(isPinned ? "thread_detail.profile.unpin" : "thread_detail.profile.pin"), testId: "thread-profile-pin", onClick: () => togglePin(conversation) }))] })), (canLeaveGroup || showDeleteHistory) && (_jsxs(Card, { children: [canLeaveGroup && (_jsx(ProfileActionRow, { icon: "logout-o", label: t("thread_detail.leave_group.action"), testId: "thread-profile-leave-group", danger: true, onClick: onLeaveGroup })), showDeleteHistory && (_jsx(ProfileActionRow, { icon: "trash-alt-o", label: t("thread_detail.profile.delete_history"), testId: "thread-profile-delete-history", danger: true }))] })), _jsx(PinThreadLimitModal, { open: isLimitOpen, onClose: closeLimit })] }));
19
20
  };
20
21
  export default ThreadProfileActionRows;
@@ -5,9 +5,11 @@ export interface ThreadSearchContentProps {
5
5
  conversation: ConversationItem;
6
6
  inputRef: RefObject<InputRef>;
7
7
  onClose: () => void;
8
+ /** Runs when a hit is picked, before the panel closes. */
9
+ onJump?: () => void;
8
10
  /** Host toast for a hit that was revoked meanwhile; the SDK toast shows otherwise. */
9
11
  onSearchMessageRevoked?: () => void;
10
12
  }
11
- declare const ThreadSearchContent: ({ conversation, inputRef, onClose, onSearchMessageRevoked, }: ThreadSearchContentProps) => import("react/jsx-runtime").JSX.Element;
13
+ declare const ThreadSearchContent: ({ conversation, inputRef, onClose, onJump, onSearchMessageRevoked, }: ThreadSearchContentProps) => import("react/jsx-runtime").JSX.Element;
12
14
  export default ThreadSearchContent;
13
15
  //# sourceMappingURL=ThreadSearchContent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ThreadSearchContent.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/thread-search/ThreadSearchContent.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,EAAwC,KAAK,QAAQ,EAAE,MAAM,MAAM,CAAC;AAI3E,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,8BAA8B,CAAC;AAYtC,MAAM,WAAW,wBAAwB;IACvC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,sFAAsF;IACtF,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;CACrC;AAGD,QAAA,MAAM,mBAAmB,GAAI,8DAK1B,wBAAwB,4CAoJ1B,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"ThreadSearchContent.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/thread-search/ThreadSearchContent.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,EAAwC,KAAK,QAAQ,EAAE,MAAM,MAAM,CAAC;AAI3E,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,8BAA8B,CAAC;AAYtC,MAAM,WAAW,wBAAwB;IACvC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,sFAAsF;IACtF,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;CACrC;AAGD,QAAA,MAAM,mBAAmB,GAAI,sEAM1B,wBAAwB,4CAqJ1B,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -16,7 +16,7 @@ import ThreadSearchResultItem from "./ThreadSearchResultItem";
16
16
  import { THREAD_SEARCH_DEBOUNCE_MS } from "./utils";
17
17
  const SCROLL_TARGET_ID = "thread-search-results";
18
18
  // Lives inside the Drawer, which destroys it on close: every open starts from an empty keyword.
19
- const ThreadSearchContent = ({ conversation, inputRef, onClose, onSearchMessageRevoked, }) => {
19
+ const ThreadSearchContent = ({ conversation, inputRef, onClose, onJump, onSearchMessageRevoked, }) => {
20
20
  const { t } = useTranslation("conversation-inbox");
21
21
  const { t: tCommon } = useTranslation();
22
22
  const selfUserID = useAuthStore((state) => state.userID);
@@ -40,6 +40,7 @@ const ThreadSearchContent = ({ conversation, inputRef, onClose, onSearchMessageR
40
40
  store.setHeldHighlightClientMsgID(hit.clientMsgID);
41
41
  store.setSearchClientMsgID(hit.clientMsgID);
42
42
  emit("CHAT_LIST_SCROLL_TO_MESSAGE", hit.clientMsgID);
43
+ onJump === null || onJump === void 0 ? void 0 : onJump();
43
44
  onClose();
44
45
  };
45
46
  const renderBody = () => {
@@ -3,9 +3,11 @@ interface ThreadSearchPanelProps {
3
3
  open: boolean;
4
4
  conversation: ConversationItem;
5
5
  onClose: () => void;
6
+ /** Runs when a hit is picked, before the panel closes. */
7
+ onJump?: () => void;
6
8
  /** Host toast for a hit that was revoked meanwhile; the SDK toast shows otherwise. */
7
9
  onSearchMessageRevoked?: () => void;
8
10
  }
9
- declare const ThreadSearchPanel: ({ open, conversation, onClose, onSearchMessageRevoked, }: ThreadSearchPanelProps) => import("react/jsx-runtime").JSX.Element;
11
+ declare const ThreadSearchPanel: ({ open, conversation, onClose, onJump, onSearchMessageRevoked, }: ThreadSearchPanelProps) => import("react/jsx-runtime").JSX.Element;
10
12
  export default ThreadSearchPanel;
11
13
  //# sourceMappingURL=ThreadSearchPanel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ThreadSearchPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/thread-search/ThreadSearchPanel.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAGrE,UAAU,sBAAsB;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,sFAAsF;IACtF,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;CACrC;AAED,QAAA,MAAM,iBAAiB,GAAI,0DAKxB,sBAAsB,4CAyBxB,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"ThreadSearchPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/thread-search/ThreadSearchPanel.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAGrE,UAAU,sBAAsB;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,sFAAsF;IACtF,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;CACrC;AAED,QAAA,MAAM,iBAAiB,GAAI,kEAMxB,sBAAsB,4CA0BxB,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -3,10 +3,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useRef } from "react";
4
4
  import { Drawer } from "antd";
5
5
  import ThreadSearchContent from "./ThreadSearchContent";
6
- const ThreadSearchPanel = ({ open, conversation, onClose, onSearchMessageRevoked, }) => {
6
+ const ThreadSearchPanel = ({ open, conversation, onClose, onJump, onSearchMessageRevoked, }) => {
7
7
  const inputRef = useRef(null);
8
8
  return (_jsx(Drawer, { open: open, onClose: onClose, placement: "right", closeIcon: false, width: "100%", "data-cp-name": "ThreadSearchPanel", styles: { body: { padding: 0 } }, getContainer: false, destroyOnHidden: true,
9
9
  // rc-drawer focuses its own panel after mount, which beats autoFocus.
10
- afterOpenChange: (visible) => { var _a; return visible && ((_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus()); }, children: _jsx(ThreadSearchContent, { conversation: conversation, inputRef: inputRef, onClose: onClose, onSearchMessageRevoked: onSearchMessageRevoked }) }));
10
+ afterOpenChange: (visible) => { var _a; return visible && ((_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus()); }, children: _jsx(ThreadSearchContent, { conversation: conversation, inputRef: inputRef, onClose: onClose, onJump: onJump, onSearchMessageRevoked: onSearchMessageRevoked }) }));
11
11
  };
12
12
  export default ThreadSearchPanel;
@@ -1,10 +1,17 @@
1
+ export declare enum InboxTab {
2
+ Messages = "MESSAGES",
3
+ Community = "COMMUNITY"
4
+ }
1
5
  export interface ConversationInboxHeaderProps {
2
6
  onOpenSearch?: () => void;
3
7
  /** Overrides the default "tabs.messages" i18n title (e.g. Mall buyer's fixed "Tin nhắn" label). */
4
8
  title?: string;
5
9
  /** Extra action rendered after the search icon — e.g. Mall seller's contact icon. */
6
10
  rightSlot?: React.ReactNode;
11
+ /** With onChangeTab, shows the Tin nhắn | Cộng đồng tabs instead of the single title. */
12
+ activeTab?: InboxTab;
13
+ onChangeTab?: (tab: InboxTab) => void;
7
14
  }
8
- declare const ConversationInboxHeader: ({ onOpenSearch, title, rightSlot, }: ConversationInboxHeaderProps) => import("react/jsx-runtime").JSX.Element;
15
+ declare const ConversationInboxHeader: ({ onOpenSearch, title, rightSlot, activeTab, onChangeTab, }: ConversationInboxHeaderProps) => import("react/jsx-runtime").JSX.Element;
9
16
  export default ConversationInboxHeader;
10
17
  //# sourceMappingURL=ConversationInboxHeader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationInboxHeader.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-inbox/ConversationInboxHeader.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,4BAA4B;IAC3C,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,mGAAmG;IACnG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC7B;AAED,QAAA,MAAM,uBAAuB,GAAI,qCAI9B,4BAA4B,4CAiC9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"ConversationInboxHeader.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-inbox/ConversationInboxHeader.tsx"],"names":[],"mappings":"AAMA,oBAAY,QAAQ;IAClB,QAAQ,aAAa;IACrB,SAAS,cAAc;CACxB;AAOD,MAAM,WAAW,4BAA4B;IAC3C,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,mGAAmG;IACnG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,yFAAyF;IACzF,SAAS,CAAC,EAAE,QAAQ,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAC;CACvC;AA8CD,QAAA,MAAM,uBAAuB,GAAI,6DAM9B,4BAA4B,4CA0C9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
@@ -1,9 +1,26 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import clsx from "clsx";
3
4
  import { useTranslation } from "react-i18next";
4
5
  import { Icon } from "../../components/icon";
5
- const ConversationInboxHeader = ({ onOpenSearch, title, rightSlot, }) => {
6
+ export var InboxTab;
7
+ (function (InboxTab) {
8
+ InboxTab["Messages"] = "MESSAGES";
9
+ InboxTab["Community"] = "COMMUNITY";
10
+ })(InboxTab || (InboxTab = {}));
11
+ const INBOX_TABS = [
12
+ { key: InboxTab.Messages, labelKey: "tabs.messages" },
13
+ { key: InboxTab.Community, labelKey: "tabs.community" },
14
+ ];
15
+ const HeaderTab = ({ label, active, testId, onClick }) => {
16
+ const content = (_jsxs(_Fragment, { children: [_jsx("span", { className: clsx("pb-1.5 text-center text-[16px] font-bold leading-[160%] tracking-[0.16px]", active ? "text-[var(--dc-color-primary)]" : "text-gray-400"), children: label }), _jsx("span", { className: clsx("h-[3px] w-full rounded-t", active ? "bg-[var(--dc-color-primary)]" : "bg-transparent") })] }));
17
+ const className = "flex h-full flex-col items-center justify-end px-3";
18
+ if (!onClick)
19
+ return _jsx("div", { className: className, children: content });
20
+ return (_jsx("button", { type: "button", role: "tab", "aria-selected": active, "data-testid": testId, className: clsx(className, "cursor-pointer"), onClick: onClick, children: content }));
21
+ };
22
+ const ConversationInboxHeader = ({ onOpenSearch, title, rightSlot, activeTab = InboxTab.Messages, onChangeTab, }) => {
6
23
  const { t } = useTranslation("conversation-inbox");
7
- return (_jsx("div", { "data-testid": "conversation-inbox-header", "data-cp-name": "ConversationInboxHeader", className: "flex w-full flex-col border-b border-gray-100 bg-white", children: _jsxs("div", { className: "flex h-12 items-end px-3", children: [_jsxs("div", { className: "flex h-full flex-col items-center justify-end px-3", children: [_jsx("span", { className: "pb-1.5 text-center text-[16px] font-bold leading-[160%] tracking-[0.16px] text-[var(--dc-color-primary)]", children: title !== null && title !== void 0 ? title : t("tabs.messages") }), _jsx("span", { className: "h-[3px] w-full rounded-t bg-[var(--dc-color-primary)]" })] }), _jsxs("div", { className: "ml-auto mb-1.5 flex shrink-0 items-center gap-2", children: [onOpenSearch && (_jsx("button", { type: "button", "data-testid": "conversation-inbox-search", "aria-label": t("search_placeholder"), className: "flex shrink-0 cursor-pointer items-center justify-center p-1 text-[var(--dc-color-nav-icon)]", onClick: onOpenSearch, children: _jsx(Icon, { icon: "search-o", size: 24 }) })), rightSlot] })] }) }));
24
+ return (_jsx("div", { "data-testid": "conversation-inbox-header", "data-cp-name": "ConversationInboxHeader", className: "flex w-full flex-col border-b border-gray-100 bg-white", children: _jsxs("div", { className: "flex h-12 items-end px-3", children: [onChangeTab ? (_jsx("div", { role: "tablist", className: "flex h-full items-end", children: INBOX_TABS.map((tab) => (_jsx(HeaderTab, { label: t(tab.labelKey), active: activeTab === tab.key, testId: `conversation-inbox-tab-${tab.key.toLowerCase()}`, onClick: () => onChangeTab(tab.key) }, tab.key))) })) : (_jsx(HeaderTab, { label: title !== null && title !== void 0 ? title : t("tabs.messages"), active: true })), _jsxs("div", { className: "ml-auto mb-1.5 flex shrink-0 items-center gap-2", children: [onOpenSearch && (_jsx("button", { type: "button", "data-testid": "conversation-inbox-search", "aria-label": t("search_placeholder"), className: "flex shrink-0 cursor-pointer items-center justify-center p-1 text-[var(--dc-color-nav-icon)]", onClick: onOpenSearch, children: _jsx(Icon, { icon: "search-o", size: 24 }) })), rightSlot] })] }) }));
8
25
  };
9
26
  export default ConversationInboxHeader;
@@ -0,0 +1,3 @@
1
+ declare const PublicGroupList: () => import("react/jsx-runtime").JSX.Element;
2
+ export default PublicGroupList;
3
+ //# sourceMappingURL=PublicGroupList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PublicGroupList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-inbox/PublicGroupList.tsx"],"names":[],"mappings":"AAoBA,QAAA,MAAM,eAAe,+CAqFpB,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Spin } from "antd";
4
+ import InfiniteScroll from "react-infinite-scroll-component";
5
+ import { useTranslation } from "react-i18next";
6
+ import { useChatContext } from "../../context/ChatContext";
7
+ import { useChatNavigate } from "../../hooks/conversation/useChatNavigate";
8
+ import { useSearchPublicGroups } from "../../hooks/search/useSearchPublicGroups";
9
+ import useConversationStore from "../../store/conversation";
10
+ import { ConnectStatus } from "../../types/chat";
11
+ import { ContactSearchStateMessage, PublicGroupResultItem, buildContactSearchParams, } from "./contact-search";
12
+ const SCROLL_TARGET_ID = "inbox-public-group-scroll";
13
+ // Inbox `Cộng đồng` tab: every public group the seller can see, joined or not,
14
+ // from the same searchPublicGroups call chat-mobile's CommunityTab uses.
15
+ const PublicGroupList = () => {
16
+ const { t } = useTranslation("conversation-inbox");
17
+ const onNavigate = useChatNavigate();
18
+ const setPendingResolveTarget = useConversationStore((state) => state.setPendingResolveTarget);
19
+ const { connectStatus } = useChatContext();
20
+ // The tab can be opened while the engine still boots; a call then fails at
21
+ // once and would leave the error state with nothing to retry it.
22
+ const { groups, fetchNextPage, hasNextPage, isFetchingNextPage, isPending, isError, } = useSearchPublicGroups({
23
+ searchTerm: "",
24
+ allowEmptyTerm: true,
25
+ enabled: connectStatus === ConnectStatus.Connected,
26
+ });
27
+ const handleOpenGroup = (groupID) => {
28
+ const target = { groupId: groupID };
29
+ setPendingResolveTarget(target);
30
+ onNavigate(`${window.location.pathname}?${buildContactSearchParams(target).toString()}`);
31
+ };
32
+ if (isPending) {
33
+ return (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Spin, {}) }));
34
+ }
35
+ if (isError) {
36
+ return (_jsx(ContactSearchStateMessage, { testId: "inbox-public-group-failed", children: t("contact_search.failed") }));
37
+ }
38
+ if (groups.length === 0) {
39
+ return (_jsx(ContactSearchStateMessage, { testId: "inbox-public-group-empty", children: t("community.empty") }));
40
+ }
41
+ return (_jsx("div", { id: SCROLL_TARGET_ID, "data-testid": "inbox-public-group-list", "data-cp-name": "PublicGroupList", className: "flex-1 overflow-y-auto", children: _jsx(InfiniteScroll, { dataLength: groups.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: groups.map((group) => (_jsx(PublicGroupResultItem, { group: group, onOpen: handleOpenGroup, stayOnJoin: true }, group.groupID))) }) }));
42
+ };
43
+ export default PublicGroupList;
@@ -2,7 +2,9 @@ import type { SearchPublicGroupInfo } from "@droppii-org/wasm-client-sdk";
2
2
  interface PublicGroupResultItemProps {
3
3
  group: SearchPublicGroupInfo;
4
4
  onOpen: (groupID: string) => void;
5
+ /** Inbox `Cộng đồng` tab: a join keeps the seller on the list and toasts instead of opening the thread. */
6
+ stayOnJoin?: boolean;
5
7
  }
6
- declare const PublicGroupResultItem: ({ group, onOpen, }: PublicGroupResultItemProps) => import("react/jsx-runtime").JSX.Element;
8
+ declare const PublicGroupResultItem: ({ group, onOpen, stayOnJoin, }: PublicGroupResultItemProps) => import("react/jsx-runtime").JSX.Element;
7
9
  export default PublicGroupResultItem;
8
10
  //# sourceMappingURL=PublicGroupResultItem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PublicGroupResultItem.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/PublicGroupResultItem.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAK1E,UAAU,0BAA0B;IAClC,KAAK,EAAE,qBAAqB,CAAC;IAC7B,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,QAAA,MAAM,qBAAqB,GAAI,oBAG5B,0BAA0B,4CA4E5B,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"PublicGroupResultItem.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/PublicGroupResultItem.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAK1E,UAAU,0BAA0B;IAClC,KAAK,EAAE,qBAAqB,CAAC;IAC7B,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,2GAA2G;IAC3G,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,QAAA,MAAM,qBAAqB,GAAI,gCAI5B,0BAA0B,4CAyF5B,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
@@ -1,22 +1,30 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import clsx from "clsx";
4
- import { Spin } from "antd";
4
+ import { Spin, message as toastMessage } from "antd";
5
5
  import { useTranslation } from "react-i18next";
6
6
  import { useJoinPublicGroup } from "../../../hooks/group/useJoinPublicGroup";
7
7
  import GroupAvatar from "../../../components/group/GroupAvatar";
8
8
  import { CONTACT_SEARCH_AVATAR_SIZE } from "./constants";
9
- const PublicGroupResultItem = ({ group, onOpen, }) => {
9
+ const PublicGroupResultItem = ({ group, onOpen, stayOnJoin = false, }) => {
10
10
  var _a, _b;
11
11
  const { t } = useTranslation("conversation-inbox");
12
+ const { t: tCommon } = useTranslation("common");
12
13
  const { mutate: joinGroup, isPending } = useJoinPublicGroup();
13
- // Joined groups open straight away; the rest join first, then open.
14
+ // Joined groups open straight away; search joins first, then opens.
14
15
  const handleAction = () => {
15
16
  if (group.isJoined) {
16
17
  onOpen(group.groupID);
17
18
  return;
18
19
  }
19
- joinGroup({ groupID: group.groupID }, { onSuccess: () => onOpen(group.groupID) });
20
+ if (!stayOnJoin) {
21
+ joinGroup({ groupID: group.groupID }, { onSuccess: () => onOpen(group.groupID) });
22
+ return;
23
+ }
24
+ joinGroup({ groupID: group.groupID }, {
25
+ onSuccess: () => toastMessage.success(tCommon("public_group_joined")),
26
+ onError: () => toastMessage.error(tCommon("join_group_failed")),
27
+ });
20
28
  };
21
29
  return (_jsxs("div", { "data-cp-name": "PublicGroupResultItem", "data-testid": `contact-search-public-group-${group.groupID}`, className: "flex items-center gap-2 border-b border-gray-25 bg-white p-3", children: [_jsxs("button", { type: "button", className: "flex min-w-0 flex-1 items-center gap-2 text-left", onClick: () => onOpen(group.groupID), children: [_jsx(GroupAvatar, { faceURL: group.faceURL, groupName: group.groupName, size: CONTACT_SEARCH_AVATAR_SIZE, className: "shrink-0" }), _jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-1 pb-0.5", children: [_jsx("span", { className: "truncate text-[16px] font-medium leading-[1.6] text-black", children: group.groupName }), _jsxs("span", { className: "flex min-w-0 flex-col text-[14px] leading-[1.6] tracking-[0.14px] text-gray-600", children: [_jsx("span", { className: "truncate", children: t("contact_search.member_count", {
22
30
  count: (_a = group.memberCount) !== null && _a !== void 0 ? _a : 0,
@@ -1 +1 @@
1
- {"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AAiDA,eAAO,MAAM,cAAc,YAwnB1B,CAAC"}
1
+ {"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AAkDA,eAAO,MAAM,cAAc,YA4nB1B,CAAC"}
@@ -20,6 +20,7 @@ import { useQueryClient } from "@tanstack/react-query";
20
20
  import { PeerType } from "../../types/conversation";
21
21
  import { ignoreInternalLatestMessage } from "../../utils/common";
22
22
  import { invalidateSessionQueries } from "../../utils/queryHelpers";
23
+ import { refreshPublicGroupLists } from "../../hooks/group/useJoinPublicGroup";
23
24
  import emitter from "../../utils/events";
24
25
  const notPushType = [MessageType.TypingMessage, MessageType.RevokeMessage];
25
26
  export const useGlobalEvent = () => {
@@ -381,6 +382,8 @@ export const useGlobalEvent = () => {
381
382
  conversationList: state.conversationList.filter((conversation) => conversation.groupID !== data.groupID),
382
383
  }));
383
384
  }
385
+ // Public-group cards turn back to Tham gia (or drop out when disbanded).
386
+ void refreshPublicGroupLists(queryClient, data.groupID);
384
387
  };
385
388
  // Thin wrappers matching the SDK's fixed callback signature, forwarding
386
389
  // to leftGroupHandler with the right reason.
@@ -434,6 +437,8 @@ export const useGlobalEvent = () => {
434
437
  void queryClient.invalidateQueries({
435
438
  queryKey: [QUERY_KEYS.IS_JOINED_GROUP, data.groupID],
436
439
  });
440
+ // A first-time join from another device or an invite flips the card to Truy cập.
441
+ void refreshPublicGroupLists(queryClient, data.groupID);
437
442
  };
438
443
  const setIMListener = () => {
439
444
  //account
@@ -0,0 +1,4 @@
1
+ import { type ConversationItem } from "@droppii-org/wasm-client-sdk";
2
+ export type GroupSendState = "checking" | "restricted" | "allowed";
3
+ export declare const useGroupSendRestriction: (conversation?: ConversationItem | null) => GroupSendState;
4
+ //# sourceMappingURL=useGroupSendRestriction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGroupSendRestriction.d.ts","sourceRoot":"","sources":["../../../src/hooks/group/useGroupSendRestriction.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AAKtC,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,YAAY,GAAG,SAAS,CAAC;AAMnE,eAAO,MAAM,uBAAuB,GAClC,eAAe,gBAAgB,GAAG,IAAI,KACrC,cA4BF,CAAC"}
@@ -0,0 +1,31 @@
1
+ "use client";
2
+ import { GroupMemberRole, GroupStatus, } from "@droppii-org/wasm-client-sdk";
3
+ import useConversationStore from "../../store/conversation";
4
+ import { PeerType } from "../../types/conversation";
5
+ import { useGroupPermission } from "./useGroupPermission";
6
+ // A confirmed member is restricted with no SEND_MESSAGE permission, a muted
7
+ // group (owner/admin exempt) or an own mute. "checking" lasts until the store
8
+ // finishes this thread's membership check (it sets currentConversation last),
9
+ // so a restricted member never sees the composer flash first.
10
+ export const useGroupSendRestriction = (conversation) => {
11
+ var _a;
12
+ const isConfirmedGroup = (conversation === null || conversation === void 0 ? void 0 : conversation.peerType) === PeerType.GROUP;
13
+ const groupID = conversation === null || conversation === void 0 ? void 0 : conversation.groupID;
14
+ const isChecked = useConversationStore((state) => { var _a; return ((_a = state.currentConversation) === null || _a === void 0 ? void 0 : _a.conversationID) === (conversation === null || conversation === void 0 ? void 0 : conversation.conversationID); });
15
+ const groupInfo = useConversationStore((state) => groupID ? state.currentGroupInfo.get(groupID) : undefined);
16
+ const member = useConversationStore((state) => groupID ? state.currentMemberInGroup.get(groupID) : undefined);
17
+ const permissions = useGroupPermission(groupID);
18
+ if (!isConfirmedGroup)
19
+ return "allowed";
20
+ if (!isChecked)
21
+ return "checking";
22
+ // Non-members are handled by the view-only footer, not this notice.
23
+ if (!member)
24
+ return "allowed";
25
+ const isGroupMuted = (groupInfo === null || groupInfo === void 0 ? void 0 : groupInfo.status) === GroupStatus.Muted &&
26
+ member.roleLevel === GroupMemberRole.Normal;
27
+ const isMemberMuted = ((_a = member.muteEndTime) !== null && _a !== void 0 ? _a : 0) > Date.now();
28
+ return !permissions.sendMessage || isGroupMuted || isMemberMuted
29
+ ? "restricted"
30
+ : "allowed";
31
+ };
@@ -1,6 +1,8 @@
1
+ import { type QueryClient } from "@tanstack/react-query";
1
2
  interface JoinPublicGroupInput {
2
3
  groupID: string;
3
4
  }
5
+ export declare const refreshPublicGroupLists: (queryClient: QueryClient, groupID: string) => Promise<void>;
4
6
  export declare const useJoinPublicGroup: () => import("@tanstack/react-query").UseMutationResult<unknown, Error, JoinPublicGroupInput, unknown>;
5
7
  export {};
6
8
  //# sourceMappingURL=useJoinPublicGroup.d.ts.map