@droppii-org/chat-sdk 0.4.1 → 0.4.3
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.
- package/dist/assets/images/searchEmpty.png +0 -0
- package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
- package/dist/components/conversation/DeskConversationList.js +22 -8
- package/dist/components/conversation-detail/ConversationDetailInput.d.ts.map +1 -1
- package/dist/components/conversation-detail/ConversationDetailInput.js +142 -4
- package/dist/components/conversation-detail/mention/BizMentionMenu.d.ts +15 -0
- package/dist/components/conversation-detail/mention/BizMentionMenu.d.ts.map +1 -0
- package/dist/components/conversation-detail/mention/BizMentionMenu.js +11 -0
- package/dist/components/conversation-detail/mention/buildMentionPayload.d.ts +4 -0
- package/dist/components/conversation-detail/mention/buildMentionPayload.d.ts.map +1 -0
- package/dist/components/conversation-detail/mention/buildMentionPayload.js +23 -0
- package/dist/components/conversation-detail/mention/getCaretCoordinates.d.ts +11 -0
- package/dist/components/conversation-detail/mention/getCaretCoordinates.d.ts.map +1 -0
- package/dist/components/conversation-detail/mention/getCaretCoordinates.js +36 -0
- package/dist/components/conversation-detail/mention/useMentionTokens.d.ts +21 -0
- package/dist/components/conversation-detail/mention/useMentionTokens.d.ts.map +1 -0
- package/dist/components/conversation-detail/mention/useMentionTokens.js +58 -0
- package/dist/components/conversation-detail/mention/useMentionTrigger.d.ts +7 -0
- package/dist/components/conversation-detail/mention/useMentionTrigger.d.ts.map +1 -0
- package/dist/components/conversation-detail/mention/useMentionTrigger.js +19 -0
- package/dist/components/conversation-inbox/ConversationInboxHeader.d.ts +4 -1
- package/dist/components/conversation-inbox/ConversationInboxHeader.d.ts.map +1 -1
- package/dist/components/conversation-inbox/ConversationInboxHeader.js +3 -2
- package/dist/components/conversation-inbox/contact-search/ContactSearchFilters.d.ts +8 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchFilters.d.ts.map +1 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchFilters.js +19 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchPanel.d.ts +7 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchPanel.d.ts.map +1 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchPanel.js +116 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchResultItem.d.ts +9 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchResultItem.d.ts.map +1 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchResultItem.js +34 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchSectionHeader.d.ts +8 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchSectionHeader.d.ts.map +1 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchSectionHeader.js +7 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchStateMessage.d.ts +8 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchStateMessage.d.ts.map +1 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchStateMessage.js +9 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchTabs.d.ts +10 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchTabs.d.ts.map +1 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchTabs.js +39 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchViewMoreBar.d.ts +6 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchViewMoreBar.d.ts.map +1 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchViewMoreBar.js +9 -0
- package/dist/components/conversation-inbox/contact-search/constants.d.ts +18 -0
- package/dist/components/conversation-inbox/contact-search/constants.d.ts.map +1 -0
- package/dist/components/conversation-inbox/contact-search/constants.js +23 -0
- package/dist/components/conversation-inbox/contact-search/index.d.ts +10 -0
- package/dist/components/conversation-inbox/contact-search/index.d.ts.map +1 -0
- package/dist/components/conversation-inbox/contact-search/index.js +9 -0
- package/dist/components/conversation-inbox/contact-search/utils.d.ts +6 -0
- package/dist/components/conversation-inbox/contact-search/utils.d.ts.map +1 -0
- package/dist/components/conversation-inbox/contact-search/utils.js +44 -0
- package/dist/components/message/GroupMembersDrawer.d.ts.map +1 -1
- package/dist/components/message/GroupMembersDrawer.js +12 -2
- package/dist/components/message/footer/mention/useMentionOptions.d.ts +1 -0
- package/dist/components/message/footer/mention/useMentionOptions.d.ts.map +1 -1
- package/dist/components/message/footer/mention/useMentionOptions.js +1 -1
- package/dist/components/thread/GroupThreadInfo.d.ts.map +1 -1
- package/dist/components/thread/GroupThreadInfo.js +12 -2
- package/dist/components/thread/ThreadInfo.d.ts.map +1 -1
- package/dist/components/thread/ThreadInfo.js +4 -1
- package/dist/constants/images.d.ts +1 -0
- package/dist/constants/images.d.ts.map +1 -1
- package/dist/constants/images.js +2 -0
- package/dist/hooks/search/useSearchContacts.d.ts +18 -0
- package/dist/hooks/search/useSearchContacts.d.ts.map +1 -0
- package/dist/hooks/search/useSearchContacts.js +39 -0
- package/dist/locales/vi/conversation-inbox.json +22 -1
- package/dist/screens/conversation-inbox/index.d.ts.map +1 -1
- package/dist/screens/conversation-inbox/index.js +14 -3
- package/dist/services/query.d.ts +1 -0
- package/dist/services/query.d.ts.map +1 -1
- package/dist/services/query.js +1 -0
- package/dist/services/routes.d.ts +1 -0
- package/dist/services/routes.d.ts.map +1 -1
- package/dist/services/routes.js +1 -0
- package/dist/styles/global.css +1 -1
- package/dist/types/dto.d.ts +26 -0
- package/dist/types/dto.d.ts.map +1 -1
- package/dist/types/dto.js +10 -1
- package/package.json +2 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
interface ContactSearchPanelProps {
|
|
2
|
+
open: boolean;
|
|
3
|
+
onClose: () => void;
|
|
4
|
+
}
|
|
5
|
+
declare const ContactSearchPanel: ({ open, onClose }: ContactSearchPanelProps) => import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export default ContactSearchPanel;
|
|
7
|
+
//# sourceMappingURL=ContactSearchPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContactSearchPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/ContactSearchPanel.tsx"],"names":[],"mappings":"AA2BA,UAAU,uBAAuB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,QAAA,MAAM,kBAAkB,GAAI,mBAAmB,uBAAuB,4CAoOrE,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Drawer, Input, Spin } from "antd";
|
|
5
|
+
import { useDebounce } from "ahooks";
|
|
6
|
+
import InfiniteScroll from "react-infinite-scroll-component";
|
|
7
|
+
import { useTranslation } from "react-i18next";
|
|
8
|
+
import { Icon } from "../../../components/icon";
|
|
9
|
+
import { useChatContext } from "../../../context/ChatContext";
|
|
10
|
+
import { useSearchContacts } from "../../../hooks/search/useSearchContacts";
|
|
11
|
+
import ContactSearchFilters from "./ContactSearchFilters";
|
|
12
|
+
import ContactSearchResultItem from "./ContactSearchResultItem";
|
|
13
|
+
import ContactSearchSectionHeader from "./ContactSearchSectionHeader";
|
|
14
|
+
import ContactSearchStateMessage from "./ContactSearchStateMessage";
|
|
15
|
+
import ContactSearchTabs from "./ContactSearchTabs";
|
|
16
|
+
import ContactSearchViewMoreBar from "./ContactSearchViewMoreBar";
|
|
17
|
+
import { CONTACT_SEARCH_DEBOUNCE_MS, CONTACT_SEARCH_ICON_SIZE, CONTACT_SEARCH_PREVIEW_LIMIT, ContactSearchTab, } from "./constants";
|
|
18
|
+
import { buildContactSearchParams } from "./utils";
|
|
19
|
+
const SCROLL_TARGET_ID = "contact-search-scroll";
|
|
20
|
+
const ContactSearchPanel = ({ open, onClose }) => {
|
|
21
|
+
const { t } = useTranslation("conversation-inbox");
|
|
22
|
+
const { onNavigate } = useChatContext();
|
|
23
|
+
const [keyword, setKeyword] = useState("");
|
|
24
|
+
const [activeType, setActiveType] = useState();
|
|
25
|
+
const [activeTab, setActiveTab] = useState(ContactSearchTab.All);
|
|
26
|
+
const debouncedKeyword = useDebounce(keyword, {
|
|
27
|
+
wait: CONTACT_SEARCH_DEBOUNCE_MS,
|
|
28
|
+
});
|
|
29
|
+
const trimmedKeyword = debouncedKeyword.trim();
|
|
30
|
+
// The keyword and filter belong to one search session — reset both whenever
|
|
31
|
+
// the panel is reopened so a stale query never flashes.
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!open) {
|
|
34
|
+
setKeyword("");
|
|
35
|
+
setActiveType(undefined);
|
|
36
|
+
setActiveTab(ContactSearchTab.All);
|
|
37
|
+
}
|
|
38
|
+
}, [open]);
|
|
39
|
+
const { contacts, totalElements, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, } = useSearchContacts({
|
|
40
|
+
searchTerm: trimmedKeyword,
|
|
41
|
+
type: activeType,
|
|
42
|
+
enabled: open,
|
|
43
|
+
});
|
|
44
|
+
const handleSelectContact = (contact) => {
|
|
45
|
+
const params = buildContactSearchParams(contact);
|
|
46
|
+
onClose();
|
|
47
|
+
onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(`${window.location.pathname}?${params.toString()}`);
|
|
48
|
+
};
|
|
49
|
+
// Filters belong to the `Liên hệ` tab only, so leaving it drops the active
|
|
50
|
+
// type — `Tất cả` always previews every contact type.
|
|
51
|
+
const handleChangeTab = (tab) => {
|
|
52
|
+
setActiveTab(tab);
|
|
53
|
+
if (tab !== ContactSearchTab.Contacts)
|
|
54
|
+
setActiveType(undefined);
|
|
55
|
+
};
|
|
56
|
+
const renderContactItems = (items) => items.map((contact) => (_jsx(ContactSearchResultItem, { contact: contact, searchTerm: trimmedKeyword, onSelect: handleSelectContact }, contact.id)));
|
|
57
|
+
// Every contact tab shares the same not-ready states before it can render
|
|
58
|
+
// results; returns null once there is a list to show.
|
|
59
|
+
const renderPlaceholderState = () => {
|
|
60
|
+
if (!trimmedKeyword) {
|
|
61
|
+
return (_jsx(ContactSearchStateMessage, { testId: "contact-search-initial", children: t("contact_search.initial") }));
|
|
62
|
+
}
|
|
63
|
+
if (isLoading) {
|
|
64
|
+
return (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Spin, {}) }));
|
|
65
|
+
}
|
|
66
|
+
if (isError) {
|
|
67
|
+
return (_jsx(ContactSearchStateMessage, { testId: "contact-search-failed", children: t("contact_search.failed") }));
|
|
68
|
+
}
|
|
69
|
+
if (contacts.length === 0) {
|
|
70
|
+
return (_jsxs(ContactSearchStateMessage, { testId: "contact-search-no-result", children: [t("contact_search.no_result_title"), _jsx("br", {}), t("contact_search.no_result_hint")] }));
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
};
|
|
74
|
+
// `Tất cả` only previews the contact section and hands off to `Liên hệ`;
|
|
75
|
+
// the message section belongs to the message-search ticket.
|
|
76
|
+
const renderAllTab = () => {
|
|
77
|
+
const placeholder = renderPlaceholderState();
|
|
78
|
+
if (placeholder)
|
|
79
|
+
return placeholder;
|
|
80
|
+
const hasMore = contacts.length > CONTACT_SEARCH_PREVIEW_LIMIT || !!hasNextPage;
|
|
81
|
+
const openContactsTab = () => handleChangeTab(ContactSearchTab.Contacts);
|
|
82
|
+
return (_jsxs("div", { className: "flex-1 overflow-y-auto", children: [_jsx(ContactSearchSectionHeader, { title: t("contact_search.tabs.contacts"), count: totalElements || undefined, onSelect: openContactsTab }), renderContactItems(contacts.slice(0, CONTACT_SEARCH_PREVIEW_LIMIT)), hasMore && _jsx(ContactSearchViewMoreBar, { onSelect: openContactsTab })] }));
|
|
83
|
+
};
|
|
84
|
+
const renderContactList = () => (_jsx("div", { id: SCROLL_TARGET_ID, className: "flex-1 overflow-y-auto", children: _jsx(InfiniteScroll, { dataLength: contacts.length, next: fetchNextPage, hasMore: !!hasNextPage, scrollableTarget: SCROLL_TARGET_ID, loader: isFetchingNextPage ? (_jsx("div", { className: "flex items-center justify-center py-3", children: _jsx(Spin, { size: "small" }) })) : null, children: renderContactItems(contacts) }) }));
|
|
85
|
+
// Filters stay mounted even with no results so the seller can switch type.
|
|
86
|
+
const renderContactsTab = () => {
|
|
87
|
+
var _a;
|
|
88
|
+
return (_jsxs(_Fragment, { children: [_jsx(ContactSearchFilters, { activeType: activeType, onChange: setActiveType }), (_a = renderPlaceholderState()) !== null && _a !== void 0 ? _a : renderContactList()] }));
|
|
89
|
+
};
|
|
90
|
+
const tabItems = [
|
|
91
|
+
{
|
|
92
|
+
key: ContactSearchTab.All,
|
|
93
|
+
label: t("contact_search.tabs.all"),
|
|
94
|
+
children: renderAllTab(),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
key: ContactSearchTab.Contacts,
|
|
98
|
+
label: t("contact_search.tabs.contacts"),
|
|
99
|
+
children: renderContactsTab(),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
key: ContactSearchTab.Messages,
|
|
103
|
+
label: t("contact_search.tabs.messages"),
|
|
104
|
+
children: (_jsx(ContactSearchStateMessage, { testId: "contact-search-messages", children: t("contact_search.messages_not_available") })),
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
return (_jsx(Drawer, { open: open, onClose: onClose, placement: "right", closeIcon: false, width: "100%", "data-cp-name": "ContactSearchPanel", styles: { body: { padding: 0 } }, getContainer: false, destroyOnHidden: true, children: _jsxs("div", { className: "flex h-full flex-col bg-white", children: [_jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [_jsx("button", { type: "button", "data-testid": "contact-search-back", "aria-label": t("contact_search.back"), className: "flex shrink-0 cursor-pointer items-center justify-center p-1 text-primary-400", onClick: onClose, children: _jsx(Icon, { icon: "arrow-left-o", size: 24 }) }), _jsx(Input, { allowClear: true, autoFocus: true, value: keyword, "data-testid": "contact-search-input", placeholder: t("search_placeholder"), prefix: _jsx("span", { className: "flex h-10 w-10 items-center justify-center", children: _jsx(Icon, { icon: "search-o", size: CONTACT_SEARCH_ICON_SIZE }) }),
|
|
108
|
+
// Padding lives on the prefix/input instead of the wrapper so the
|
|
109
|
+
// icon keeps its 40x40 box from the design.
|
|
110
|
+
className: "!h-10 !rounded-lg !border-none !bg-gray-25 !p-0 !shadow-none", classNames: {
|
|
111
|
+
prefix: "!m-0 text-gray-600",
|
|
112
|
+
input: "!bg-transparent !px-0 !text-[14px] !leading-[1.6] !tracking-[0.14px] !text-gray-900",
|
|
113
|
+
suffix: "!mr-4",
|
|
114
|
+
}, onChange: (event) => setKeyword(event.target.value) })] }), _jsx(ContactSearchTabs, { activeTab: activeTab, items: tabItems, onChange: handleChangeTab })] }) }));
|
|
115
|
+
};
|
|
116
|
+
export default ContactSearchPanel;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ContactSearchItem } from "../../../types/dto";
|
|
2
|
+
interface ContactSearchResultItemProps {
|
|
3
|
+
contact: ContactSearchItem;
|
|
4
|
+
searchTerm: string;
|
|
5
|
+
onSelect: (contact: ContactSearchItem) => void;
|
|
6
|
+
}
|
|
7
|
+
declare const ContactSearchResultItem: ({ contact, searchTerm, onSelect, }: ContactSearchResultItemProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export default ContactSearchResultItem;
|
|
9
|
+
//# sourceMappingURL=ContactSearchResultItem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContactSearchResultItem.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/ContactSearchResultItem.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAE,iBAAiB,EAAqB,MAAM,aAAa,CAAC;AAGnE,UAAU,4BAA4B;IACpC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAChD;AAED,QAAA,MAAM,uBAAuB,GAAI,oCAI9B,4BAA4B,4CAiE9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
import { Icon } from "../../../components/icon";
|
|
5
|
+
import ConversationPeerAvatar from "../../../components/conversation-inbox/ConversationPeerAvatar";
|
|
6
|
+
import { highlightSearch, sanitizeHtml } from "../../../utils/common";
|
|
7
|
+
import { resolvePeerBadgeLabel } from "../../../utils/conversation";
|
|
8
|
+
import { ContactSearchType } from "../../../types/dto";
|
|
9
|
+
import { formatLastOnlineTime, resolveContactPeerType } from "./utils";
|
|
10
|
+
const ContactSearchResultItem = ({ contact, searchTerm, onSelect, }) => {
|
|
11
|
+
const { t } = useTranslation("conversation-inbox");
|
|
12
|
+
const isGroup = contact.type === ContactSearchType.Group;
|
|
13
|
+
const peerType = resolveContactPeerType(contact.type);
|
|
14
|
+
// Supporting line differs per contact type (spec §Contact Result Content).
|
|
15
|
+
// PDP intentionally has none — its last online time was dropped from spec.
|
|
16
|
+
const buildSupportingText = () => {
|
|
17
|
+
var _a;
|
|
18
|
+
if (isGroup) {
|
|
19
|
+
return t("contact_search.member_count", {
|
|
20
|
+
count: (_a = contact.memberCount) !== null && _a !== void 0 ? _a : 0,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
if (contact.type === ContactSearchType.PdpBot)
|
|
24
|
+
return "";
|
|
25
|
+
return formatLastOnlineTime(contact.lastOnlineTime);
|
|
26
|
+
};
|
|
27
|
+
const supportingText = buildSupportingText();
|
|
28
|
+
return (_jsxs("button", { type: "button", "data-cp-name": "ContactSearchResultItem", "data-testid": `contact-search-result-${contact.id}`, className: "flex w-full items-center gap-3 border-b border-gray-25 px-3 py-2.5 text-left hover:bg-gray-10", onClick: () => onSelect(contact), children: [_jsx(ConversationPeerAvatar, { peerType: peerType, displayName: contact.targetName, avatarUrl: contact.targetAvatar, badgeLabel: resolvePeerBadgeLabel(peerType, t) }), _jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-1", children: [isGroup && (_jsx(Icon, { icon: "user-three-b", size: 16, className: "shrink-0 text-gray-900", "aria-hidden": true })), _jsx("span", { className: "min-w-0 truncate text-[16px] font-medium leading-[1.6] text-gray-900",
|
|
29
|
+
// highlightSearch returns a <mark> around the matched part only.
|
|
30
|
+
dangerouslySetInnerHTML: {
|
|
31
|
+
__html: sanitizeHtml(highlightSearch(contact.targetName || "", searchTerm)),
|
|
32
|
+
} })] }), supportingText && (_jsx("span", { className: "truncate text-[14px] leading-[1.6] text-gray-500", children: supportingText }))] })] }));
|
|
33
|
+
};
|
|
34
|
+
export default ContactSearchResultItem;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface ContactSearchSectionHeaderProps {
|
|
2
|
+
title: string;
|
|
3
|
+
count?: number;
|
|
4
|
+
onSelect: () => void;
|
|
5
|
+
}
|
|
6
|
+
declare const ContactSearchSectionHeader: ({ title, count, onSelect, }: ContactSearchSectionHeaderProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export default ContactSearchSectionHeader;
|
|
8
|
+
//# sourceMappingURL=ContactSearchSectionHeader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContactSearchSectionHeader.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/ContactSearchSectionHeader.tsx"],"names":[],"mappings":"AAIA,UAAU,+BAA+B;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,0BAA0B,GAAI,6BAIjC,+BAA+B,4CA2BjC,CAAC;AAEF,eAAe,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Icon } from "../../../components/icon";
|
|
4
|
+
const ContactSearchSectionHeader = ({ title, count, onSelect, }) => {
|
|
5
|
+
return (_jsxs("button", { type: "button", "data-cp-name": "ContactSearchSectionHeader", "data-testid": "contact-search-section-header", className: "flex w-full items-center gap-4 bg-white px-3 py-2 text-left", onClick: onSelect, children: [_jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-[16px] font-bold leading-[1.6] tracking-[0.16px] text-gray-900", children: title }), count !== undefined && (_jsx("span", { className: "text-[14px] font-medium leading-[1.6] text-gray-400", children: ` (${count})` }))] }), _jsx(Icon, { icon: "angle-right-o", size: 20, className: "shrink-0 text-gray-400", "aria-hidden": true })] }));
|
|
6
|
+
};
|
|
7
|
+
export default ContactSearchSectionHeader;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
interface ContactSearchStateMessageProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
testId?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const ContactSearchStateMessage: ({ children, testId, }: ContactSearchStateMessageProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export default ContactSearchStateMessage;
|
|
8
|
+
//# sourceMappingURL=ContactSearchStateMessage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContactSearchStateMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/ContactSearchStateMessage.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,UAAU,8BAA8B;IACtC,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID,QAAA,MAAM,yBAAyB,GAAI,uBAGhC,8BAA8B,4CAqBhC,CAAC;AAEF,eAAe,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { images } from "../../../constants/images";
|
|
4
|
+
// Shared body for the initial / no-result / failed states: the same 100px
|
|
5
|
+
// illustration above centered gray copy.
|
|
6
|
+
const ContactSearchStateMessage = ({ children, testId, }) => {
|
|
7
|
+
return (_jsxs("div", { "data-cp-name": "ContactSearchStateMessage", "data-testid": testId, className: "flex flex-1 flex-col items-center gap-3 bg-white px-8 py-[42px]", children: [_jsx("div", { className: "flex h-[100px] w-[100px] shrink-0 items-center justify-center", children: _jsx("img", { src: images.searchEmpty, alt: "", "aria-hidden": "true", className: "h-[87px] w-[101px] max-w-none" }) }), _jsx("p", { className: "w-full text-center text-[16px] leading-[1.6] tracking-[0.16px] text-gray-600", children: children })] }));
|
|
8
|
+
};
|
|
9
|
+
export default ContactSearchStateMessage;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type TabsProps } from "antd";
|
|
2
|
+
import { ContactSearchTab } from "./constants";
|
|
3
|
+
interface ContactSearchTabsProps {
|
|
4
|
+
activeTab: ContactSearchTab;
|
|
5
|
+
items: TabsProps["items"];
|
|
6
|
+
onChange: (tab: ContactSearchTab) => void;
|
|
7
|
+
}
|
|
8
|
+
declare const ContactSearchTabs: ({ activeTab, items, onChange, }: ContactSearchTabsProps) => import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export default ContactSearchTabs;
|
|
10
|
+
//# sourceMappingURL=ContactSearchTabs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContactSearchTabs.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/ContactSearchTabs.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,MAAM,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAiC/C,UAAU,sBAAsB;IAC9B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IAC1B,QAAQ,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC3C;AAED,QAAA,MAAM,iBAAiB,GAAI,iCAIxB,sBAAsB,4CAgBxB,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Tabs } from "antd";
|
|
4
|
+
// antd's line tabs are restyled to the design: no nav margin, adjacent tabs,
|
|
5
|
+
// 14px bold labels, and a 3px rounded ink bar in the brand colour.
|
|
6
|
+
const TAB_CLASS_NAMES = [
|
|
7
|
+
"flex flex-1 flex-col overflow-hidden",
|
|
8
|
+
"[&_.ant-tabs-nav]:mb-0 [&_.ant-tabs-nav]:h-[42px] [&_.ant-tabs-nav]:bg-white",
|
|
9
|
+
"[&_.ant-tabs-nav]:before:border-b [&_.ant-tabs-nav]:before:border-gray-25",
|
|
10
|
+
"[&_.ant-tabs-nav-wrap]:h-full [&_.ant-tabs-nav-list]:h-full",
|
|
11
|
+
// Design padding is `2px 12px 0 12px` — no bottom padding, the tab stretches
|
|
12
|
+
// to the bar height and the indicator sits flush at its bottom edge.
|
|
13
|
+
"[&_.ant-tabs-tab]:h-full [&_.ant-tabs-tab]:px-3 [&_.ant-tabs-tab]:pb-0 [&_.ant-tabs-tab]:pt-0.5",
|
|
14
|
+
"[&_.ant-tabs-tab+.ant-tabs-tab]:ml-0",
|
|
15
|
+
"[&_.ant-tabs-tab-btn]:text-[14px] [&_.ant-tabs-tab-btn]:font-bold",
|
|
16
|
+
"[&_.ant-tabs-tab-btn]:leading-[1.6] [&_.ant-tabs-tab-btn]:tracking-[0.14px]",
|
|
17
|
+
"[&_.ant-tabs-tab-btn]:!text-gray-400",
|
|
18
|
+
"[&_.ant-tabs-tab-active_.ant-tabs-tab-btn]:!text-primary-400",
|
|
19
|
+
"[&_.ant-tabs-ink-bar]:h-[3px] [&_.ant-tabs-ink-bar]:rounded",
|
|
20
|
+
"[&_.ant-tabs-ink-bar]:bg-primary-400",
|
|
21
|
+
"[&_.ant-tabs-content-holder]:flex [&_.ant-tabs-content-holder]:flex-1",
|
|
22
|
+
"[&_.ant-tabs-content-holder]:overflow-hidden",
|
|
23
|
+
"[&_.ant-tabs-content]:h-full",
|
|
24
|
+
// Only the active pane gets sizing — styling every `.ant-tabs-tabpane`
|
|
25
|
+
// overrides the height:0 antd uses to hide the inactive ones, which made
|
|
26
|
+
// two panes render side by side.
|
|
27
|
+
"[&_.ant-tabs-tabpane-active]:flex [&_.ant-tabs-tabpane-active]:h-full",
|
|
28
|
+
"[&_.ant-tabs-tabpane-active]:flex-col",
|
|
29
|
+
].join(" ");
|
|
30
|
+
// The design draws the indicator under the label only, while antd spans the
|
|
31
|
+
// whole tab including its horizontal padding.
|
|
32
|
+
const TAB_HORIZONTAL_PADDING = 24;
|
|
33
|
+
const ContactSearchTabs = ({ activeTab, items, onChange, }) => {
|
|
34
|
+
return (_jsx(Tabs, { "data-cp-name": "ContactSearchTabs", activeKey: activeTab, items: items, className: TAB_CLASS_NAMES, tabBarGutter: 0, indicator: {
|
|
35
|
+
size: (origin) => origin - TAB_HORIZONTAL_PADDING,
|
|
36
|
+
align: "center",
|
|
37
|
+
}, destroyOnHidden: true, onChange: (key) => onChange(key) }));
|
|
38
|
+
};
|
|
39
|
+
export default ContactSearchTabs;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
interface ContactSearchViewMoreBarProps {
|
|
2
|
+
onSelect: () => void;
|
|
3
|
+
}
|
|
4
|
+
declare const ContactSearchViewMoreBar: ({ onSelect, }: ContactSearchViewMoreBarProps) => import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
export default ContactSearchViewMoreBar;
|
|
6
|
+
//# sourceMappingURL=ContactSearchViewMoreBar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContactSearchViewMoreBar.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/ContactSearchViewMoreBar.tsx"],"names":[],"mappings":"AAKA,UAAU,6BAA6B;IACrC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,wBAAwB,GAAI,eAE/B,6BAA6B,4CA4B/B,CAAC;AAEF,eAAe,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
import { Icon } from "../../../components/icon";
|
|
5
|
+
const ContactSearchViewMoreBar = ({ onSelect, }) => {
|
|
6
|
+
const { t } = useTranslation("conversation-inbox");
|
|
7
|
+
return (_jsx("div", { "data-cp-name": "ContactSearchViewMoreBar", className: "flex w-full flex-col items-center border-t border-gray-25 bg-white", children: _jsx("div", { className: "flex w-full items-center justify-center p-1", children: _jsxs("button", { type: "button", "data-testid": "contact-search-view-all", className: "flex h-8 items-center justify-center gap-2 px-3", onClick: onSelect, children: [_jsx("span", { className: "whitespace-nowrap text-center text-[13px] font-medium leading-[1.6] text-primary-400", children: t("contact_search.view_all") }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "shrink-0 text-primary-400", "aria-hidden": true })] }) }) }));
|
|
8
|
+
};
|
|
9
|
+
export default ContactSearchViewMoreBar;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ContactSearchType } from "../../../types/dto";
|
|
2
|
+
export declare const CONTACT_SEARCH_DEBOUNCE_MS = 300;
|
|
3
|
+
export declare const CONTACT_SEARCH_ICON_SIZE = 18;
|
|
4
|
+
export declare const CONTACT_SEARCH_PREVIEW_LIMIT = 5;
|
|
5
|
+
export declare enum ContactSearchTab {
|
|
6
|
+
All = "ALL",
|
|
7
|
+
Contacts = "CONTACTS",
|
|
8
|
+
Messages = "MESSAGES"
|
|
9
|
+
}
|
|
10
|
+
export declare const CONTACT_SEARCH_TABS: {
|
|
11
|
+
tab: ContactSearchTab;
|
|
12
|
+
labelKey: string;
|
|
13
|
+
}[];
|
|
14
|
+
export declare const CONTACT_SEARCH_FILTERS: {
|
|
15
|
+
type: ContactSearchType;
|
|
16
|
+
labelKey: string;
|
|
17
|
+
}[];
|
|
18
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAI3C,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,oBAAY,gBAAgB;IAC1B,GAAG,QAAQ;IACX,QAAQ,aAAa;IACrB,QAAQ,aAAa;CACtB;AAED,eAAO,MAAM,mBAAmB,EAAE;IAChC,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB,EAIA,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE;IACnC,IAAI,EAAE,iBAAiB,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB,EAKA,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ContactSearchType } from "../../../types/dto";
|
|
2
|
+
export const CONTACT_SEARCH_DEBOUNCE_MS = 300;
|
|
3
|
+
export const CONTACT_SEARCH_ICON_SIZE = 18;
|
|
4
|
+
// `Tất cả` shows a capped preview of the contact section before handing the
|
|
5
|
+
// seller over to the full `Liên hệ` tab.
|
|
6
|
+
export const CONTACT_SEARCH_PREVIEW_LIMIT = 5;
|
|
7
|
+
export var ContactSearchTab;
|
|
8
|
+
(function (ContactSearchTab) {
|
|
9
|
+
ContactSearchTab["All"] = "ALL";
|
|
10
|
+
ContactSearchTab["Contacts"] = "CONTACTS";
|
|
11
|
+
ContactSearchTab["Messages"] = "MESSAGES";
|
|
12
|
+
})(ContactSearchTab || (ContactSearchTab = {}));
|
|
13
|
+
export const CONTACT_SEARCH_TABS = [
|
|
14
|
+
{ tab: ContactSearchTab.All, labelKey: "contact_search.tabs.all" },
|
|
15
|
+
{ tab: ContactSearchTab.Contacts, labelKey: "contact_search.tabs.contacts" },
|
|
16
|
+
{ tab: ContactSearchTab.Messages, labelKey: "contact_search.tabs.messages" },
|
|
17
|
+
];
|
|
18
|
+
export const CONTACT_SEARCH_FILTERS = [
|
|
19
|
+
{ type: ContactSearchType.Customer, labelKey: "contact_search.filters.customer" },
|
|
20
|
+
{ type: ContactSearchType.PdpBot, labelKey: "contact_search.filters.pdp" },
|
|
21
|
+
{ type: ContactSearchType.Group, labelKey: "contact_search.filters.group" },
|
|
22
|
+
{ type: ContactSearchType.User, labelKey: "contact_search.filters.seller" },
|
|
23
|
+
];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { default as ContactSearchPanel } from "./ContactSearchPanel";
|
|
2
|
+
export { default as ContactSearchFilters } from "./ContactSearchFilters";
|
|
3
|
+
export { default as ContactSearchResultItem } from "./ContactSearchResultItem";
|
|
4
|
+
export { default as ContactSearchStateMessage } from "./ContactSearchStateMessage";
|
|
5
|
+
export { default as ContactSearchTabs } from "./ContactSearchTabs";
|
|
6
|
+
export { default as ContactSearchSectionHeader } from "./ContactSearchSectionHeader";
|
|
7
|
+
export { default as ContactSearchViewMoreBar } from "./ContactSearchViewMoreBar";
|
|
8
|
+
export * from "./constants";
|
|
9
|
+
export * from "./utils";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACnF,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AACrF,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACjF,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as ContactSearchPanel } from "./ContactSearchPanel";
|
|
2
|
+
export { default as ContactSearchFilters } from "./ContactSearchFilters";
|
|
3
|
+
export { default as ContactSearchResultItem } from "./ContactSearchResultItem";
|
|
4
|
+
export { default as ContactSearchStateMessage } from "./ContactSearchStateMessage";
|
|
5
|
+
export { default as ContactSearchTabs } from "./ContactSearchTabs";
|
|
6
|
+
export { default as ContactSearchSectionHeader } from "./ContactSearchSectionHeader";
|
|
7
|
+
export { default as ContactSearchViewMoreBar } from "./ContactSearchViewMoreBar";
|
|
8
|
+
export * from "./constants";
|
|
9
|
+
export * from "./utils";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ContactSearchItem, ContactSearchType } from "../../../types/dto";
|
|
2
|
+
import { PeerType } from "../../../types/conversation";
|
|
3
|
+
export declare const resolveContactPeerType: (type: ContactSearchType) => PeerType;
|
|
4
|
+
export declare const formatLastOnlineTime: (lastOnlineTime: string | null) => string;
|
|
5
|
+
export declare const buildContactSearchParams: (contact: ContactSearchItem) => URLSearchParams;
|
|
6
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAahD,eAAO,MAAM,sBAAsB,GAAI,MAAM,iBAAiB,KAAG,QAChC,CAAC;AAIlC,eAAO,MAAM,oBAAoB,GAAI,gBAAgB,MAAM,GAAG,IAAI,KAAG,MACd,CAAC;AAIxD,eAAO,MAAM,wBAAwB,GACnC,SAAS,iBAAiB,KACzB,eA0BF,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import dayjs from "dayjs";
|
|
2
|
+
import relativeTime from "dayjs/plugin/relativeTime";
|
|
3
|
+
import { ContactSearchType } from "../../../types/dto";
|
|
4
|
+
import { PeerType } from "../../../types/conversation";
|
|
5
|
+
dayjs.extend(relativeTime);
|
|
6
|
+
// Search results reuse the inbox avatar, which drives its Mall / Bot badge
|
|
7
|
+
// off PeerType rather than the search-specific contact type.
|
|
8
|
+
const CONTACT_TYPE_TO_PEER_TYPE = {
|
|
9
|
+
[ContactSearchType.Customer]: PeerType.CUSTOMER,
|
|
10
|
+
[ContactSearchType.PdpBot]: PeerType.BOT,
|
|
11
|
+
[ContactSearchType.Group]: PeerType.GROUP,
|
|
12
|
+
[ContactSearchType.User]: PeerType.USER,
|
|
13
|
+
};
|
|
14
|
+
export const resolveContactPeerType = (type) => CONTACT_TYPE_TO_PEER_TYPE[type];
|
|
15
|
+
// The Vietnamese dayjs locale is already registered in the SDK layout, so
|
|
16
|
+
// fromNow() renders "1 giờ trước" without extra setup here.
|
|
17
|
+
export const formatLastOnlineTime = (lastOnlineTime) => lastOnlineTime ? dayjs(lastOnlineTime).fromNow() : "";
|
|
18
|
+
// Each contact type resolves to a different route param; the conversation
|
|
19
|
+
// detail screen turns it into an existing thread or the new-chat flow.
|
|
20
|
+
export const buildContactSearchParams = (contact) => {
|
|
21
|
+
var _a;
|
|
22
|
+
const params = new URLSearchParams(window.location.search);
|
|
23
|
+
params.delete("threadId");
|
|
24
|
+
params.delete("userId");
|
|
25
|
+
params.delete("groupId");
|
|
26
|
+
params.delete("botId");
|
|
27
|
+
if (contact.type === ContactSearchType.Group) {
|
|
28
|
+
if (contact.targetGroupId)
|
|
29
|
+
params.set("groupId", contact.targetGroupId);
|
|
30
|
+
return params;
|
|
31
|
+
}
|
|
32
|
+
// PDP bots carry their id in targetBotId; targetUserId is null for them.
|
|
33
|
+
if (contact.type === ContactSearchType.PdpBot) {
|
|
34
|
+
if (contact.targetBotId)
|
|
35
|
+
params.set("botId", contact.targetBotId);
|
|
36
|
+
return params;
|
|
37
|
+
}
|
|
38
|
+
// Individual contacts: sellers carry targetUserId, customers carry
|
|
39
|
+
// targetCustomerId — the other field is null in each case.
|
|
40
|
+
const peerId = (_a = contact.targetUserId) !== null && _a !== void 0 ? _a : contact.targetCustomerId;
|
|
41
|
+
if (peerId)
|
|
42
|
+
params.set("userId", peerId);
|
|
43
|
+
return params;
|
|
44
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GroupMembersDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/message/GroupMembersDrawer.tsx"],"names":[],"mappings":"AAUA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAiB/C,UAAU,uBAAuB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA0CD,QAAA,MAAM,kBAAkB,GAAI,wDAKzB,uBAAuB,
|
|
1
|
+
{"version":3,"file":"GroupMembersDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/message/GroupMembersDrawer.tsx"],"names":[],"mappings":"AAUA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAiB/C,UAAU,uBAAuB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA0CD,QAAA,MAAM,kBAAkB,GAAI,wDAKzB,uBAAuB,4CAwjBzB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
|
|
@@ -55,6 +55,12 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
|
|
|
55
55
|
const { results: searchResults, isSearching } = useSearchGroupMembers(groupID, debouncedSearch);
|
|
56
56
|
const { mutateAsync: leaveConversation, isPending: isLeaving } = useLeaveConversation();
|
|
57
57
|
const permissions = useGroupPermission(groupID);
|
|
58
|
+
// REMOVE_MEMBER is a blanket per-actor permission with no target-role
|
|
59
|
+
// granularity — BE still rejects an Admin removing another Admin/Owner
|
|
60
|
+
// (role hierarchy), so the FE must gate per-target here too, or the user
|
|
61
|
+
// reaches the confirm flow and only then sees a rejection toast.
|
|
62
|
+
const actingMemberRoleLevel = useConversationStore((state) => { var _a; return groupID ? (_a = state.currentMemberInGroup.get(groupID)) === null || _a === void 0 ? void 0 : _a.roleLevel : undefined; });
|
|
63
|
+
const actingIsOwner = actingMemberRoleLevel === GroupMemberRole.Owner;
|
|
58
64
|
const { mutateAsync: setMemberPermissions } = useSetGroupMemberPermissions();
|
|
59
65
|
const { mutateAsync: kickMember } = useKickGroupMember();
|
|
60
66
|
const { mutateAsync: setMemberRole } = useSetGroupMemberRole();
|
|
@@ -336,7 +342,11 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
|
|
|
336
342
|
label: t("remove_from_group"),
|
|
337
343
|
icon: _jsx(Icon, { icon: "user-del-o", size: 16 }),
|
|
338
344
|
danger: true,
|
|
339
|
-
|
|
345
|
+
// Owner may remove anyone; Admin may only remove members
|
|
346
|
+
// that are neither the Owner nor another Admin.
|
|
347
|
+
disabled: !permissions.removeMember ||
|
|
348
|
+
member.isOwner ||
|
|
349
|
+
(member.isAdmin && !actingIsOwner),
|
|
340
350
|
onClick: () => setConfirmMemberAction({ type: "remove", member }),
|
|
341
351
|
},
|
|
342
352
|
{
|
|
@@ -360,7 +370,7 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
|
|
|
360
370
|
userID: adminPermissionMember.userID,
|
|
361
371
|
permissions: adminPermissionMember.permissions,
|
|
362
372
|
}
|
|
363
|
-
: undefined, onMemberPermissionsSaved: patchMemberPermissions })), _jsx(ConfirmModal, { open: showLeaveConfirm, onOk: handleConfirmLeave, onCancel: () => setShowLeaveConfirm(false), 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: (confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.type) === "remove", onOk: handleConfirmRemoveMember, onCancel: () => setConfirmMemberAction(null), title: t("remove_member_confirm_title"), okText: t("
|
|
373
|
+
: undefined, onMemberPermissionsSaved: patchMemberPermissions })), _jsx(ConfirmModal, { open: showLeaveConfirm, onOk: handleConfirmLeave, onCancel: () => setShowLeaveConfirm(false), 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: (confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.type) === "remove", onOk: handleConfirmRemoveMember, onCancel: () => setConfirmMemberAction(null), title: t("remove_member_confirm_title"), okText: t("agree"), cancelText: t("cancel"), okType: "danger", children: _jsx("p", { children: t("remove_member_confirm_message", {
|
|
364
374
|
name: confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.member.nickname,
|
|
365
375
|
}) }) }), _jsx(ConfirmModal, { open: (confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.type) === "transfer-owner", onOk: handleConfirmTransferOwner, onCancel: () => setConfirmMemberAction(null), title: t("transfer_owner_confirm_title"), okText: t("transfer_owner"), cancelText: t("cancel"), okType: "danger", children: _jsx("p", { children: t("transfer_owner_confirm_message", {
|
|
366
376
|
name: confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.member.nickname,
|
|
@@ -4,5 +4,6 @@ export interface MentionOption {
|
|
|
4
4
|
faceURL?: string;
|
|
5
5
|
isAtAll: boolean;
|
|
6
6
|
}
|
|
7
|
+
export declare const ALL_MEMBERS_USER_ID = "__all__";
|
|
7
8
|
export declare const useMentionOptions: (groupID: string | undefined, query: string) => MentionOption[];
|
|
8
9
|
//# sourceMappingURL=useMentionOptions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMentionOptions.d.ts","sourceRoot":"","sources":["../../../../../src/components/message/footer/mention/useMentionOptions.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;
|
|
1
|
+
{"version":3,"file":"useMentionOptions.d.ts","sourceRoot":"","sources":["../../../../../src/components/message/footer/mention/useMentionOptions.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAW7C,eAAO,MAAM,iBAAiB,GAC5B,SAAS,MAAM,GAAG,SAAS,EAC3B,OAAO,MAAM,KACZ,aAAa,EAuCf,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import { useTranslation } from "react-i18next";
|
|
3
3
|
import { useGroupMembers } from "../../../../hooks/group/useGroupMembers";
|
|
4
|
-
const ALL_MEMBERS_USER_ID = "__all__";
|
|
4
|
+
export const ALL_MEMBERS_USER_ID = "__all__";
|
|
5
5
|
// Prefix matches rank above mid-string matches, then alphabetical — matches
|
|
6
6
|
// the "closest match first" ranking spec'd for this feature (not just a
|
|
7
7
|
// plain filter). No keyword: falls back to plain A-Z (AC-Open default).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GroupThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/GroupThreadInfo.tsx"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -46,6 +46,13 @@ const GroupThreadInfo = ({ onClose, config }) => {
|
|
|
46
46
|
const [activeRow, setActiveRow] = useState(null);
|
|
47
47
|
const [notifyEnabled, setNotifyEnabled] = useState((conversationData === null || conversationData === void 0 ? void 0 : conversationData.recvMsgOpt) !== MessageReceiveOptType.NotNotify);
|
|
48
48
|
const avatarInputRef = useRef(null);
|
|
49
|
+
const descriptionInputRef = useRef(null);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
var _a;
|
|
52
|
+
if (editingField === "description") {
|
|
53
|
+
(_a = descriptionInputRef.current) === null || _a === void 0 ? void 0 : _a.focus({ cursor: "end" });
|
|
54
|
+
}
|
|
55
|
+
}, [editingField]);
|
|
49
56
|
// changeGroupMute() persists via GroupItem.status (GroupStatus.Muted),
|
|
50
57
|
// refreshed into currentGroupInfo on OnGroupInfoChanged — same pattern as
|
|
51
58
|
// notifyEnabled below, not a locally-tracked flag.
|
|
@@ -121,7 +128,10 @@ const GroupThreadInfo = ({ onClose, config }) => {
|
|
|
121
128
|
event.target.value = "";
|
|
122
129
|
if (!file || !groupID)
|
|
123
130
|
return;
|
|
124
|
-
|
|
131
|
+
// .jfif self-reports as image/jpeg, so MIME type alone can't reject it —
|
|
132
|
+
// must also check the extension isn't in the disallowed list.
|
|
133
|
+
const hasDisallowedExtension = /\.jfif\s*$/i.test(file.name);
|
|
134
|
+
if (hasDisallowedExtension || !ACCEPTED_IMAGE_TYPES.includes(file.type)) {
|
|
125
135
|
message.error(t("avatar_invalid_format"));
|
|
126
136
|
return;
|
|
127
137
|
}
|
|
@@ -218,7 +228,7 @@ const GroupThreadInfo = ({ onClose, config }) => {
|
|
|
218
228
|
}
|
|
219
229
|
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-primary-500 text-primary-500 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-primary-500 text-white text-sm font-medium disabled:bg-gray-100 disabled:text-gray-300", disabled: isSavingGroupInfo ||
|
|
220
230
|
!nameValue.trim() ||
|
|
221
|
-
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, {
|
|
231
|
+
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-primary-500 text-primary-500 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-primary-500 text-white text-sm font-medium disabled:bg-gray-100 disabled:text-gray-300", disabled: isSavingGroupInfo ||
|
|
222
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") }) }))] }));
|
|
223
233
|
};
|
|
224
234
|
export default GroupThreadInfo;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/ThreadInfo.tsx"],"names":[],"mappings":"AAGA,OAAwB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAQ3E,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACrC;AAED,QAAA,MAAM,UAAU,GAAI,kCAAyC,eAAe,
|
|
1
|
+
{"version":3,"file":"ThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/ThreadInfo.tsx"],"names":[],"mappings":"AAGA,OAAwB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAQ3E,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACrC;AAED,QAAA,MAAM,UAAU,GAAI,kCAAyC,eAAe,mDA2D3E,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
|
@@ -31,7 +31,10 @@ const ThreadInfo = ({ isOpen = true, onClose, groupConfig }) => {
|
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
33
|
if (isGroup) {
|
|
34
|
-
|
|
34
|
+
// Keyed on conversationId so switching threads remounts GroupThreadInfo
|
|
35
|
+
// instead of reusing the instance — otherwise its local edit state
|
|
36
|
+
// (editingField/nameValue/descriptionValue) leaks from the previous thread.
|
|
37
|
+
return isOpen ? (_jsx(GroupThreadInfo, { onClose: onClose, config: groupConfig }, conversationId)) : null;
|
|
35
38
|
}
|
|
36
39
|
if (!isOpen && !isBot) {
|
|
37
40
|
return null;
|