@droppii-org/chat-sdk 0.6.8 → 0.6.10

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 (61) hide show
  1. package/dist/components/chat-bubble/ChatBubble.d.ts +2 -1
  2. package/dist/components/chat-bubble/ChatBubble.d.ts.map +1 -1
  3. package/dist/components/chat-bubble/ChatBubble.js +3 -3
  4. package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
  5. package/dist/components/conversation/DeskConversationList.js +3 -2
  6. package/dist/components/conversation-detail/ConversationMessageList.d.ts +7 -1
  7. package/dist/components/conversation-detail/ConversationMessageList.d.ts.map +1 -1
  8. package/dist/components/conversation-detail/ConversationMessageList.js +17 -3
  9. package/dist/components/group/InviteLinkPanel.d.ts +14 -0
  10. package/dist/components/group/InviteLinkPanel.d.ts.map +1 -0
  11. package/dist/components/group/InviteLinkPanel.js +61 -0
  12. package/dist/components/media-resources/MediaResourceTabs.d.ts.map +1 -1
  13. package/dist/components/media-resources/MediaResourceTabs.js +11 -1
  14. package/dist/components/message/MessageList.d.ts +1 -0
  15. package/dist/components/message/MessageList.d.ts.map +1 -1
  16. package/dist/components/message/MessageList.js +14 -5
  17. package/dist/components/message/item/index.d.ts +2 -1
  18. package/dist/components/message/item/index.d.ts.map +1 -1
  19. package/dist/components/message/item/index.js +19 -7
  20. package/dist/components/message/toolbox/types.d.ts +1 -0
  21. package/dist/components/message/toolbox/types.d.ts.map +1 -1
  22. package/dist/components/message/toolbox/types.js +1 -0
  23. package/dist/components/message/toolbox/useMessageToolboxVisibility.d.ts +1 -0
  24. package/dist/components/message/toolbox/useMessageToolboxVisibility.d.ts.map +1 -1
  25. package/dist/components/message/toolbox/useMessageToolboxVisibility.js +7 -3
  26. package/dist/components/thread/GroupThreadInfo.d.ts.map +1 -1
  27. package/dist/components/thread/GroupThreadInfo.js +17 -1
  28. package/dist/hooks/conversation/useActiveConversation.d.ts.map +1 -1
  29. package/dist/hooks/conversation/useActiveConversation.js +34 -7
  30. package/dist/hooks/group/useInviteLinkAction.d.ts +23 -0
  31. package/dist/hooks/group/useInviteLinkAction.d.ts.map +1 -0
  32. package/dist/hooks/group/useInviteLinkAction.js +83 -0
  33. package/dist/locales/vi/common.json +18 -1
  34. package/dist/screens/chat-bubble/index.d.ts +5 -0
  35. package/dist/screens/chat-bubble/index.d.ts.map +1 -1
  36. package/dist/screens/chat-bubble/index.js +2 -2
  37. package/dist/screens/conversation-detail/index.d.ts +6 -1
  38. package/dist/screens/conversation-detail/index.d.ts.map +1 -1
  39. package/dist/screens/conversation-detail/index.js +5 -2
  40. package/dist/screens/conversation-inbox/index.d.ts +6 -0
  41. package/dist/screens/conversation-inbox/index.d.ts.map +1 -1
  42. package/dist/screens/conversation-inbox/index.js +2 -2
  43. package/dist/screens/desk-message/index.d.ts +2 -1
  44. package/dist/screens/desk-message/index.d.ts.map +1 -1
  45. package/dist/screens/desk-message/index.js +3 -3
  46. package/dist/services/joinLink.d.ts +18 -0
  47. package/dist/services/joinLink.d.ts.map +1 -0
  48. package/dist/services/joinLink.js +30 -0
  49. package/dist/services/query.d.ts +1 -0
  50. package/dist/services/query.d.ts.map +1 -1
  51. package/dist/services/query.js +1 -0
  52. package/dist/services/routes.d.ts +2 -0
  53. package/dist/services/routes.d.ts.map +1 -1
  54. package/dist/services/routes.js +2 -0
  55. package/dist/styles/global.css +1 -1
  56. package/dist/types/dto.d.ts +6 -0
  57. package/dist/types/dto.d.ts.map +1 -1
  58. package/dist/utils/inviteLink.d.ts +14 -0
  59. package/dist/utils/inviteLink.d.ts.map +1 -0
  60. package/dist/utils/inviteLink.js +20 -0
  61. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"useActiveConversation.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useActiveConversation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE,eAAO,MAAM,qBAAqB,GAChC,mBAAmB,oBAAoB,EAAE,SAoD1C,CAAC"}
1
+ {"version":3,"file":"useActiveConversation.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useActiveConversation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE,eAAO,MAAM,qBAAqB,GAChC,mBAAmB,oBAAoB,EAAE,SAwF1C,CAAC"}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { useEffect } from "react";
2
+ import { useEffect, useRef } from "react";
3
3
  import { useSearchParams } from "next/navigation";
4
4
  import useConversationStore from "../../store/conversation";
5
5
  export const useActiveConversation = (conversationItems) => {
@@ -16,21 +16,47 @@ export const useActiveConversation = (conversationItems) => {
16
16
  const setSelectedConversationId = useConversationStore((state) => state.setSelectedConversationId);
17
17
  const setConversationData = useConversationStore((state) => state.setConversationData);
18
18
  const clearSelectedConversation = useConversationStore((state) => state.clearSelectedConversation);
19
+ const setSearchClientMsgID = useConversationStore((state) => state.setSearchClientMsgID);
20
+ // Which ?messageId deep link has already been pushed into the store, keyed
21
+ // by thread. Without it, the effect's re-runs (conversationItems changes on
22
+ // every list mutation) would keep re-applying the URL's message and yank the
23
+ // user back there after they jumped somewhere else in the same thread.
24
+ const appliedMessageTargetRef = useRef(null);
19
25
  useEffect(() => {
20
- const threadId = typeof window !== "undefined"
21
- ? new URLSearchParams(window.location.search).get("threadId")
26
+ var _a;
27
+ const params = typeof window !== "undefined"
28
+ ? new URLSearchParams(window.location.search)
22
29
  : null;
30
+ const threadId = (_a = params === null || params === void 0 ? void 0 : params.get("threadId")) !== null && _a !== void 0 ? _a : null;
23
31
  if (!threadId) {
32
+ appliedMessageTargetRef.current = null;
24
33
  clearSelectedConversation();
25
34
  return;
26
35
  }
27
36
  setSelectedConversationId(threadId);
37
+ // Deep link to one message (copy-message-link). Only ever APPLIED, never
38
+ // cleared from here: an in-app jump (search result, pinned message) sets
39
+ // searchClientMsgID without touching the URL, and this effect re-runs on
40
+ // unrelated list mutations — clearing on an absent messageId would wipe it.
41
+ const messageId = (params === null || params === void 0 ? void 0 : params.get("messageId")) || "";
42
+ const messageTargetKey = `${threadId}:${messageId}`;
43
+ const isNewMessageTarget = !!messageId && appliedMessageTargetRef.current !== messageTargetKey;
44
+ if (isNewMessageTarget) {
45
+ appliedMessageTargetRef.current = messageTargetKey;
46
+ }
28
47
  const matchedThread = conversationItems.find((item) => item.conversationID === threadId);
29
- if (!matchedThread)
30
- return;
31
48
  const currentData = useConversationStore.getState().conversationData;
32
- if ((currentData === null || currentData === void 0 ? void 0 : currentData.conversationID) !== threadId) {
33
- setConversationData(matchedThread);
49
+ if (matchedThread && (currentData === null || currentData === void 0 ? void 0 : currentData.conversationID) !== threadId) {
50
+ // setConversationData owns searchClientMsgID — passing undefined here is
51
+ // what resets it on a normal thread switch, so route the deep-linked
52
+ // message through it rather than setting it separately and racing.
53
+ setConversationData(matchedThread, isNewMessageTarget ? messageId : undefined);
54
+ return;
55
+ }
56
+ // Thread already active, or not in the loaded list yet: setConversationData
57
+ // won't run (or won't run for a while), so apply the target directly.
58
+ if (isNewMessageTarget) {
59
+ setSearchClientMsgID(messageId);
34
60
  }
35
61
  }, [
36
62
  conversationItems,
@@ -38,5 +64,6 @@ export const useActiveConversation = (conversationItems) => {
38
64
  setSelectedConversationId,
39
65
  setConversationData,
40
66
  clearSelectedConversation,
67
+ setSearchClientMsgID,
41
68
  ]);
42
69
  };
@@ -0,0 +1,23 @@
1
+ interface UseInviteLinkActionParams {
2
+ groupID?: string;
3
+ conversationID?: string;
4
+ enabled?: boolean;
5
+ }
6
+ type InviteLinkConfirmAction = "regenerate" | "revoke" | null;
7
+ export declare const useInviteLinkAction: ({ groupID, conversationID, enabled, }: UseInviteLinkActionParams) => {
8
+ link: import("../../types/dto").IJoinLinkResponse | null | undefined;
9
+ isOwner: boolean;
10
+ isLoading: boolean;
11
+ isError: boolean;
12
+ refetch: (options?: import("@tanstack/react-query").RefetchOptions) => Promise<import("@tanstack/react-query").QueryObserverResult<import("../../types/dto").IJoinLinkResponse | null, Error>>;
13
+ isMutating: boolean;
14
+ confirmAction: InviteLinkConfirmAction;
15
+ createLink: () => Promise<void>;
16
+ openRegenerateConfirm: () => void;
17
+ openRevokeConfirm: () => void;
18
+ closeConfirm: () => void;
19
+ confirmRegenerate: () => Promise<void>;
20
+ confirmRevoke: () => Promise<void>;
21
+ };
22
+ export {};
23
+ //# sourceMappingURL=useInviteLinkAction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useInviteLinkAction.d.ts","sourceRoot":"","sources":["../../../src/hooks/group/useInviteLinkAction.ts"],"names":[],"mappings":"AAYA,UAAU,yBAAyB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,KAAK,uBAAuB,GAAG,YAAY,GAAG,QAAQ,GAAG,IAAI,CAAC;AAE9D,eAAO,MAAM,mBAAmB,GAAI,uCAIjC,yBAAyB;;;;;;;;;;;;;;CAuF3B,CAAC"}
@@ -0,0 +1,83 @@
1
+ "use client";
2
+ import { useState } from "react";
3
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { message } from "antd";
5
+ import { useTranslation } from "react-i18next";
6
+ import { GroupMemberRole } from "@droppii-org/wasm-client-sdk";
7
+ import useConversationStore from "../../store/conversation";
8
+ import useAuthStore from "../../store/auth";
9
+ import { QUERY_KEYS } from "../../services/query";
10
+ import { createJoinLink, getJoinLink, revokeJoinLink } from "../../services/joinLink";
11
+ export const useInviteLinkAction = ({ groupID, conversationID, enabled = true, }) => {
12
+ const { t } = useTranslation();
13
+ const queryClient = useQueryClient();
14
+ const currentGroupInfo = useConversationStore((state) => groupID ? state.currentGroupInfo.get(groupID) : undefined);
15
+ const currentMemberInGroup = useConversationStore((state) => groupID ? state.currentMemberInGroup.get(groupID) : undefined);
16
+ const selfUserID = useAuthStore((state) => state.userID);
17
+ // ownerUserID is the reliable source here; roleLevel is frequently undefined
18
+ // in this host (see useLeaveGroupAction.ts:73-80). Admin is intentionally NOT
19
+ // treated as owner — Confluence §4 restricts create/replace/revoke to Owner.
20
+ const isOwner = (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.ownerUserID) && selfUserID
21
+ ? currentGroupInfo.ownerUserID === selfUserID
22
+ : (currentMemberInGroup === null || currentMemberInGroup === void 0 ? void 0 : currentMemberInGroup.roleLevel) === GroupMemberRole.Owner;
23
+ const [confirmAction, setConfirmAction] = useState(null);
24
+ const { data: link, isLoading, isError, refetch, } = useQuery({
25
+ queryKey: [QUERY_KEYS.GET_JOIN_LINK, conversationID],
26
+ queryFn: () => getJoinLink(conversationID),
27
+ enabled: !!conversationID && enabled,
28
+ staleTime: 0, // another owner session can rotate the code at any time
29
+ });
30
+ const { mutateAsync: createLinkMutate, isPending: isCreating } = useMutation({
31
+ mutationFn: () => createJoinLink(conversationID),
32
+ });
33
+ const { mutateAsync: revokeLinkMutate, isPending: isRevoking } = useMutation({
34
+ mutationFn: () => revokeJoinLink(conversationID),
35
+ });
36
+ const createLink = async () => {
37
+ if (!conversationID || !isOwner)
38
+ return;
39
+ try {
40
+ const created = await createLinkMutate();
41
+ queryClient.setQueryData([QUERY_KEYS.GET_JOIN_LINK, conversationID], created);
42
+ setConfirmAction(null);
43
+ message.success(t("invite_link_create_success"));
44
+ }
45
+ catch (_error) {
46
+ setConfirmAction(null);
47
+ message.error(t("invite_link_create_failed"));
48
+ }
49
+ };
50
+ const confirmRegenerate = createLink; // same call — BE create == create-or-replace
51
+ const confirmRevoke = async () => {
52
+ if (!conversationID || !isOwner)
53
+ return;
54
+ try {
55
+ await revokeLinkMutate();
56
+ queryClient.setQueryData([QUERY_KEYS.GET_JOIN_LINK, conversationID], null);
57
+ setConfirmAction(null);
58
+ message.success(t("invite_link_revoke_success"));
59
+ }
60
+ catch (_error) {
61
+ setConfirmAction(null);
62
+ message.error(t("invite_link_revoke_failed"));
63
+ }
64
+ };
65
+ return {
66
+ link,
67
+ isOwner,
68
+ isLoading,
69
+ // A failed fetch must never be shown as "no link exists" — that would
70
+ // read as an invitation to create/replace when a live link may still be
71
+ // active. Callers should render a distinct retry state on isError.
72
+ isError,
73
+ refetch,
74
+ isMutating: isCreating || isRevoking,
75
+ confirmAction,
76
+ createLink,
77
+ openRegenerateConfirm: () => setConfirmAction("regenerate"),
78
+ openRevokeConfirm: () => setConfirmAction("revoke"),
79
+ closeConfirm: () => setConfirmAction(null),
80
+ confirmRegenerate,
81
+ confirmRevoke,
82
+ };
83
+ };
@@ -87,6 +87,7 @@
87
87
  "pin_limit_modal_description": "Chỉ được ghim tối đa {{max}} tin nhắn. Vui lòng bỏ ghim một tin nhắn hiện có trước khi ghim tin nhắn mới.",
88
88
  "pin_limit_modal_confirm": "Đã hiểu",
89
89
  "context_menu_copy_message": "Sao chép tin nhắn",
90
+ "context_menu_copy_message_link": "Sao chép đường dẫn tin nhắn",
90
91
  "copy_message_success": "Sao chép tin nhắn thành công",
91
92
  "context_menu_revoke": "Thu hồi",
92
93
  "revoke_message_confirm_title": "Thu hồi tin nhắn?",
@@ -244,7 +245,23 @@
244
245
  "update_member_permissions_success": "Cập nhật quyền thành viên thành công",
245
246
  "update_admin_permissions_success": "Cập nhật quyền quản trị viên thành công",
246
247
  "invite_via_link": "Mời vào nhóm bằng link",
247
- "invite_via_link_pending": "Tính năng đang được phát triển",
248
+ "invite_link_empty_owner_title": "Tạo link tham gia nhóm",
249
+ "invite_link_empty_owner_subtitle": "Mọi người có thể tham gia nhóm của bạn khi truy cập link này",
250
+ "invite_link_create_button": "Tạo link mới",
251
+ "invite_link_empty_member_title": "Chưa có link tham gia nhóm",
252
+ "invite_link_empty_member_subtitle": "Liên hệ quản trị viên để yêu cầu tạo link mới.",
253
+ "invite_link_box_label": "Invite Link",
254
+ "invite_link_load_failed": "Không thể tải thông tin link. Vui lòng thử lại.",
255
+ "invite_link_action_copy": "Sao chép",
256
+ "invite_link_action_regenerate": "Tạo mới",
257
+ "invite_link_action_revoke": "Thu hồi",
258
+ "invite_link_confirm_title": "Xác nhận",
259
+ "invite_link_regenerate_confirm_message": "Sau khi tạo lại, mọi người không thể sử dụng link này để tham gia nhóm nữa. Bạn có chắc chắn muốn hủy?",
260
+ "invite_link_revoke_confirm_message": "Sau khi hủy, mọi người không thể sử dụng link này để tham gia nhóm nữa. Bạn có chắc chắn muốn hủy?",
261
+ "invite_link_create_success": "Đã tạo link tham gia nhóm",
262
+ "invite_link_create_failed": "Tạo link tham gia nhóm thất bại",
263
+ "invite_link_revoke_success": "Đã thu hồi link tham gia nhóm",
264
+ "invite_link_revoke_failed": "Thu hồi link tham gia nhóm thất bại",
248
265
  "delete_conversation": "Xóa trò chuyện",
249
266
  "disband_group": "Giải tán nhóm",
250
267
  "update_group_info_failed": "Cập nhật thông tin nhóm thất bại",
@@ -1,6 +1,11 @@
1
1
  interface DChatBubbleProps {
2
2
  conversationID?: string;
3
3
  className?: string;
4
+ /**
5
+ * Host-supplied: builds and copies the deep link for one message. Omit it
6
+ * and the "copy message link" context-menu action does not appear.
7
+ */
8
+ onCopyMessageLink?: (conversationId: string, clientMsgID: string) => void;
4
9
  }
5
10
  declare const DChatBubble: (props: DChatBubbleProps) => import("react/jsx-runtime").JSX.Element;
6
11
  export default DChatBubble;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/chat-bubble/index.tsx"],"names":[],"mappings":"AAOA,UAAU,gBAAgB;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAiD3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/chat-bubble/index.tsx"],"names":[],"mappings":"AAOA,UAAU,gBAAgB;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3E;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAmD3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -7,7 +7,7 @@ import useConversationStore from "../../store/conversation";
7
7
  import { DChatSDK } from "../../constants/sdk";
8
8
  const DChatBubble = (props) => {
9
9
  var _a;
10
- const { conversationID, className } = props;
10
+ const { conversationID, className, onCopyMessageLink } = props;
11
11
  const { connectStatus } = useChatContext();
12
12
  const conversationList = useConversationStore((state) => state.conversationList);
13
13
  const setSelectedConversationId = useConversationStore((state) => state.setSelectedConversationId);
@@ -36,6 +36,6 @@ const DChatBubble = (props) => {
36
36
  });
37
37
  }
38
38
  }, [connectStatus, conversationList === null || conversationList === void 0 ? void 0 : conversationList.length, resolvedConversationID]);
39
- return _jsx(ChatBubble, { className: className });
39
+ return (_jsx(ChatBubble, { className: className, onCopyMessageLink: onCopyMessageLink }));
40
40
  };
41
41
  export default DChatBubble;
@@ -4,7 +4,12 @@ export interface DChatConversationDetailProps extends ConversationDetailInputPro
4
4
  className?: string;
5
5
  children?: React.ReactNode;
6
6
  onBack?: () => void;
7
+ /**
8
+ * Host-supplied deep-link builder, forwarded to ConversationMessageList.
9
+ * Ignored when `children` replaces the default message list.
10
+ */
11
+ onCopyMessageLink?: (conversationId: string, clientMsgID: string) => void;
7
12
  }
8
- declare const DChatConversationDetail: ({ className, children, onBack, conversationId, userId, groupId, botId, ...inputProps }: DChatConversationDetailProps) => import("react/jsx-runtime").JSX.Element | null;
13
+ declare const DChatConversationDetail: ({ className, children, onBack, conversationId, userId, groupId, botId, onCopyMessageLink, ...inputProps }: DChatConversationDetailProps) => import("react/jsx-runtime").JSX.Element | null;
9
14
  export default DChatConversationDetail;
10
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/conversation-detail/index.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAWrF,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,6CAA6C,CAAC;AAGrD,MAAM,WAAW,4BACf,SAAQ,4BAA4B,EACpC,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,QAAA,MAAM,uBAAuB,GAAI,wFAS9B,4BAA4B,mDAoN9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/conversation-detail/index.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAWrF,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,6CAA6C,CAAC;AAGrD,MAAM,WAAW,4BACf,SAAQ,4BAA4B,EACpC,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3E;AAED,QAAA,MAAM,uBAAuB,GAAI,2GAY9B,4BAA4B,mDAyN9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
@@ -31,7 +31,10 @@ import { useResolveConversation, } from "../../hooks/conversation/useResolveConv
31
31
  import { isBotPeer } from "../../utils/conversation";
32
32
  const DChatConversationDetail = (_a) => {
33
33
  var _b;
34
- var { className, children, onBack, conversationId, userId, groupId, botId } = _a, inputProps = __rest(_a, ["className", "children", "onBack", "conversationId", "userId", "groupId", "botId"]);
34
+ var { className, children, onBack, conversationId, userId, groupId, botId,
35
+ // Destructured out of the rest spread on purpose: everything left in
36
+ // inputProps goes to ConversationDetailInput, which has no business with it.
37
+ onCopyMessageLink } = _a, inputProps = __rest(_a, ["className", "children", "onBack", "conversationId", "userId", "groupId", "botId", "onCopyMessageLink"]);
35
38
  const { t } = useTranslation("conversation-inbox");
36
39
  const { useUrlState = true } = useChatContext();
37
40
  const onNavigate = useChatNavigate();
@@ -97,6 +100,6 @@ const DChatConversationDetail = (_a) => {
97
100
  !botFlow.flowIsEnd &&
98
101
  botFlow.shouldShowButtons &&
99
102
  botFlow.currentButtonsBlock ? (_jsx(BotButtonMessage, { buttons: botFlow.currentButtonsBlock.data.buttons, disabled: botFlow.isProcessing, onSelect: botFlow.selectButton, onRestart: botFlow.restart })) : null;
100
- return (_jsxs("div", { "data-testid": "conversation-detail-root", className: clsx("relative flex h-full w-full flex-col overflow-hidden bg-white", className), children: [_jsx(ConversationDetailHeader, { conversation: conversation, onBack: handleBack, onProfileClick: openProfile }), _jsx(PinnedMessageBar, {}), _jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: children !== null && children !== void 0 ? children : _jsx(ConversationMessageList, { bottomSlot: botButtonsSlot }) }), footer, isBotThread && (_jsxs(_Fragment, { children: [_jsx(BotMenuSheet, { open: isMenuOpen, items: botFlow.menuItems, disabled: botFlow.isProcessing, onClose: closeMenu, onSelect: botFlow.selectMenuOption }), _jsx(CsSessionConfirmModal, { open: botFlow.isCsConfirmOpen, loading: botFlow.isProcessing, onCancel: botFlow.cancelMenuOption, onConfirm: botFlow.confirmEndCsSession })] })), _jsx(ThreadProfilePanel, { open: isProfileOpen, conversation: conversation, onClose: closeProfile, onSelectResource: setResourceTab }), _jsx(MediaResourcePanel, { open: resourceTab !== null, initialTab: resourceTab !== null && resourceTab !== void 0 ? resourceTab : MediaResourceTabKey.Image, onClose: () => setResourceTab(null) })] }));
103
+ return (_jsxs("div", { "data-testid": "conversation-detail-root", className: clsx("relative flex h-full w-full flex-col overflow-hidden bg-white", className), children: [_jsx(ConversationDetailHeader, { conversation: conversation, onBack: handleBack, onProfileClick: openProfile }), _jsx(PinnedMessageBar, {}), _jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: children !== null && children !== void 0 ? children : (_jsx(ConversationMessageList, { bottomSlot: botButtonsSlot, onCopyMessageLink: onCopyMessageLink })) }), footer, isBotThread && (_jsxs(_Fragment, { children: [_jsx(BotMenuSheet, { open: isMenuOpen, items: botFlow.menuItems, disabled: botFlow.isProcessing, onClose: closeMenu, onSelect: botFlow.selectMenuOption }), _jsx(CsSessionConfirmModal, { open: botFlow.isCsConfirmOpen, loading: botFlow.isProcessing, onCancel: botFlow.cancelMenuOption, onConfirm: botFlow.confirmEndCsSession })] })), _jsx(ThreadProfilePanel, { open: isProfileOpen, conversation: conversation, onClose: closeProfile, onSelectResource: setResourceTab }), _jsx(MediaResourcePanel, { open: resourceTab !== null, initialTab: resourceTab !== null && resourceTab !== void 0 ? resourceTab : MediaResourceTabKey.Image, onClose: () => setResourceTab(null) })] }));
101
104
  };
102
105
  export default DChatConversationDetail;
@@ -10,6 +10,12 @@ export interface DChatConversationInboxProps {
10
10
  showSearch?: boolean;
11
11
  headerTitle?: string;
12
12
  headerRightSlot?: React.ReactNode;
13
+ /**
14
+ * Host-supplied: builds and copies the deep link for one message. Forwarded
15
+ * to the detail screen's message list. Omit it and the "copy message link"
16
+ * context-menu action does not appear.
17
+ */
18
+ onCopyMessageLink?: (conversationId: string, clientMsgID: string) => void;
13
19
  }
14
20
  declare const DChatConversationInbox: (props: DChatConversationInboxProps) => import("react/jsx-runtime").JSX.Element;
15
21
  export default DChatConversationInbox;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/conversation-inbox/index.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG/D,MAAM,WAAW,2BAA2B;IAC1C,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,6FAA6F;IAC7F,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,yEAAyE;IACzE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACnC;AAoLD,QAAA,MAAM,sBAAsB,GAAI,OAAO,2BAA2B,4CAQjE,CAAC;AAEF,eAAe,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/conversation-inbox/index.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG/D,MAAM,WAAW,2BAA2B;IAC1C,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,6FAA6F;IAC7F,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,yEAAyE;IACzE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAClC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3E;AAsLD,QAAA,MAAM,sBAAsB,GAAI,OAAO,2BAA2B,4CAQjE,CAAC;AAEF,eAAe,sBAAsB,CAAC"}
@@ -53,7 +53,7 @@ const useInboxConversationList = (chatCategory) => {
53
53
  const isApplicationTypeValid = Object.values(DChatApplicationType).includes(applicationType);
54
54
  return Object.assign(Object.assign({}, useConversationList({ enabled: isApplicationTypeValid, chatCategory })), { isApplicationTypeValid });
55
55
  };
56
- const DChatConversationInboxBody = ({ onSelectThread, className, moreOptions, showSearch = true, headerTitle, headerRightSlot, target, conversationList, }) => {
56
+ const DChatConversationInboxBody = ({ onSelectThread, className, moreOptions, showSearch = true, headerTitle, headerRightSlot, onCopyMessageLink, target, conversationList, }) => {
57
57
  const { t } = useTranslation("conversation-inbox");
58
58
  const selectedConversationId = useConversationStore((state) => state.selectedConversationId);
59
59
  const [isSearchOpen, { setTrue: openSearch, setFalse: closeSearch }] = useBoolean(false);
@@ -71,7 +71,7 @@ const DChatConversationInboxBody = ({ onSelectThread, className, moreOptions, sh
71
71
  groupId ||
72
72
  botId);
73
73
  if (hasConversationTarget) {
74
- return (_jsx(DChatConversationDetail, { className: className, moreOptions: moreOptions, conversationId: threadId !== null && threadId !== void 0 ? threadId : undefined, userId: userId, groupId: groupId, botId: botId }));
74
+ return (_jsx(DChatConversationDetail, { className: className, moreOptions: moreOptions, conversationId: threadId !== null && threadId !== void 0 ? threadId : undefined, userId: userId, groupId: groupId, botId: botId, onCopyMessageLink: onCopyMessageLink }));
75
75
  }
76
76
  return (_jsxs("div", { "data-testid": "conversation-inbox-root", className: clsx(
77
77
  // `relative` + `overflow-hidden` keep ContactSearchPanel's Drawer
@@ -2,7 +2,8 @@ import { GroupThreadInfoConfig } from "../../components/thread/GroupThreadInfo";
2
2
  interface DChatDeskMessageProps {
3
3
  openCreateCannedModal?: () => void;
4
4
  groupThreadInfoConfig?: GroupThreadInfoConfig;
5
+ onCopyMessageLink?: (conversationId: string, clientMsgID: string) => void;
5
6
  }
6
- declare const DChatDeskMessage: ({ openCreateCannedModal, groupThreadInfoConfig, }: DChatDeskMessageProps) => import("react/jsx-runtime").JSX.Element;
7
+ declare const DChatDeskMessage: ({ openCreateCannedModal, groupThreadInfoConfig, onCopyMessageLink, }: DChatDeskMessageProps) => import("react/jsx-runtime").JSX.Element;
7
8
  export default DChatDeskMessage;
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/desk-message/index.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAE5E,UAAU,qBAAqB;IAC7B,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;CAC/C;AAED,QAAA,MAAM,gBAAgB,GAAI,mDAGvB,qBAAqB,4CAqCvB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/desk-message/index.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAE5E,UAAU,qBAAqB;IAC7B,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3E;AAED,QAAA,MAAM,gBAAgB,GAAI,sEAIvB,qBAAqB,4CAyCvB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -9,11 +9,11 @@ import { ConnectStatus } from "../../types/chat";
9
9
  import useConversationStore from "../../store/conversation";
10
10
  import DeskAssignedSession from "../../components/session/DeskAssignedSession";
11
11
  import ThreadInfo from "../../components/thread/ThreadInfo";
12
- const DChatDeskMessage = ({ openCreateCannedModal, groupThreadInfoConfig, }) => {
12
+ const DChatDeskMessage = ({ openCreateCannedModal, groupThreadInfoConfig, onCopyMessageLink, }) => {
13
13
  const selectedConversationId = useConversationStore((state) => state.selectedConversationId);
14
14
  const searchClientMsgID = useConversationStore((state) => state.searchClientMsgID);
15
- const [isThreadInfoOpen, { toggle: toggleThreadInfo, setFalse: closeThreadInfo }] = useBoolean(false);
15
+ const [isThreadInfoOpen, { toggle: toggleThreadInfo, setFalse: closeThreadInfo },] = useBoolean(false);
16
16
  const { connectStatus } = useChatContext();
17
- return (_jsx(_Fragment, { children: connectStatus === ConnectStatus.Connected ? (_jsxs("div", { className: "flex flex-1 flex-row h-screen bg-[#f5f5f5]", children: [_jsx(DeskAssignedSession, {}), _jsx(DeskConversationList, {}), _jsx(MessageList, { conversationId: selectedConversationId, searchClientMsgID: searchClientMsgID, openCreateCannedModal: openCreateCannedModal, onOpenThreadInfo: toggleThreadInfo }), _jsx(ThreadInfo, { isOpen: isThreadInfoOpen, onClose: closeThreadInfo, groupConfig: groupThreadInfoConfig })] })) : (_jsx("div", { className: "flex flex-1 flex-row h-screen bg-[#f5f5f5]", children: connectStatus === ConnectStatus.Connecting && _jsx(Spin, { fullscreen: true }) })) }));
17
+ return (_jsx(_Fragment, { children: connectStatus === ConnectStatus.Connected ? (_jsxs("div", { className: "flex flex-1 flex-row h-screen bg-[#f5f5f5]", children: [_jsx(DeskAssignedSession, {}), _jsx(DeskConversationList, {}), _jsx(MessageList, { conversationId: selectedConversationId, searchClientMsgID: searchClientMsgID, openCreateCannedModal: openCreateCannedModal, onOpenThreadInfo: toggleThreadInfo, onCopyMessageLink: onCopyMessageLink }), _jsx(ThreadInfo, { isOpen: isThreadInfoOpen, onClose: closeThreadInfo, groupConfig: groupThreadInfoConfig })] })) : (_jsx("div", { className: "flex flex-1 flex-row h-screen bg-[#f5f5f5]", children: connectStatus === ConnectStatus.Connecting && _jsx(Spin, { fullscreen: true }) })) }));
18
18
  };
19
19
  export default DChatDeskMessage;
@@ -0,0 +1,18 @@
1
+ import { IJoinLinkResponse } from "../types/dto";
2
+ /**
3
+ * Fetches the active join link of a conversation.
4
+ *
5
+ * @param conversationId - conversation.conversationID (NOT groupID)
6
+ * @returns The active link, or null when the group has no link / it was revoked
7
+ */
8
+ export declare const getJoinLink: (conversationId: string) => Promise<IJoinLinkResponse | null>;
9
+ /**
10
+ * Creates a join link, replacing any existing one (BE handles replace itself —
11
+ * there is no separate "replace" endpoint; the old code stops working).
12
+ */
13
+ export declare const createJoinLink: (conversationId: string) => Promise<IJoinLinkResponse | null>;
14
+ /**
15
+ * Revokes the active join link. Afterwards getJoinLink() returns null.
16
+ */
17
+ export declare const revokeJoinLink: (conversationId: string) => Promise<boolean>;
18
+ //# sourceMappingURL=joinLink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"joinLink.d.ts","sourceRoot":"","sources":["../../src/services/joinLink.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAE9D;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GACtB,gBAAgB,MAAM,KACrB,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAMlC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,GACzB,gBAAgB,MAAM,KACrB,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAMlC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,GACzB,gBAAgB,MAAM,KACrB,OAAO,CAAC,OAAO,CAMjB,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { apiInstance } from "../services/api";
2
+ import { ENDPOINTS } from "../services/routes";
3
+ /**
4
+ * Fetches the active join link of a conversation.
5
+ *
6
+ * @param conversationId - conversation.conversationID (NOT groupID)
7
+ * @returns The active link, or null when the group has no link / it was revoked
8
+ */
9
+ export const getJoinLink = async (conversationId) => {
10
+ var _a, _b;
11
+ const res = await apiInstance.get(ENDPOINTS.chatService.joinLink(conversationId));
12
+ return (_b = (_a = res === null || res === void 0 ? void 0 : res.data) === null || _a === void 0 ? void 0 : _a.data) !== null && _b !== void 0 ? _b : null;
13
+ };
14
+ /**
15
+ * Creates a join link, replacing any existing one (BE handles replace itself —
16
+ * there is no separate "replace" endpoint; the old code stops working).
17
+ */
18
+ export const createJoinLink = async (conversationId) => {
19
+ var _a, _b;
20
+ const res = await apiInstance.post(ENDPOINTS.chatService.joinLink(conversationId));
21
+ return (_b = (_a = res === null || res === void 0 ? void 0 : res.data) === null || _a === void 0 ? void 0 : _a.data) !== null && _b !== void 0 ? _b : null;
22
+ };
23
+ /**
24
+ * Revokes the active join link. Afterwards getJoinLink() returns null.
25
+ */
26
+ export const revokeJoinLink = async (conversationId) => {
27
+ var _a;
28
+ const res = await apiInstance.post(ENDPOINTS.chatService.revokeJoinLink(conversationId));
29
+ return ((_a = res === null || res === void 0 ? void 0 : res.data) === null || _a === void 0 ? void 0 : _a.data) === true;
30
+ };
@@ -44,5 +44,6 @@ export declare const QUERY_KEYS: {
44
44
  SEARCH_PUBLIC_GROUPS: string;
45
45
  GET_BLACK_LIST: string;
46
46
  DELETE_CONVERSATION: string;
47
+ GET_JOIN_LINK: string;
47
48
  };
48
49
  //# sourceMappingURL=query.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/services/query.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8CtB,CAAC"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/services/query.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+CtB,CAAC"}
@@ -44,4 +44,5 @@ export const QUERY_KEYS = {
44
44
  SEARCH_PUBLIC_GROUPS: "SEARCH_PUBLIC_GROUPS",
45
45
  GET_BLACK_LIST: "GET_BLACK_LIST",
46
46
  DELETE_CONVERSATION: "DELETE_CONVERSATION",
47
+ GET_JOIN_LINK: "GET_JOIN_LINK",
47
48
  };
@@ -26,6 +26,8 @@ export declare const ENDPOINTS: {
26
26
  reopenSession: string;
27
27
  joinSession: (sessionId: string) => string;
28
28
  leaveConversation: (conversationId: string) => string;
29
+ joinLink: (conversationId: string) => string;
30
+ revokeJoinLink: (conversationId: string) => string;
29
31
  getConversationsImDetail: string;
30
32
  getConversationGroups: string;
31
33
  searchConversationGroups: string;
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/services/routes.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS;;;;;;mCAMS,MAAM;;0CAGC,MAAM,UAAU,MAAM;6BAEnC,MAAM;;oDAEiB,MAAM;uCAEnB,MAAM;kCAEX,MAAM;;oCAGJ,MAAM;mCAEP,MAAM;uCAEF,MAAM;;;;;;;;;iCAUZ,MAAM;4CAEK,MAAM;;;;;gDAOF,MAAM;qCAEjB,MAAM;uCAEJ,MAAM;0CAEH,MAAM;yCAEP,MAAM;8BAEjB,MAAM;wCACI,MAAM;;;;;;;;;;;;;;CAezC,CAAC"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/services/routes.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS;;;;;;mCAMS,MAAM;;0CAGC,MAAM,UAAU,MAAM;6BAEnC,MAAM;;oDAEiB,MAAM;uCAEnB,MAAM;kCAEX,MAAM;;oCAGJ,MAAM;mCAEP,MAAM;uCAEF,MAAM;;;;;;;;;iCAUZ,MAAM;4CAEK,MAAM;mCAEf,MAAM;yCAEA,MAAM;;;;;gDAOC,MAAM;qCAEjB,MAAM;uCAEJ,MAAM;0CAEH,MAAM;yCAEP,MAAM;8BAEjB,MAAM;wCACI,MAAM;;;;;;;;;;;;;;CAezC,CAAC"}
@@ -26,6 +26,8 @@ export const ENDPOINTS = {
26
26
  reopenSession: "chat-service/v1/crm/sessions/reopen",
27
27
  joinSession: (sessionId) => `chat-service/v1/crm/sessions/${sessionId}/join`,
28
28
  leaveConversation: (conversationId) => `chat-service/v1/crm/conversations/${conversationId}/leave`,
29
+ joinLink: (conversationId) => `chat-service/v1/crm/conversations/${conversationId}/join-link`,
30
+ revokeJoinLink: (conversationId) => `chat-service/v1/crm/conversations/${conversationId}/join-link/revoke`,
29
31
  getConversationsImDetail: "/chat-service/v1/crm/conversations/im-detail",
30
32
  getConversationGroups: "/chat-service/v1/crm/conversations/groups",
31
33
  searchConversationGroups: "/chat-service/v1/crm/conversations/groups/search",