@droppii-org/chat-sdk 0.1.96 → 0.2.0
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/components/compose/ComposeNewMessageModal.d.ts +7 -0
- package/dist/components/compose/ComposeNewMessageModal.d.ts.map +1 -0
- package/dist/components/compose/ComposeNewMessageModal.js +61 -0
- package/dist/components/conversation/ConversationByGroupItem.d.ts +7 -0
- package/dist/components/conversation/ConversationByGroupItem.d.ts.map +1 -0
- package/dist/components/conversation/ConversationByGroupItem.js +50 -0
- package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
- package/dist/components/conversation/DeskConversationList.js +15 -6
- package/dist/components/conversation/ThreadCard.js +1 -1
- package/dist/components/group/AddMemberModal.d.ts +9 -0
- package/dist/components/group/AddMemberModal.d.ts.map +1 -0
- package/dist/components/group/AddMemberModal.js +70 -0
- package/dist/components/group/AdminPermissionModal.d.ts +9 -0
- package/dist/components/group/AdminPermissionModal.d.ts.map +1 -0
- package/dist/components/group/AdminPermissionModal.js +63 -0
- package/dist/components/group/CreateGroupForm.d.ts +6 -0
- package/dist/components/group/CreateGroupForm.d.ts.map +1 -0
- package/dist/components/group/CreateGroupForm.js +74 -0
- package/dist/components/group/GroupMemberPicker.d.ts +8 -0
- package/dist/components/group/GroupMemberPicker.d.ts.map +1 -0
- package/dist/components/group/GroupMemberPicker.js +44 -0
- package/dist/components/group/MemberPermissionPanel.d.ts +8 -0
- package/dist/components/group/MemberPermissionPanel.d.ts.map +1 -0
- package/dist/components/group/MemberPermissionPanel.js +53 -0
- package/dist/components/media-resources/FileResourceList.js +1 -1
- package/dist/components/media-resources/LinkResourceItem.js +1 -1
- package/dist/components/message/GroupMembersDrawer.d.ts.map +1 -1
- package/dist/components/message/GroupMembersDrawer.js +210 -28
- package/dist/components/message/MessageHeader.d.ts +2 -1
- package/dist/components/message/MessageHeader.d.ts.map +1 -1
- package/dist/components/message/MessageHeader.js +7 -2
- package/dist/components/message/MessageList.d.ts +1 -0
- package/dist/components/message/MessageList.d.ts.map +1 -1
- package/dist/components/message/MessageList.js +7 -2
- package/dist/components/message/SelectSession.d.ts.map +1 -1
- package/dist/components/message/SelectSession.js +3 -3
- package/dist/components/session/DeskAssignedSession.d.ts.map +1 -1
- package/dist/components/session/DeskAssignedSession.js +4 -1
- package/dist/components/session/DeskTeamInbox.js +1 -1
- package/dist/components/session/SessionFilterMenu.d.ts +3 -1
- package/dist/components/session/SessionFilterMenu.d.ts.map +1 -1
- package/dist/components/session/SessionFilterMenu.js +8 -5
- package/dist/components/session/sessionMenuItems.d.ts +3 -1
- package/dist/components/session/sessionMenuItems.d.ts.map +1 -1
- package/dist/components/session/sessionMenuItems.js +11 -1
- package/dist/components/thread/GroupThreadInfo.d.ts +11 -0
- package/dist/components/thread/GroupThreadInfo.d.ts.map +1 -0
- package/dist/components/thread/GroupThreadInfo.js +156 -0
- package/dist/components/thread/ManualAssignPopover.js +2 -2
- package/dist/components/thread/SessionSection.d.ts.map +1 -1
- package/dist/components/thread/SessionSection.js +2 -8
- package/dist/components/thread/SingleThreadInfo.d.ts +6 -0
- package/dist/components/thread/SingleThreadInfo.d.ts.map +1 -0
- package/dist/components/thread/SingleThreadInfo.js +70 -0
- package/dist/components/thread/ThreadInfo.d.ts +7 -1
- package/dist/components/thread/ThreadInfo.d.ts.map +1 -1
- package/dist/components/thread/ThreadInfo.js +14 -2
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +3 -0
- package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
- package/dist/hooks/global/useGlobalEvent.js +39 -0
- package/dist/hooks/group/useAddGroupMember.d.ts +7 -0
- package/dist/hooks/group/useAddGroupMember.d.ts.map +1 -0
- package/dist/hooks/group/useAddGroupMember.js +20 -0
- package/dist/hooks/group/useCreateGroup.d.ts +12 -0
- package/dist/hooks/group/useCreateGroup.d.ts.map +1 -0
- package/dist/hooks/group/useCreateGroup.js +62 -0
- package/dist/hooks/group/useDismissGroup.d.ts +4 -0
- package/dist/hooks/group/useDismissGroup.d.ts.map +1 -0
- package/dist/hooks/group/useDismissGroup.js +22 -0
- package/dist/hooks/group/useEligibleAccounts.d.ts +13 -0
- package/dist/hooks/group/useEligibleAccounts.d.ts.map +1 -0
- package/dist/hooks/group/useEligibleAccounts.js +23 -0
- package/dist/hooks/group/useEligibleAccountsForGroup.d.ts +9 -0
- package/dist/hooks/group/useEligibleAccountsForGroup.d.ts.map +1 -0
- package/dist/hooks/group/useEligibleAccountsForGroup.js +26 -0
- package/dist/hooks/group/useGetGroupCandidates.d.ts +14 -0
- package/dist/hooks/group/useGetGroupCandidates.d.ts.map +1 -0
- package/dist/hooks/group/useGetGroupCandidates.js +37 -0
- package/dist/hooks/group/useGroupMemberActions.d.ts +25 -0
- package/dist/hooks/group/useGroupMemberActions.d.ts.map +1 -0
- package/dist/hooks/group/useGroupMemberActions.js +54 -0
- package/dist/hooks/group/useGroupPermission.d.ts +15 -0
- package/dist/hooks/group/useGroupPermission.d.ts.map +1 -0
- package/dist/hooks/group/useGroupPermission.js +70 -0
- package/dist/hooks/group/useJoinedGroups.d.ts +13 -0
- package/dist/hooks/group/useJoinedGroups.d.ts.map +1 -0
- package/dist/hooks/group/useJoinedGroups.js +33 -0
- package/dist/hooks/group/useSearchGroupMembers.d.ts +8 -0
- package/dist/hooks/group/useSearchGroupMembers.d.ts.map +1 -0
- package/dist/hooks/group/useSearchGroupMembers.js +37 -0
- package/dist/hooks/group/useSetMemberPermission.d.ts +12 -0
- package/dist/hooks/group/useSetMemberPermission.d.ts.map +1 -0
- package/dist/hooks/group/useSetMemberPermission.js +32 -0
- package/dist/hooks/group/useToggleGroupSendMessage.d.ts +7 -0
- package/dist/hooks/group/useToggleGroupSendMessage.d.ts.map +1 -0
- package/dist/hooks/group/useToggleGroupSendMessage.js +22 -0
- package/dist/hooks/group/useUpdateGroupInfo.d.ts +7 -0
- package/dist/hooks/group/useUpdateGroupInfo.d.ts.map +1 -0
- package/dist/hooks/group/useUpdateGroupInfo.js +15 -0
- package/dist/hooks/session/useBlockUser.d.ts +10 -0
- package/dist/hooks/session/useBlockUser.d.ts.map +1 -0
- package/dist/hooks/session/useBlockUser.js +49 -0
- package/dist/hooks/session/useDeleteConversation.d.ts +4 -0
- package/dist/hooks/session/useDeleteConversation.d.ts.map +1 -0
- package/dist/hooks/session/useDeleteConversation.js +20 -0
- package/dist/hooks/session/useGetListSessionByConversation.d.ts +2 -1
- package/dist/hooks/session/useGetListSessionByConversation.d.ts.map +1 -1
- package/dist/hooks/session/useGetListSessionByConversation.js +2 -2
- package/dist/hooks/session/useToggleConversationNotification.d.ts +7 -0
- package/dist/hooks/session/useToggleConversationNotification.d.ts.map +1 -0
- package/dist/hooks/session/useToggleConversationNotification.js +15 -0
- package/dist/locales/vi/common.json +64 -1
- package/dist/locales/vi/conversation-inbox.json +33 -0
- package/dist/screens/desk-message/index.d.ts +3 -1
- package/dist/screens/desk-message/index.d.ts.map +1 -1
- package/dist/screens/desk-message/index.js +4 -2
- package/dist/services/query.d.ts +4 -0
- package/dist/services/query.d.ts.map +1 -1
- package/dist/services/query.js +4 -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/store/groupPermissionOverrides.d.ts +23 -0
- package/dist/store/groupPermissionOverrides.d.ts.map +1 -0
- package/dist/store/groupPermissionOverrides.js +12 -0
- package/dist/store/session.d.ts +3 -0
- package/dist/store/session.d.ts.map +1 -1
- package/dist/store/session.js +3 -1
- package/dist/styles/global.css +1 -1
- package/dist/types/dto.d.ts +12 -0
- package/dist/types/dto.d.ts.map +1 -1
- package/dist/utils/events.d.ts +1 -0
- package/dist/utils/events.d.ts.map +1 -1
- package/dist/utils/groupName.d.ts +8 -0
- package/dist/utils/groupName.d.ts.map +1 -0
- package/dist/utils/groupName.js +6 -0
- package/package.json +2 -2
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
interface ComposeNewMessageModalProps {
|
|
2
|
+
open: boolean;
|
|
3
|
+
onClose: () => void;
|
|
4
|
+
}
|
|
5
|
+
declare const ComposeNewMessageModal: ({ open, onClose, }: ComposeNewMessageModalProps) => import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export default ComposeNewMessageModal;
|
|
7
|
+
//# sourceMappingURL=ComposeNewMessageModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComposeNewMessageModal.d.ts","sourceRoot":"","sources":["../../../src/components/compose/ComposeNewMessageModal.tsx"],"names":[],"mappings":"AAeA,UAAU,2BAA2B;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAID,QAAA,MAAM,sBAAsB,GAAI,oBAG7B,2BAA2B,4CAwI7B,CAAC;AAEF,eAAe,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useDebounce } from "ahooks";
|
|
4
|
+
import { Avatar, Empty, Input, Modal, Spin } from "antd";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
7
|
+
import { useTranslation } from "react-i18next";
|
|
8
|
+
import { SessionType } from "@droppii-org/wasm-client-sdk";
|
|
9
|
+
import { Icon } from "../../components/icon";
|
|
10
|
+
import { useEligibleAccounts } from "../../hooks/group/useEligibleAccounts";
|
|
11
|
+
import { DChatSDK } from "../../constants/sdk";
|
|
12
|
+
import useConversationStore from "../../store/conversation";
|
|
13
|
+
import CreateGroupForm from "../../components/group/CreateGroupForm";
|
|
14
|
+
const ComposeNewMessageModal = ({ open, onClose, }) => {
|
|
15
|
+
const { t } = useTranslation("conversation-inbox");
|
|
16
|
+
const [step, setStep] = useState("compose");
|
|
17
|
+
const [search, setSearch] = useState("");
|
|
18
|
+
const [openingUserID, setOpeningUserID] = useState(null);
|
|
19
|
+
const debouncedSearch = useDebounce(search, { wait: 300 });
|
|
20
|
+
const { accounts, isLoading } = useEligibleAccounts(debouncedSearch, open);
|
|
21
|
+
const router = useRouter();
|
|
22
|
+
const pathname = usePathname();
|
|
23
|
+
const searchParams = useSearchParams();
|
|
24
|
+
const setConversationData = useConversationStore((state) => state.setConversationData);
|
|
25
|
+
const setSelectedConversationId = useConversationStore((state) => state.setSelectedConversationId);
|
|
26
|
+
const handleClose = () => {
|
|
27
|
+
setSearch("");
|
|
28
|
+
setStep("compose");
|
|
29
|
+
onClose();
|
|
30
|
+
};
|
|
31
|
+
const handleAfterClose = () => {
|
|
32
|
+
// Reset only after the close animation finishes so the outgoing
|
|
33
|
+
// content doesn't visibly swap mid-transition.
|
|
34
|
+
setStep("compose");
|
|
35
|
+
};
|
|
36
|
+
const handleOpenDirectMessage = async (account) => {
|
|
37
|
+
if (openingUserID)
|
|
38
|
+
return;
|
|
39
|
+
setOpeningUserID(account.userID);
|
|
40
|
+
try {
|
|
41
|
+
const { data: conversation } = await DChatSDK.getOneConversation({
|
|
42
|
+
sourceID: account.userID,
|
|
43
|
+
sessionType: SessionType.Single,
|
|
44
|
+
});
|
|
45
|
+
setConversationData(conversation);
|
|
46
|
+
setSelectedConversationId(conversation.conversationID);
|
|
47
|
+
const newSearchParams = new URLSearchParams(searchParams);
|
|
48
|
+
newSearchParams.set("threadId", conversation.conversationID);
|
|
49
|
+
newSearchParams.delete("userId");
|
|
50
|
+
newSearchParams.delete("groupId");
|
|
51
|
+
newSearchParams.delete("botId");
|
|
52
|
+
router.push(`${pathname}?${newSearchParams.toString()}`);
|
|
53
|
+
handleClose();
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
setOpeningUserID(null);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
return (_jsx(Modal, { title: step === "create-group" ? t("create_group.title") : t("compose.title"), open: open, onCancel: handleClose, afterClose: handleAfterClose, footer: null, centered: true, forceRender: true, width: step === "create-group" ? 720 : undefined, getContainer: false, children: step === "compose" ? (_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(Input, { size: "large", value: search, onChange: (event) => setSearch(event.target.value), placeholder: t("compose.search_member_placeholder"), prefix: _jsx(Icon, { icon: "search-o", size: 18, className: "text-gray-400" }) }), _jsxs("button", { type: "button", className: "flex items-center gap-3 rounded-xl bg-primary-50 px-4 py-3.5 text-left transition-colors hover:bg-primary-100", onClick: () => setStep("create-group"), children: [_jsx("span", { className: "flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-white", children: _jsx(Icon, { icon: "user-three-b", size: 22, className: "text-primary-400" }) }), _jsx("span", { className: "flex-1 text-base font-semibold text-gray-900", children: t("compose.create_group") }), _jsx(Icon, { icon: "angle-right-o", size: 18, className: "text-gray-400" })] }), !debouncedSearch && (_jsxs("div", { className: "flex items-center gap-2 px-1", children: [_jsx("span", { className: "shrink-0 text-xs font-semibold uppercase tracking-wide text-gray-400", children: t("compose.recent_suggestions") }), _jsx("span", { className: "h-px flex-1 bg-gray-100" })] })), _jsx("div", { className: "max-h-80 overflow-y-auto", children: isLoading ? (_jsx("div", { className: "flex items-center justify-center py-6", children: _jsx(Spin, {}) })) : accounts.length === 0 ? (_jsx(Empty, { description: t("compose.no_member_found") })) : (accounts.map((account) => (_jsxs("div", { className: "flex cursor-pointer items-center gap-3 border-b border-gray-50 px-1 py-3 transition-colors last:border-b-0 hover:bg-[#f5f5f5]", onClick: () => handleOpenDirectMessage(account), children: [_jsx(Avatar, { src: account.avatar, size: 44, className: "shrink-0", children: account.displayName.charAt(0) || "A" }), _jsx("span", { className: "flex-1 truncate text-base font-medium text-gray-900", children: account.displayName }), openingUserID === account.userID && _jsx(Spin, { size: "small" })] }, account.userID)))) })] })) : (_jsx(CreateGroupForm, { onDone: handleClose })) }));
|
|
60
|
+
};
|
|
61
|
+
export default ComposeNewMessageModal;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { GroupItem } from "@droppii-org/wasm-client-sdk";
|
|
2
|
+
interface ConversationByGroupItemProps {
|
|
3
|
+
group: GroupItem;
|
|
4
|
+
}
|
|
5
|
+
declare const ConversationByGroupItem: ({ group }: ConversationByGroupItemProps) => import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export default ConversationByGroupItem;
|
|
7
|
+
//# sourceMappingURL=ConversationByGroupItem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConversationByGroupItem.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/ConversationByGroupItem.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,SAAS,EAAe,MAAM,8BAA8B,CAAC;AAMtE,UAAU,4BAA4B;IACpC,KAAK,EAAE,SAAS,CAAC;CAClB;AAED,QAAA,MAAM,uBAAuB,GAAI,WAAW,4BAA4B,4CAiEvE,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Avatar } from "antd";
|
|
5
|
+
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
6
|
+
import { useTranslation } from "react-i18next";
|
|
7
|
+
import { SessionType } from "@droppii-org/wasm-client-sdk";
|
|
8
|
+
import useConversationStore from "../../store/conversation";
|
|
9
|
+
import { DChatSDK } from "../../constants/sdk";
|
|
10
|
+
import { formatTimestamp } from "../../utils/common";
|
|
11
|
+
import ThreadCard from "./ThreadCard";
|
|
12
|
+
const ConversationByGroupItem = ({ group }) => {
|
|
13
|
+
var _a;
|
|
14
|
+
const { t } = useTranslation();
|
|
15
|
+
const [isOpening, setIsOpening] = useState(false);
|
|
16
|
+
const router = useRouter();
|
|
17
|
+
const pathname = usePathname();
|
|
18
|
+
const searchParams = useSearchParams();
|
|
19
|
+
const setConversationData = useConversationStore((state) => state.setConversationData);
|
|
20
|
+
const setSelectedConversationId = useConversationStore((state) => state.setSelectedConversationId);
|
|
21
|
+
const updateConversationList = useConversationStore((state) => state.updateConversationList);
|
|
22
|
+
const isSelected = useConversationStore((state) => state.selectedSourceId === group.groupID);
|
|
23
|
+
const handleClick = async () => {
|
|
24
|
+
if (isOpening)
|
|
25
|
+
return;
|
|
26
|
+
setIsOpening(true);
|
|
27
|
+
try {
|
|
28
|
+
const { data: conversation } = await DChatSDK.getOneConversation({
|
|
29
|
+
sourceID: group.groupID,
|
|
30
|
+
sessionType: SessionType.Group,
|
|
31
|
+
});
|
|
32
|
+
setConversationData(conversation);
|
|
33
|
+
updateConversationList([conversation], "filter");
|
|
34
|
+
setSelectedConversationId(conversation.conversationID);
|
|
35
|
+
const newSearchParams = new URLSearchParams(searchParams);
|
|
36
|
+
newSearchParams.set("threadId", conversation.conversationID);
|
|
37
|
+
newSearchParams.delete("userId");
|
|
38
|
+
newSearchParams.delete("groupId");
|
|
39
|
+
newSearchParams.delete("botId");
|
|
40
|
+
router.push(`${pathname}?${newSearchParams.toString()}`);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
setIsOpening(false);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
return (_jsx(ThreadCard, { onClick: handleClick, isSelected: isSelected, preview: t("member_count", { count: group.memberCount }), time: group.createTime
|
|
47
|
+
? formatTimestamp(group.createTime, { hasTime: true })
|
|
48
|
+
: undefined, avatar: _jsx(Avatar, { size: 48, src: group.faceURL, children: ((_a = group.groupName) === null || _a === void 0 ? void 0 : _a.charAt(0)) || "G" }), title: _jsx("h3", { className: "truncate text-sm font-semibold text-gray-900", children: group.groupName }) }));
|
|
49
|
+
};
|
|
50
|
+
export default ConversationByGroupItem;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"AA4BA,QAAA,MAAM,oBAAoB,+CAwRzB,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
|
|
@@ -11,18 +11,22 @@ import SearchConversation from "../../components/search-conversation";
|
|
|
11
11
|
import useSessionStore from "../../store/session";
|
|
12
12
|
import InfiniteScroll from "react-infinite-scroll-component";
|
|
13
13
|
import ConversationBySessionItem from "./ConversationBySessionItem";
|
|
14
|
+
import ConversationByGroupItem from "./ConversationByGroupItem";
|
|
14
15
|
import { useGetSession } from "../../hooks/session/useGetSession";
|
|
15
16
|
import { useSortedSessions } from "../../hooks/session/useSortedSessions";
|
|
16
17
|
import { useSessionLatestPreview } from "../../hooks/session/useSessionLatestPreview";
|
|
17
18
|
import { useGetTeamSessionSummary } from "../../hooks/session/useGetTeamSessionSummary";
|
|
19
|
+
import { useJoinedGroups } from "../../hooks/group/useJoinedGroups";
|
|
18
20
|
import { DChatSDK } from "../../constants/sdk";
|
|
19
21
|
import emitter from "../../utils/events";
|
|
20
|
-
import { getFilterConfig } from "../../components/session/sessionMenuItems";
|
|
22
|
+
import { ALL_GROUPS_KEY, getFilterConfig, } from "../../components/session/sessionMenuItems";
|
|
23
|
+
import ComposeNewMessageModal from "../../components/compose/ComposeNewMessageModal";
|
|
21
24
|
const DeskConversationList = () => {
|
|
22
25
|
var _a, _b;
|
|
23
26
|
const searchInputRef = useRef(null);
|
|
24
27
|
const { t } = useTranslation();
|
|
25
28
|
const [search, setSearch] = useState("");
|
|
29
|
+
const [isComposeOpen, setIsComposeOpen] = useState(false);
|
|
26
30
|
const [showSearch, { setTrue: setShowSearchTrue, setFalse: setShowSearchFalse },] = useBoolean(false);
|
|
27
31
|
const searchParams = useSearchParams();
|
|
28
32
|
const setConversationData = useConversationStore((state) => state.setConversationData);
|
|
@@ -30,11 +34,16 @@ const DeskConversationList = () => {
|
|
|
30
34
|
const conversationList = useConversationStore((state) => state.conversationList);
|
|
31
35
|
const updateConversationList = useConversationStore((state) => state.updateConversationList);
|
|
32
36
|
const filterSummary = useSessionStore((state) => state.filterSummary);
|
|
37
|
+
const viewMode = useSessionStore((state) => state.viewMode);
|
|
38
|
+
const isAllGroupsView = viewMode === "allGroups";
|
|
33
39
|
const { data: teamSummaries } = useGetTeamSessionSummary();
|
|
34
|
-
const { dataFlatten: rawSessions, hasNextPage, fetchNextPage, refetch, } = useGetSession({ filter: filterSummary });
|
|
40
|
+
const { dataFlatten: rawSessions, hasNextPage, fetchNextPage, refetch, } = useGetSession({ filter: filterSummary, options: { isEnabled: !isAllGroupsView } });
|
|
41
|
+
const { groups: joinedGroups, fetchNextPage: fetchNextGroupsPage, hasNextPage: hasNextGroupsPage, isLoading: isJoinedGroupsLoading, } = useJoinedGroups({ enabled: isAllGroupsView });
|
|
35
42
|
const latestPreviewMap = useSessionLatestPreview(rawSessions);
|
|
36
43
|
const sessions = useSortedSessions(rawSessions, latestPreviewMap);
|
|
37
|
-
const currentFilterKey =
|
|
44
|
+
const currentFilterKey = isAllGroupsView
|
|
45
|
+
? ALL_GROUPS_KEY
|
|
46
|
+
: filterSummary.status || filterSummary.tag || "";
|
|
38
47
|
const filterConfig = getFilterConfig(currentFilterKey);
|
|
39
48
|
const pathName = filterSummary.teamId
|
|
40
49
|
? ((_a = teamSummaries === null || teamSummaries === void 0 ? void 0 : teamSummaries.find((team) => team.teamId === filterSummary.teamId)) === null || _a === void 0 ? void 0 : _a.teamName) || ""
|
|
@@ -91,14 +100,14 @@ const DeskConversationList = () => {
|
|
|
91
100
|
setShowSearchTrue();
|
|
92
101
|
}
|
|
93
102
|
setSearch(e.target.value);
|
|
94
|
-
}, className: "rounded-lg text-sm flex-1 h-[36px]", size: "large", allowClear: true, onClick: setShowSearchTrue, value: search, autoFocus: false, onClear: onCloseSearch }), showSearch
|
|
103
|
+
}, className: "rounded-lg text-sm flex-1 h-[36px]", size: "large", allowClear: true, onClick: setShowSearchTrue, value: search, autoFocus: false, onClear: onCloseSearch }), showSearch ? (_jsx(Button, { onClick: onCloseSearch, variant: "outlined", className: "p-0 w-[36px] h-[36px] text-gray-500", children: _jsx(Icon, { icon: "close-b", size: 22 }) })) : (_jsx(Button, { "data-testid": "conversation-list-compose-icon", onClick: () => setIsComposeOpen(true), variant: "outlined", className: "p-0 w-[36px] h-[36px] shrink-0 text-gray-500", children: _jsx(Icon, { icon: "edit-b", size: 20 }) }))] }), _jsxs("div", { className: "flex items-center justify-between mt-2", children: [_jsx("span", { className: "text-sm font-semibold text-blue-500 truncate", children: pathName }), isAllGroupsView ? (_jsxs("span", { className: "flex items-center gap-1.5 shrink-0", children: [_jsx(Icon, { icon: "user-three-o", size: 16, className: "text-gray-500" }), _jsx("span", { className: "text-sm font-semibold text-gray-800", children: t("all_groups") })] })) : (filterConfig && (_jsxs("span", { className: "flex items-center gap-1.5 shrink-0", children: [_jsx(Icon, { icon: filterConfig.iconName, size: 16, className: "text-gray-500" }), _jsx("span", { className: "text-sm font-semibold text-gray-800", children: t(filterConfig.labelKey) })] })))] })] }), _jsxs("div", { id: "scrollableConversationsDiv", style: {
|
|
95
104
|
height: "100%",
|
|
96
105
|
overflow: showSearch ? "hidden" : "auto",
|
|
97
106
|
position: "relative",
|
|
98
|
-
}, children: [_jsx(InfiniteScroll, { dataLength: (sessions === null || sessions === void 0 ? void 0 : sessions.length) || 0, next: fetchNextPage, hasMore: hasNextPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableConversationsDiv", children: (sessions === null || sessions === void 0 ? void 0 : sessions.length) === 0 ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-gray-300" }), description: t("no_conversation") }) })) : ((_b = sessions === null || sessions === void 0 ? void 0 : sessions.map) === null || _b === void 0 ? void 0 : _b.call(sessions, (session) => (_jsx(ConversationBySessionItem, { sessionItem: session, latestPreview: latestPreviewMap.get(session.conversationId) }, session.conversationId)))) }), _jsx(Drawer, { open: showSearch, mask: false, closeIcon: false, styles: {
|
|
107
|
+
}, children: [isAllGroupsView ? (isJoinedGroupsLoading ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Spin, {}) })) : !(joinedGroups === null || joinedGroups === void 0 ? void 0 : joinedGroups.length) ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-gray-300" }), description: t("no_conversation") }) })) : (_jsx(InfiniteScroll, { dataLength: joinedGroups.length, next: fetchNextGroupsPage, hasMore: !!hasNextGroupsPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableConversationsDiv", children: joinedGroups.map((group) => (_jsx(ConversationByGroupItem, { group: group }, group.groupID))) }))) : (_jsx(InfiniteScroll, { dataLength: (sessions === null || sessions === void 0 ? void 0 : sessions.length) || 0, next: fetchNextPage, hasMore: hasNextPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableConversationsDiv", children: (sessions === null || sessions === void 0 ? void 0 : sessions.length) === 0 ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-gray-300" }), description: t("no_conversation") }) })) : ((_b = sessions === null || sessions === void 0 ? void 0 : sessions.map) === null || _b === void 0 ? void 0 : _b.call(sessions, (session) => (_jsx(ConversationBySessionItem, { sessionItem: session, latestPreview: latestPreviewMap.get(session.conversationId) }, session.conversationId)))) })), _jsx(Drawer, { open: showSearch, mask: false, closeIcon: false, styles: {
|
|
99
108
|
body: {
|
|
100
109
|
padding: 0,
|
|
101
110
|
},
|
|
102
|
-
}, getContainer: false, width: "100%", children: _jsx(SearchConversation, { searchTerm: debouncedSearch }) })] })] }));
|
|
111
|
+
}, getContainer: false, width: "100%", children: _jsx(SearchConversation, { searchTerm: debouncedSearch }) })] }), _jsx(ComposeNewMessageModal, { open: isComposeOpen, onClose: () => setIsComposeOpen(false) })] }));
|
|
103
112
|
};
|
|
104
113
|
export default DeskConversationList;
|
|
@@ -9,7 +9,7 @@ const UnreadBadge = ({ count }) => {
|
|
|
9
9
|
};
|
|
10
10
|
const ThreadCard = ({ avatar, title, preview, time, unreadCount = 0, isSelected = false, onClick, testId, }) => {
|
|
11
11
|
const hasUnread = unreadCount > 0;
|
|
12
|
-
return (_jsxs("button", { type: "button", onClick: onClick, "data-testid": testId, className: clsx("relative flex w-full shrink-0 items-start gap-2 border-b border-gray-25 p-3 text-left transition-colors hover:bg-
|
|
12
|
+
return (_jsxs("button", { type: "button", onClick: onClick, "data-testid": testId, className: clsx("relative flex w-full shrink-0 items-start gap-2 border-b border-gray-25 p-3 text-left transition-colors hover:bg-[#f5f5f5] focus:outline-none focus-visible:ring-2 focus-visible:ring-chat-ring", isSelected ? "bg-blue-50" : "bg-white"), children: [isSelected && (_jsx("span", { className: "absolute bottom-0 left-0 top-0 w-1 bg-blue-500" })), avatar, _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsxs("div", { className: "flex min-h-[25.6px] items-center justify-between gap-1", children: [_jsx("div", { className: "min-w-0 flex-1", children: title }), time && (_jsx("span", { className: "shrink-0 text-[12px] font-normal leading-[160%] text-gray-400 [font-feature-settings:'liga'_off,'clig'_off]", children: time }))] }), _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("p", { className: clsx("min-w-0 flex-1 truncate text-[14px] leading-[160%] [font-feature-settings:'liga'_off,'clig'_off]", hasUnread
|
|
13
13
|
? "font-[510] text-black"
|
|
14
14
|
: "font-normal tracking-[0.14px] text-gray-600"), children: preview }), hasUnread && _jsx(UnreadBadge, { count: unreadCount })] })] })] }));
|
|
15
15
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface AddMemberModalProps {
|
|
2
|
+
open: boolean;
|
|
3
|
+
groupID: string;
|
|
4
|
+
existingMemberIDs: string[];
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
}
|
|
7
|
+
declare const AddMemberModal: ({ open, groupID, existingMemberIDs, onClose, }: AddMemberModalProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export default AddMemberModal;
|
|
9
|
+
//# sourceMappingURL=AddMemberModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AddMemberModal.d.ts","sourceRoot":"","sources":["../../../src/components/group/AddMemberModal.tsx"],"names":[],"mappings":"AAYA,UAAU,mBAAmB;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,QAAA,MAAM,cAAc,GAAI,gDAKrB,mBAAmB,4CA2LrB,CAAC;AAEF,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useDebounce } from "ahooks";
|
|
4
|
+
import { Avatar, Button, Empty, Input, Modal, Spin, message } from "antd";
|
|
5
|
+
import clsx from "clsx";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import { useTranslation } from "react-i18next";
|
|
8
|
+
import { Icon } from "../../components/icon";
|
|
9
|
+
import { useEligibleAccountsForGroup } from "../../hooks/group/useEligibleAccountsForGroup";
|
|
10
|
+
import { useAddGroupMember } from "../../hooks/group/useAddGroupMember";
|
|
11
|
+
const AddMemberModal = ({ open, groupID, existingMemberIDs, onClose, }) => {
|
|
12
|
+
const { t } = useTranslation("conversation-inbox");
|
|
13
|
+
const { t: tCommon } = useTranslation();
|
|
14
|
+
const [search, setSearch] = useState("");
|
|
15
|
+
const [selectedMembers, setSelectedMembers] = useState([]);
|
|
16
|
+
const debouncedSearch = useDebounce(search, { wait: 300 });
|
|
17
|
+
const { accounts, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } = useEligibleAccountsForGroup(debouncedSearch, existingMemberIDs, open);
|
|
18
|
+
const { mutateAsync: addGroupMember, isPending: isSubmitting } = useAddGroupMember();
|
|
19
|
+
const selectedIds = new Set(selectedMembers.map((member) => member.userID));
|
|
20
|
+
const toggleMember = (account) => {
|
|
21
|
+
if (selectedIds.has(account.userID)) {
|
|
22
|
+
setSelectedMembers((prev) => prev.filter((member) => member.userID !== account.userID));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
setSelectedMembers((prev) => [...prev, account]);
|
|
26
|
+
};
|
|
27
|
+
const handleRemoveMember = (userID) => {
|
|
28
|
+
setSelectedMembers((prev) => prev.filter((member) => member.userID !== userID));
|
|
29
|
+
};
|
|
30
|
+
const handleClearAll = () => {
|
|
31
|
+
setSelectedMembers([]);
|
|
32
|
+
};
|
|
33
|
+
const handleClose = () => {
|
|
34
|
+
setSearch("");
|
|
35
|
+
setSelectedMembers([]);
|
|
36
|
+
onClose();
|
|
37
|
+
};
|
|
38
|
+
const handleListScroll = (event) => {
|
|
39
|
+
if (!hasNextPage || isFetchingNextPage)
|
|
40
|
+
return;
|
|
41
|
+
const { scrollTop, scrollHeight, clientHeight } = event.currentTarget;
|
|
42
|
+
if (scrollHeight - scrollTop - clientHeight < 80) {
|
|
43
|
+
void fetchNextPage();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const handleSubmit = async () => {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
if (!groupID || selectedMembers.length === 0)
|
|
49
|
+
return;
|
|
50
|
+
try {
|
|
51
|
+
await addGroupMember({
|
|
52
|
+
groupID,
|
|
53
|
+
userIDList: selectedMembers.map((member) => member.userID),
|
|
54
|
+
});
|
|
55
|
+
message.success(t("add_member.add_member_success"));
|
|
56
|
+
handleClose();
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const errorMessage = ((_b = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.message) || t("add_member.add_member_failed");
|
|
60
|
+
message.error(errorMessage);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
return (_jsx(Modal, { centered: true, open: open, onCancel: handleClose, footer: null, title: t("add_member.title"), getContainer: false, children: _jsxs("div", { className: "flex flex-col gap-3 pt-2", children: [_jsx(Input, { value: search, onChange: (event) => setSearch(event.target.value), placeholder: t("create_group.search_member_placeholder"), prefix: _jsx(Icon, { icon: "search-o", size: 16, className: "text-gray-400" }) }), _jsxs("div", { className: "flex items-center justify-between px-1", children: [_jsx("span", { className: "text-sm text-gray-500", children: t("create_group.selected_count", { count: selectedMembers.length }) }), selectedMembers.length > 0 && (_jsx("button", { type: "button", className: "text-sm text-primary-400 hover:text-primary-500 hover:underline", onClick: handleClearAll, children: t("create_group.clear_all") }))] }), selectedMembers.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2", children: selectedMembers.map((member) => (_jsxs("span", { className: "flex items-center gap-1.5 rounded-full bg-gray-100 pl-1 pr-2 py-1", children: [_jsx(Avatar, { src: member.avatar, size: 20, className: "shrink-0", children: member.displayName.charAt(0) || "A" }), _jsx("span", { className: "max-w-[120px] truncate text-sm text-gray-900", children: member.displayName }), _jsx(Icon, { icon: "close-b", size: 12, className: "shrink-0 cursor-pointer text-gray-400", onClick: () => handleRemoveMember(member.userID) })] }, member.userID))) })), _jsx("div", { className: "max-h-72 overflow-y-auto", onScroll: handleListScroll, children: isLoading ? (_jsx("div", { className: "flex items-center justify-center py-6", children: _jsx(Spin, {}) })) : accounts.length === 0 ? (_jsx(Empty, { description: t("create_group.no_member_found") })) : (_jsxs(_Fragment, { children: [accounts.map((account) => {
|
|
64
|
+
const isSelected = selectedIds.has(account.userID);
|
|
65
|
+
return (_jsxs("div", { className: "flex cursor-pointer items-center gap-3 rounded-lg px-3 py-3 transition-colors duration-150 hover:bg-[#f5f5f5]", onClick: () => toggleMember(account), children: [_jsx(Avatar, { src: account.avatar, size: 40, className: "shrink-0", children: account.displayName.charAt(0) || "A" }), _jsx("span", { className: "flex-1 truncate text-base font-medium text-gray-900", children: account.displayName }), _jsx("span", { className: clsx("flex h-4 w-4 shrink-0 items-center justify-center rounded border", isSelected
|
|
66
|
+
? "border-primary-400 bg-primary-400"
|
|
67
|
+
: "border-gray-300 bg-white"), children: isSelected && (_jsx(Icon, { icon: "check-b", size: 11, className: "text-white" })) })] }, account.userID));
|
|
68
|
+
}), isFetchingNextPage && (_jsx("div", { className: "flex items-center justify-center py-3", children: _jsx(Spin, { size: "small" }) }))] })) }), _jsxs("div", { className: "flex justify-end gap-2 pt-2", children: [_jsx(Button, { onClick: handleClose, children: tCommon("cancel") }), _jsx(Button, { type: "primary", disabled: selectedMembers.length === 0, loading: isSubmitting, onClick: handleSubmit, children: t("add_member.confirm") })] })] }) }));
|
|
69
|
+
};
|
|
70
|
+
export default AddMemberModal;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface AdminPermissionModalProps {
|
|
2
|
+
open: boolean;
|
|
3
|
+
groupID: string;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
inline?: boolean;
|
|
6
|
+
}
|
|
7
|
+
declare const AdminPermissionModal: ({ open, groupID, onClose, inline, }: AdminPermissionModalProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
8
|
+
export default AdminPermissionModal;
|
|
9
|
+
//# sourceMappingURL=AdminPermissionModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AdminPermissionModal.d.ts","sourceRoot":"","sources":["../../../src/components/group/AdminPermissionModal.tsx"],"names":[],"mappings":"AAYA,UAAU,yBAAyB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IAIpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAMD,QAAA,MAAM,oBAAoB,GAAI,qCAK3B,yBAAyB,mDA0J3B,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { ConfigProvider, Modal, Switch, message } from "antd";
|
|
5
|
+
import { useTranslation } from "react-i18next";
|
|
6
|
+
import { useGroupPermission } from "../../hooks/group/useGroupPermission";
|
|
7
|
+
import { Icon } from "../../components/icon";
|
|
8
|
+
import { PERMISSION_SWITCH_COLOR } from "../../constants";
|
|
9
|
+
import useGroupPermissionOverridesStore from "../../store/groupPermissionOverrides";
|
|
10
|
+
// Mocked config UI (DROPPII-30248 follow-up, "Configure Admin Permissions")
|
|
11
|
+
// — applies to the whole Admin role in the group, not a per-member override
|
|
12
|
+
// (confirmed with PO). No BE persistence yet, committed to the in-memory
|
|
13
|
+
// override store on "Lưu" only.
|
|
14
|
+
const AdminPermissionModal = ({ open, groupID, onClose, inline = false, }) => {
|
|
15
|
+
const { t } = useTranslation();
|
|
16
|
+
const permissions = useGroupPermission(groupID);
|
|
17
|
+
const setAdminOverrides = useGroupPermissionOverridesStore((state) => state.setAdminOverrides);
|
|
18
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
19
|
+
const [pending, setPending] = useState({
|
|
20
|
+
sendMessage: permissions.sendMessage,
|
|
21
|
+
pinMessage: permissions.pinMessage,
|
|
22
|
+
viewMemberInformation: permissions.viewMemberInformation,
|
|
23
|
+
addMember: permissions.addMember,
|
|
24
|
+
removeMember: permissions.removeMember,
|
|
25
|
+
updateGroupInfo: permissions.updateGroupName,
|
|
26
|
+
});
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!open)
|
|
29
|
+
return;
|
|
30
|
+
setPending({
|
|
31
|
+
sendMessage: permissions.sendMessage,
|
|
32
|
+
pinMessage: permissions.pinMessage,
|
|
33
|
+
viewMemberInformation: permissions.viewMemberInformation,
|
|
34
|
+
addMember: permissions.addMember,
|
|
35
|
+
removeMember: permissions.removeMember,
|
|
36
|
+
updateGroupInfo: permissions.updateGroupName,
|
|
37
|
+
});
|
|
38
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
39
|
+
}, [open]);
|
|
40
|
+
const hasChanges = pending.sendMessage !== permissions.sendMessage ||
|
|
41
|
+
pending.pinMessage !== permissions.pinMessage ||
|
|
42
|
+
pending.viewMemberInformation !== permissions.viewMemberInformation ||
|
|
43
|
+
pending.addMember !== permissions.addMember ||
|
|
44
|
+
pending.removeMember !== permissions.removeMember ||
|
|
45
|
+
pending.updateGroupInfo !== permissions.updateGroupName;
|
|
46
|
+
const handleSave = async () => {
|
|
47
|
+
if (!groupID)
|
|
48
|
+
return;
|
|
49
|
+
setIsSaving(true);
|
|
50
|
+
setAdminOverrides(groupID, pending);
|
|
51
|
+
message.success(t("update_admin_permissions_success"));
|
|
52
|
+
setIsSaving(false);
|
|
53
|
+
onClose();
|
|
54
|
+
};
|
|
55
|
+
const rows = (_jsx(ConfigProvider, { theme: { token: { colorPrimary: PERMISSION_SWITCH_COLOR } }, children: _jsxs("div", { className: "flex flex-col py-2", children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-3", children: [_jsx("span", { className: "text-sm", children: t("permission_send_message") }), _jsx(Switch, { checked: pending.sendMessage, onChange: (checked) => setPending((prev) => (Object.assign(Object.assign({}, prev), { sendMessage: checked }))) })] }), _jsxs("div", { className: "flex items-center justify-between px-4 py-3", children: [_jsx("span", { className: "text-sm", children: t("permission_pin_unpin_message") }), _jsx(Switch, { checked: pending.pinMessage, onChange: (checked) => setPending((prev) => (Object.assign(Object.assign({}, prev), { pinMessage: checked }))) })] }), _jsxs("div", { className: "flex items-center justify-between px-4 py-3", children: [_jsx("span", { className: "text-sm", children: t("permission_view_member_info") }), _jsx(Switch, { checked: pending.viewMemberInformation, onChange: (checked) => setPending((prev) => (Object.assign(Object.assign({}, prev), { viewMemberInformation: checked }))) })] }), _jsxs("div", { className: "flex items-center justify-between px-4 py-3", children: [_jsx("span", { className: "text-sm", children: t("permission_add_member") }), _jsx(Switch, { checked: pending.addMember, onChange: (checked) => setPending((prev) => (Object.assign(Object.assign({}, prev), { addMember: checked }))) })] }), _jsxs("div", { className: "flex items-center justify-between px-4 py-3", children: [_jsx("span", { className: "text-sm", children: t("permission_remove_member") }), _jsx(Switch, { checked: pending.removeMember, onChange: (checked) => setPending((prev) => (Object.assign(Object.assign({}, prev), { removeMember: checked }))) })] }), _jsxs("div", { className: "flex items-center justify-between px-4 py-3", children: [_jsx("span", { className: "text-sm", children: t("permission_update_group_info") }), _jsx(Switch, { checked: pending.updateGroupInfo, onChange: (checked) => setPending((prev) => (Object.assign(Object.assign({}, prev), { updateGroupInfo: checked }))) })] })] }) }));
|
|
56
|
+
if (inline) {
|
|
57
|
+
if (!open)
|
|
58
|
+
return null;
|
|
59
|
+
return (_jsxs("div", { className: "absolute inset-0 bg-white flex flex-col z-10", children: [_jsxs("div", { className: "flex w-full items-center border-b border-gray-100 p-2", children: [_jsx("button", { type: "button", className: "flex shrink-0 cursor-pointer items-center justify-center p-2 text-primary-400", onClick: onClose, children: _jsx(Icon, { icon: "arrow-left-o", size: 24 }) }), _jsx("p", { className: "min-w-0 flex-1 truncate px-2 text-[18px] font-bold leading-[1.6] text-gray-900", children: t("admin_permission_settings") })] }), _jsxs("div", { className: "flex-1 overflow-y-auto", children: [rows, _jsxs("div", { className: "flex items-center justify-end gap-2 px-4 py-3 border-t border-gray-100", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-full border border-gray-300 text-gray-700 text-sm font-medium", onClick: onClose, children: t("cancel") }), _jsx("button", { type: "button", className: "px-4 py-1.5 rounded-full bg-primary-400 text-white text-sm font-medium disabled:bg-gray-200 disabled:text-gray-400", disabled: !hasChanges || isSaving, onClick: handleSave, children: t("save") })] })] })] }));
|
|
60
|
+
}
|
|
61
|
+
return (_jsx(Modal, { centered: true, open: open, onOk: handleSave, onCancel: onClose, title: t("admin_permission_settings"), okText: t("save"), cancelText: t("cancel"), confirmLoading: isSaving, okButtonProps: { disabled: !hasChanges }, getContainer: false, children: _jsx("div", { className: "mt-2", children: rows }) }));
|
|
62
|
+
};
|
|
63
|
+
export default AdminPermissionModal;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CreateGroupForm.d.ts","sourceRoot":"","sources":["../../../src/components/group/CreateGroupForm.tsx"],"names":[],"mappings":"AAcA,UAAU,oBAAoB;IAC5B,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,QAAA,MAAM,eAAe,GAAI,YAAY,oBAAoB,4CAqKxD,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Button, Input, Upload } from "antd";
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
import { useMemo, useState } from "react";
|
|
6
|
+
import { useTranslation } from "react-i18next";
|
|
7
|
+
import { Icon } from "../../components/icon";
|
|
8
|
+
import { useCreateGroup } from "../../hooks/group/useCreateGroup";
|
|
9
|
+
import GroupMemberPicker from "./GroupMemberPicker";
|
|
10
|
+
const GROUP_NAME_MAX_LENGTH = 255;
|
|
11
|
+
const CreateGroupForm = ({ onDone }) => {
|
|
12
|
+
const { t } = useTranslation("conversation-inbox");
|
|
13
|
+
const { createGroup } = useCreateGroup();
|
|
14
|
+
const [groupName, setGroupName] = useState("");
|
|
15
|
+
// AC-Privacy1: only set at creation time, not editable after — no need
|
|
16
|
+
// for an "update privacy" path anywhere else in this modal.
|
|
17
|
+
const [isPublic, setIsPublic] = useState(false);
|
|
18
|
+
const [avatarFile, setAvatarFile] = useState(null);
|
|
19
|
+
const [avatarPreviewUrl, setAvatarPreviewUrl] = useState("");
|
|
20
|
+
const [selectedMembers, setSelectedMembers] = useState([]);
|
|
21
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
22
|
+
const canSubmit = selectedMembers.length > 0 && !isSubmitting;
|
|
23
|
+
const handleAvatarSelect = (file) => {
|
|
24
|
+
// Local preview only — there's no dedicated avatar-upload endpoint yet.
|
|
25
|
+
// faceURL falls back to this object URL until BE provides one; group
|
|
26
|
+
// creation itself doesn't block on it (Q3/Q4-style non-block gap).
|
|
27
|
+
const previewUrl = URL.createObjectURL(file);
|
|
28
|
+
setAvatarPreviewUrl(previewUrl);
|
|
29
|
+
setAvatarFile({ uid: file.name, name: file.name });
|
|
30
|
+
return false;
|
|
31
|
+
};
|
|
32
|
+
const handleSubmit = async () => {
|
|
33
|
+
if (!canSubmit)
|
|
34
|
+
return;
|
|
35
|
+
setIsSubmitting(true);
|
|
36
|
+
try {
|
|
37
|
+
await createGroup({
|
|
38
|
+
groupName,
|
|
39
|
+
isPublic,
|
|
40
|
+
members: selectedMembers,
|
|
41
|
+
faceURL: avatarPreviewUrl,
|
|
42
|
+
});
|
|
43
|
+
onDone();
|
|
44
|
+
}
|
|
45
|
+
catch (_a) {
|
|
46
|
+
// Error already surfaced via message.error inside useCreateGroup.
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
setIsSubmitting(false);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const privacyOptions = useMemo(() => [
|
|
53
|
+
{
|
|
54
|
+
value: false,
|
|
55
|
+
icon: "lock-b",
|
|
56
|
+
label: t("create_group.privacy_private"),
|
|
57
|
+
description: t("create_group.privacy_private_desc"),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
value: true,
|
|
61
|
+
icon: "globe-b",
|
|
62
|
+
label: t("create_group.privacy_public"),
|
|
63
|
+
description: t("create_group.privacy_public_desc"),
|
|
64
|
+
},
|
|
65
|
+
], [t]);
|
|
66
|
+
return (_jsxs("div", { className: "flex max-h-[70vh] flex-col", children: [_jsxs("div", { className: "flex flex-col overflow-y-auto pr-1", children: [_jsxs("div", { className: "flex items-center gap-4 py-6", children: [_jsx(Upload, { accept: "image/jpeg, image/png, image/jpg", showUploadList: false, beforeUpload: handleAvatarSelect, children: _jsxs("div", { className: "relative flex h-20 w-20 shrink-0 cursor-pointer items-center justify-center rounded-2xl bg-primary-50", children: [avatarPreviewUrl ? (
|
|
67
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
68
|
+
_jsx("img", { src: avatarPreviewUrl, alt: "", className: "h-full w-full rounded-2xl object-cover" })) : (_jsx(Icon, { icon: "user-three-b", size: 32, className: "text-primary-400" })), _jsx("span", { className: "absolute -bottom-1 -right-1 flex h-7 w-7 items-center justify-center rounded-full bg-gray-500", children: _jsx(Icon, { icon: "camera-b", size: 14, className: "text-white" }) })] }) }), _jsx(Input, { size: "large", value: groupName, onChange: (event) => setGroupName(event.target.value.slice(0, GROUP_NAME_MAX_LENGTH)), maxLength: GROUP_NAME_MAX_LENGTH, placeholder: t("create_group.name_placeholder"), className: "flex-1 text-base" })] }), _jsxs("div", { className: "flex flex-col gap-3 border-t border-gray-100 py-6", children: [_jsx("span", { className: "text-xs font-semibold uppercase tracking-wide text-gray-400", children: t("create_group.privacy_label") }), _jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: privacyOptions.map((option) => (_jsxs("button", { type: "button", onClick: () => setIsPublic(option.value), className: clsx("rounded-xl border-2 p-4 text-left transition-colors", isPublic === option.value
|
|
69
|
+
? "border-primary-400 bg-primary-50"
|
|
70
|
+
: "border-gray-200 bg-white"), children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { icon: option.icon, size: 18, className: isPublic === option.value
|
|
71
|
+
? "text-primary-400"
|
|
72
|
+
: "text-gray-400" }), _jsx("span", { className: "text-base font-semibold text-gray-900", children: option.label })] }), _jsx("p", { className: "mt-2 text-sm text-gray-500", children: option.description })] }, String(option.value)))) })] }), _jsx("div", { className: "border-t border-gray-100 py-6", children: _jsx(GroupMemberPicker, { selectedMembers: selectedMembers, onChangeSelectedMembers: setSelectedMembers }) })] }), _jsxs("div", { className: "flex shrink-0 justify-end gap-2 pt-4", children: [_jsx(Button, { onClick: onDone, children: t("create_group.cancel") }), _jsx(Button, { type: "primary", disabled: !canSubmit, loading: isSubmitting, onClick: handleSubmit, children: t("create_group.submit") })] })] }));
|
|
73
|
+
};
|
|
74
|
+
export default CreateGroupForm;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EligibleAccountItem } from "../../hooks/group/useEligibleAccounts";
|
|
2
|
+
interface GroupMemberPickerProps {
|
|
3
|
+
selectedMembers: EligibleAccountItem[];
|
|
4
|
+
onChangeSelectedMembers: (members: EligibleAccountItem[]) => void;
|
|
5
|
+
}
|
|
6
|
+
declare const GroupMemberPicker: ({ selectedMembers, onChangeSelectedMembers, }: GroupMemberPickerProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export default GroupMemberPicker;
|
|
8
|
+
//# sourceMappingURL=GroupMemberPicker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GroupMemberPicker.d.ts","sourceRoot":"","sources":["../../../src/components/group/GroupMemberPicker.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAE7E,UAAU,sBAAsB;IAC9B,eAAe,EAAE,mBAAmB,EAAE,CAAC;IACvC,uBAAuB,EAAE,CAAC,OAAO,EAAE,mBAAmB,EAAE,KAAK,IAAI,CAAC;CACnE;AAED,QAAA,MAAM,iBAAiB,GAAI,+CAGxB,sBAAsB,4CAoJxB,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useDebounce } from "ahooks";
|
|
4
|
+
import { Avatar, Empty, Input, Spin } from "antd";
|
|
5
|
+
import clsx from "clsx";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import { useTranslation } from "react-i18next";
|
|
8
|
+
import { Icon } from "../../components/icon";
|
|
9
|
+
import { useEligibleAccounts } from "../../hooks/group/useEligibleAccounts";
|
|
10
|
+
const GroupMemberPicker = ({ selectedMembers, onChangeSelectedMembers, }) => {
|
|
11
|
+
const { t } = useTranslation("conversation-inbox");
|
|
12
|
+
const [search, setSearch] = useState("");
|
|
13
|
+
const debouncedSearch = useDebounce(search, { wait: 300 });
|
|
14
|
+
const { accounts, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } = useEligibleAccounts(debouncedSearch);
|
|
15
|
+
const handleListScroll = (event) => {
|
|
16
|
+
if (!hasNextPage || isFetchingNextPage)
|
|
17
|
+
return;
|
|
18
|
+
const { scrollTop, scrollHeight, clientHeight } = event.currentTarget;
|
|
19
|
+
if (scrollHeight - scrollTop - clientHeight < 80) {
|
|
20
|
+
void fetchNextPage();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const selectedIds = new Set(selectedMembers.map((member) => member.userID));
|
|
24
|
+
const toggleMember = (account) => {
|
|
25
|
+
if (selectedIds.has(account.userID)) {
|
|
26
|
+
onChangeSelectedMembers(selectedMembers.filter((member) => member.userID !== account.userID));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
onChangeSelectedMembers([...selectedMembers, account]);
|
|
30
|
+
};
|
|
31
|
+
const handleRemoveMember = (userID) => {
|
|
32
|
+
onChangeSelectedMembers(selectedMembers.filter((member) => member.userID !== userID));
|
|
33
|
+
};
|
|
34
|
+
const handleClearAll = () => {
|
|
35
|
+
onChangeSelectedMembers([]);
|
|
36
|
+
};
|
|
37
|
+
return (_jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { className: "flex flex-col gap-2 rounded-xl border border-gray-200 p-3", children: [_jsx(Input, { value: search, onChange: (event) => setSearch(event.target.value), placeholder: t("create_group.search_member_placeholder"), prefix: _jsx(Icon, { icon: "search-o", size: 16, className: "text-gray-400" }) }), _jsx("div", { className: "max-h-64 overflow-y-auto", onScroll: handleListScroll, children: isLoading ? (_jsx("div", { className: "flex items-center justify-center py-6", children: _jsx(Spin, {}) })) : accounts.length === 0 ? (_jsx(Empty, { description: t("create_group.no_member_found") })) : (_jsxs(_Fragment, { children: [accounts.map((account) => {
|
|
38
|
+
const isSelected = selectedIds.has(account.userID);
|
|
39
|
+
return (_jsxs("div", { className: "flex cursor-pointer items-center gap-3 rounded-lg px-3 py-3 transition-colors duration-150 hover:bg-[#f5f5f5]", onClick: () => toggleMember(account), children: [_jsx(Avatar, { src: account.avatar, size: 40, className: "shrink-0", children: account.displayName.charAt(0) || "A" }), _jsx("span", { className: "flex-1 truncate text-base font-medium text-gray-900", children: account.displayName }), _jsx("span", { className: clsx("flex h-4 w-4 shrink-0 items-center justify-center rounded border", isSelected
|
|
40
|
+
? "border-primary-400 bg-primary-400"
|
|
41
|
+
: "border-gray-300 bg-white"), children: isSelected && (_jsx(Icon, { icon: "check-b", size: 11, className: "text-white" })) })] }, account.userID));
|
|
42
|
+
}), isFetchingNextPage && (_jsx("div", { className: "flex items-center justify-center py-3", children: _jsx(Spin, { size: "small" }) }))] })) })] }), _jsxs("div", { className: "flex flex-col gap-2 rounded-xl border border-gray-200 p-3", children: [_jsxs("div", { className: "flex items-center justify-between px-1", children: [_jsx("span", { className: "text-sm text-gray-500", children: t("create_group.selected_count", { count: selectedMembers.length }) }), selectedMembers.length > 0 && (_jsx("button", { type: "button", className: "text-sm text-primary-400 hover:text-primary-500 hover:underline", onClick: handleClearAll, children: t("create_group.clear_all") }))] }), _jsx("div", { className: "max-h-64 overflow-y-auto", children: selectedMembers.length === 0 ? (_jsxs("div", { className: "flex h-full min-h-[200px] flex-col items-center justify-center gap-2 text-center", children: [_jsx(Icon, { icon: "user-three-b", size: 28, className: "text-gray-300" }), _jsx("span", { className: "text-sm text-gray-400", children: t("create_group.no_member_selected") })] })) : (selectedMembers.map((member) => (_jsxs("div", { className: "flex items-center gap-3 rounded-lg px-3 py-3", children: [_jsx(Avatar, { src: member.avatar, size: 40, className: "shrink-0", children: member.displayName.charAt(0) || "A" }), _jsx("span", { className: "flex-1 truncate text-base font-medium text-gray-900", children: member.displayName }), _jsx(Icon, { icon: "close-b", size: 14, className: "shrink-0 cursor-pointer text-gray-400", onClick: () => handleRemoveMember(member.userID) })] }, member.userID)))) })] })] }));
|
|
43
|
+
};
|
|
44
|
+
export default GroupMemberPicker;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface MemberPermissionPanelProps {
|
|
2
|
+
open: boolean;
|
|
3
|
+
groupID: string;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
}
|
|
6
|
+
declare const MemberPermissionPanel: ({ open, groupID, onClose }: MemberPermissionPanelProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
7
|
+
export default MemberPermissionPanel;
|
|
8
|
+
//# sourceMappingURL=MemberPermissionPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MemberPermissionPanel.d.ts","sourceRoot":"","sources":["../../../src/components/group/MemberPermissionPanel.tsx"],"names":[],"mappings":"AAYA,UAAU,0BAA0B;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAMD,QAAA,MAAM,qBAAqB,GAAI,4BAA4B,0BAA0B,mDAqHpF,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
|