@droppii-org/chat-sdk 0.4.0 → 0.4.2
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-detail/ConversationDetailInput.d.ts.map +1 -1
- package/dist/components/conversation-detail/ConversationDetailInput.js +142 -4
- package/dist/components/conversation-detail/LeaveGroupBlockedModal.d.ts +12 -0
- package/dist/components/conversation-detail/LeaveGroupBlockedModal.d.ts.map +1 -0
- package/dist/components/conversation-detail/LeaveGroupBlockedModal.js +18 -0
- package/dist/components/conversation-detail/LeaveGroupConfirmModal.d.ts +16 -0
- package/dist/components/conversation-detail/LeaveGroupConfirmModal.d.ts.map +1 -0
- package/dist/components/conversation-detail/LeaveGroupConfirmModal.js +21 -0
- package/dist/components/conversation-detail/ThreadProfilePanel.d.ts.map +1 -1
- package/dist/components/conversation-detail/ThreadProfilePanel.js +12 -2
- package/dist/components/conversation-detail/index.d.ts +4 -0
- package/dist/components/conversation-detail/index.d.ts.map +1 -1
- package/dist/components/conversation-detail/index.js +2 -0
- 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/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/search-conversation/SearchAll.d.ts.map +1 -1
- package/dist/components/search-conversation/SearchAll.js +9 -3
- package/dist/components/search-conversation/SearchConversationAsGroups.d.ts +6 -0
- package/dist/components/search-conversation/SearchConversationAsGroups.d.ts.map +1 -0
- package/dist/components/search-conversation/SearchConversationAsGroups.js +20 -0
- package/dist/components/search-conversation/index.d.ts +2 -1
- package/dist/components/search-conversation/index.d.ts.map +1 -1
- package/dist/components/search-conversation/index.js +7 -0
- package/dist/components/search-conversation/item/SearchItemAsGroup.d.ts +8 -0
- package/dist/components/search-conversation/item/SearchItemAsGroup.d.ts.map +1 -0
- package/dist/components/search-conversation/item/SearchItemAsGroup.js +34 -0
- 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/global/useGlobalEvent.d.ts.map +1 -1
- package/dist/hooks/global/useGlobalEvent.js +11 -1
- package/dist/hooks/group/useConversationGroups.d.ts +5 -1
- package/dist/hooks/group/useConversationGroups.d.ts.map +1 -1
- package/dist/hooks/group/useConversationGroups.js +3 -3
- package/dist/hooks/group/useLeaveGroupAction.d.ts +24 -0
- package/dist/hooks/group/useLeaveGroupAction.d.ts.map +1 -0
- package/dist/hooks/group/useLeaveGroupAction.js +79 -0
- package/dist/hooks/group/useQuitGroup.d.ts +6 -0
- package/dist/hooks/group/useQuitGroup.d.ts.map +1 -0
- package/dist/hooks/group/useQuitGroup.js +62 -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/hooks/search/useSearchGroups.d.ts +16 -0
- package/dist/hooks/search/useSearchGroups.d.ts.map +1 -0
- package/dist/hooks/search/useSearchGroups.js +65 -0
- package/dist/locales/vi/common.json +3 -0
- package/dist/locales/vi/conversation-inbox.json +36 -2
- 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 +2 -0
- package/dist/services/query.d.ts.map +1 -1
- package/dist/services/query.js +2 -0
- package/dist/services/routes.d.ts +2 -0
- package/dist/services/routes.d.ts.map +1 -1
- package/dist/services/routes.js +2 -0
- package/dist/store/conversation.d.ts.map +1 -1
- package/dist/store/conversation.js +3 -0
- package/dist/store/type.d.ts +2 -0
- package/dist/store/type.d.ts.map +1 -1
- package/dist/styles/global.css +1 -1
- package/dist/types/dto.d.ts +29 -1
- package/dist/types/dto.d.ts.map +1 -1
- package/dist/types/dto.js +10 -1
- package/package.json +2 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
// Mirrors @lexical/react/LexicalTypeaheadMenuPlugin's useBasicTypeaheadTriggerMatch
|
|
3
|
+
// regex for trigger="@", minLength=0 (the exact config MentionMenu.tsx uses),
|
|
4
|
+
// so BIZ's plain-textarea trigger behavior matches CRM's Lexical composer.
|
|
5
|
+
// The {0,75} + trailing $ means a query over 75 chars doesn't get
|
|
6
|
+
// truncated — it simply stops triggering, same as the Lexical original.
|
|
7
|
+
const TRIGGER_REGEX = /(^|\s|\()([@]((?:[^@.,+*?$@|#{}()^\-[\]\\/!%'"~=<>_:;\s]){0,75}))$/;
|
|
8
|
+
export const matchMentionTrigger = (text) => {
|
|
9
|
+
const match = TRIGGER_REGEX.exec(text);
|
|
10
|
+
if (!match)
|
|
11
|
+
return null;
|
|
12
|
+
const boundary = match[1];
|
|
13
|
+
const query = match[3];
|
|
14
|
+
return {
|
|
15
|
+
triggerStart: match.index + boundary.length,
|
|
16
|
+
query,
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export const useMentionTrigger = (value, caretIndex) => useMemo(() => matchMentionTrigger(value.slice(0, caretIndex)), [value, caretIndex]);
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
export interface ConversationInboxHeaderProps {
|
|
2
|
+
onOpenSearch?: () => void;
|
|
3
|
+
}
|
|
4
|
+
declare const ConversationInboxHeader: ({ onOpenSearch, }: ConversationInboxHeaderProps) => import("react/jsx-runtime").JSX.Element;
|
|
2
5
|
export default ConversationInboxHeader;
|
|
3
6
|
//# sourceMappingURL=ConversationInboxHeader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConversationInboxHeader.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-inbox/ConversationInboxHeader.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ConversationInboxHeader.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-inbox/ConversationInboxHeader.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,4BAA4B;IAC3C,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,QAAA,MAAM,uBAAuB,GAAI,mBAE9B,4BAA4B,4CA8B9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useTranslation } from "react-i18next";
|
|
4
|
-
|
|
4
|
+
import { Icon } from "../../components/icon";
|
|
5
|
+
const ConversationInboxHeader = ({ onOpenSearch, }) => {
|
|
5
6
|
const { t } = useTranslation("conversation-inbox");
|
|
6
|
-
return (_jsx("div", { "data-testid": "conversation-inbox-header", "data-cp-name": "ConversationInboxHeader", className: "flex w-full flex-col border-b border-gray-100 bg-white", children:
|
|
7
|
+
return (_jsx("div", { "data-testid": "conversation-inbox-header", "data-cp-name": "ConversationInboxHeader", className: "flex w-full flex-col border-b border-gray-100 bg-white", children: _jsxs("div", { className: "flex h-12 items-end px-3", children: [_jsxs("div", { className: "flex h-full flex-col items-center justify-end px-3", children: [_jsx("span", { className: "pb-1.5 text-center text-[16px] font-bold leading-[160%] tracking-[0.16px] text-primary-400", children: t("tabs.messages") }), _jsx("span", { className: "h-[3px] w-full rounded-t bg-primary-400" })] }), onOpenSearch && (_jsx("button", { type: "button", "data-testid": "conversation-inbox-search", "aria-label": t("search_placeholder"), className: "ml-auto mb-1.5 flex shrink-0 cursor-pointer items-center justify-center p-1 text-primary-400", onClick: onOpenSearch, children: _jsx(Icon, { icon: "search-o", size: 24 }) }))] }) }));
|
|
7
8
|
};
|
|
8
9
|
export default ConversationInboxHeader;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ContactSearchType } from "../../../types/dto";
|
|
2
|
+
interface ContactSearchFiltersProps {
|
|
3
|
+
activeType?: ContactSearchType;
|
|
4
|
+
onChange: (type?: ContactSearchType) => void;
|
|
5
|
+
}
|
|
6
|
+
declare const ContactSearchFilters: ({ activeType, onChange, }: ContactSearchFiltersProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export default ContactSearchFilters;
|
|
8
|
+
//# sourceMappingURL=ContactSearchFilters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContactSearchFilters.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-inbox/contact-search/ContactSearchFilters.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGhD,UAAU,yBAAyB;IACjC,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC9C;AAED,QAAA,MAAM,oBAAoB,GAAI,2BAG3B,yBAAyB,4CA0C3B,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
5
|
+
import { CONTACT_SEARCH_FILTERS } from "./constants";
|
|
6
|
+
const ContactSearchFilters = ({ activeType, onChange, }) => {
|
|
7
|
+
const { t } = useTranslation("conversation-inbox");
|
|
8
|
+
// Selecting the active chip again clears the filter and returns all types.
|
|
9
|
+
const handleSelect = (type) => {
|
|
10
|
+
onChange(activeType === type ? undefined : type);
|
|
11
|
+
};
|
|
12
|
+
return (_jsx("div", { "data-cp-name": "ContactSearchFilters", "data-testid": "contact-search-filters", className: "w-full border-b border-gray-25 bg-white py-3", children: _jsx("div", { className: "flex items-start gap-2 overflow-x-auto px-3", children: CONTACT_SEARCH_FILTERS.map(({ type, labelKey }) => {
|
|
13
|
+
const isActive = activeType === type;
|
|
14
|
+
return (_jsx("button", { type: "button", "aria-pressed": isActive, "data-testid": `contact-search-filter-${type}`, className: clsx("flex h-7 shrink-0 cursor-pointer items-center justify-center whitespace-nowrap rounded-lg border px-3", "text-center text-[12px] font-medium leading-[1.6] tracking-[0.12px]", isActive
|
|
15
|
+
? "border-primary-600 bg-primary-25 text-primary-600"
|
|
16
|
+
: "border-gray-200 text-gray-900"), onClick: () => handleSelect(type), children: t(labelKey) }, type));
|
|
17
|
+
}) }) }));
|
|
18
|
+
};
|
|
19
|
+
export default ContactSearchFilters;
|
|
@@ -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
|
+
};
|
|
@@ -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":"SearchAll.d.ts","sourceRoot":"","sources":["../../../src/components/search-conversation/SearchAll.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SearchAll.d.ts","sourceRoot":"","sources":["../../../src/components/search-conversation/SearchAll.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAE,wBAAwB,EAAE,MAAM,GAAG,CAAC;AAG7C,UAAU,0BAA0B;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,CAAC,GAAG,EAAE,wBAAwB,KAAK,IAAI,CAAC;CACvD;AACD,QAAA,MAAM,qBAAqB,GAAI,+BAG5B,0BAA0B,4CA2J5B,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
|
|
@@ -8,6 +8,8 @@ import { Icon } from "../../components/icon";
|
|
|
8
8
|
import SearchItemAsMessage from "./item/SearchItemAsMessage";
|
|
9
9
|
import { useGetSession } from "../../hooks/session/useGetSession";
|
|
10
10
|
import SearchItemAsUser from "./item/SearchItemAsUser";
|
|
11
|
+
import SearchItemAsGroup from "./item/SearchItemAsGroup";
|
|
12
|
+
import { useSearchGroups } from "../../hooks/search/useSearchGroups";
|
|
11
13
|
import { SearchConversationTabKey } from ".";
|
|
12
14
|
import { PAGE_SIZE } from "../../constants";
|
|
13
15
|
const SearchConversationAll = ({ searchTerm = "", setActiveKey, }) => {
|
|
@@ -25,13 +27,17 @@ const SearchConversationAll = ({ searchTerm = "", setActiveKey, }) => {
|
|
|
25
27
|
},
|
|
26
28
|
options: { pageSize: PAGE_SIZE.xs },
|
|
27
29
|
});
|
|
30
|
+
const { groups, isLoading: isLoadingGroup, hasNextPage: hasNextPageGroup, } = useSearchGroups({
|
|
31
|
+
searchTerm: searchTerm.trim(),
|
|
32
|
+
pageSize: PAGE_SIZE.xs,
|
|
33
|
+
});
|
|
28
34
|
if (searchTerm.trim() === "" ||
|
|
29
|
-
(messages.length === 0 && sessions.length === 0)) {
|
|
35
|
+
(messages.length === 0 && sessions.length === 0 && groups.length === 0)) {
|
|
30
36
|
return (_jsx("div", { className: "h-full overflow-auto", children: _jsx(Empty, { description: t("no_conversation") }) }));
|
|
31
37
|
}
|
|
32
|
-
if (isLoadingMessage || isLoadingSession) {
|
|
38
|
+
if (isLoadingMessage || isLoadingSession || isLoadingGroup) {
|
|
33
39
|
return (_jsx("div", { className: "h-full overflow-auto flex items-center justify-center", children: _jsx(Spin, {}) }));
|
|
34
40
|
}
|
|
35
|
-
return (_jsxs("div", { className: "h-full overflow-auto", "data-cp-name": "SearchConversationAll", children: [sessions.length > 0 && (_jsxs("div", { children: [_jsxs("div", { className: "flex items-center justify-between px-3 min-h-[32px]", children: [_jsx("span", { className: "text-xs font-medium uppercase flex-1 text-gray-600", children: t("users") }), _jsx(Button, { type: "link", icon: _jsx(Icon, { icon: "angle-right-o", size: 18, className: "!align-[-4px]" }), iconPosition: "end", className: clsx("p-0 gap-1", !hasNextPageSession && "invisible"), onClick: () => setActiveKey === null || setActiveKey === void 0 ? void 0 : setActiveKey(SearchConversationTabKey.Users), children: t("see_more") })] }), _jsx("div", { children: sessions.map((session) => (_jsx(SearchItemAsUser, { session: session, searchTerm: searchTerm }, session.conversationId))) })] })), messages.length > 0 && (_jsxs("div", { children: [_jsxs("div", { className: "flex items-center justify-between px-3 min-h-[32px]", children: [_jsx("span", { className: "text-xs font-medium uppercase flex-1 text-gray-600", children: t("messages") }), _jsx(Button, { type: "link", icon: _jsx(Icon, { icon: "angle-right-o", size: 18, className: "!align-[-4px]" }), iconPosition: "end", className: clsx("p-0 gap-1", !hasNextPageMessage && "invisible"), onClick: () => setActiveKey === null || setActiveKey === void 0 ? void 0 : setActiveKey(SearchConversationTabKey.Messages), children: t("see_more") })] }), _jsx("div", { children: messages.map((message) => (_jsx(SearchItemAsMessage, { message: message.chatLog, searchTerm: searchTerm, displayTag: message.displayTag }, message.chatLog.clientMsgID))) })] }))] }));
|
|
41
|
+
return (_jsxs("div", { className: "h-full overflow-auto", "data-cp-name": "SearchConversationAll", children: [sessions.length > 0 && (_jsxs("div", { children: [_jsxs("div", { className: "flex items-center justify-between px-3 min-h-[32px]", children: [_jsx("span", { className: "text-xs font-medium uppercase flex-1 text-gray-600", children: t("users") }), _jsx(Button, { type: "link", icon: _jsx(Icon, { icon: "angle-right-o", size: 18, className: "!align-[-4px]" }), iconPosition: "end", className: clsx("p-0 gap-1", !hasNextPageSession && "invisible"), onClick: () => setActiveKey === null || setActiveKey === void 0 ? void 0 : setActiveKey(SearchConversationTabKey.Users), children: t("see_more") })] }), _jsx("div", { children: sessions.map((session) => (_jsx(SearchItemAsUser, { session: session, searchTerm: searchTerm }, session.conversationId))) })] })), messages.length > 0 && (_jsxs("div", { children: [_jsxs("div", { className: "flex items-center justify-between px-3 min-h-[32px]", children: [_jsx("span", { className: "text-xs font-medium uppercase flex-1 text-gray-600", children: t("messages") }), _jsx(Button, { type: "link", icon: _jsx(Icon, { icon: "angle-right-o", size: 18, className: "!align-[-4px]" }), iconPosition: "end", className: clsx("p-0 gap-1", !hasNextPageMessage && "invisible"), onClick: () => setActiveKey === null || setActiveKey === void 0 ? void 0 : setActiveKey(SearchConversationTabKey.Messages), children: t("see_more") })] }), _jsx("div", { children: messages.map((message) => (_jsx(SearchItemAsMessage, { message: message.chatLog, searchTerm: searchTerm, displayTag: message.displayTag }, message.chatLog.clientMsgID))) })] })), groups.length > 0 && (_jsxs("div", { children: [_jsxs("div", { className: "flex items-center justify-between px-3 min-h-[32px]", children: [_jsx("span", { className: "text-xs font-medium uppercase flex-1 text-gray-600", children: t("groups") }), _jsx(Button, { type: "link", icon: _jsx(Icon, { icon: "angle-right-o", size: 18, className: "!align-[-4px]" }), iconPosition: "end", className: clsx("p-0 gap-1", !hasNextPageGroup && "invisible"), onClick: () => setActiveKey === null || setActiveKey === void 0 ? void 0 : setActiveKey(SearchConversationTabKey.Groups), children: t("see_more") })] }), _jsx("div", { children: groups.map((group) => (_jsx(SearchItemAsGroup, { group: group, searchTerm: searchTerm }, group.conversationId))) })] }))] }));
|
|
36
42
|
};
|
|
37
43
|
export default SearchConversationAll;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
interface SearchConversationAsGroupsProps {
|
|
2
|
+
searchTerm: string;
|
|
3
|
+
}
|
|
4
|
+
declare const SearchConversationAsGroups: (props: SearchConversationAsGroupsProps) => import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
export default SearchConversationAsGroups;
|
|
6
|
+
//# sourceMappingURL=SearchConversationAsGroups.d.ts.map
|