@droppii-org/chat-sdk 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/components/common/ConfirmModal.d.ts +18 -0
  2. package/dist/components/common/ConfirmModal.d.ts.map +1 -0
  3. package/dist/components/common/ConfirmModal.js +15 -0
  4. package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
  5. package/dist/components/conversation/DeskConversationList.js +20 -14
  6. package/dist/components/group/AdminPermissionModal.d.ts.map +1 -1
  7. package/dist/components/group/AdminPermissionModal.js +37 -13
  8. package/dist/components/message/GroupMembersDrawer.d.ts.map +1 -1
  9. package/dist/components/message/GroupMembersDrawer.js +27 -14
  10. package/dist/components/message/MessageList.d.ts.map +1 -1
  11. package/dist/components/message/MessageList.js +17 -3
  12. package/dist/components/message/item/MessageBubble.d.ts +3 -2
  13. package/dist/components/message/item/MessageBubble.d.ts.map +1 -1
  14. package/dist/components/message/item/MessageBubble.js +8 -2
  15. package/dist/components/message/item/index.d.ts.map +1 -1
  16. package/dist/components/message/item/index.js +22 -7
  17. package/dist/components/thread/GroupThreadInfo.d.ts.map +1 -1
  18. package/dist/components/thread/GroupThreadInfo.js +43 -14
  19. package/dist/components/thread/SingleThreadInfo.d.ts.map +1 -1
  20. package/dist/components/thread/SingleThreadInfo.js +3 -2
  21. package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
  22. package/dist/hooks/global/useGlobalEvent.js +32 -3
  23. package/dist/hooks/group/useGroupPermission.js +1 -1
  24. package/dist/hooks/session/useConversationSessionState.d.ts.map +1 -1
  25. package/dist/hooks/session/useConversationSessionState.js +1 -0
  26. package/dist/layout/index.d.ts.map +1 -1
  27. package/dist/layout/index.js +5 -0
  28. package/dist/locales/vi/common.json +14 -8
  29. package/dist/store/conversation.d.ts.map +1 -1
  30. package/dist/store/conversation.js +16 -4
  31. package/dist/styles/global.css +1 -1
  32. package/dist/utils/common.d.ts +1 -0
  33. package/dist/utils/common.d.ts.map +1 -1
  34. package/dist/utils/common.js +18 -0
  35. package/dist/utils/fileValidation.d.ts.map +1 -1
  36. package/dist/utils/fileValidation.js +1 -1
  37. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
  import { Input, Modal, Switch, message } from "antd";
4
+ import ConfirmModal from "../../components/common/ConfirmModal";
4
5
  import { useTranslation } from "react-i18next";
5
6
  import { GroupMemberRole, GroupStatus, MessageReceiveOptType } from "@droppii-org/wasm-client-sdk";
6
7
  import { Icon } from "../../components/icon";
@@ -13,12 +14,12 @@ import { useToggleGroupSendMessage } from "../../hooks/group/useToggleGroupSendM
13
14
  import { useDismissGroup } from "../../hooks/group/useDismissGroup";
14
15
  import { useGroupPermission } from "../../hooks/group/useGroupPermission";
15
16
  import { useUploadFile } from "../../hooks/common/useUploadFile";
16
- import { validateFile } from "../../utils/fileValidation";
17
+ import { ACCEPTED_IMAGE_TYPES, MAX_IMAGE_SIZE_MB } from "../../utils/fileValidation";
17
18
  import AdminPermissionModal from "../../components/group/AdminPermissionModal";
18
19
  import MemberPermissionPanel from "../../components/group/MemberPermissionPanel";
19
20
  import GroupCompositeAvatar from "../../components/group/GroupCompositeAvatar";
20
21
  const GroupThreadInfo = ({ onClose, config }) => {
21
- var _a, _b, _c;
22
+ var _a, _b, _c, _d;
22
23
  const { t } = useTranslation();
23
24
  const conversationData = useConversationStore((state) => state.conversationData);
24
25
  const currentGroupInfo = useConversationStore((state) => state.currentGroupInfo);
@@ -31,6 +32,10 @@ const GroupThreadInfo = ({ onClose, config }) => {
31
32
  // per-permission.
32
33
  const isAdminOrOwner = (currentMemberInGroup === null || currentMemberInGroup === void 0 ? void 0 : currentMemberInGroup.roleLevel) === GroupMemberRole.Admin ||
33
34
  (currentMemberInGroup === null || currentMemberInGroup === void 0 ? void 0 : currentMemberInGroup.roleLevel) === GroupMemberRole.Owner;
35
+ // "Cho phép gửi tin nhắn" (group-wide mute) is owner-only per BR-001 —
36
+ // unlike other admin-manageable rows in this panel, admins may view but
37
+ // not change this toggle.
38
+ const isOwner = (currentMemberInGroup === null || currentMemberInGroup === void 0 ? void 0 : currentMemberInGroup.roleLevel) === GroupMemberRole.Owner;
34
39
  const [editingField, setEditingField] = useState(null);
35
40
  const [nameValue, setNameValue] = useState((_a = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName) !== null && _a !== void 0 ? _a : "");
36
41
  const [descriptionValue, setDescriptionValue] = useState((_b = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) !== null && _b !== void 0 ? _b : "");
@@ -48,7 +53,7 @@ const GroupThreadInfo = ({ onClose, config }) => {
48
53
  useEffect(() => {
49
54
  setAllowSendMessage((currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.status) !== GroupStatus.Muted);
50
55
  }, [currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.status]);
51
- const { mutate: updateGroupInfo } = useUpdateGroupInfo();
56
+ const { mutate: updateGroupInfo, isPending: isSavingGroupInfo } = useUpdateGroupInfo();
52
57
  const { mutate: uploadFile, isPending: isAvatarUploading } = useUploadFile();
53
58
  const { mutate: toggleSendMessage, isPending: isTogglingSendMessage } = useToggleGroupSendMessage();
54
59
  const { mutate: toggleNotification, isPending: isTogglingNotification } = useToggleConversationNotification();
@@ -65,12 +70,23 @@ const GroupThreadInfo = ({ onClose, config }) => {
65
70
  setDescriptionValue((_a = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) !== null && _a !== void 0 ? _a : "");
66
71
  setEditingField("description");
67
72
  };
73
+ const cancelEditName = () => {
74
+ var _a;
75
+ setNameValue((_a = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName) !== null && _a !== void 0 ? _a : "");
76
+ setEditingField(null);
77
+ };
68
78
  const saveName = () => {
79
+ if (isSavingGroupInfo)
80
+ return;
69
81
  const trimmed = nameValue.trim();
70
- setEditingField(null);
71
- if (!groupID || !trimmed || trimmed === (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName))
82
+ if (!groupID || !trimmed || trimmed === (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName)) {
83
+ setEditingField(null);
72
84
  return;
73
- updateGroupInfo({ groupID, groupName: trimmed }, { onError: () => message.error(t("update_group_info_failed")) });
85
+ }
86
+ updateGroupInfo({ groupID, groupName: trimmed }, {
87
+ onSuccess: () => setEditingField(null),
88
+ onError: () => message.error(t("update_group_info_failed")),
89
+ });
74
90
  };
75
91
  const cancelEditDescription = () => {
76
92
  var _a;
@@ -78,11 +94,17 @@ const GroupThreadInfo = ({ onClose, config }) => {
78
94
  setEditingField(null);
79
95
  };
80
96
  const saveDescription = () => {
97
+ if (isSavingGroupInfo)
98
+ return;
81
99
  const trimmed = descriptionValue.trim();
82
- setEditingField(null);
83
- if (!groupID || trimmed === (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction))
100
+ if (!groupID || trimmed === (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction)) {
101
+ setEditingField(null);
84
102
  return;
85
- updateGroupInfo({ groupID, introduction: trimmed }, { onError: () => message.error(t("update_group_info_failed")) });
103
+ }
104
+ updateGroupInfo({ groupID, introduction: trimmed }, {
105
+ onSuccess: () => setEditingField(null),
106
+ onError: () => message.error(t("update_group_info_failed")),
107
+ });
86
108
  };
87
109
  const handleAvatarClick = () => {
88
110
  var _a;
@@ -96,9 +118,13 @@ const GroupThreadInfo = ({ onClose, config }) => {
96
118
  event.target.value = "";
97
119
  if (!file || !groupID)
98
120
  return;
99
- const validation = validateFile(file, t);
100
- if (!validation.isValid) {
101
- message.error(validation.error);
121
+ if (!ACCEPTED_IMAGE_TYPES.includes(file.type)) {
122
+ message.error(t("avatar_invalid_format"));
123
+ return;
124
+ }
125
+ const fileSizeMB = file.size / 1024 / 1024;
126
+ if (fileSizeMB > MAX_IMAGE_SIZE_MB) {
127
+ message.error(t("avatar_size_exceeded", { maxSize: MAX_IMAGE_SIZE_MB }));
102
128
  return;
103
129
  }
104
130
  uploadFile(file, {
@@ -125,7 +151,7 @@ const GroupThreadInfo = ({ onClose, config }) => {
125
151
  toggleNotification({ conversationID, notify: checked });
126
152
  };
127
153
  const handleToggleSendMessage = (checked) => {
128
- if (!groupID)
154
+ if (!groupID || !isOwner)
129
155
  return;
130
156
  const previous = allowSendMessage;
131
157
  setAllowSendMessage(checked);
@@ -187,6 +213,9 @@ const GroupThreadInfo = ({ onClose, config }) => {
187
213
  if (!groupID) {
188
214
  return null;
189
215
  }
190
- return (_jsxs("div", { className: "h-full w-[330px] bg-white shadow-md flex flex-col relative overflow-hidden", children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-4 border-b border-gray-100", children: [_jsx("span", { className: "text-lg font-medium", children: t("thread_info_title") }), _jsx("button", { type: "button", className: "flex items-center justify-center w-8 h-8 rounded-full text-gray-500 hover:bg-gray-100", onClick: onClose, children: _jsx(Icon, { icon: "close-o", size: 20 }) })] }), _jsxs("div", { className: "flex-1 overflow-y-auto", children: [_jsxs("div", { className: "flex items-center gap-3 px-4 py-4", children: [_jsxs("div", { className: "relative shrink-0", children: [_jsx(GroupCompositeAvatar, { groupID: groupID, faceURL: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.faceURL, ownerUserID: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.ownerUserID, groupName: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName, size: 56 }), isAvatarUploading && (_jsx("span", { className: "absolute inset-0 flex items-center justify-center rounded-full bg-black/30", children: _jsx("span", { className: "h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent" }) })), permissions.updateGroupAvatar && (_jsxs(_Fragment, { children: [_jsx("input", { ref: avatarInputRef, type: "file", accept: "image/jpeg, image/png, image/jpg", className: "hidden", onChange: handleAvatarSelect }), _jsx("button", { type: "button", disabled: isAvatarUploading, onClick: handleAvatarClick, className: "absolute -bottom-1 -right-1 flex items-center justify-center w-5 h-5 rounded-full bg-black text-white", children: _jsx(Icon, { icon: "camera-o", size: 12 }) })] }))] }), _jsx("div", { className: "flex-1 min-w-0", children: editingField === "name" ? (_jsx(Input, { autoFocus: true, value: nameValue, onChange: (event) => setNameValue(event.target.value), onPressEnter: saveName, onBlur: saveName, maxLength: 100 })) : (_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("p", { className: "text-base font-medium truncate", children: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName }), permissions.updateGroupName && (_jsx(Icon, { icon: "edit-o", size: 16, className: "shrink-0 cursor-pointer text-gray-500", onClick: startEditName }))] })) })] }), _jsx("div", { className: "px-4 pb-4", children: editingField === "description" ? (_jsxs("div", { className: "flex flex-col gap-3 bg-[#f5f5f5] rounded-lg p-3", children: [_jsxs("span", { className: "flex items-center gap-2 text-sm font-medium text-gray-900", children: [_jsx(Icon, { icon: "info-circle-o", size: 18, className: "text-gray-500" }), t("description")] }), _jsx(Input.TextArea, { autoFocus: true, value: descriptionValue, onChange: (event) => setDescriptionValue(event.target.value), placeholder: t("enter_group_description"), autoSize: { minRows: 1, maxRows: 4 }, maxLength: 500, className: "bg-white" }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-full border border-primary-500 text-primary-500 text-sm font-medium", onClick: cancelEditDescription, children: t("cancel") }), _jsx("button", { type: "button", className: "px-4 py-1.5 rounded-full text-primary-500 text-sm font-medium disabled:text-gray-300", disabled: descriptionValue.trim() === ((_c = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) !== null && _c !== void 0 ? _c : ""), onClick: saveDescription, children: t("save") })] })] })) : (_jsxs("div", { className: "flex items-start justify-between gap-2 bg-[#f5f5f5] rounded-lg px-3 py-2.5", children: [_jsxs("div", { className: "flex items-start gap-2 flex-1 min-w-0", children: [_jsx(Icon, { icon: "info-circle-o", size: 18, className: "shrink-0 mt-0.5 text-gray-900" }), _jsxs("div", { className: "flex flex-col gap-0.5 min-w-0", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: t("description") }), _jsx("p", { className: "text-sm text-gray-400 truncate", children: (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) || t("no_description") })] })] }), permissions.updateGroupDescription && (_jsx(Icon, { icon: "edit-o", size: 16, className: "shrink-0 cursor-pointer text-gray-500", onClick: startEditDescription }))] })) }), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 rounded-lg overflow-hidden", children: [_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-o", size: 18 }), t("notification")] }), _jsx(Switch, { checked: notifyEnabled, onChange: handleToggleNotification, loading: isTogglingNotification })] }), (config === null || config === void 0 ? void 0 : config.showAllowSendMessage) && (_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-check-o", size: 18 }), t("allow_send_message")] }), _jsx(Switch, { checked: allowSendMessage, onChange: handleToggleSendMessage, loading: isTogglingSendMessage })] })), isAdminOrOwner && (config === null || config === void 0 ? void 0 : config.showMutedChatList) && (_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("muted-chat-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-off-o", size: 18 }), t("muted_chat_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })), _jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("block-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("block_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })] }), permissions.configGroupPermission && (_jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 rounded-lg overflow-hidden", children: [_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("member-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-o", size: 18 }), t("member_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }), _jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("admin-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-done-o", size: 18 }), t("admin_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })] })), _jsx("div", { className: "mx-4 mt-3 rounded-lg overflow-hidden bg-[#f5f5f5]", children: _jsxs("button", { type: "button", className: "w-full flex items-center gap-3 px-3 py-3 text-left text-sm text-gray-400 cursor-not-allowed", disabled: true, title: t("invite_via_link_pending"), children: [_jsx(Icon, { icon: "link-alt-o", size: 18 }), t("invite_via_link")] }) }), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 mb-4 rounded-lg overflow-hidden", children: [_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("delete"), children: [_jsx(Icon, { icon: "trash-o", size: 18 }), t("delete_conversation")] }), _jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("leave"), children: [_jsx(Icon, { icon: "logout-o", size: 18 }), t("leave_group")] }), isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("disband"), children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("disband_group")] }))] })] }), _jsx(Modal, { centered: true, open: confirmAction === "leave", onOk: handleConfirmLeave, onCancel: () => setConfirmAction(null), title: t("leave_group_confirm_title"), okText: t("leave_group"), cancelText: t("cancel"), okType: "danger", confirmLoading: isLeaving, getContainer: false, forceRender: true, children: _jsx("p", { children: t("leave_group_confirm_message") }) }), _jsx(Modal, { centered: true, open: confirmAction === "disband", onOk: handleConfirmDisband, onCancel: () => setConfirmAction(null), title: t("disband_group_confirm_title"), okText: t("disband_group"), cancelText: t("cancel"), okType: "danger", confirmLoading: isDismissing, getContainer: false, forceRender: true, children: _jsx("p", { children: t("disband_group_confirm_message") }) }), _jsx(Modal, { centered: true, open: confirmAction === "delete", onOk: handleConfirmDelete, onCancel: () => setConfirmAction(null), title: t("delete_conversation_confirm_title"), okText: t("delete_conversation"), cancelText: t("cancel"), okType: "danger", confirmLoading: isDeleting, getContainer: false, forceRender: true, children: _jsx("p", { children: t("delete_conversation_confirm_message") }) }), _jsx(MemberPermissionPanel, { open: activeRow === "member-permission", groupID: groupID, onClose: () => setActiveRow(null) }), _jsx(AdminPermissionModal, { open: activeRow === "admin-permission", groupID: groupID, onClose: () => setActiveRow(null), inline: true }), (activeRow === "muted-chat-list" || activeRow === "block-list") && (_jsx(Modal, { centered: true, open: true, footer: null, onCancel: () => setActiveRow(null), title: t(activeRow === "muted-chat-list" ? "muted_chat_list" : "block_list"), getContainer: false, forceRender: true, children: _jsx("p", { className: "text-sm text-gray-500", children: t("feature_coming_soon") }) }))] }));
216
+ return (_jsxs("div", { className: "h-full w-[330px] bg-white shadow-md flex flex-col relative overflow-hidden", children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-4 border-b border-gray-100", children: [_jsx("span", { className: "text-lg font-medium", children: t("thread_info_title") }), _jsx("button", { type: "button", className: "flex items-center justify-center w-8 h-8 rounded-full text-gray-500 hover:bg-gray-100", onClick: onClose, children: _jsx(Icon, { icon: "close-o", size: 20 }) })] }), _jsxs("div", { className: "flex-1 overflow-y-auto", children: [_jsxs("div", { className: "flex items-center gap-3 px-4 py-4", children: [_jsxs("div", { className: "relative shrink-0", children: [_jsx(GroupCompositeAvatar, { groupID: groupID, faceURL: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.faceURL, ownerUserID: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.ownerUserID, groupName: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName, size: 56 }), isAvatarUploading && (_jsx("span", { className: "absolute inset-0 flex items-center justify-center rounded-full bg-black/30", children: _jsx("span", { className: "h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent" }) })), permissions.updateGroupAvatar && (_jsxs(_Fragment, { children: [_jsx("input", { ref: avatarInputRef, type: "file", accept: ACCEPTED_IMAGE_TYPES.join(", "), className: "hidden", onChange: handleAvatarSelect }), _jsx("button", { type: "button", disabled: isAvatarUploading, onClick: handleAvatarClick, className: "absolute -bottom-1 -right-1 flex items-center justify-center w-5 h-5 rounded-full bg-black text-white", children: _jsx(Icon, { icon: "camera-o", size: 12 }) })] }))] }), _jsx("div", { className: "flex-1 min-w-0", children: editingField !== "name" && (_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("p", { className: "text-base font-medium truncate", children: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName }), permissions.updateGroupName && (_jsx(Icon, { icon: "edit-o", size: 16, className: "shrink-0 cursor-pointer text-gray-500", onClick: startEditName }))] })) })] }), editingField === "name" && (_jsx("div", { className: "px-4 pb-4", children: _jsxs("div", { className: "flex flex-col gap-3 bg-[#f5f5f5] rounded-lg p-3", children: [_jsx(Input, { autoFocus: true, value: nameValue, onChange: (event) => setNameValue(event.target.value), maxLength: 255, showCount: true, disabled: isSavingGroupInfo, className: "bg-white" }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg border border-primary-500 text-primary-500 text-sm font-medium disabled:border-gray-200 disabled:text-gray-300", disabled: isSavingGroupInfo, onClick: cancelEditName, children: t("cancel") }), _jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg bg-primary-500 text-white text-sm font-medium disabled:bg-gray-100 disabled:text-gray-300", disabled: isSavingGroupInfo ||
217
+ !nameValue.trim() ||
218
+ nameValue.trim() === ((_c = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName) !== null && _c !== void 0 ? _c : ""), onClick: saveName, children: t("save") })] })] }) })), _jsx("div", { className: "px-4 pb-4", children: editingField === "description" ? (_jsxs("div", { className: "flex flex-col gap-3 bg-[#f5f5f5] rounded-lg p-3", children: [_jsxs("span", { className: "flex items-center gap-2 text-sm font-medium text-gray-900", children: [_jsx(Icon, { icon: "info-circle-o", size: 18, className: "text-gray-500" }), t("description")] }), _jsx(Input.TextArea, { autoFocus: true, value: descriptionValue, onChange: (event) => setDescriptionValue(event.target.value), placeholder: t("enter_group_description"), autoSize: { minRows: 1, maxRows: 4 }, maxLength: 500, showCount: true, disabled: isSavingGroupInfo, className: "bg-white" }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg border border-primary-500 text-primary-500 text-sm font-medium disabled:border-gray-200 disabled:text-gray-300", disabled: isSavingGroupInfo, onClick: cancelEditDescription, children: t("cancel") }), _jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg bg-primary-500 text-white text-sm font-medium disabled:bg-gray-100 disabled:text-gray-300", disabled: isSavingGroupInfo ||
219
+ descriptionValue.trim() === ((_d = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) !== null && _d !== void 0 ? _d : ""), onClick: saveDescription, children: t("save") })] })] })) : (_jsxs("div", { className: "flex items-start justify-between gap-2 bg-[#f5f5f5] rounded-lg px-3 py-2.5", children: [_jsxs("div", { className: "flex items-start gap-2 flex-1 min-w-0", children: [_jsx(Icon, { icon: "info-circle-o", size: 18, className: "shrink-0 mt-0.5 text-gray-900" }), _jsxs("div", { className: "flex flex-col gap-0.5 min-w-0", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: t("description") }), _jsx("p", { className: "text-sm text-gray-400 truncate", children: (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) || t("no_description") })] })] }), permissions.updateGroupDescription && (_jsx(Icon, { icon: "edit-o", size: 16, className: "shrink-0 cursor-pointer text-gray-500", onClick: startEditDescription }))] })) }), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 rounded-lg overflow-hidden", children: [_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-o", size: 18 }), t("notification")] }), _jsx(Switch, { checked: notifyEnabled, onChange: handleToggleNotification, loading: isTogglingNotification })] }), (config === null || config === void 0 ? void 0 : config.showAllowSendMessage) && isOwner && (_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-check-o", size: 18 }), t("allow_send_message")] }), _jsx(Switch, { checked: allowSendMessage, onChange: handleToggleSendMessage, loading: isTogglingSendMessage })] })), isAdminOrOwner && (config === null || config === void 0 ? void 0 : config.showMutedChatList) && (_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("muted-chat-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-off-o", size: 18 }), t("muted_chat_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })), isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("block-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("block_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }))] }), permissions.configGroupPermission && (_jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 rounded-lg overflow-hidden", children: [_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("member-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-o", size: 18 }), t("member_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }), _jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("admin-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-done-o", size: 18 }), t("admin_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })] })), _jsx("div", { className: "mx-4 mt-3 rounded-lg overflow-hidden bg-[#f5f5f5]", children: _jsxs("button", { type: "button", className: "w-full flex items-center gap-3 px-3 py-3 text-left text-sm text-gray-400 cursor-not-allowed", disabled: true, title: t("invite_via_link_pending"), children: [_jsx(Icon, { icon: "link-alt-o", size: 18 }), t("invite_via_link")] }) }), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 mb-4 rounded-lg overflow-hidden", children: [isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("delete"), children: [_jsx(Icon, { icon: "trash-o", size: 18 }), t("delete_conversation")] })), _jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("leave"), children: [_jsx(Icon, { icon: "logout-o", size: 18 }), t("leave_group")] }), isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("disband"), children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("disband_group")] }))] })] }), _jsx(ConfirmModal, { open: confirmAction === "leave", onOk: handleConfirmLeave, onCancel: () => setConfirmAction(null), title: t("leave_group_confirm_title"), okText: t("leave_group"), cancelText: t("cancel"), okType: "danger", confirmLoading: isLeaving, children: _jsx("p", { children: t("leave_group_confirm_message") }) }), _jsx(ConfirmModal, { open: confirmAction === "disband", onOk: handleConfirmDisband, onCancel: () => setConfirmAction(null), title: t("disband_group_confirm_title"), okText: t("disband_group"), cancelText: t("cancel"), okType: "danger", confirmLoading: isDismissing, children: _jsx("p", { children: t("disband_group_confirm_message") }) }), _jsx(ConfirmModal, { open: confirmAction === "delete", onOk: handleConfirmDelete, onCancel: () => setConfirmAction(null), title: t("delete_conversation_confirm_title"), okText: t("delete_conversation"), cancelText: t("cancel"), okType: "danger", confirmLoading: isDeleting, children: _jsx("p", { children: t("delete_conversation_confirm_message") }) }), _jsx(MemberPermissionPanel, { open: activeRow === "member-permission", groupID: groupID, onClose: () => setActiveRow(null) }), _jsx(AdminPermissionModal, { open: activeRow === "admin-permission", groupID: groupID, onClose: () => setActiveRow(null), inline: true }), (activeRow === "muted-chat-list" || activeRow === "block-list") && (_jsx(Modal, { centered: true, open: true, footer: null, onCancel: () => setActiveRow(null), title: t(activeRow === "muted-chat-list" ? "muted_chat_list" : "block_list"), getContainer: false, forceRender: true, children: _jsx("p", { className: "text-sm text-gray-500", children: t("feature_coming_soon") }) }))] }));
191
220
  };
192
221
  export default GroupThreadInfo;
@@ -1 +1 @@
1
- {"version":3,"file":"SingleThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/SingleThreadInfo.tsx"],"names":[],"mappings":"AAYA,UAAU,qBAAqB;IAC7B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,gBAAgB,GAAI,aAAa,qBAAqB,mDAoJ3D,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"SingleThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/SingleThreadInfo.tsx"],"names":[],"mappings":"AAaA,UAAU,qBAAqB;IAC7B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,gBAAgB,GAAI,aAAa,qBAAqB,mDAiJ3D,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
- import { Avatar, Modal, Switch, message } from "antd";
3
+ import { Avatar, Switch, message } from "antd";
4
+ import ConfirmModal from "../../components/common/ConfirmModal";
4
5
  import { useTranslation } from "react-i18next";
5
6
  import { MessageReceiveOptType } from "@droppii-org/wasm-client-sdk";
6
7
  import { Icon } from "../../components/icon";
@@ -65,6 +66,6 @@ const SingleThreadInfo = ({ onClose }) => {
65
66
  if (!conversationID) {
66
67
  return null;
67
68
  }
68
- return (_jsxs("div", { className: "h-full w-[330px] bg-white shadow-md flex flex-col relative", children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-4 border-b border-gray-100", children: [_jsx("span", { className: "text-lg font-medium", children: t("thread_info_title") }), _jsx("button", { type: "button", className: "flex items-center justify-center w-8 h-8 rounded-full text-gray-500 hover:bg-gray-100", onClick: onClose, children: _jsx(Icon, { icon: "close-o", size: 20 }) })] }), _jsxs("div", { className: "flex-1 overflow-y-auto", children: [_jsxs("div", { className: "flex items-center gap-3 px-4 py-4", children: [_jsx(Avatar, { src: avatar, size: 56, className: "shrink-0", children: ((_a = displayName === null || displayName === void 0 ? void 0 : displayName.charAt) === null || _a === void 0 ? void 0 : _a.call(displayName, 0)) || "A" }), _jsxs("div", { className: "flex-1 min-w-0 flex items-center gap-2", children: [_jsx("p", { className: "text-base font-medium truncate", children: displayName }), isBot && (_jsx("span", { className: "shrink-0 rounded px-1.5 py-0.5 text-xs font-semibold bg-orange-50 text-orange-500", children: t("bot_tag") }))] })] }), _jsx("div", { className: "bg-[#f5f5f5] mx-4 rounded-lg overflow-hidden", children: _jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-o", size: 18 }), t("notification")] }), _jsx(Switch, { checked: notifyEnabled, onChange: handleToggleNotification, loading: isTogglingNotification })] }) }), _jsx("div", { className: "bg-[#f5f5f5] mx-4 mt-3 rounded-lg overflow-hidden", children: _jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm w-full", onClick: handleToggleBlock, disabled: isTogglingBlock, children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t(isBlocked ? "unblock_user" : "block_user")] }) }), _jsx("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 rounded-lg overflow-hidden", children: _jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmDelete(true), children: [_jsx(Icon, { icon: "trash-o", size: 18 }), t("delete_conversation")] }) })] }), _jsx(Modal, { centered: true, open: confirmDelete, onOk: handleConfirmDelete, onCancel: () => setConfirmDelete(false), title: t("delete_conversation_confirm_title"), okText: t("delete_conversation"), cancelText: t("cancel"), okType: "danger", confirmLoading: isDeleting, getContainer: false, forceRender: true, children: _jsx("p", { children: t("delete_conversation_confirm_message") }) })] }));
69
+ return (_jsxs("div", { className: "h-full w-[330px] bg-white shadow-md flex flex-col relative", children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-4 border-b border-gray-100", children: [_jsx("span", { className: "text-lg font-medium", children: t("thread_info_title") }), _jsx("button", { type: "button", className: "flex items-center justify-center w-8 h-8 rounded-full text-gray-500 hover:bg-gray-100", onClick: onClose, children: _jsx(Icon, { icon: "close-o", size: 20 }) })] }), _jsxs("div", { className: "flex-1 overflow-y-auto", children: [_jsxs("div", { className: "flex items-center gap-3 px-4 py-4", children: [_jsx(Avatar, { src: avatar, size: 56, className: "shrink-0", children: ((_a = displayName === null || displayName === void 0 ? void 0 : displayName.charAt) === null || _a === void 0 ? void 0 : _a.call(displayName, 0)) || "A" }), _jsxs("div", { className: "flex-1 min-w-0 flex items-center gap-2", children: [_jsx("p", { className: "text-base font-medium truncate", children: displayName }), isBot && (_jsx("span", { className: "shrink-0 rounded px-1.5 py-0.5 text-xs font-semibold bg-orange-50 text-orange-500", children: t("bot_tag") }))] })] }), _jsx("div", { className: "bg-[#f5f5f5] mx-4 rounded-lg overflow-hidden", children: _jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-o", size: 18 }), t("notification")] }), _jsx(Switch, { checked: notifyEnabled, onChange: handleToggleNotification, loading: isTogglingNotification })] }) }), _jsx("div", { className: "bg-[#f5f5f5] mx-4 mt-3 rounded-lg overflow-hidden", children: _jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm w-full", onClick: handleToggleBlock, disabled: isTogglingBlock, children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t(isBlocked ? "unblock_user" : "block_user")] }) }), _jsx("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 rounded-lg overflow-hidden", children: _jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmDelete(true), children: [_jsx(Icon, { icon: "trash-o", size: 18 }), t("delete_conversation")] }) })] }), _jsx(ConfirmModal, { open: confirmDelete, onOk: handleConfirmDelete, onCancel: () => setConfirmDelete(false), title: t("delete_conversation_confirm_title"), okText: t("delete_conversation"), cancelText: t("cancel"), okType: "danger", confirmLoading: isDeleting, children: _jsx("p", { children: t("delete_conversation_confirm_message") }) })] }));
69
70
  };
70
71
  export default SingleThreadInfo;
@@ -1 +1 @@
1
- {"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AAuCA,eAAO,MAAM,cAAc,YA8Y1B,CAAC"}
1
+ {"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AAwCA,eAAO,MAAM,cAAc,YA8a1B,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { useEffect } from "react";
2
+ import { usePathname, useRouter, useSearchParams } from "next/navigation";
2
3
  import { useLatest } from "ahooks";
3
4
  import { DChatSDK } from "../../constants/sdk";
4
5
  import { CbEvents, MessageType, SessionType, } from "@droppii-org/wasm-client-sdk";
@@ -22,11 +23,16 @@ export const useGlobalEvent = () => {
22
23
  const { user } = useChatContext();
23
24
  const userRef = useLatest(user);
24
25
  const queryClient = useQueryClient();
26
+ const router = useRouter();
27
+ const pathname = usePathname();
28
+ const searchParams = useSearchParams();
29
+ const searchParamsRef = useLatest(searchParams);
25
30
  const { updateConnectStatus, updateSyncStatus, getSelfUserInfo } = useChatContext();
26
31
  const updateConversationList = useConversationStore((state) => state.updateConversationList);
27
32
  const getConversationListByReq = useConversationStore((state) => state.getConversationListByReq);
28
33
  const getCurrentGroupInfoByReq = useConversationStore((state) => state.getCurrentGroupInfoByReq);
29
34
  const getSelfGroupPermissionByReq = useConversationStore((state) => state.getSelfGroupPermissionByReq);
35
+ const getCurrentMemberInGroupByReq = useConversationStore((state) => state.getCurrentMemberInGroupByReq);
30
36
  const { mutate: refetchChatToken } = useRefetchChatToken();
31
37
  const accessToken = useAuthStore((state) => state.accessToken);
32
38
  const chatToken = useAuthStore((state) => state.chatToken);
@@ -43,6 +49,9 @@ export const useGlobalEvent = () => {
43
49
  await Promise.all([
44
50
  getCurrentGroupInfoByReq(currentConversation.groupID),
45
51
  getSelfGroupPermissionByReq(currentConversation.groupID),
52
+ // roleLevel comes only from this call — without it, isAdminOrOwner
53
+ // checks (disband, mute toggle, etc.) fail closed.
54
+ getCurrentMemberInGroupByReq(currentConversation.groupID),
46
55
  ]);
47
56
  };
48
57
  const revokedMessageHandler = ({ data }) => {
@@ -210,10 +219,28 @@ export const useGlobalEvent = () => {
210
219
  const groupMemberInfoChangedHandler = () => {
211
220
  void refreshCurrentGroupState();
212
221
  };
213
- const groupDismissedHandler = ({ data }) => {
222
+ // Shared by OnGroupDismissed (group disbanded for everyone) and
223
+ // OnJoinedGroupDeleted (this client specifically removed from the group,
224
+ // e.g. kicked by an admin) — both leave the current user out of the group,
225
+ // so the local conversation state needs the same cleanup either way.
226
+ //
227
+ // Clearing the Zustand store alone isn't enough: both consumer screens
228
+ // (DeskConversationList for CRM, useActiveConversation for BIZ) re-derive
229
+ // the open conversation from the ?threadId= URL param on their own
230
+ // effects, so they'd immediately re-select the (now filtered-out) group
231
+ // unless the URL itself is cleared too — hence the router.replace below.
232
+ const leftGroupHandler = ({ data }) => {
233
+ var _a;
214
234
  const currentConversation = useConversationStore.getState().currentConversation;
215
235
  if ((currentConversation === null || currentConversation === void 0 ? void 0 : currentConversation.groupID) === data.groupID) {
216
236
  useConversationStore.getState().clearSelectedConversation();
237
+ const newSearchParams = new URLSearchParams((_a = searchParamsRef.current) === null || _a === void 0 ? void 0 : _a.toString());
238
+ newSearchParams.delete("threadId");
239
+ newSearchParams.delete("userId");
240
+ newSearchParams.delete("groupId");
241
+ newSearchParams.delete("botId");
242
+ const query = newSearchParams.toString();
243
+ router.replace(query ? `${pathname}?${query}` : pathname);
217
244
  }
218
245
  useConversationStore.setState((state) => ({
219
246
  conversationList: state.conversationList.filter((conversation) => conversation.groupID !== data.groupID),
@@ -258,7 +285,8 @@ export const useGlobalEvent = () => {
258
285
  // group info
259
286
  DChatSDK.on(CbEvents.OnGroupInfoChanged, groupInfoChangedHandler);
260
287
  DChatSDK.on(CbEvents.OnGroupMemberInfoChanged, groupMemberInfoChangedHandler);
261
- DChatSDK.on(CbEvents.OnGroupDismissed, groupDismissedHandler);
288
+ DChatSDK.on(CbEvents.OnGroupDismissed, leftGroupHandler);
289
+ DChatSDK.on(CbEvents.OnJoinedGroupDeleted, leftGroupHandler);
262
290
  // black list
263
291
  DChatSDK.on(CbEvents.OnBlackAdded, blackListChangedHandler);
264
292
  DChatSDK.on(CbEvents.OnBlackDeleted, blackListChangedHandler);
@@ -291,7 +319,8 @@ export const useGlobalEvent = () => {
291
319
  // group info
292
320
  DChatSDK.off(CbEvents.OnGroupInfoChanged, groupInfoChangedHandler);
293
321
  DChatSDK.off(CbEvents.OnGroupMemberInfoChanged, groupMemberInfoChangedHandler);
294
- DChatSDK.off(CbEvents.OnGroupDismissed, groupDismissedHandler);
322
+ DChatSDK.off(CbEvents.OnGroupDismissed, leftGroupHandler);
323
+ DChatSDK.off(CbEvents.OnJoinedGroupDeleted, leftGroupHandler);
295
324
  // black list
296
325
  DChatSDK.off(CbEvents.OnBlackAdded, blackListChangedHandler);
297
326
  DChatSDK.off(CbEvents.OnBlackDeleted, blackListChangedHandler);
@@ -48,7 +48,7 @@ const ROLE_DEFAULT_PERMISSIONS = {
48
48
  };
49
49
  export const permissionListToGroupPermissions = (permissionList, roleLevel) => {
50
50
  var _a;
51
- if (!permissionList) {
51
+ if (!permissionList || permissionList.length === 0) {
52
52
  return ((_a = ROLE_DEFAULT_PERMISSIONS[roleLevel]) !== null && _a !== void 0 ? _a : ROLE_DEFAULT_PERMISSIONS[GroupMemberRole.Normal]);
53
53
  }
54
54
  return Object.fromEntries(Object.entries(GROUP_PERMISSION_KEY).map(([field, permissionString]) => [
@@ -1 +1 @@
1
- {"version":3,"file":"useConversationSessionState.d.ts","sourceRoot":"","sources":["../../../src/hooks/session/useConversationSessionState.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAI3D,UAAU,iCAAiC;IACzC,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,2BAA2B,GAAI,gDAIzC,iCAAiC;;;;;CAmBnC,CAAC"}
1
+ {"version":3,"file":"useConversationSessionState.d.ts","sourceRoot":"","sources":["../../../src/hooks/session/useConversationSessionState.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAI3D,UAAU,iCAAiC;IACzC,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,2BAA2B,GAAI,gDAIzC,iCAAiC;;;;;CAoBnC,CAAC"}
@@ -7,6 +7,7 @@ export const useConversationSessionState = ({ conversationId, conversationType,
7
7
  const currentGroupId = isGroupConversation ? groupId : undefined;
8
8
  const { data: conversationSessions, refetch: refetchConversationSessions, } = useGetListSessionByConversation({
9
9
  conversationId,
10
+ enabled: !isGroupConversation,
10
11
  });
11
12
  const latestConversationSession = conversationSessions === null || conversationSessions === void 0 ? void 0 : conversationSessions[0];
12
13
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/layout/index.tsx"],"names":[],"mappings":"AAIA,OAAO,iBAAiB,CAAC;AAKzB,UAAU,eAAe;IACvB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AACD,QAAA,MAAM,UAAU,GAAI,cAAc,eAAe,4CAKhD,CAAC;AAEF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/layout/index.tsx"],"names":[],"mappings":"AAIA,OAAO,iBAAiB,CAAC;AAUzB,UAAU,eAAe;IACvB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AACD,QAAA,MAAM,UAAU,GAAI,cAAc,eAAe,4CAKhD,CAAC;AAEF,eAAe,UAAU,CAAC"}
@@ -3,7 +3,12 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useGlobalEvent } from "../hooks/global/useGlobalEvent";
4
4
  import dayjs from "dayjs";
5
5
  import "dayjs/locale/vi";
6
+ import utc from "dayjs/plugin/utc";
7
+ import timezone from "dayjs/plugin/timezone";
6
8
  import { useSyncUsersInfo } from "../hooks/user/useUsersInfo";
9
+ dayjs.extend(utc);
10
+ dayjs.extend(timezone);
11
+ dayjs.tz.setDefault("Asia/Ho_Chi_Minh");
7
12
  dayjs.locale("vi");
8
13
  const MainLayout = ({ children }) => {
9
14
  useGlobalEvent();
@@ -102,6 +102,8 @@
102
102
  "select_status": "Chọn trạng thái",
103
103
  "invalid_file_format": "{{fileName}} không đúng định dạng JPG, JPEG, PNG hoặc VIDEO",
104
104
  "file_size_exceeded": "{{fileName}} có kích thước tập tin vượt quá {{maxSize}}MB",
105
+ "avatar_invalid_format": "Vui lòng dùng file PNG, JPG, JPEG, WEBP",
106
+ "avatar_size_exceeded": "Vui lòng dùng ảnh dưới {{maxSize}}MB",
105
107
  "file_processing_error": "Đã xảy ra lỗi khi xử lý tệp",
106
108
  "invalid_document_format": "{{fileName}} không đúng định dạng (chỉ hỗ trợ PDF, DOC, DOCX)",
107
109
  "view_message": "Xem tin nhắn",
@@ -154,19 +156,22 @@
154
156
  "mute_member_success": "Đã tắt quyền gửi tin nhắn",
155
157
  "unmute_member_success": "Đã bật lại quyền gửi tin nhắn",
156
158
  "send_message": "Gửi tin nhắn",
157
- "transfer_owner": "Chuyển quyền chủ nhóm",
158
- "transfer_owner_success": "Đã chuyển quyền chủ nhóm",
159
- "transfer_owner_confirm_title": "Chuyển quyền chủ nhóm",
160
- "transfer_owner_confirm_message": "Bạn có chắc muốn chuyển quyền chủ nhóm cho {{name}}? Bạn sẽ trở thành quản trị viên.",
159
+ "transfer_owner": "Chuyển quyền sở hữu nhóm",
160
+ "transfer_owner_success": "Đã chuyển quyền sở hữu nhóm",
161
+ "transfer_owner_confirm_title": "Chuyển quyền sở hữu nhóm",
162
+ "transfer_owner_confirm_message": "Bạn có chắc muốn chuyển quyền sở hữu nhóm cho {{name}}?",
161
163
  "assign_as_admin": "Chỉ định làm quản trị viên",
162
164
  "assign_as_admin_success": "Đã chỉ định làm quản trị viên",
165
+ "assign_admin_confirm_title": "Cấp quyền quản trị viên",
166
+ "assign_admin_confirm_message": "Bạn chắc chắn muốn cấp quyền quản trị viên cho \"{{name}}\" ?",
167
+ "agree": "Đồng ý",
163
168
  "remove_from_admin": "Xoá khỏi quản trị viên",
164
169
  "remove_from_admin_success": "Đã xoá khỏi quản trị viên",
165
170
  "remove_from_group": "Xoá khỏi nhóm",
166
171
  "remove_member_success": "Đã xoá thành viên khỏi nhóm",
167
- "remove_member_failed": "Không thể xoá thành viên. Vui lòng thử lại",
168
- "remove_member_confirm_title": "Xoá thành viên khỏi nhóm",
169
- "remove_member_confirm_message": "Bạn chắc muốn xoá {{name}} khỏi nhóm?",
172
+ "remove_member_failed": "Xoá thành viên không thành công, vui lòng thử lại!",
173
+ "remove_member_confirm_title": "Xóa khỏi nhóm",
174
+ "remove_member_confirm_message": "Bạn chắc chắn muốn xóa \"{{name}}\" khỏi nhóm ?",
170
175
  "remove_and_block": "Xoá và chặn",
171
176
  "choose_emoji": "Chọn biểu tượng cảm xúc",
172
177
  "emoji_smileys": "Mặt cười",
@@ -191,6 +196,7 @@
191
196
  "team_messages": "Tin nhắn nhóm",
192
197
  "join_group_required": "Bạn phải là thành viên để gửi tin nhắn",
193
198
  "send_message_disabled": "Chỉ quản trị viên được phép gửi tin nhắn",
199
+ "send_message_disabled_group_mute": "Chỉ chủ nhóm và quản trị viên được phép gửi tin nhắn",
194
200
  "join_group": "Tham gia",
195
201
  "join_group_success": "Tham gia nhóm thành công",
196
202
  "join_group_failed": "Tham gia nhóm thất bại",
@@ -245,4 +251,4 @@
245
251
  "block_user_success": "Đã chặn người dùng",
246
252
  "unblock_user_success": "Đã bỏ chặn người dùng",
247
253
  "block_user_failed": "Thao tác chặn/bỏ chặn thất bại"
248
- }
254
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../src/store/conversation.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAM3C,QAAA,MAAM,oBAAoB,gFAgSvB,CAAC;AAEJ,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../src/store/conversation.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAM3C,QAAA,MAAM,oBAAoB,gFA4SvB,CAAC;AAEJ,eAAe,oBAAoB,CAAC"}
@@ -102,6 +102,9 @@ const useConversationStore = create((set, get) => ({
102
102
  await Promise.all([
103
103
  get().getCurrentGroupInfoByReq(conversation.groupID),
104
104
  get().getSelfGroupPermissionByReq(conversation.groupID),
105
+ // roleLevel comes only from this call — without it, role checks
106
+ // fail closed until an unrelated WS event happens to refresh it.
107
+ get().getCurrentMemberInGroupByReq(conversation.groupID),
105
108
  ]);
106
109
  }
107
110
  else {
@@ -152,13 +155,14 @@ const useConversationStore = create((set, get) => ({
152
155
  memberInfo = data[0];
153
156
  }
154
157
  catch (error) {
155
- set(() => ({ currentMemberInGroup: undefined }));
156
158
  console.error("Error fetching group members:", error);
157
159
  return;
158
160
  }
159
161
  const currentConversationData = get().conversationData;
160
162
  const shouldApplyMemberInfo = (currentConversationData === null || currentConversationData === void 0 ? void 0 : currentConversationData.groupID) === groupID &&
161
163
  isGroupSession(currentConversationData === null || currentConversationData === void 0 ? void 0 : currentConversationData.conversationType);
164
+ // Runs concurrently with getSelfGroupPermissionByReq — only apply to
165
+ // the conversation still open, so a stale response can't clobber it.
162
166
  if (!shouldApplyMemberInfo) {
163
167
  return;
164
168
  }
@@ -186,9 +190,17 @@ const useConversationStore = create((set, get) => ({
186
190
  if (!shouldApplyMemberInfo || !selfInfo) {
187
191
  return;
188
192
  }
189
- set((state) => ({
190
- currentMemberInGroup: Object.assign(Object.assign({}, state.currentMemberInGroup), { groupID, userID: selfInfo.userID, nickname: selfInfo.nickname, faceURL: selfInfo.faceURL, permissions: selfInfo.permissions }),
191
- }));
193
+ set((state) => {
194
+ var _a;
195
+ // Only merge onto a record for the SAME group, or a race can spread
196
+ // a stale conversation's roleLevel onto the new groupID.
197
+ const base = ((_a = state.currentMemberInGroup) === null || _a === void 0 ? void 0 : _a.groupID) === groupID
198
+ ? state.currentMemberInGroup
199
+ : undefined;
200
+ return {
201
+ currentMemberInGroup: Object.assign(Object.assign({}, base), { groupID, userID: selfInfo.userID, nickname: selfInfo.nickname, faceURL: selfInfo.faceURL, permissions: selfInfo.permissions }),
202
+ };
203
+ });
192
204
  },
193
205
  clearSelectedConversation: () => {
194
206
  set({