@droppii-org/chat-sdk 0.2.0 → 0.3.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/assets/images/muteNotificationWarning.png +0 -0
- package/dist/components/conversation/ConversationByGroupItem.d.ts +5 -3
- package/dist/components/conversation/ConversationByGroupItem.d.ts.map +1 -1
- package/dist/components/conversation/ConversationByGroupItem.js +23 -33
- package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
- package/dist/components/conversation/DeskConversationList.js +4 -3
- package/dist/components/conversation/SwipeActionButton.d.ts +11 -0
- package/dist/components/conversation/SwipeActionButton.d.ts.map +1 -0
- package/dist/components/conversation/SwipeActionButton.js +7 -0
- package/dist/components/conversation/SwipeableRow.d.ts +10 -0
- package/dist/components/conversation/SwipeableRow.d.ts.map +1 -0
- package/dist/components/conversation/SwipeableRow.js +62 -0
- package/dist/components/conversation/ThreadCard.d.ts +2 -1
- package/dist/components/conversation/ThreadCard.d.ts.map +1 -1
- package/dist/components/conversation/ThreadCard.js +2 -2
- package/dist/components/conversation-detail/ConversationMessageList.d.ts.map +1 -1
- package/dist/components/conversation-detail/ConversationMessageList.js +4 -1
- package/dist/components/conversation-inbox/ConversationInboxItem.d.ts.map +1 -1
- package/dist/components/conversation-inbox/ConversationInboxItem.js +14 -1
- package/dist/components/conversation-inbox/ConversationMuteSwipeAction.d.ts +8 -0
- package/dist/components/conversation-inbox/ConversationMuteSwipeAction.d.ts.map +1 -0
- package/dist/components/conversation-inbox/ConversationMuteSwipeAction.js +31 -0
- package/dist/components/conversation-inbox/MuteConversationConfirmModal.d.ts +9 -0
- package/dist/components/conversation-inbox/MuteConversationConfirmModal.d.ts.map +1 -0
- package/dist/components/conversation-inbox/MuteConversationConfirmModal.js +13 -0
- package/dist/components/conversation-inbox/index.d.ts +4 -0
- package/dist/components/conversation-inbox/index.d.ts.map +1 -1
- package/dist/components/conversation-inbox/index.js +2 -0
- package/dist/components/group/AddMemberModal.d.ts +1 -2
- package/dist/components/group/AddMemberModal.d.ts.map +1 -1
- package/dist/components/group/AddMemberModal.js +2 -2
- package/dist/components/message/GroupMembersDrawer.d.ts.map +1 -1
- package/dist/components/message/GroupMembersDrawer.js +22 -15
- package/dist/components/message/MessageList.d.ts.map +1 -1
- package/dist/components/message/MessageList.js +31 -2
- package/dist/constants/im.d.ts +13 -0
- package/dist/constants/im.d.ts.map +1 -1
- package/dist/constants/im.js +16 -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 +10 -2
- package/dist/hooks/group/useConversationGroups.d.ts +20 -0
- package/dist/hooks/group/useConversationGroups.d.ts.map +1 -0
- package/dist/hooks/group/useConversationGroups.js +116 -0
- package/dist/hooks/group/useCreateGroup.d.ts.map +1 -1
- package/dist/hooks/group/useCreateGroup.js +34 -5
- package/dist/hooks/group/useEligibleAccountsForGroup.d.ts +1 -1
- package/dist/hooks/group/useEligibleAccountsForGroup.d.ts.map +1 -1
- package/dist/hooks/group/useEligibleAccountsForGroup.js +6 -9
- package/dist/hooks/group/useGetGroupCandidates.d.ts +2 -1
- package/dist/hooks/group/useGetGroupCandidates.d.ts.map +1 -1
- package/dist/hooks/group/useGetGroupCandidates.js +3 -2
- package/dist/hooks/group/useGroupMemberActions.d.ts +6 -0
- package/dist/hooks/group/useGroupMemberActions.d.ts.map +1 -1
- package/dist/hooks/group/useGroupMemberActions.js +12 -0
- package/dist/hooks/group/useGroupPermission.d.ts +3 -1
- package/dist/hooks/group/useGroupPermission.d.ts.map +1 -1
- package/dist/hooks/group/useGroupPermission.js +33 -37
- package/dist/hooks/session/useMuteConversation.d.ts +8 -0
- package/dist/hooks/session/useMuteConversation.d.ts.map +1 -0
- package/dist/hooks/session/useMuteConversation.js +37 -0
- package/dist/hooks/session/useSessionLatestPreview.d.ts +6 -2
- package/dist/hooks/session/useSessionLatestPreview.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/locales/vi/common.json +14 -0
- package/dist/locales/vi/conversation-inbox.json +16 -0
- package/dist/services/query.d.ts +1 -1
- package/dist/services/query.js +1 -1
- 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/conversation.d.ts.map +1 -1
- package/dist/store/conversation.js +25 -1
- package/dist/store/type.d.ts +1 -0
- package/dist/store/type.d.ts.map +1 -1
- package/dist/styles/global.css +1 -1
- package/dist/types/dto.d.ts +8 -0
- package/dist/types/dto.d.ts.map +1 -1
- package/dist/utils/common.d.ts.map +1 -1
- package/dist/utils/common.js +20 -2
- package/dist/utils/imCommon.d.ts +1 -0
- package/dist/utils/imCommon.d.ts.map +1 -1
- package/dist/utils/messageLog.d.ts +4 -0
- package/dist/utils/messageLog.d.ts.map +1 -1
- package/dist/utils/messageLog.js +113 -0
- package/package.json +3 -2
- package/dist/hooks/group/useJoinedGroups.d.ts +0 -13
- package/dist/hooks/group/useJoinedGroups.d.ts.map +0 -1
- package/dist/hooks/group/useJoinedGroups.js +0 -33
|
@@ -11,11 +11,11 @@ import { useGroupMembers } from "../../hooks/group/useGroupMembers";
|
|
|
11
11
|
import { useSearchGroupMembers } from "../../hooks/group/useSearchGroupMembers";
|
|
12
12
|
import { useLeaveConversation } from "../../hooks/session/useLeaveConversation";
|
|
13
13
|
import { useGroupPermission } from "../../hooks/group/useGroupPermission";
|
|
14
|
-
import {
|
|
14
|
+
import { GROUP_PERMISSION_KEY } from "../../constants/im";
|
|
15
|
+
import { useKickGroupMember, useSetGroupMemberPermissions, useSetGroupMemberRole, useTransferGroupOwner, } from "../../hooks/group/useGroupMemberActions";
|
|
15
16
|
import { Icon } from "../../components/icon";
|
|
16
17
|
import AddMemberModal from "../../components/group/AddMemberModal";
|
|
17
18
|
import AdminPermissionModal from "../../components/group/AdminPermissionModal";
|
|
18
|
-
const isMemberMuted = (member) => Boolean(member.muteEndTime) && member.muteEndTime * 1000 > Date.now();
|
|
19
19
|
const getMemberDisplayName = (member) => {
|
|
20
20
|
return member.nickname || member.userID || "";
|
|
21
21
|
};
|
|
@@ -43,17 +43,15 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
|
|
|
43
43
|
const debouncedSearch = useDebounce(search, { wait: 300 });
|
|
44
44
|
const authUserID = useAuthStore((state) => state.userID);
|
|
45
45
|
const conversationData = useConversationStore((state) => state.conversationData);
|
|
46
|
-
const currentMemberInGroup = useConversationStore((state) => state.currentMemberInGroup);
|
|
47
46
|
const { members, isLoading, error, refetch } = useGroupMembers(groupID);
|
|
48
47
|
const { results: searchResults, isSearching } = useSearchGroupMembers(groupID, debouncedSearch);
|
|
49
48
|
const { mutateAsync: leaveConversation, isPending: isLeaving } = useLeaveConversation();
|
|
50
49
|
const permissions = useGroupPermission(groupID);
|
|
51
|
-
const { mutateAsync:
|
|
50
|
+
const { mutateAsync: setMemberPermissions } = useSetGroupMemberPermissions();
|
|
52
51
|
const { mutateAsync: kickMember } = useKickGroupMember();
|
|
53
52
|
const { mutateAsync: setMemberRole } = useSetGroupMemberRole();
|
|
54
53
|
const { mutateAsync: transferOwner } = useTransferGroupOwner();
|
|
55
|
-
const canAddMember =
|
|
56
|
-
(currentMemberInGroup === null || currentMemberInGroup === void 0 ? void 0 : currentMemberInGroup.roleLevel) === GroupMemberRole.Owner;
|
|
54
|
+
const canAddMember = permissions.addMember;
|
|
57
55
|
let customerUserID = "";
|
|
58
56
|
try {
|
|
59
57
|
const exConversationInfo = JSON.parse((conversationData === null || conversationData === void 0 ? void 0 : conversationData.ex) || "{}");
|
|
@@ -67,8 +65,10 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
|
|
|
67
65
|
const sortedMembers = useMemo(() => {
|
|
68
66
|
return [...sourceMembers]
|
|
69
67
|
.map((member) => {
|
|
68
|
+
var _a;
|
|
70
69
|
const isOwner = member.userID === customerUserID ||
|
|
71
70
|
member.roleLevel === GroupMemberRole.Owner;
|
|
71
|
+
const permissions = ((_a = member.permissions) !== null && _a !== void 0 ? _a : []);
|
|
72
72
|
return {
|
|
73
73
|
userID: member.userID,
|
|
74
74
|
nickname: getMemberDisplayName(member),
|
|
@@ -77,7 +77,8 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
|
|
|
77
77
|
isCurrentUser: member.userID === authUserID,
|
|
78
78
|
isOwner,
|
|
79
79
|
isAdmin: member.roleLevel === GroupMemberRole.Admin,
|
|
80
|
-
|
|
80
|
+
permissions,
|
|
81
|
+
isSendMessageDisabled: !permissions.includes(GROUP_PERMISSION_KEY.sendMessage),
|
|
81
82
|
// Mockup only shows a role label under the owner/creator row —
|
|
82
83
|
// Admin/Member rows show name only.
|
|
83
84
|
roleLabel: isOwner
|
|
@@ -119,17 +120,21 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
|
|
|
119
120
|
}
|
|
120
121
|
setShowLeaveConfirm(true);
|
|
121
122
|
};
|
|
122
|
-
const
|
|
123
|
+
const handleToggleMemberSendMessage = async (member) => {
|
|
123
124
|
var _a, _b;
|
|
124
125
|
if (!groupID)
|
|
125
126
|
return;
|
|
127
|
+
const sendMessageKey = GROUP_PERMISSION_KEY.sendMessage;
|
|
128
|
+
const nextPermissions = member.isSendMessageDisabled
|
|
129
|
+
? [...member.permissions, sendMessageKey]
|
|
130
|
+
: member.permissions.filter((permission) => permission !== sendMessageKey);
|
|
126
131
|
try {
|
|
127
|
-
await
|
|
132
|
+
await setMemberPermissions({
|
|
128
133
|
groupID,
|
|
129
134
|
userID: member.userID,
|
|
130
|
-
|
|
135
|
+
permissions: nextPermissions,
|
|
131
136
|
});
|
|
132
|
-
message.success(member.
|
|
137
|
+
message.success(member.isSendMessageDisabled
|
|
133
138
|
? t("unmute_member_success")
|
|
134
139
|
: t("mute_member_success"));
|
|
135
140
|
void refetch();
|
|
@@ -249,10 +254,12 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
|
|
|
249
254
|
},
|
|
250
255
|
{
|
|
251
256
|
key: "mute",
|
|
252
|
-
label: member.
|
|
257
|
+
label: member.isSendMessageDisabled
|
|
258
|
+
? t("unmute_member")
|
|
259
|
+
: t("mute_member"),
|
|
253
260
|
icon: _jsx(Icon, { icon: "bell-off-b", size: 16 }),
|
|
254
261
|
disabled: !permissions.removeMember,
|
|
255
|
-
onClick: () => void
|
|
262
|
+
onClick: () => void handleToggleMemberSendMessage(member),
|
|
256
263
|
},
|
|
257
264
|
{
|
|
258
265
|
key: "transfer-owner",
|
|
@@ -311,10 +318,10 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
|
|
|
311
318
|
padding: 0,
|
|
312
319
|
height: "100%",
|
|
313
320
|
},
|
|
314
|
-
}, getContainer: false, width: 360, children: _jsxs("div", { className: "flex flex-col h-full bg-white
|
|
321
|
+
}, getContainer: false, width: 360, children: _jsxs("div", { className: "flex flex-col h-full bg-white", children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-4 border-b border-gray-100", children: [_jsx("span", { className: "text-lg font-medium", children: t("members") }), _jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0", onClick: handleCloseDrawer, children: _jsx(Icon, { icon: "close-b", size: 22 }) })] }), _jsxs("div", { className: "flex items-center gap-2 px-4 py-3", children: [_jsx(Input, { value: search, onChange: (event) => setSearch(event.target.value), placeholder: t("search_member_placeholder"), prefix: _jsx(Icon, { icon: "search-o", size: 16, className: "text-gray-400" }), className: "h-10" }), canAddMember && (_jsx(Button, { type: "primary", shape: "default", className: "w-10 h-10 p-0 shrink-0 flex items-center justify-center rounded-lg", onClick: () => setShowAddMember(true), children: _jsx(Icon, { icon: "user-add-b", size: 18 }) }))] }), _jsx("div", { className: "flex-1 overflow-y-auto", children: renderContent() })] }) }), _jsx(Modal, { centered: true, open: showLeaveConfirm, onOk: handleConfirmLeave, onCancel: () => setShowLeaveConfirm(false), title: t("leave_group_confirm_title"), okText: t("leave_group"), cancelText: t("cancel"), okType: "danger", confirmLoading: isLeaving, getContainer: false, children: _jsx("p", { children: t("leave_group_confirm_message") }) }), _jsx(Modal, { centered: true, open: (confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.type) === "remove", onOk: handleConfirmRemoveMember, onCancel: () => setConfirmMemberAction(null), title: t("remove_member_confirm_title"), okText: t("remove_from_group"), cancelText: t("cancel"), okType: "danger", getContainer: false, children: _jsx("p", { children: t("remove_member_confirm_message", {
|
|
315
322
|
name: confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.member.nickname,
|
|
316
323
|
}) }) }), _jsx(Modal, { centered: true, open: (confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.type) === "transfer-owner", onOk: handleConfirmTransferOwner, onCancel: () => setConfirmMemberAction(null), title: t("transfer_owner_confirm_title"), okText: t("transfer_owner"), cancelText: t("cancel"), okType: "danger", getContainer: false, children: _jsx("p", { children: t("transfer_owner_confirm_message", {
|
|
317
324
|
name: confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.member.nickname,
|
|
318
|
-
}) }) }), groupID && (_jsx(AddMemberModal, { open: showAddMember, groupID: groupID,
|
|
325
|
+
}) }) }), groupID && (_jsx(AddMemberModal, { open: showAddMember, groupID: groupID, onClose: () => setShowAddMember(false) })), groupID && (_jsx(AdminPermissionModal, { open: showAdminPermission, groupID: groupID, onClose: () => setShowAdminPermission(false) }))] }));
|
|
319
326
|
};
|
|
320
327
|
export default GroupMembersDrawer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AA8BA,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAmR3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useMessage } from "../../hooks/message/useMessage";
|
|
4
|
-
import { Button, Empty, Modal, Spin } from "antd";
|
|
4
|
+
import { Button, Empty, Modal, Spin, message as toastMessage } from "antd";
|
|
5
5
|
import { useCallback, useEffect, useState } from "react";
|
|
6
6
|
import dayjs from "dayjs";
|
|
7
7
|
import isToday from "dayjs/plugin/isToday";
|
|
@@ -17,11 +17,13 @@ import { useBoolean } from "ahooks";
|
|
|
17
17
|
import { useMessageListScroll } from "../../hooks/message/useMessageListScroll";
|
|
18
18
|
import { useChatContext } from "../../context/ChatContext";
|
|
19
19
|
import { useRevokeMessage } from "../../hooks/message/useRevokeMessage";
|
|
20
|
+
import { usePinMessage } from "../../hooks/message/usePinMessage";
|
|
20
21
|
import { useConversationSessionState } from "../../hooks/session/useConversationSessionState";
|
|
21
22
|
import { useJoinGroupFlow } from "../../hooks/session/useJoinGroupFlow";
|
|
22
23
|
import { useGroupPermission } from "../../hooks/group/useGroupPermission";
|
|
23
24
|
import { isGroupSession } from "../../utils/imCommon";
|
|
24
25
|
import { ConnectStatus, SessionStatus } from "../../types/chat";
|
|
26
|
+
import PinLimitModal from "../../components/conversation-detail/PinLimitModal";
|
|
25
27
|
dayjs.extend(isToday);
|
|
26
28
|
const MessageList = (props) => {
|
|
27
29
|
const { t } = useTranslation();
|
|
@@ -31,12 +33,17 @@ const MessageList = (props) => {
|
|
|
31
33
|
const permissions = useGroupPermission(conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID);
|
|
32
34
|
const isGroupChat = isGroupSession(conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationType);
|
|
33
35
|
const canSendMessage = !isGroupChat || permissions.sendMessage;
|
|
36
|
+
const canPinMessage = !isGroupChat || permissions.pinMessage;
|
|
34
37
|
const setQuotedMessage = useConversationStore((state) => state.setQuotedMessage);
|
|
35
38
|
const setSearchClientMsgID = useConversationStore((state) => state.setSearchClientMsgID);
|
|
36
39
|
const [openMenuId, setOpenMenuId] = useState(null);
|
|
37
40
|
const [selectedItem, setSelectedItem] = useState(null);
|
|
38
41
|
const [showConfirmRevoke, { setTrue: openConfirmRevoke, setFalse: closeConfirmRevoke },] = useBoolean(false);
|
|
39
42
|
const { revokeMessage, loading: isRevoking } = useRevokeMessage();
|
|
43
|
+
const { pinMessage, unpinMessage } = usePinMessage();
|
|
44
|
+
const pinnedTotalCount = useConversationStore((state) => state.pinnedTotalCount);
|
|
45
|
+
const pinnedRemainingCount = useConversationStore((state) => state.pinnedRemainingCount);
|
|
46
|
+
const [pinLimitModalOpen, setPinLimitModalOpen] = useState(false);
|
|
40
47
|
const { groupId, latestConversationSession, refetchConversationSessions, } = useConversationSessionState({
|
|
41
48
|
conversationId,
|
|
42
49
|
conversationType: conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationType,
|
|
@@ -75,6 +82,28 @@ const MessageList = (props) => {
|
|
|
75
82
|
setSearchClientMsgID(clientMsgID);
|
|
76
83
|
}
|
|
77
84
|
}, [scrollToMessage, loadState.messageList, setSearchClientMsgID]);
|
|
85
|
+
const handlePinMessage = useCallback(async (message) => {
|
|
86
|
+
if (pinnedRemainingCount <= 0 && pinnedTotalCount > 0) {
|
|
87
|
+
setPinLimitModalOpen(true);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
await pinMessage(conversationId, message.clientMsgID);
|
|
92
|
+
toastMessage.success(t("pin_message_success"));
|
|
93
|
+
}
|
|
94
|
+
catch (_a) {
|
|
95
|
+
toastMessage.error(t("pin_message_failed"));
|
|
96
|
+
}
|
|
97
|
+
}, [conversationId, pinMessage, pinnedRemainingCount, pinnedTotalCount, t]);
|
|
98
|
+
const handleUnpinMessage = useCallback(async (message) => {
|
|
99
|
+
try {
|
|
100
|
+
await unpinMessage(conversationId, message.clientMsgID);
|
|
101
|
+
toastMessage.success(t("unpin_message_success"));
|
|
102
|
+
}
|
|
103
|
+
catch (_a) {
|
|
104
|
+
toastMessage.error(t("unpin_message_failed"));
|
|
105
|
+
}
|
|
106
|
+
}, [conversationId, unpinMessage, t]);
|
|
78
107
|
useEffect(() => {
|
|
79
108
|
emitter.on("CHAT_LIST_SCROLL_TO_BOTTOM", scrollToBottom);
|
|
80
109
|
emitter.on("CHAT_LIST_SCROLL_TO_MESSAGE", scrollToMessage);
|
|
@@ -107,6 +136,6 @@ const MessageList = (props) => {
|
|
|
107
136
|
if (!conversationData) {
|
|
108
137
|
return (_jsx("div", { className: "flex flex-1 items-center justify-center h-full", children: connectStatus !== ConnectStatus.Connected ? (_jsx(Spin, {})) : (_jsx(Empty, { description: t("no_conversation_data") })) }));
|
|
109
138
|
}
|
|
110
|
-
return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full min-w-0 bg-white", style: { overflowX: "hidden" }, children: [_jsx(MessageHeader, { onClose: onClose, currentSession: latestConversationSession, isJoined: isJoined, onOpenThreadInfo: onOpenThreadInfo }), _jsx(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, children: loadState.messageList.map((message, _, array) => (_jsx(MessageItem, { message: message, allMessages: array, contextMenuOpen: openMenuId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenMenuId(open ? message.clientMsgID : null), onRevokeMessage: handleOpenRevoke, onQuoteMessage: setQuotedMessage, onPressQuoteMessage: onPressQuoteMessage }, message.clientMsgID))) }), moreNewLoading && (_jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) })), isCheckingMembership ? (_jsx("div", { className: "border-t bg-white py-4 flex items-center justify-center", children: _jsx(Spin, {}) })) : (latestConversationSession === null || latestConversationSession === void 0 ? void 0 : latestConversationSession.status) === SessionStatus.UNASSIGNED ? (_jsx(UnassignedSessionFooter, { sessionId: latestConversationSession.id, teamId: latestConversationSession.teamId })) : shouldRestrictToLastSession ? (_jsxs("div", { className: "border-t bg-white py-4 flex flex-col items-center gap-2", children: [_jsx("p", { className: "text-sm text-gray-500", children: t("join_group_required") }), _jsx(Button, { type: "primary", onClick: joinGroup, loading: isJoining, children: t("join_group") })] })) : !canSendMessage ? (_jsx("div", { className: "border-t bg-[#f5f5f5] py-3 flex items-center justify-center", children: _jsx("p", { className: "text-sm text-gray-500", children: t("send_message_disabled") }) })) : (_jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })), _jsx(Modal, { centered: true, open: showConfirmRevoke, onOk: onRevokeMessage, onCancel: handleCloseRevoke, title: t("revoke_message_confirm_title"), okText: t("revoke"), cancelText: t("cancel"), okType: "danger", confirmLoading: isRevoking, getContainer: false, forceRender: true, children: _jsx("p", { children: t("revoke_message_confirm_message") }) })] }));
|
|
139
|
+
return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full min-w-0 bg-white", style: { overflowX: "hidden" }, children: [_jsx(MessageHeader, { onClose: onClose, currentSession: latestConversationSession, isJoined: isJoined, onOpenThreadInfo: onOpenThreadInfo }), _jsx(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, children: loadState.messageList.map((message, _, array) => (_jsx(MessageItem, { message: message, allMessages: array, contextMenuOpen: openMenuId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenMenuId(open ? message.clientMsgID : null), onRevokeMessage: handleOpenRevoke, onQuoteMessage: setQuotedMessage, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined }, message.clientMsgID))) }), moreNewLoading && (_jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) })), isCheckingMembership ? (_jsx("div", { className: "border-t bg-white py-4 flex items-center justify-center", children: _jsx(Spin, {}) })) : (latestConversationSession === null || latestConversationSession === void 0 ? void 0 : latestConversationSession.status) === SessionStatus.UNASSIGNED ? (_jsx(UnassignedSessionFooter, { sessionId: latestConversationSession.id, teamId: latestConversationSession.teamId })) : shouldRestrictToLastSession ? (_jsxs("div", { className: "border-t bg-white py-4 flex flex-col items-center gap-2", children: [_jsx("p", { className: "text-sm text-gray-500", children: t("join_group_required") }), _jsx(Button, { type: "primary", onClick: joinGroup, loading: isJoining, children: t("join_group") })] })) : !canSendMessage ? (_jsx("div", { className: "border-t bg-[#f5f5f5] py-3 flex items-center justify-center", children: _jsx("p", { className: "text-sm text-gray-500", children: t("send_message_disabled") }) })) : (_jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })), _jsx(Modal, { centered: true, open: showConfirmRevoke, onOk: onRevokeMessage, onCancel: handleCloseRevoke, title: t("revoke_message_confirm_title"), okText: t("revoke"), cancelText: t("cancel"), okType: "danger", confirmLoading: isRevoking, getContainer: false, forceRender: true, children: _jsx("p", { children: t("revoke_message_confirm_message") }) }), _jsx(PinLimitModal, { open: pinLimitModalOpen, max: pinnedTotalCount, onClose: () => setPinLimitModalOpen(false) })] }));
|
|
111
140
|
};
|
|
112
141
|
export default MessageList;
|
package/dist/constants/im.d.ts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
1
|
import { SessionType } from "@droppii-org/wasm-client-sdk";
|
|
2
2
|
export declare const GroupSessionTypes: SessionType[];
|
|
3
|
+
export declare const GROUP_PERMISSION_KEY: {
|
|
4
|
+
readonly sendMessage: "SEND_MESSAGE";
|
|
5
|
+
readonly pinMessage: "PIN_MESSAGE";
|
|
6
|
+
readonly updateGroupName: "UPDATE_GROUP_NAME";
|
|
7
|
+
readonly updateGroupAvatar: "UPDATE_GROUP_AVATAR";
|
|
8
|
+
readonly updateGroupDescription: "UPDATE_GROUP_DESCRIPTION";
|
|
9
|
+
readonly addMember: "ADD_MEMBER";
|
|
10
|
+
readonly removeMember: "REMOVE_MEMBER";
|
|
11
|
+
readonly addAdmin: "ADD_ADMIN";
|
|
12
|
+
readonly transferOwnership: "TRANSFER_OWNERSHIP";
|
|
13
|
+
readonly configGroupPermission: "CONFIG_GROUP_PERMISSION";
|
|
14
|
+
readonly viewMemberInformation: "VIEW_MEMBER_INFORMATION";
|
|
15
|
+
};
|
|
3
16
|
//# sourceMappingURL=im.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"im.d.ts","sourceRoot":"","sources":["../../src/constants/im.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,eAAO,MAAM,iBAAiB,eAAgD,CAAC"}
|
|
1
|
+
{"version":3,"file":"im.d.ts","sourceRoot":"","sources":["../../src/constants/im.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,eAAO,MAAM,iBAAiB,eAAgD,CAAC;AAK/E,eAAO,MAAM,oBAAoB;;;;;;;;;;;;CAYvB,CAAC"}
|
package/dist/constants/im.js
CHANGED
|
@@ -1,2 +1,18 @@
|
|
|
1
1
|
import { SessionType } from "@droppii-org/wasm-client-sdk";
|
|
2
2
|
export const GroupSessionTypes = [SessionType.Group, SessionType.WorkingGroup];
|
|
3
|
+
// Raw permission strings BE returns in SelfUserInfo.permissions /
|
|
4
|
+
// GroupMemberItem.permissions (DROPPII-30247). Keys match the Go constants
|
|
5
|
+
// (permission.SendMessage, etc) — see DROPPII-30248 BE update.
|
|
6
|
+
export const GROUP_PERMISSION_KEY = {
|
|
7
|
+
sendMessage: "SEND_MESSAGE",
|
|
8
|
+
pinMessage: "PIN_MESSAGE",
|
|
9
|
+
updateGroupName: "UPDATE_GROUP_NAME",
|
|
10
|
+
updateGroupAvatar: "UPDATE_GROUP_AVATAR",
|
|
11
|
+
updateGroupDescription: "UPDATE_GROUP_DESCRIPTION",
|
|
12
|
+
addMember: "ADD_MEMBER",
|
|
13
|
+
removeMember: "REMOVE_MEMBER",
|
|
14
|
+
addAdmin: "ADD_ADMIN",
|
|
15
|
+
transferOwnership: "TRANSFER_OWNERSHIP",
|
|
16
|
+
configGroupPermission: "CONFIG_GROUP_PERMISSION",
|
|
17
|
+
viewMemberInformation: "VIEW_MEMBER_INFORMATION",
|
|
18
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/constants/images.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/constants/images.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,MAAM;;;;;CAKlB,CAAC"}
|
package/dist/constants/images.js
CHANGED
|
@@ -2,8 +2,10 @@ const conversationBg = new URL("../assets/images/conversationBg.png", import.met
|
|
|
2
2
|
const imageFailed = new URL("../assets/images/imageFailed.png", import.meta.url)
|
|
3
3
|
.href;
|
|
4
4
|
const emptyResources = new URL("../assets/images/emptyResources.png", import.meta.url).href;
|
|
5
|
+
const muteNotificationWarning = new URL("../assets/images/muteNotificationWarning.png", import.meta.url).href;
|
|
5
6
|
export const images = {
|
|
6
7
|
conversationBg,
|
|
7
8
|
imageFailed,
|
|
8
9
|
emptyResources,
|
|
10
|
+
muteNotificationWarning,
|
|
9
11
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AAuCA,eAAO,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AAuCA,eAAO,MAAM,cAAc,YA8Y1B,CAAC"}
|
|
@@ -26,7 +26,7 @@ export const useGlobalEvent = () => {
|
|
|
26
26
|
const updateConversationList = useConversationStore((state) => state.updateConversationList);
|
|
27
27
|
const getConversationListByReq = useConversationStore((state) => state.getConversationListByReq);
|
|
28
28
|
const getCurrentGroupInfoByReq = useConversationStore((state) => state.getCurrentGroupInfoByReq);
|
|
29
|
-
const
|
|
29
|
+
const getSelfGroupPermissionByReq = useConversationStore((state) => state.getSelfGroupPermissionByReq);
|
|
30
30
|
const { mutate: refetchChatToken } = useRefetchChatToken();
|
|
31
31
|
const accessToken = useAuthStore((state) => state.accessToken);
|
|
32
32
|
const chatToken = useAuthStore((state) => state.chatToken);
|
|
@@ -42,7 +42,7 @@ export const useGlobalEvent = () => {
|
|
|
42
42
|
}
|
|
43
43
|
await Promise.all([
|
|
44
44
|
getCurrentGroupInfoByReq(currentConversation.groupID),
|
|
45
|
-
|
|
45
|
+
getSelfGroupPermissionByReq(currentConversation.groupID),
|
|
46
46
|
]);
|
|
47
47
|
};
|
|
48
48
|
const revokedMessageHandler = ({ data }) => {
|
|
@@ -204,6 +204,12 @@ export const useGlobalEvent = () => {
|
|
|
204
204
|
const groupInfoChangedHandler = () => {
|
|
205
205
|
void refreshCurrentGroupState();
|
|
206
206
|
};
|
|
207
|
+
// BE pushes this when a member's role/permissions change (DROPPII-30248
|
|
208
|
+
// spec: actions must always check the LATEST permissions, not stale UI) —
|
|
209
|
+
// refetch so useGroupPermission picks up the new permissions[] right away.
|
|
210
|
+
const groupMemberInfoChangedHandler = () => {
|
|
211
|
+
void refreshCurrentGroupState();
|
|
212
|
+
};
|
|
207
213
|
const groupDismissedHandler = ({ data }) => {
|
|
208
214
|
const currentConversation = useConversationStore.getState().currentConversation;
|
|
209
215
|
if ((currentConversation === null || currentConversation === void 0 ? void 0 : currentConversation.groupID) === data.groupID) {
|
|
@@ -251,6 +257,7 @@ export const useGlobalEvent = () => {
|
|
|
251
257
|
DChatSDK.on(CbEvents.OnJoinedGroupAdded, joinedGroupAddedHandler);
|
|
252
258
|
// group info
|
|
253
259
|
DChatSDK.on(CbEvents.OnGroupInfoChanged, groupInfoChangedHandler);
|
|
260
|
+
DChatSDK.on(CbEvents.OnGroupMemberInfoChanged, groupMemberInfoChangedHandler);
|
|
254
261
|
DChatSDK.on(CbEvents.OnGroupDismissed, groupDismissedHandler);
|
|
255
262
|
// black list
|
|
256
263
|
DChatSDK.on(CbEvents.OnBlackAdded, blackListChangedHandler);
|
|
@@ -283,6 +290,7 @@ export const useGlobalEvent = () => {
|
|
|
283
290
|
DChatSDK.off(CbEvents.OnJoinedGroupAdded, joinedGroupAddedHandler);
|
|
284
291
|
// group info
|
|
285
292
|
DChatSDK.off(CbEvents.OnGroupInfoChanged, groupInfoChangedHandler);
|
|
293
|
+
DChatSDK.off(CbEvents.OnGroupMemberInfoChanged, groupMemberInfoChangedHandler);
|
|
286
294
|
DChatSDK.off(CbEvents.OnGroupDismissed, groupDismissedHandler);
|
|
287
295
|
// black list
|
|
288
296
|
DChatSDK.off(CbEvents.OnBlackAdded, blackListChangedHandler);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ConversationItem } from "@droppii-org/wasm-client-sdk";
|
|
2
|
+
import { BaseResponse, IConversationGroupItem } from "../../types/dto";
|
|
3
|
+
interface UseConversationGroupsParams {
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
pageSize?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ConversationGroupDisplayItem {
|
|
8
|
+
groupId: string;
|
|
9
|
+
conversationId: string;
|
|
10
|
+
conversation: ConversationItem;
|
|
11
|
+
}
|
|
12
|
+
export declare const useConversationGroups: ({ enabled, pageSize, }?: UseConversationGroupsParams) => {
|
|
13
|
+
groups: ConversationGroupDisplayItem[];
|
|
14
|
+
fetchNextPage: (options?: import("@tanstack/react-query").FetchNextPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<BaseResponse<IConversationGroupItem[]>, unknown>, Error>>;
|
|
15
|
+
hasNextPage: boolean;
|
|
16
|
+
isFetchingNextPage: boolean;
|
|
17
|
+
isLoading: boolean;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=useConversationGroups.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useConversationGroups.d.ts","sourceRoot":"","sources":["../../../src/hooks/group/useConversationGroups.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAe,MAAM,8BAA8B,CAAC;AAK7E,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAKnE,UAAU,2BAA2B;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,gBAAgB,CAAC;CAChC;AAiED,eAAO,MAAM,qBAAqB,GAAI,yBAGnC,2BAAgC;;;;;;CAoElC,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
4
|
+
import { SessionType } from "@droppii-org/wasm-client-sdk";
|
|
5
|
+
import { apiInstance } from "../../services/api";
|
|
6
|
+
import { ENDPOINTS } from "../../services/routes";
|
|
7
|
+
import { QUERY_KEYS } from "../../services/query";
|
|
8
|
+
import { PAGE_SIZE } from "../../constants";
|
|
9
|
+
import useAuthStore from "../../store/auth";
|
|
10
|
+
import useConversationStore from "../../store/conversation";
|
|
11
|
+
import { DChatSDK } from "../../constants/sdk";
|
|
12
|
+
// Placeholder used until the store has synced the real ConversationItem for
|
|
13
|
+
// a brand new group (e.g. right after create-group, or first time this list
|
|
14
|
+
// loads) — carries just enough for ConversationByGroupItem to render
|
|
15
|
+
// something instead of the row disappearing while the sync is in flight.
|
|
16
|
+
const buildFallbackConversation = (group) => ({
|
|
17
|
+
conversationID: group.conversationId,
|
|
18
|
+
conversationType: SessionType.Group,
|
|
19
|
+
userID: "",
|
|
20
|
+
groupID: group.groupId,
|
|
21
|
+
showName: group.groupName,
|
|
22
|
+
faceURL: group.groupAvatar || "",
|
|
23
|
+
recvMsgOpt: 0,
|
|
24
|
+
unreadCount: 0,
|
|
25
|
+
groupAtType: 0,
|
|
26
|
+
latestMsg: "",
|
|
27
|
+
latestMsgSendTime: group.latestMessageAt
|
|
28
|
+
? new Date(group.latestMessageAt).getTime()
|
|
29
|
+
: 0,
|
|
30
|
+
draftText: "",
|
|
31
|
+
draftTextTime: 0,
|
|
32
|
+
burnDuration: 0,
|
|
33
|
+
maxSeq: 0,
|
|
34
|
+
minSeq: 0,
|
|
35
|
+
msgDestructTime: 0,
|
|
36
|
+
isPinned: false,
|
|
37
|
+
isNotInGroup: false,
|
|
38
|
+
isPrivateChat: false,
|
|
39
|
+
isMsgDestruct: false,
|
|
40
|
+
attachedInfo: "",
|
|
41
|
+
});
|
|
42
|
+
// Same join pattern as useGetSession: the REST endpoint only returns group
|
|
43
|
+
// metadata (no latestMsg) — sync each group's full ConversationItem (which
|
|
44
|
+
// carries latestMsg/latestMsgSendTime/unreadCount) into the shared
|
|
45
|
+
// conversationList store via the SDK, then join on conversationId.
|
|
46
|
+
const syncGroupConversationsToStore = async (groups) => {
|
|
47
|
+
const currentList = useConversationStore.getState().conversationList;
|
|
48
|
+
const newGroups = groups.filter((group) => !currentList.some((conversation) => conversation.conversationID === group.conversationId));
|
|
49
|
+
if (!newGroups.length)
|
|
50
|
+
return;
|
|
51
|
+
try {
|
|
52
|
+
const { data } = await DChatSDK.getMultipleConversation(newGroups.map((group) => group.conversationId));
|
|
53
|
+
if (data === null || data === void 0 ? void 0 : data.length) {
|
|
54
|
+
useConversationStore.getState().updateConversationList(data, "filter");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error("syncGroupConversationsToStore", error);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
export const useConversationGroups = ({ enabled = true, pageSize = PAGE_SIZE.medium, } = {}) => {
|
|
62
|
+
const conversationList = useConversationStore((state) => state.conversationList);
|
|
63
|
+
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = useInfiniteQuery({
|
|
64
|
+
initialPageParam: 1,
|
|
65
|
+
queryKey: [QUERY_KEYS.GET_CONVERSATION_GROUPS, pageSize],
|
|
66
|
+
queryFn: async ({ pageParam = 1 }) => {
|
|
67
|
+
var _a, _b;
|
|
68
|
+
const applicationType = useAuthStore.getState().applicationType;
|
|
69
|
+
const res = await apiInstance.get(ENDPOINTS.chatService.getConversationGroups, {
|
|
70
|
+
params: {
|
|
71
|
+
page: pageParam,
|
|
72
|
+
pageSize,
|
|
73
|
+
applicationType,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
if ((_b = (_a = res === null || res === void 0 ? void 0 : res.data) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.length) {
|
|
77
|
+
void syncGroupConversationsToStore(res.data.data).catch((error) => {
|
|
78
|
+
console.error("syncGroupConversationsToStore", error);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return res.data;
|
|
82
|
+
},
|
|
83
|
+
getNextPageParam: (lastPage) => {
|
|
84
|
+
var _a, _b;
|
|
85
|
+
const dataLength = ((_a = lastPage === null || lastPage === void 0 ? void 0 : lastPage.data) === null || _a === void 0 ? void 0 : _a.length) || 0;
|
|
86
|
+
const currentPage = ((_b = lastPage === null || lastPage === void 0 ? void 0 : lastPage.pageable) === null || _b === void 0 ? void 0 : _b.pageNumber) || 1;
|
|
87
|
+
return dataLength < pageSize ? undefined : currentPage + 1;
|
|
88
|
+
},
|
|
89
|
+
enabled,
|
|
90
|
+
});
|
|
91
|
+
const groups = useMemo(() => {
|
|
92
|
+
if (!data)
|
|
93
|
+
return [];
|
|
94
|
+
// Render straight from the REST response — the list must show every
|
|
95
|
+
// group the API returned immediately, even before syncGroupConversationsToStore
|
|
96
|
+
// (async) has had a chance to populate conversationList. Use the synced
|
|
97
|
+
// ConversationItem when available (real latestMsg/unreadCount), else a
|
|
98
|
+
// fallback built from the REST fields so the row never disappears.
|
|
99
|
+
const allItems = data.pages.flatMap((page) => page.data);
|
|
100
|
+
return allItems.map((group) => {
|
|
101
|
+
const syncedConversation = conversationList === null || conversationList === void 0 ? void 0 : conversationList.find((conversation) => conversation.conversationID === group.conversationId);
|
|
102
|
+
return {
|
|
103
|
+
groupId: group.groupId,
|
|
104
|
+
conversationId: group.conversationId,
|
|
105
|
+
conversation: syncedConversation !== null && syncedConversation !== void 0 ? syncedConversation : buildFallbackConversation(group),
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
}, [data, conversationList]);
|
|
109
|
+
return {
|
|
110
|
+
groups,
|
|
111
|
+
fetchNextPage,
|
|
112
|
+
hasNextPage,
|
|
113
|
+
isFetchingNextPage,
|
|
114
|
+
isLoading,
|
|
115
|
+
};
|
|
116
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCreateGroup.d.ts","sourceRoot":"","sources":["../../../src/hooks/group/useCreateGroup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useCreateGroup.d.ts","sourceRoot":"","sources":["../../../src/hooks/group/useCreateGroup.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAIjE,UAAU,iBAAiB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAqCD,eAAO,MAAM,cAAc;6DAQ2B,iBAAiB;CAuDtE,CAAC"}
|
|
@@ -1,13 +1,41 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useCallback } from "react";
|
|
3
3
|
import { useRouter } from "next/navigation";
|
|
4
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
4
5
|
import { message } from "antd";
|
|
5
6
|
import { useTranslation } from "react-i18next";
|
|
6
7
|
import { GroupType, GroupVerificationType, GroupVisibility, SessionType } from "@droppii-org/wasm-client-sdk";
|
|
7
8
|
import { DChatSDK } from "../../constants/sdk";
|
|
8
9
|
import useAuthStore from "../../store/auth";
|
|
9
10
|
import { generateDefaultGroupName } from "../../utils/groupName";
|
|
11
|
+
import { QUERY_KEYS } from "../../services/query";
|
|
10
12
|
const DEFAULT_GROUP_FACE_URL = "/droppii.jpeg";
|
|
13
|
+
const GET_CONVERSATION_RETRY_COUNT = 4;
|
|
14
|
+
const GET_CONVERSATION_RETRY_DELAY_MS = 500;
|
|
15
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
16
|
+
// createGroup()'s own response succeeding doesn't mean the local WASM store
|
|
17
|
+
// has the new conversation yet — the WS event that inserts it locally can
|
|
18
|
+
// arrive after an unspecified delay (same reasoning as
|
|
19
|
+
// useGlobalEvent.ts's joinedGroupAddedHandler). Retry briefly instead of
|
|
20
|
+
// surfacing "create group failed" for a group that was, in fact, created.
|
|
21
|
+
const getOneConversationWithRetry = async (groupID) => {
|
|
22
|
+
let lastError;
|
|
23
|
+
for (let attempt = 0; attempt < GET_CONVERSATION_RETRY_COUNT; attempt++) {
|
|
24
|
+
try {
|
|
25
|
+
return await DChatSDK.getOneConversation({
|
|
26
|
+
sourceID: groupID,
|
|
27
|
+
sessionType: SessionType.Group,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
lastError = error;
|
|
32
|
+
if (attempt < GET_CONVERSATION_RETRY_COUNT - 1) {
|
|
33
|
+
await delay(GET_CONVERSATION_RETRY_DELAY_MS);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
throw lastError;
|
|
38
|
+
};
|
|
11
39
|
// Q4 (non-block): calling DChatSDK.createGroup() directly — the SDK's own
|
|
12
40
|
// eligibility check at submission time (if any) is whatever openIM does
|
|
13
41
|
// server-side; there's no separate BE revalidation endpoint yet. If members
|
|
@@ -17,6 +45,7 @@ export const useCreateGroup = () => {
|
|
|
17
45
|
const { t } = useTranslation("conversation-inbox");
|
|
18
46
|
const { t: tCommon } = useTranslation();
|
|
19
47
|
const router = useRouter();
|
|
48
|
+
const queryClient = useQueryClient();
|
|
20
49
|
const userID = useAuthStore((state) => state.userID);
|
|
21
50
|
const createGroup = useCallback(async ({ groupName, isPublic, members, faceURL }) => {
|
|
22
51
|
var _a, _b;
|
|
@@ -42,11 +71,11 @@ export const useCreateGroup = () => {
|
|
|
42
71
|
visibility: isPublic ? GroupVisibility.Public : GroupVisibility.Private,
|
|
43
72
|
},
|
|
44
73
|
});
|
|
45
|
-
const { data: conversation } = await
|
|
46
|
-
sourceID: group.groupID,
|
|
47
|
-
sessionType: SessionType.Group,
|
|
48
|
-
});
|
|
74
|
+
const { data: conversation } = await getOneConversationWithRetry(group.groupID);
|
|
49
75
|
message.success(t("create_group.create_group_success"));
|
|
76
|
+
void queryClient.invalidateQueries({
|
|
77
|
+
queryKey: [QUERY_KEYS.GET_CONVERSATION_GROUPS],
|
|
78
|
+
});
|
|
50
79
|
const searchParams = new URLSearchParams();
|
|
51
80
|
searchParams.set("threadId", conversation.conversationID);
|
|
52
81
|
router.push(`/chat?${searchParams.toString()}`);
|
|
@@ -57,6 +86,6 @@ export const useCreateGroup = () => {
|
|
|
57
86
|
message.error(errorMessage);
|
|
58
87
|
throw error;
|
|
59
88
|
}
|
|
60
|
-
}, [router, t, tCommon, userID]);
|
|
89
|
+
}, [router, t, tCommon, userID, queryClient]);
|
|
61
90
|
return { createGroup };
|
|
62
91
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EligibleAccountItem } from "./useEligibleAccounts";
|
|
2
|
-
export declare const useEligibleAccountsForGroup: (search: string,
|
|
2
|
+
export declare const useEligibleAccountsForGroup: (search: string, groupID: string, enabled?: boolean) => {
|
|
3
3
|
accounts: EligibleAccountItem[];
|
|
4
4
|
isLoading: boolean;
|
|
5
5
|
fetchNextPage: (options?: import("@tanstack/react-query").FetchNextPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<import("../../types/dto").BaseResponse<import("../../types/dto").IGroupCandidateResponse[]>, unknown>, Error>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEligibleAccountsForGroup.d.ts","sourceRoot":"","sources":["../../../src/hooks/group/useEligibleAccountsForGroup.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"useEligibleAccountsForGroup.d.ts","sourceRoot":"","sources":["../../../src/hooks/group/useEligibleAccountsForGroup.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAIjE,eAAO,MAAM,2BAA2B,GACtC,QAAQ,MAAM,EACd,SAAS,MAAM,EACf,iBAAc;;;;;;CA0Bf,CAAC"}
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import { useGetGroupCandidates } from "./useGetGroupCandidates";
|
|
3
|
-
//
|
|
4
|
-
// (
|
|
5
|
-
|
|
6
|
-
export const useEligibleAccountsForGroup = (search, existingMemberIDs, enabled = true) => {
|
|
3
|
+
// Server excludes accounts already in groupID from the candidate list
|
|
4
|
+
// (DROPPII-30248 BE update) — no client-side filtering needed anymore.
|
|
5
|
+
export const useEligibleAccountsForGroup = (search, groupID, enabled = true) => {
|
|
7
6
|
const { candidates, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = useGetGroupCandidates({
|
|
8
7
|
searchTerm: search.trim(),
|
|
8
|
+
groupID,
|
|
9
9
|
enabled,
|
|
10
10
|
});
|
|
11
|
-
const
|
|
12
|
-
const accounts = useMemo(() => candidates
|
|
13
|
-
.filter((candidate) => !existingMemberIDSet.has(candidate.id))
|
|
14
|
-
.map((candidate) => ({
|
|
11
|
+
const accounts = useMemo(() => candidates.map((candidate) => ({
|
|
15
12
|
userID: candidate.id,
|
|
16
13
|
displayName: candidate.fullName,
|
|
17
14
|
avatar: candidate.avatar,
|
|
18
|
-
})), [candidates
|
|
15
|
+
})), [candidates]);
|
|
19
16
|
return {
|
|
20
17
|
accounts,
|
|
21
18
|
isLoading,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { BaseResponse, IGroupCandidateResponse } from "../../types/dto";
|
|
2
2
|
interface UseGetGroupCandidatesParams {
|
|
3
3
|
searchTerm?: string;
|
|
4
|
+
groupID?: string;
|
|
4
5
|
enabled?: boolean;
|
|
5
6
|
}
|
|
6
|
-
export declare const useGetGroupCandidates: ({ searchTerm, enabled, }: UseGetGroupCandidatesParams) => {
|
|
7
|
+
export declare const useGetGroupCandidates: ({ searchTerm, groupID, enabled, }: UseGetGroupCandidatesParams) => {
|
|
7
8
|
candidates: IGroupCandidateResponse[];
|
|
8
9
|
fetchNextPage: (options?: import("@tanstack/react-query").FetchNextPageOptions) => Promise<import("@tanstack/react-query").InfiniteQueryObserverResult<import("@tanstack/react-query").InfiniteData<BaseResponse<IGroupCandidateResponse[]>, unknown>, Error>>;
|
|
9
10
|
hasNextPage: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGetGroupCandidates.d.ts","sourceRoot":"","sources":["../../../src/hooks/group/useGetGroupCandidates.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAIpE,UAAU,2BAA2B;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"useGetGroupCandidates.d.ts","sourceRoot":"","sources":["../../../src/hooks/group/useGetGroupCandidates.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAIpE,UAAU,2BAA2B;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IAIpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,eAAO,MAAM,qBAAqB,GAAI,mCAInC,2BAA2B;;;;;;CAsC7B,CAAC"}
|