@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,6 +1,7 @@
1
1
  interface ChatBubbleProps {
2
2
  className?: string;
3
+ onCopyMessageLink?: (conversationId: string, clientMsgID: string) => void;
3
4
  }
4
- declare const ChatBubble: ({ className }: ChatBubbleProps) => import("react/jsx-runtime").JSX.Element;
5
+ declare const ChatBubble: ({ className, onCopyMessageLink }: ChatBubbleProps) => import("react/jsx-runtime").JSX.Element;
5
6
  export default ChatBubble;
6
7
  //# sourceMappingURL=ChatBubble.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatBubble.d.ts","sourceRoot":"","sources":["../../../src/components/chat-bubble/ChatBubble.tsx"],"names":[],"mappings":"AASA,UAAU,eAAe;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,QAAA,MAAM,UAAU,GAAI,eAAe,eAAe,4CAwGjD,CAAC;AAEF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"ChatBubble.d.ts","sourceRoot":"","sources":["../../../src/components/chat-bubble/ChatBubble.tsx"],"names":[],"mappings":"AASA,UAAU,eAAe;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3E;AAED,QAAA,MAAM,UAAU,GAAI,kCAAkC,eAAe,4CA8GpE,CAAC;AAEF,eAAe,UAAU,CAAC"}
@@ -7,7 +7,7 @@ import useConversationStore from "../../store/conversation";
7
7
  import { useIsMobile } from "../../hooks/common/useIsMobile";
8
8
  import { useBoolean } from "ahooks";
9
9
  import { ANTD_PRIMARY_COLOR_VAR } from "../../constants";
10
- const ChatBubble = ({ className }) => {
10
+ const ChatBubble = ({ className, onCopyMessageLink }) => {
11
11
  const isMobile = useIsMobile();
12
12
  const selectedConversationId = useConversationStore((state) => state.selectedConversationId);
13
13
  const searchClientMsgID = useConversationStore((state) => state.searchClientMsgID);
@@ -24,9 +24,9 @@ const ChatBubble = ({ className }) => {
24
24
  body: { padding: 0 },
25
25
  }, classNames: {
26
26
  wrapper: "!z-[9999]",
27
- }, width: "100%", push: false, children: _jsx(MessageList, { conversationId: selectedConversationId, className: "flex-1", onClose: setOpenFalse, searchClientMsgID: searchClientMsgID }) })] }));
27
+ }, width: "100%", push: false, children: _jsx(MessageList, { conversationId: selectedConversationId, className: "flex-1", onClose: setOpenFalse, searchClientMsgID: searchClientMsgID, onCopyMessageLink: onCopyMessageLink }) })] }));
28
28
  }
29
- return (_jsx(Popover, { placement: "topLeft", trigger: "click", open: isOpen, destroyOnHidden: true, content: () => (_jsx("div", { className: "w-[400px] h-[600px]", children: _jsx(MessageList, { conversationId: selectedConversationId, className: "flex-1", onClose: setOpenFalse, searchClientMsgID: searchClientMsgID }) })), styles: { body: { padding: 0 } }, children: _jsx(ConfigProvider, { theme: { token: { colorPrimary: ANTD_PRIMARY_COLOR_VAR } }, children: _jsx(FloatButton, { icon: _jsx("span", { className: "text-white", children: isOpen ? _jsx(CloseOutlined, {}) : _jsx(MessageOutlined, {}) }), type: "primary", style: {
29
+ return (_jsx(Popover, { placement: "topLeft", trigger: "click", open: isOpen, destroyOnHidden: true, content: () => (_jsx("div", { className: "w-[400px] h-[600px]", children: _jsx(MessageList, { conversationId: selectedConversationId, className: "flex-1", onClose: setOpenFalse, searchClientMsgID: searchClientMsgID, onCopyMessageLink: onCopyMessageLink }) })), styles: { body: { padding: 0 } }, children: _jsx(ConfigProvider, { theme: { token: { colorPrimary: ANTD_PRIMARY_COLOR_VAR } }, children: _jsx(FloatButton, { icon: _jsx("span", { className: "text-white", children: isOpen ? _jsx(CloseOutlined, {}) : _jsx(MessageOutlined, {}) }), type: "primary", style: {
30
30
  right: 24,
31
31
  bottom: 24,
32
32
  width: 60,
@@ -1 +1 @@
1
- {"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"AA4BA,QAAA,MAAM,oBAAoB,+CAuVzB,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"AA4BA,QAAA,MAAM,oBAAoB,+CAwVzB,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -83,6 +83,7 @@ const DeskConversationList = () => {
83
83
  }, [filterSummary, isAllGroupsView]);
84
84
  useEffect(() => {
85
85
  const threadId = searchParams.get("threadId");
86
+ const startClientMsgID = searchParams.get("messageId") || undefined;
86
87
  if (!threadId) {
87
88
  useConversationStore.getState().clearSelectedConversation();
88
89
  return;
@@ -98,7 +99,7 @@ const DeskConversationList = () => {
98
99
  // threadId can come from a notification/stale search click — force a
99
100
  // fresh membership check instead of trusting the groupID cache.
100
101
  if (selectedConversation) {
101
- setConversationData(selectedConversation, undefined, {
102
+ setConversationData(selectedConversation, startClientMsgID, {
102
103
  forceRevalidate: true,
103
104
  });
104
105
  }
@@ -108,7 +109,7 @@ const DeskConversationList = () => {
108
109
  // (e.g. the user was removed from that group mid-flight) — only
109
110
  // apply the result if the URL still points at this conversation.
110
111
  if (res.data.length > 0 && searchParams.get("threadId") === threadId) {
111
- setConversationData(res.data[0], undefined, {
112
+ setConversationData(res.data[0], startClientMsgID, {
112
113
  forceRevalidate: true,
113
114
  });
114
115
  updateConversationList(res.data, "filter");
@@ -1,7 +1,13 @@
1
1
  import { type ReactNode } from "react";
2
2
  export interface ConversationMessageListProps {
3
3
  bottomSlot?: ReactNode;
4
+ /**
5
+ * Supplied by the host app — the SDK has no route table, so building the
6
+ * deep link (and copying it) is the host's job. Omit it and the "copy
7
+ * message link" context-menu action is hidden entirely.
8
+ */
9
+ onCopyMessageLink?: (conversationId: string, clientMsgID: string) => void;
4
10
  }
5
- declare const ConversationMessageList: ({ bottomSlot }: ConversationMessageListProps) => import("react/jsx-runtime").JSX.Element;
11
+ declare const ConversationMessageList: ({ bottomSlot, onCopyMessageLink, }: ConversationMessageListProps) => import("react/jsx-runtime").JSX.Element;
6
12
  export default ConversationMessageList;
7
13
  //# sourceMappingURL=ConversationMessageList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationMessageList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ConversationMessageList.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAmBzE,MAAM,WAAW,4BAA4B;IAC3C,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,QAAA,MAAM,uBAAuB,GAAI,gBAAgB,4BAA4B,4CAoM5E,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"ConversationMessageList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ConversationMessageList.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAmBzE,MAAM,WAAW,4BAA4B;IAC3C,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3E;AAED,QAAA,MAAM,uBAAuB,GAAI,oCAG9B,4BAA4B,4CA0N9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
@@ -17,7 +17,7 @@ import { isGroupChat as resolveIsGroupChat } from "../../utils/conversation";
17
17
  import emitter from "../../utils/events";
18
18
  import MessageItem from "../../components/message/item";
19
19
  import PinLimitModal from "./PinLimitModal";
20
- const ConversationMessageList = ({ bottomSlot }) => {
20
+ const ConversationMessageList = ({ bottomSlot, onCopyMessageLink, }) => {
21
21
  const { t } = useTranslation("conversation-inbox");
22
22
  const { t: tCommon } = useTranslation();
23
23
  const { user } = useChatContext();
@@ -57,7 +57,13 @@ const ConversationMessageList = ({ bottomSlot }) => {
57
57
  catch (_a) {
58
58
  toastMessage.error(tCommon("pin_message_failed"));
59
59
  }
60
- }, [conversationId, pinMessage, pinnedRemainingCount, pinnedTotalCount, tCommon]);
60
+ }, [
61
+ conversationId,
62
+ pinMessage,
63
+ pinnedRemainingCount,
64
+ pinnedTotalCount,
65
+ tCommon,
66
+ ]);
61
67
  const handleUnpinMessage = useCallback(async (message) => {
62
68
  try {
63
69
  await unpinMessage(conversationId, message.clientMsgID);
@@ -87,6 +93,14 @@ const ConversationMessageList = ({ bottomSlot }) => {
87
93
  setSearchClientMsgID(clientMsgID);
88
94
  }
89
95
  }, [scrollToMessage, loadState.messageList, setSearchClientMsgID]);
96
+ // MessageItem only knows its own clientMsgID — bind the conversation here,
97
+ // and keep the callback undefined when the host gave us none so MessageItem
98
+ // can drop the menu entry instead of rendering a no-op.
99
+ const handleCopyMessageLink = useCallback((clientMsgID) => {
100
+ if (!conversationId || !clientMsgID)
101
+ return;
102
+ onCopyMessageLink === null || onCopyMessageLink === void 0 ? void 0 : onCopyMessageLink(conversationId, clientMsgID);
103
+ }, [onCopyMessageLink, conversationId]);
90
104
  useEffect(() => {
91
105
  emitter.on("CONVERSATION_CHAT_SCROLL_TO_BOTTOM_IF_AT_BOTTOM", scrollToBottomIfAtBottom);
92
106
  emitter.on("CHAT_LIST_SCROLL_TO_MESSAGE", scrollToMessage);
@@ -99,6 +113,6 @@ const ConversationMessageList = ({ bottomSlot }) => {
99
113
  target: scrollRef,
100
114
  enable: !!openToolboxMessageId,
101
115
  });
102
- return (_jsxs("div", { "data-testid": "conversation-message-list", "data-cp-name": "ConversationMessageList", className: "relative flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-white", children: [isMessageLoading ? (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Spin, {}) })) : (_jsxs(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableConversationMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, containerClassName: "h-full", empty: _jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Empty, { description: t("empty.no_messages") }) }), children: [bottomSlot, loadState.messageList.map((message, index, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat, forceHideTip: index === 0 && !!bottomSlot, contextMenuOpen: openToolboxMessageId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenToolboxMessageId(open ? message.clientMsgID : null), onQuoteMessage: setQuotedMessage, onRevokeMessage: handleRevokeMessage, timeRevealed: revealedTimeMsgId === message.clientMsgID, onToggleTimeReveal: handleToggleTimeReveal, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined, enableLongPress: true }, message.clientMsgID)))] })), _jsx(PinLimitModal, { open: pinLimitModalOpen, max: pinnedTotalCount, onClose: () => setPinLimitModalOpen(false) })] }));
116
+ return (_jsxs("div", { "data-testid": "conversation-message-list", "data-cp-name": "ConversationMessageList", className: "relative flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-white", children: [isMessageLoading ? (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Spin, {}) })) : (_jsxs(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableConversationMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, containerClassName: "h-full", empty: _jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Empty, { description: t("empty.no_messages") }) }), children: [bottomSlot, loadState.messageList.map((message, index, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat, forceHideTip: index === 0 && !!bottomSlot, contextMenuOpen: openToolboxMessageId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenToolboxMessageId(open ? message.clientMsgID : null), onQuoteMessage: setQuotedMessage, onRevokeMessage: handleRevokeMessage, timeRevealed: revealedTimeMsgId === message.clientMsgID, onToggleTimeReveal: handleToggleTimeReveal, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined, onCopyMessageLink: handleCopyMessageLink, enableLongPress: true }, message.clientMsgID)))] })), _jsx(PinLimitModal, { open: pinLimitModalOpen, max: pinnedTotalCount, onClose: () => setPinLimitModalOpen(false) })] }));
103
117
  };
104
118
  export default ConversationMessageList;
@@ -0,0 +1,14 @@
1
+ interface InviteLinkPanelProps {
2
+ open: boolean;
3
+ groupID: string;
4
+ conversationID: string;
5
+ onClose: () => void;
6
+ }
7
+ /**
8
+ * Invite-link management for a group (DROPPII-30551). Renders inline within the
9
+ * "Thông tin" sidebar (back arrow, no gray background), same as
10
+ * MemberPermissionPanel — not as a Modal/Drawer.
11
+ */
12
+ declare const InviteLinkPanel: ({ open, groupID, conversationID, onClose }: InviteLinkPanelProps) => import("react/jsx-runtime").JSX.Element | null;
13
+ export default InviteLinkPanel;
14
+ //# sourceMappingURL=InviteLinkPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InviteLinkPanel.d.ts","sourceRoot":"","sources":["../../../src/components/group/InviteLinkPanel.tsx"],"names":[],"mappings":"AASA,UAAU,oBAAoB;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;GAIG;AACH,QAAA,MAAM,eAAe,GAAI,4CAA4C,oBAAoB,mDAuLxF,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -0,0 +1,61 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Dropdown, message } from "antd";
4
+ import { useTranslation } from "react-i18next";
5
+ import ConfirmModal from "../../components/common/ConfirmModal";
6
+ import { Icon } from "../../components/icon";
7
+ import { useInviteLinkAction } from "../../hooks/group/useInviteLinkAction";
8
+ import { buildInviteLink } from "../../utils/inviteLink";
9
+ /**
10
+ * Invite-link management for a group (DROPPII-30551). Renders inline within the
11
+ * "Thông tin" sidebar (back arrow, no gray background), same as
12
+ * MemberPermissionPanel — not as a Modal/Drawer.
13
+ */
14
+ const InviteLinkPanel = ({ open, groupID, conversationID, onClose }) => {
15
+ const { t } = useTranslation();
16
+ const { link, isOwner, isLoading, isError, refetch, isMutating, confirmAction, createLink, openRegenerateConfirm, openRevokeConfirm, closeConfirm, confirmRegenerate, confirmRevoke, } = useInviteLinkAction({ groupID, conversationID, enabled: open });
17
+ if (!open)
18
+ return null;
19
+ const inviteUrl = (link === null || link === void 0 ? void 0 : link.code) ? buildInviteLink(link.code) : "";
20
+ // isError must short-circuit before the empty states — a failed fetch is
21
+ // NOT "no link exists" (see useInviteLinkAction.ts). Showing "Tạo link mới"
22
+ // on a transient network error would invite the owner to replace a link
23
+ // that may still be active.
24
+ const showOwnerEmptyState = !isLoading && !isError && !link && isOwner;
25
+ const showMemberEmptyState = !isLoading && !isError && !link && !isOwner;
26
+ const handleCopy = () => {
27
+ if (!inviteUrl)
28
+ return;
29
+ navigator.clipboard.writeText(inviteUrl);
30
+ message.success(t("copied_to_clipboard"));
31
+ };
32
+ // Icons are brand-blue in the design (not currentColor/gray) — matches the
33
+ // back-arrow header icon convention already used in MemberPermissionPanel.tsx.
34
+ const ownerMenuItems = [
35
+ {
36
+ key: "copy",
37
+ label: t("invite_link_action_copy"),
38
+ icon: _jsx(Icon, { icon: "copy-o", size: 16, color: "var(--dc-color-primary)" }),
39
+ },
40
+ {
41
+ key: "regenerate",
42
+ label: t("invite_link_action_regenerate"),
43
+ icon: _jsx(Icon, { icon: "refresh-o", size: 16, color: "var(--dc-color-primary)" }),
44
+ },
45
+ {
46
+ key: "revoke",
47
+ label: t("invite_link_action_revoke"),
48
+ icon: _jsx(Icon, { icon: "unlink-alt-o", size: 16, color: "var(--dc-color-primary)" }),
49
+ },
50
+ ];
51
+ const handleMenuClick = ({ key }) => {
52
+ if (key === "copy")
53
+ handleCopy();
54
+ if (key === "regenerate")
55
+ openRegenerateConfirm();
56
+ if (key === "revoke")
57
+ openRevokeConfirm();
58
+ };
59
+ return (_jsxs("div", { className: "absolute inset-0 bg-white flex flex-col z-10", "data-cp-name": "InviteLinkPanel", children: [_jsxs("div", { className: "flex w-full items-center border-b border-gray-100 p-2", children: [_jsx("button", { type: "button", className: "flex shrink-0 cursor-pointer items-center justify-center p-2 text-[var(--dc-color-primary)]", 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("invite_via_link") })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: [showOwnerEmptyState && (_jsxs("div", { className: "flex flex-col items-center gap-4 text-center", children: [_jsx("div", { className: "flex h-24 w-24 items-center justify-center rounded-full bg-gray-100", children: _jsx(Icon, { icon: "link-alt-o", size: 40, className: "text-gray-300" }) }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-base font-bold text-gray-900", children: t("invite_link_empty_owner_title") }), _jsx("p", { className: "text-sm text-gray-400", children: t("invite_link_empty_owner_subtitle") })] }), _jsx("button", { type: "button", className: "w-full rounded-full bg-[var(--dc-color-primary)] px-4 py-2.5 text-sm font-medium text-white disabled:bg-gray-200 disabled:text-gray-400", disabled: isMutating, onClick: createLink, children: t("invite_link_create_button") })] })), showMemberEmptyState && (_jsxs("div", { className: "flex flex-col items-center gap-4 text-center", children: [_jsx("div", { className: "flex h-24 w-24 items-center justify-center rounded-full bg-gray-100", children: _jsx(Icon, { icon: "link-alt-o", size: 40, className: "text-gray-300" }) }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-base font-bold text-gray-900", children: t("invite_link_empty_member_title") }), _jsx("p", { className: "text-sm text-gray-400", children: t("invite_link_empty_member_subtitle") })] })] })), isError && (_jsxs("div", { className: "flex flex-col items-center gap-3 text-center", children: [_jsx("p", { className: "text-sm text-gray-400", children: t("invite_link_load_failed") }), _jsx("button", { type: "button", className: "rounded-full border border-gray-300 px-4 py-1.5 text-sm font-medium text-gray-700", onClick: () => refetch(), children: t("retry") })] })), !isLoading && !isError && link && (_jsxs("div", { className: "flex flex-col gap-2 rounded-lg bg-gray-25 p-3", children: [_jsx("p", { className: "text-sm font-semibold text-gray-900", children: t("invite_link_box_label") }), _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("p", { className: "min-w-0 flex-1 break-all rounded-lg bg-white px-3 py-2 text-sm text-gray-700", children: inviteUrl }), isOwner ? (_jsx(Dropdown, { menu: { items: ownerMenuItems, onClick: handleMenuClick }, trigger: ["click"], children: _jsx("button", { type: "button", className: "flex shrink-0 items-center justify-center p-2 text-gray-500", children: _jsx(Icon, { icon: "more-horizontal-o", size: 18 }) }) })) : (_jsx("button", { type: "button", className: "flex shrink-0 items-center justify-center p-2 text-gray-500", onClick: handleCopy, "aria-label": t("invite_link_action_copy"), children: _jsx(Icon, { icon: "copy-o", size: 18, color: "var(--dc-color-primary)" }) }))] })] }))] }), _jsx(ConfirmModal, { open: confirmAction === "regenerate", onOk: confirmRegenerate, onCancel: closeConfirm, title: t("invite_link_confirm_title"), okText: t("agree"), cancelText: t("cancel"), confirmLoading: isMutating, children: _jsx("p", { children: t("invite_link_regenerate_confirm_message") }) }), _jsx(ConfirmModal, { open: confirmAction === "revoke", onOk: confirmRevoke, onCancel: closeConfirm, title: t("invite_link_confirm_title"), okText: t("agree"), cancelText: t("cancel"), confirmLoading: isMutating, children: _jsx("p", { children: t("invite_link_revoke_confirm_message") }) })] }));
60
+ };
61
+ export default InviteLinkPanel;
@@ -1 +1 @@
1
- {"version":3,"file":"MediaResourceTabs.d.ts","sourceRoot":"","sources":["../../../src/components/media-resources/MediaResourceTabs.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAOlD,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,mBAAmB,CAAC;CAClC;AAED,QAAA,MAAM,iBAAiB,GAAI,iBAExB,sBAAsB,4CAcxB,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"MediaResourceTabs.d.ts","sourceRoot":"","sources":["../../../src/components/media-resources/MediaResourceTabs.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAOlD,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,mBAAmB,CAAC;CAClC;AAED,QAAA,MAAM,iBAAiB,GAAI,iBAExB,sBAAsB,4CAyBxB,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState } from "react";
3
+ import { useEffect, useState } from "react";
4
4
  import { MediaResourceTabKey } from "./constants";
5
5
  import MediaResourceTabBar from "./MediaResourceTabBar";
6
6
  import ImageResourceGrid from "./ImageResourceGrid";
@@ -9,6 +9,16 @@ import FileResourceList from "./FileResourceList";
9
9
  import LinkResourceList from "./LinkResourceList";
10
10
  const MediaResourceTabs = ({ initialTab = MediaResourceTabKey.Image, }) => {
11
11
  const [activeKey, setActiveKey] = useState(initialTab);
12
+ // MediaResourcePanel's Drawer uses forceRender, so this component stays
13
+ // mounted across close/reopen cycles instead of remounting fresh each
14
+ // time — useState(initialTab) alone only applies on the very first
15
+ // mount, so every entry point after the first (e.g. clicking "Video" in
16
+ // MediaEntryCard after "Ảnh" was already opened once) was silently
17
+ // ignored and the panel kept showing whichever tab happened to be active
18
+ // when it first mounted. Re-sync whenever the caller passes a new target.
19
+ useEffect(() => {
20
+ setActiveKey(initialTab);
21
+ }, [initialTab]);
12
22
  return (_jsxs("div", { "data-cp-name": "MediaResourceTabs", className: "flex h-full flex-col bg-white", children: [_jsx(MediaResourceTabBar, { activeKey: activeKey, onChange: setActiveKey }), _jsxs("div", { className: "min-h-0 flex-1", children: [activeKey === MediaResourceTabKey.Image && _jsx(ImageResourceGrid, {}), activeKey === MediaResourceTabKey.Video && _jsx(VideoResourceGrid, {}), activeKey === MediaResourceTabKey.File && _jsx(FileResourceList, {}), activeKey === MediaResourceTabKey.Link && _jsx(LinkResourceList, {})] })] }));
13
23
  };
14
24
  export default MediaResourceTabs;
@@ -5,6 +5,7 @@ interface MessageListProps {
5
5
  onClose?: () => void;
6
6
  openCreateCannedModal?: () => void;
7
7
  onOpenThreadInfo?: () => void;
8
+ onCopyMessageLink?: (conversationId: string, clientMsgID: string) => void;
8
9
  }
9
10
  declare const MessageList: (props: MessageListProps) => import("react/jsx-runtime").JSX.Element;
10
11
  export default MessageList;
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AAiCA,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAkV3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AAiCA,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3E;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAsW3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -31,7 +31,7 @@ dayjs.extend(isToday);
31
31
  const MessageList = (props) => {
32
32
  const { t } = useTranslation();
33
33
  const { user, connectStatus } = useChatContext();
34
- const { onClose, conversationId, searchClientMsgID, openCreateCannedModal, onOpenThreadInfo, } = props;
34
+ const { onClose, conversationId, searchClientMsgID, openCreateCannedModal, onOpenThreadInfo, onCopyMessageLink, } = props;
35
35
  const conversationData = useConversationStore((state) => state.conversationData);
36
36
  const permissions = useGroupPermission(conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID);
37
37
  const isGroupChat = isGroupConversation(conversationData !== null && conversationData !== void 0 ? conversationData : undefined);
@@ -63,7 +63,9 @@ const MessageList = (props) => {
63
63
  // by design, not because it's a removed member.
64
64
  const isConfirmedNonMember = isConfirmedGroup && isGroupInfoFresh && !currentMemberInGroup;
65
65
  const canSendMessage = !isConfirmedGroup ||
66
- (!isConfirmedNonMember && permissions.sendMessage && !isRestrictedByGroupMute);
66
+ (!isConfirmedNonMember &&
67
+ permissions.sendMessage &&
68
+ !isRestrictedByGroupMute);
67
69
  const canPinMessage = !isConfirmedGroup || (!isConfirmedNonMember && permissions.pinMessage);
68
70
  const setQuotedMessage = useConversationStore((state) => state.setQuotedMessage);
69
71
  const setSearchClientMsgID = useConversationStore((state) => state.setSearchClientMsgID);
@@ -79,7 +81,7 @@ const MessageList = (props) => {
79
81
  const pinnedTotalCount = useConversationStore((state) => state.pinnedTotalCount);
80
82
  const pinnedRemainingCount = useConversationStore((state) => state.pinnedRemainingCount);
81
83
  const [pinLimitModalOpen, setPinLimitModalOpen] = useState(false);
82
- const { rawGroupId, latestConversationSession, refetchConversationSessions, } = useConversationSessionState({
84
+ const { rawGroupId, latestConversationSession, refetchConversationSessions } = useConversationSessionState({
83
85
  conversationId,
84
86
  conversationType: conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationType,
85
87
  peerType: conversationData === null || conversationData === void 0 ? void 0 : conversationData.peerType,
@@ -148,6 +150,11 @@ const MessageList = (props) => {
148
150
  toastMessage.error(t("unpin_message_failed"));
149
151
  }
150
152
  }, [conversationId, unpinMessage, t]);
153
+ const handleCopyMessageLink = useCallback((clientMsgID) => {
154
+ if (!conversationId || !clientMsgID)
155
+ return;
156
+ onCopyMessageLink === null || onCopyMessageLink === void 0 ? void 0 : onCopyMessageLink(conversationId, clientMsgID);
157
+ }, [onCopyMessageLink, conversationId]);
151
158
  useEffect(() => {
152
159
  emitter.on("CHAT_LIST_SCROLL_TO_BOTTOM", scrollToBottom);
153
160
  emitter.on("CHAT_LIST_SCROLL_TO_MESSAGE", scrollToMessage);
@@ -180,8 +187,10 @@ const MessageList = (props) => {
180
187
  if (!conversationData) {
181
188
  return (_jsx("div", { className: "flex flex-1 items-center justify-center h-full", children: connectStatus !== ConnectStatus.Connected ? (_jsx(Spin, {})) : (_jsx(Empty, { description: t("no_conversation_data") })) }));
182
189
  }
183
- return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full min-w-0 overflow-hidden bg-white", "data-cp-name": "MessageList", children: [_jsx(MessageHeader, { onClose: onClose, currentSession: latestConversationSession, isJoined: isJoined, onOpenThreadInfo: onOpenThreadInfo }), _jsx(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, children: loadState.messageList.map((message, _, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat, contextMenuOpen: openMenuId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenMenuId(open ? message.clientMsgID : null), onRevokeMessage: handleOpenRevoke, onQuoteMessage: setQuotedMessage, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined, timeRevealed: revealedTimeMsgId === message.clientMsgID, onToggleTimeReveal: handleToggleTimeReveal }, message.clientMsgID))) }), moreNewLoading && (_jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) })), (Boolean(rawGroupId) && isCheckingMembership) || !isGroupStateFresh ? (_jsx("div", { className: "border-t bg-white py-4 flex items-center justify-center", children: _jsx(Spin, {}) })) : (latestConversationSession === null || latestConversationSession === void 0 ? void 0 : latestConversationSession.status) === SessionStatus.UNASSIGNED ? (_jsx(UnassignedSessionFooter, { sessionId: latestConversationSession.id, teamId: latestConversationSession.teamId })) : !isConfirmedGroup && shouldRestrictToLastSession ? (_jsxs("div", { className: "border-t bg-white py-4 flex flex-col items-center gap-2", "data-cp-name": "JoinGroupRequired", children: [_jsx("p", { className: "text-sm text-gray-500", children: t("join_group_required") }), _jsx(Button, { type: "primary", onClick: joinGroup, loading: isJoining, children: t("join_group") })] })) : isViewOnlyMember ? (
190
+ return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full min-w-0 overflow-hidden bg-white", "data-cp-name": "MessageList", children: [_jsx(MessageHeader, { onClose: onClose, currentSession: latestConversationSession, isJoined: isJoined, onOpenThreadInfo: onOpenThreadInfo }), _jsx(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, children: loadState.messageList.map((message, _, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat, contextMenuOpen: openMenuId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenMenuId(open ? message.clientMsgID : null), onRevokeMessage: handleOpenRevoke, onQuoteMessage: setQuotedMessage, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined, timeRevealed: revealedTimeMsgId === message.clientMsgID, onToggleTimeReveal: handleToggleTimeReveal, onCopyMessageLink: handleCopyMessageLink }, message.clientMsgID))) }), moreNewLoading && (_jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) })), (Boolean(rawGroupId) && isCheckingMembership) || !isGroupStateFresh ? (_jsx("div", { className: "border-t bg-white py-4 flex items-center justify-center", children: _jsx(Spin, {}) })) : (latestConversationSession === null || latestConversationSession === void 0 ? void 0 : latestConversationSession.status) === SessionStatus.UNASSIGNED ? (_jsx(UnassignedSessionFooter, { sessionId: latestConversationSession.id, teamId: latestConversationSession.teamId })) : !isConfirmedGroup && shouldRestrictToLastSession ? (_jsxs("div", { className: "border-t bg-white py-4 flex flex-col items-center gap-2", "data-cp-name": "JoinGroupRequired", children: [_jsx("p", { className: "text-sm text-gray-500", children: t("join_group_required") }), _jsx(Button, { type: "primary", onClick: joinGroup, loading: isJoining, children: t("join_group") })] })) : isViewOnlyMember ? (
184
191
  // Brief fallback while isJoined catches up after the kick event.
185
- _jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })) : !canSendMessage ? (_jsx("div", { className: "border-t bg-[#f5f5f5] py-3 flex items-center justify-center", children: _jsx("p", { className: "text-sm text-gray-500", children: t(isRestrictedByGroupMute ? "send_message_disabled_group_mute" : "send_message_disabled") }) })) : (_jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })), _jsx(ConfirmModal, { open: showConfirmRevoke, onOk: onRevokeMessage, onCancel: handleCloseRevoke, title: t("revoke_message_confirm_title"), okText: t("revoke"), cancelText: t("cancel"), okType: "danger", confirmLoading: isRevoking, children: _jsx("p", { children: t("revoke_message_confirm_message") }) }), _jsx(PinLimitModal, { open: pinLimitModalOpen, max: pinnedTotalCount, onClose: () => setPinLimitModalOpen(false) })] }));
192
+ _jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })) : !canSendMessage ? (_jsx("div", { className: "border-t bg-[#f5f5f5] py-3 flex items-center justify-center", children: _jsx("p", { className: "text-sm text-gray-500", children: t(isRestrictedByGroupMute
193
+ ? "send_message_disabled_group_mute"
194
+ : "send_message_disabled") }) })) : (_jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })), _jsx(ConfirmModal, { open: showConfirmRevoke, onOk: onRevokeMessage, onCancel: handleCloseRevoke, title: t("revoke_message_confirm_title"), okText: t("revoke"), cancelText: t("cancel"), okType: "danger", confirmLoading: isRevoking, children: _jsx("p", { children: t("revoke_message_confirm_message") }) }), _jsx(PinLimitModal, { open: pinLimitModalOpen, max: pinnedTotalCount, onClose: () => setPinLimitModalOpen(false) })] }));
186
195
  };
187
196
  export default MessageList;
@@ -16,7 +16,8 @@ interface MessageItemProps {
16
16
  timeRevealed?: boolean;
17
17
  /** Called on bubble tap to toggle this message as the revealed one. */
18
18
  onToggleTimeReveal?: (clientMsgID: string) => void;
19
+ onCopyMessageLink?: (clientMsgID: string) => void;
19
20
  }
20
- declare const MessageItem: ({ message, allMessages, isGroupChat, forceHideTip, contextMenuOpen, onContextMenuOpenChange, onRevokeMessage, onQuoteMessage, onPressQuoteMessage, onPinMessage, onUnpinMessage, enableLongPress, timeRevealed, onToggleTimeReveal, }: MessageItemProps) => import("react/jsx-runtime").JSX.Element | null;
21
+ declare const MessageItem: ({ message, allMessages, isGroupChat, forceHideTip, contextMenuOpen, onContextMenuOpenChange, onRevokeMessage, onQuoteMessage, onPressQuoteMessage, onPinMessage, onUnpinMessage, enableLongPress, timeRevealed, onToggleTimeReveal, onCopyMessageLink, }: MessageItemProps) => import("react/jsx-runtime").JSX.Element | null;
21
22
  export default MessageItem;
22
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,WAAW,IAAI,eAAe,EAG/B,MAAM,8BAA8B,CAAC;AAyCtC,UAAU,gBAAgB;IACxB,OAAO,EAAE,eAAe,CAAC;IACzB,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,uBAAuB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,eAAe,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IACpD,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAClD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IACpD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACpD;AAkCD,QAAA,MAAM,WAAW,GAAI,uOAelB,gBAAgB,mDAkVlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,WAAW,IAAI,eAAe,EAG/B,MAAM,8BAA8B,CAAC;AAyCtC,UAAU,gBAAgB;IACxB,OAAO,EAAE,eAAe,CAAC;IACzB,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,uBAAuB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,eAAe,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IACpD,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAClD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IACpD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD;AAkCD,QAAA,MAAM,WAAW,GAAI,0PAgBlB,gBAAgB,mDA2WlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -37,14 +37,14 @@ const MessageAvatar = ({ avatarUrl, senderNickname, }) => {
37
37
  var _a;
38
38
  return (_jsx(Avatar, { size: MESSAGE_AVATAR_SIZE, src: avatarUrl, className: "shrink-0 self-end", "data-cp-name": "MessageAvatar", children: ((_a = senderNickname === null || senderNickname === void 0 ? void 0 : senderNickname.charAt) === null || _a === void 0 ? void 0 : _a.call(senderNickname, 0)) || "A" }));
39
39
  };
40
- const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip = false, contextMenuOpen, onContextMenuOpenChange, onRevokeMessage, onQuoteMessage, onPressQuoteMessage, onPinMessage, onUnpinMessage, enableLongPress = false, timeRevealed = false, onToggleTimeReveal, }) => {
40
+ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip = false, contextMenuOpen, onContextMenuOpenChange, onRevokeMessage, onQuoteMessage, onPressQuoteMessage, onPinMessage, onUnpinMessage, enableLongPress = false, timeRevealed = false, onToggleTimeReveal, onCopyMessageLink, }) => {
41
41
  var _a;
42
42
  const { t } = useTranslation();
43
43
  const { resendMessage } = useSendMessage();
44
44
  const userID = useAuthStore((state) => state.userID);
45
45
  const isCrm = useAuthStore((state) => state.isCrm);
46
46
  const conversationData = useConversationStore((state) => state.conversationData);
47
- const { isDeliveredForUi, isRevocable, isMine } = useMessageToolboxVisibility(message, userID);
47
+ const { isDeliveredForUi, isRevocable, isMine, isMessageLinkCopyable } = useMessageToolboxVisibility(message, userID);
48
48
  const contextMenuItems = useMemo(() => {
49
49
  if ((message === null || message === void 0 ? void 0 : message.contentType) === MessageType.RevokeMessage ||
50
50
  isSystemLogMessageType(message === null || message === void 0 ? void 0 : message.contentType) ||
@@ -71,7 +71,9 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
71
71
  key: isMessagePinned
72
72
  ? MessageToolboxActionKey.UNPIN
73
73
  : MessageToolboxActionKey.PIN,
74
- label: isMessagePinned ? t("context_menu_unpin") : t("context_menu_pin"),
74
+ label: isMessagePinned
75
+ ? t("context_menu_unpin")
76
+ : t("context_menu_pin"),
75
77
  icon: isMessagePinned ? "thumbtack-off-o" : "thumbtack-o",
76
78
  onClick: () => {
77
79
  if (isMessagePinned) {
@@ -96,6 +98,16 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
96
98
  },
97
99
  });
98
100
  }
101
+ if (isMessageLinkCopyable && onCopyMessageLink) {
102
+ actions.push({
103
+ key: MessageToolboxActionKey.COPY_MESSAGE_LINK,
104
+ label: t("context_menu_copy_message_link"),
105
+ icon: "link-o",
106
+ onClick: () => {
107
+ onCopyMessageLink === null || onCopyMessageLink === void 0 ? void 0 : onCopyMessageLink((message === null || message === void 0 ? void 0 : message.clientMsgID) || "");
108
+ },
109
+ });
110
+ }
99
111
  if (isRevocable && onRevokeMessage) {
100
112
  actions.push({
101
113
  key: MessageToolboxActionKey.REVOKE,
@@ -116,6 +128,8 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
116
128
  onPinMessage,
117
129
  onUnpinMessage,
118
130
  onContextMenuOpenChange,
131
+ onCopyMessageLink,
132
+ isMessageLinkCopyable,
119
133
  ]);
120
134
  const bubbleWrapperRef = useRef(null);
121
135
  useLongPress(() => onContextMenuOpenChange === null || onContextMenuOpenChange === void 0 ? void 0 : onContextMenuOpenChange(true), enableLongPress ? bubbleWrapperRef : undefined, { delay: 500 });
@@ -163,16 +177,14 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
163
177
  !isOrderMessage(previousMessage) &&
164
178
  previousMessage.sendID === (message === null || message === void 0 ? void 0 : message.sendID) &&
165
179
  isSameCalendarDay(message.sendTime, previousMessage.sendTime) &&
166
- Math.abs(dayjs(message.sendTime).diff(previousMessage.sendTime, "millisecond")) <=
167
- GROUPING_GAP_MS;
180
+ Math.abs(dayjs(message.sendTime).diff(previousMessage.sendTime, "millisecond")) <= GROUPING_GAP_MS;
168
181
  const nextSameUser = !isOrder &&
169
182
  !nextBreaksGroup &&
170
183
  !!nextMessage &&
171
184
  !isOrderMessage(nextMessage) &&
172
185
  nextMessage.sendID === (message === null || message === void 0 ? void 0 : message.sendID) &&
173
186
  isSameCalendarDay(nextMessage.sendTime, message.sendTime) &&
174
- Math.abs(dayjs(nextMessage.sendTime).diff(message.sendTime, "millisecond")) <=
175
- GROUPING_GAP_MS;
187
+ Math.abs(dayjs(nextMessage.sendTime).diff(message.sendTime, "millisecond")) <= GROUPING_GAP_MS;
176
188
  const showTimeBreak = !previousMessage ||
177
189
  !isSameCalendarDay(message.sendTime, previousMessage.sendTime) ||
178
190
  Math.abs(dayjs(message.sendTime).diff(previousMessage.sendTime, "millisecond")) > DIVIDER_GAP_MS;
@@ -5,6 +5,7 @@ export declare const MessageToolboxActionKey: {
5
5
  readonly VIEW_ORIGINAL: "VIEW_ORIGINAL";
6
6
  readonly COPY_MESSAGE: "COPY_MESSAGE";
7
7
  readonly REVOKE: "REVOKE";
8
+ readonly COPY_MESSAGE_LINK: "COPY_MESSAGE_LINK";
8
9
  };
9
10
  export type MessageToolboxActionKey = (typeof MessageToolboxActionKey)[keyof typeof MessageToolboxActionKey];
10
11
  export interface MessageToolboxAction {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB;;;;;;;CAO1B,CAAC;AAEX,MAAM,MAAM,uBAAuB,GACjC,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,OAAO,uBAAuB,CAAC,CAAC;AAEzE,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,uBAAuB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB;;;;;;;;CAQ1B,CAAC;AAEX,MAAM,MAAM,uBAAuB,GACjC,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,OAAO,uBAAuB,CAAC,CAAC;AAEzE,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,uBAAuB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB"}
@@ -5,4 +5,5 @@ export const MessageToolboxActionKey = {
5
5
  VIEW_ORIGINAL: "VIEW_ORIGINAL",
6
6
  COPY_MESSAGE: "COPY_MESSAGE",
7
7
  REVOKE: "REVOKE",
8
+ COPY_MESSAGE_LINK: "COPY_MESSAGE_LINK",
8
9
  };
@@ -3,6 +3,7 @@ interface MessageToolboxVisibility {
3
3
  isDeliveredForUi: boolean;
4
4
  isMine: boolean;
5
5
  isRevocable: boolean;
6
+ isMessageLinkCopyable: boolean;
6
7
  }
7
8
  export declare const useMessageToolboxVisibility: (message: MessageItemType, userID?: string) => MessageToolboxVisibility;
8
9
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"useMessageToolboxVisibility.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/useMessageToolboxVisibility.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,WAAW,IAAI,eAAe,EAE/B,MAAM,8BAA8B,CAAC;AAItC,UAAU,wBAAwB;IAChC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,2BAA2B,GACtC,SAAS,eAAe,EACxB,SAAS,MAAM,KACd,wBAcoB,CAAC"}
1
+ {"version":3,"file":"useMessageToolboxVisibility.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/useMessageToolboxVisibility.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,WAAW,IAAI,eAAe,EAG/B,MAAM,8BAA8B,CAAC;AAItC,UAAU,wBAAwB;IAChC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,qBAAqB,EAAE,OAAO,CAAC;CAChC;AAED,eAAO,MAAM,2BAA2B,GACtC,SAAS,eAAe,EACxB,SAAS,MAAM,KACd,wBAqBoB,CAAC"}
@@ -1,15 +1,19 @@
1
1
  import { useMemo } from "react";
2
2
  import dayjs from "dayjs";
3
- import { MessageStatus, } from "@droppii-org/wasm-client-sdk";
3
+ import { MessageStatus, MessageType, } from "@droppii-org/wasm-client-sdk";
4
4
  const REVOKE_WINDOW_DAYS = 1;
5
5
  export const useMessageToolboxVisibility = (message, userID) => useMemo(() => {
6
6
  const isMine = (message === null || message === void 0 ? void 0 : message.sendID) === userID;
7
- const isOlderThanRevokeWindow = dayjs().diff(dayjs(message === null || message === void 0 ? void 0 : message.sendTime), "days", true) >= REVOKE_WINDOW_DAYS;
7
+ const isOlderThanRevokeWindow = dayjs().diff(dayjs(message === null || message === void 0 ? void 0 : message.sendTime), "days", true) >=
8
+ REVOKE_WINDOW_DAYS;
8
9
  const isDeliveredForUi = (message === null || message === void 0 ? void 0 : message.status) !== MessageStatus.Sending &&
9
10
  (message === null || message === void 0 ? void 0 : message.status) !== MessageStatus.Failed;
11
+ const isRevocable = isMine && isDeliveredForUi && !isOlderThanRevokeWindow;
12
+ const isMessageLinkCopyable = isDeliveredForUi && (message === null || message === void 0 ? void 0 : message.contentType) !== MessageType.RevokeMessage;
10
13
  return {
11
14
  isDeliveredForUi,
12
15
  isMine,
13
- isRevocable: isMine && isDeliveredForUi && !isOlderThanRevokeWindow,
16
+ isRevocable,
17
+ isMessageLinkCopyable,
14
18
  };
15
19
  }, [userID, message]);
@@ -1 +1 @@
1
- {"version":3,"file":"GroupThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/GroupThreadInfo.tsx"],"names":[],"mappings":"AAqBA,MAAM,WAAW,qBAAqB;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AAID,QAAA,MAAM,eAAe,GAAI,qBAAqB,oBAAoB,mDAilBjE,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"GroupThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/GroupThreadInfo.tsx"],"names":[],"mappings":"AAsBA,MAAM,WAAW,qBAAqB;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AASD,QAAA,MAAM,eAAe,GAAI,qBAAqB,oBAAoB,mDA8mBjE,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -17,6 +17,7 @@ import { useUploadFile } from "../../hooks/common/useUploadFile";
17
17
  import { ACCEPTED_IMAGE_TYPES, MAX_IMAGE_SIZE_MB } from "../../utils/fileValidation";
18
18
  import AdminPermissionModal from "../../components/group/AdminPermissionModal";
19
19
  import MemberPermissionPanel from "../../components/group/MemberPermissionPanel";
20
+ import InviteLinkPanel from "../../components/group/InviteLinkPanel";
20
21
  import GroupCompositeAvatar from "../../components/group/GroupCompositeAvatar";
21
22
  const GroupThreadInfo = ({ onClose, config }) => {
22
23
  var _a, _b, _c, _d;
@@ -39,6 +40,10 @@ const GroupThreadInfo = ({ onClose, config }) => {
39
40
  // Non-member viewing an existing public group in view-only mode — no
40
41
  // longer a member, so "Rời khỏi nhóm" (leave) doesn't apply.
41
42
  const isViewOnlyMember = useConversationStore((state) => state.isViewOnlyMember);
43
+ // Feature ships to staging first (DROPPII-30551). Same env var apps/web already
44
+ // reads in src/app/layout.tsx:51 — this repo has no feature-flag service and
45
+ // this ticket is not the place to introduce one.
46
+ const isInviteLinkFeatureEnabled = process.env.NEXT_PUBLIC_ENV === "stg";
42
47
  const [editingField, setEditingField] = useState(null);
43
48
  const [nameValue, setNameValue] = useState((_a = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName) !== null && _a !== void 0 ? _a : "");
44
49
  const [descriptionValue, setDescriptionValue] = useState((_b = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) !== null && _b !== void 0 ? _b : "");
@@ -53,6 +58,17 @@ const GroupThreadInfo = ({ onClose, config }) => {
53
58
  (_a = descriptionInputRef.current) === null || _a === void 0 ? void 0 : _a.focus({ cursor: "end" });
54
59
  }
55
60
  }, [editingField]);
61
+ // Losing membership (kicked from a public group, staying view-only) can
62
+ // happen while this panel is already open — same transition
63
+ // useLeaveGroupAction.ts guards against for its own modals. The row is
64
+ // already hidden via !isViewOnlyMember, but activeRow isn't reset by that,
65
+ // so without this the invite-link panel would keep rendering/querying for
66
+ // a group the user can no longer act in.
67
+ useEffect(() => {
68
+ if (isViewOnlyMember && activeRow === "invite-link") {
69
+ setActiveRow(null);
70
+ }
71
+ }, [isViewOnlyMember, activeRow]);
56
72
  // changeGroupMute() persists via GroupItem.status (GroupStatus.Muted),
57
73
  // refreshed into currentGroupInfo on OnGroupInfoChanged — same pattern as
58
74
  // notifyEnabled below, not a locally-tracked flag.
@@ -229,6 +245,6 @@ const GroupThreadInfo = ({ onClose, config }) => {
229
245
  return (_jsxs("div", { className: "h-full w-[330px] bg-white shadow-md flex flex-col relative overflow-hidden", "data-cp-name": "GroupThreadInfo", children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-4 border-b border-gray-100", children: [_jsx("span", { className: "text-lg font-medium", children: t("thread_info_title") }), _jsx("button", { type: "button", className: "flex items-center justify-center w-8 h-8 rounded-full text-gray-500 hover:bg-gray-100", onClick: onClose, children: _jsx(Icon, { icon: "close-o", size: 20 }) })] }), _jsxs("div", { className: "flex-1 overflow-y-auto", children: [_jsxs("div", { className: "flex items-center gap-3 px-4 py-4", children: [_jsxs("div", { className: "relative shrink-0", children: [_jsx(GroupCompositeAvatar, { groupID: groupID, faceURL: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.faceURL, ownerUserID: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.ownerUserID, groupName: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName, size: 56 }), isAvatarUploading && (_jsx("span", { className: "absolute inset-0 flex items-center justify-center rounded-full bg-black/30", children: _jsx("span", { className: "h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent" }) })), permissions.updateGroupAvatar && (_jsxs(_Fragment, { children: [_jsx("input", { ref: avatarInputRef, type: "file", accept: ACCEPTED_IMAGE_TYPES.join(", "), className: "hidden", onChange: handleAvatarSelect }), _jsx("button", { type: "button", disabled: isAvatarUploading, onClick: handleAvatarClick, className: "absolute -bottom-1 -right-1 flex items-center justify-center w-5 h-5 rounded-full bg-black text-white", children: _jsx(Icon, { icon: "camera-o", size: 12 }) })] }))] }), _jsx("div", { className: "flex-1 min-w-0", children: editingField !== "name" && (_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("p", { className: "text-base font-medium truncate", children: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName }), permissions.updateGroupName && (_jsx(Icon, { icon: "edit-o", size: 16, className: "shrink-0 cursor-pointer text-gray-500", onClick: startEditName }))] })) })] }), editingField === "name" && (_jsx("div", { className: "px-4 pb-4", children: _jsxs("div", { className: "flex flex-col gap-3 bg-[#f5f5f5] rounded-lg p-3", children: [_jsx(Input, { autoFocus: true, value: nameValue, onChange: (event) => setNameValue(event.target.value), maxLength: 255, showCount: true, disabled: isSavingGroupInfo, className: "bg-white" }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg border border-[var(--dc-color-primary-hover)] text-[var(--dc-color-primary-hover)] text-sm font-medium disabled:border-gray-200 disabled:text-gray-300", disabled: isSavingGroupInfo, onClick: cancelEditName, children: t("cancel") }), _jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg bg-[var(--dc-color-primary-hover)] text-white text-sm font-medium disabled:bg-gray-100 disabled:text-gray-300", disabled: isSavingGroupInfo ||
230
246
  !nameValue.trim() ||
231
247
  nameValue.trim() === ((_c = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName) !== null && _c !== void 0 ? _c : ""), onClick: saveName, children: t("save") })] })] }) })), _jsx("div", { className: "px-4 pb-4", children: editingField === "description" ? (_jsxs("div", { className: "flex flex-col gap-3 bg-[#f5f5f5] rounded-lg p-3", children: [_jsxs("span", { className: "flex items-center gap-2 text-sm font-medium text-gray-900", children: [_jsx(Icon, { icon: "info-circle-o", size: 18, className: "text-gray-500" }), t("description")] }), _jsx(Input.TextArea, { ref: descriptionInputRef, value: descriptionValue, onChange: (event) => setDescriptionValue(event.target.value), placeholder: t("enter_group_description"), autoSize: { minRows: 1, maxRows: 4 }, maxLength: 500, disabled: isSavingGroupInfo, className: "bg-white" }), _jsxs("span", { className: "self-end text-xs text-gray-400", children: [descriptionValue.length, " / 500"] }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg border border-[var(--dc-color-primary-hover)] text-[var(--dc-color-primary-hover)] text-sm font-medium disabled:border-gray-200 disabled:text-gray-300", disabled: isSavingGroupInfo, onClick: cancelEditDescription, children: t("cancel") }), _jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg bg-[var(--dc-color-primary-hover)] text-white text-sm font-medium disabled:bg-gray-100 disabled:text-gray-300", disabled: isSavingGroupInfo ||
232
- descriptionValue.trim() === ((_d = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) !== null && _d !== void 0 ? _d : ""), onClick: saveDescription, children: t("save") })] })] })) : (_jsxs("div", { className: "flex items-start justify-between gap-2 bg-[#f5f5f5] rounded-lg px-3 py-2.5", children: [_jsxs("div", { className: "flex items-start gap-2 flex-1 min-w-0", children: [_jsx(Icon, { icon: "info-circle-o", size: 18, className: "shrink-0 mt-0.5 text-gray-900" }), _jsxs("div", { className: "flex flex-col gap-0.5 min-w-0", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: t("description") }), _jsx("p", { className: "text-sm text-gray-400 truncate", children: (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) || t("no_description") })] })] }), permissions.updateGroupDescription && (_jsx(Icon, { icon: "edit-o", size: 16, className: "shrink-0 cursor-pointer text-gray-500", onClick: startEditDescription }))] })) }), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 rounded-lg overflow-hidden", children: [_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-o", size: 18 }), t("notification")] }), _jsx(Switch, { checked: notifyEnabled, onChange: handleToggleNotification, loading: isTogglingNotification })] }), (config === null || config === void 0 ? void 0 : config.showAllowSendMessage) && isOwner && (_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-check-o", size: 18 }), t("allow_send_message")] }), _jsx(Switch, { checked: allowSendMessage, onChange: handleToggleSendMessage, loading: isTogglingSendMessage })] })), isAdminOrOwner && (config === null || config === void 0 ? void 0 : config.showMutedChatList) && (_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("muted-chat-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-off-o", size: 18 }), t("muted_chat_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })), isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("block-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("block_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }))] }), permissions.configGroupPermission && (_jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 rounded-lg overflow-hidden", children: [_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("member-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-o", size: 18 }), t("member_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }), _jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("admin-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-done-o", size: 18 }), t("admin_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })] })), _jsx("div", { className: "mx-4 mt-3 rounded-lg overflow-hidden bg-[#f5f5f5]", children: _jsxs("button", { type: "button", className: "w-full flex items-center gap-3 px-3 py-3 text-left text-sm text-gray-400 cursor-not-allowed", disabled: true, title: t("invite_via_link_pending"), children: [_jsx(Icon, { icon: "link-alt-o", size: 18 }), t("invite_via_link")] }) }), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 mb-4 rounded-lg overflow-hidden", children: [isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("delete"), children: [_jsx(Icon, { icon: "trash-o", size: 18 }), t("delete_conversation")] })), !isViewOnlyMember && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("leave"), children: [_jsx(Icon, { icon: "logout-o", size: 18 }), t("leave_group")] })), isOwner && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("disband"), children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("disband_group")] }))] })] }), _jsx(ConfirmModal, { open: confirmAction === "leave", onOk: handleConfirmLeave, onCancel: () => setConfirmAction(null), title: t("leave_group_confirm_title"), okText: t("leave_group"), cancelText: t("cancel"), okType: "danger", confirmLoading: isLeaving, children: _jsx("p", { children: t("leave_group_confirm_message") }) }), _jsx(ConfirmModal, { open: confirmAction === "disband", onOk: handleConfirmDisband, onCancel: () => setConfirmAction(null), title: t("disband_group_confirm_title"), okText: t("disband_group"), cancelText: t("cancel"), okType: "danger", confirmLoading: isDismissing, children: _jsx("p", { children: t("disband_group_confirm_message") }) }), _jsx(ConfirmModal, { open: confirmAction === "delete", onOk: handleConfirmDelete, onCancel: () => setConfirmAction(null), title: t("delete_conversation_confirm_title"), okText: t("delete_conversation"), cancelText: t("cancel"), okType: "danger", confirmLoading: isDeleting, children: _jsx("p", { children: t("delete_conversation_confirm_message") }) }), _jsx(MemberPermissionPanel, { open: activeRow === "member-permission", groupID: groupID, onClose: () => setActiveRow(null) }), _jsx(AdminPermissionModal, { open: activeRow === "admin-permission", groupID: groupID, onClose: () => setActiveRow(null), inline: true }), (activeRow === "muted-chat-list" || activeRow === "block-list") && (_jsx(Modal, { centered: true, open: true, footer: null, onCancel: () => setActiveRow(null), title: t(activeRow === "muted-chat-list" ? "muted_chat_list" : "block_list"), getContainer: false, forceRender: true, children: _jsx("p", { className: "text-sm text-gray-500", children: t("feature_coming_soon") }) }))] }));
248
+ descriptionValue.trim() === ((_d = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) !== null && _d !== void 0 ? _d : ""), onClick: saveDescription, children: t("save") })] })] })) : (_jsxs("div", { className: "flex items-start justify-between gap-2 bg-[#f5f5f5] rounded-lg px-3 py-2.5", children: [_jsxs("div", { className: "flex items-start gap-2 flex-1 min-w-0", children: [_jsx(Icon, { icon: "info-circle-o", size: 18, className: "shrink-0 mt-0.5 text-gray-900" }), _jsxs("div", { className: "flex flex-col gap-0.5 min-w-0", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: t("description") }), _jsx("p", { className: "text-sm text-gray-400 truncate", children: (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) || t("no_description") })] })] }), permissions.updateGroupDescription && (_jsx(Icon, { icon: "edit-o", size: 16, className: "shrink-0 cursor-pointer text-gray-500", onClick: startEditDescription }))] })) }), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 rounded-lg overflow-hidden", children: [_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-o", size: 18 }), t("notification")] }), _jsx(Switch, { checked: notifyEnabled, onChange: handleToggleNotification, loading: isTogglingNotification })] }), (config === null || config === void 0 ? void 0 : config.showAllowSendMessage) && isOwner && (_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-check-o", size: 18 }), t("allow_send_message")] }), _jsx(Switch, { checked: allowSendMessage, onChange: handleToggleSendMessage, loading: isTogglingSendMessage })] })), isAdminOrOwner && (config === null || config === void 0 ? void 0 : config.showMutedChatList) && (_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("muted-chat-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-off-o", size: 18 }), t("muted_chat_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })), isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("block-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("block_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }))] }), permissions.configGroupPermission && (_jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 rounded-lg overflow-hidden", children: [_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("member-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-o", size: 18 }), t("member_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }), _jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("admin-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-done-o", size: 18 }), t("admin_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })] })), isInviteLinkFeatureEnabled && !isViewOnlyMember && (_jsx("div", { className: "mx-4 mt-3 rounded-lg overflow-hidden bg-[#f5f5f5]", children: _jsxs("button", { type: "button", className: "w-full flex items-center justify-between px-3 py-3 text-left text-sm", onClick: () => setActiveRow("invite-link"), children: [_jsxs("span", { className: "flex items-center gap-3", children: [_jsx(Icon, { icon: "link-alt-o", size: 18 }), t("invite_via_link")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }) })), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 mb-4 rounded-lg overflow-hidden", children: [isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("delete"), children: [_jsx(Icon, { icon: "trash-o", size: 18 }), t("delete_conversation")] })), !isViewOnlyMember && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("leave"), children: [_jsx(Icon, { icon: "logout-o", size: 18 }), t("leave_group")] })), isOwner && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("disband"), children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("disband_group")] }))] })] }), _jsx(ConfirmModal, { open: confirmAction === "leave", onOk: handleConfirmLeave, onCancel: () => setConfirmAction(null), title: t("leave_group_confirm_title"), okText: t("leave_group"), cancelText: t("cancel"), okType: "danger", confirmLoading: isLeaving, children: _jsx("p", { children: t("leave_group_confirm_message") }) }), _jsx(ConfirmModal, { open: confirmAction === "disband", onOk: handleConfirmDisband, onCancel: () => setConfirmAction(null), title: t("disband_group_confirm_title"), okText: t("disband_group"), cancelText: t("cancel"), okType: "danger", confirmLoading: isDismissing, children: _jsx("p", { children: t("disband_group_confirm_message") }) }), _jsx(ConfirmModal, { open: confirmAction === "delete", onOk: handleConfirmDelete, onCancel: () => setConfirmAction(null), title: t("delete_conversation_confirm_title"), okText: t("delete_conversation"), cancelText: t("cancel"), okType: "danger", confirmLoading: isDeleting, children: _jsx("p", { children: t("delete_conversation_confirm_message") }) }), _jsx(MemberPermissionPanel, { open: activeRow === "member-permission", groupID: groupID, onClose: () => setActiveRow(null) }), _jsx(AdminPermissionModal, { open: activeRow === "admin-permission", groupID: groupID, onClose: () => setActiveRow(null), inline: true }), _jsx(InviteLinkPanel, { open: activeRow === "invite-link", groupID: groupID, conversationID: conversationID, onClose: () => setActiveRow(null) }), (activeRow === "muted-chat-list" || activeRow === "block-list") && (_jsx(Modal, { centered: true, open: true, footer: null, onCancel: () => setActiveRow(null), title: t(activeRow === "muted-chat-list" ? "muted_chat_list" : "block_list"), getContainer: false, forceRender: true, children: _jsx("p", { className: "text-sm text-gray-500", children: t("feature_coming_soon") }) }))] }));
233
249
  };
234
250
  export default GroupThreadInfo;