@droppii-org/chat-sdk 0.7.4 → 0.7.6

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 (90) hide show
  1. package/dist/assets/images/defaultAvatarGroup.png +0 -0
  2. package/dist/components/conversation/ConversationByGroupItem.d.ts.map +1 -1
  3. package/dist/components/conversation/ConversationByGroupItem.js +2 -2
  4. package/dist/components/conversation/ConversationBySessionItem.d.ts.map +1 -1
  5. package/dist/components/conversation/ConversationBySessionItem.js +3 -3
  6. package/dist/components/conversation/DeskBackgroundSyncStatus.d.ts +4 -0
  7. package/dist/components/conversation/DeskBackgroundSyncStatus.d.ts.map +1 -0
  8. package/dist/components/conversation/DeskBackgroundSyncStatus.js +17 -0
  9. package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
  10. package/dist/components/conversation/DeskConversationList.js +13 -3
  11. package/dist/components/conversation-detail/ConversationMessageList.d.ts.map +1 -1
  12. package/dist/components/conversation-detail/ConversationMessageList.js +3 -6
  13. package/dist/components/conversation-detail/PinnedMessagesPanel.d.ts.map +1 -1
  14. package/dist/components/conversation-detail/PinnedMessagesPanel.js +1 -1
  15. package/dist/components/conversation-detail/bot/BotMenuSheet.d.ts.map +1 -1
  16. package/dist/components/conversation-detail/bot/BotMenuSheet.js +1 -1
  17. package/dist/components/conversation-inbox/ConversationPeerAvatar.d.ts.map +1 -1
  18. package/dist/components/conversation-inbox/ConversationPeerAvatar.js +3 -1
  19. package/dist/components/conversation-inbox/contact-search/PublicGroupResultItem.d.ts.map +1 -1
  20. package/dist/components/conversation-inbox/contact-search/PublicGroupResultItem.js +3 -2
  21. package/dist/components/conversation-inbox/contact-search/constants.d.ts +0 -1
  22. package/dist/components/conversation-inbox/contact-search/constants.d.ts.map +1 -1
  23. package/dist/components/conversation-inbox/contact-search/constants.js +0 -2
  24. package/dist/components/conversation-inbox/contact-search/index.d.ts +0 -1
  25. package/dist/components/conversation-inbox/contact-search/index.d.ts.map +1 -1
  26. package/dist/components/conversation-inbox/contact-search/index.js +0 -1
  27. package/dist/components/group/GroupAvatar.d.ts +9 -0
  28. package/dist/components/group/GroupAvatar.d.ts.map +1 -0
  29. package/dist/components/group/GroupAvatar.js +13 -0
  30. package/dist/components/group/JoinGroupConfirmModal.d.ts.map +1 -1
  31. package/dist/components/group/JoinGroupConfirmModal.js +3 -2
  32. package/dist/components/message/MessageHeader.d.ts.map +1 -1
  33. package/dist/components/message/MessageHeader.js +2 -2
  34. package/dist/components/message/MessageList.d.ts.map +1 -1
  35. package/dist/components/message/MessageList.js +8 -6
  36. package/dist/components/search-conversation/item/SearchItemAsGroup.d.ts.map +1 -1
  37. package/dist/components/search-conversation/item/SearchItemAsGroup.js +2 -2
  38. package/dist/components/search-conversation/item/SearchItemAsMessage.d.ts.map +1 -1
  39. package/dist/components/search-conversation/item/SearchItemAsMessage.js +3 -1
  40. package/dist/components/thread/GroupInfoBodyCrm.d.ts +0 -2
  41. package/dist/components/thread/GroupInfoBodyCrm.d.ts.map +1 -1
  42. package/dist/components/thread/GroupInfoBodyCrm.js +2 -2
  43. package/dist/components/thread/GroupThreadInfo.d.ts.map +1 -1
  44. package/dist/components/thread/GroupThreadInfo.js +2 -2
  45. package/dist/constants/images.d.ts +1 -0
  46. package/dist/constants/images.d.ts.map +1 -1
  47. package/dist/constants/images.js +3 -0
  48. package/dist/hooks/conversation/useResolveConversation.d.ts.map +1 -1
  49. package/dist/hooks/conversation/useResolveConversation.js +2 -5
  50. package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
  51. package/dist/hooks/global/useGlobalEvent.js +2 -0
  52. package/dist/hooks/group/useCreateGroup.d.ts.map +1 -1
  53. package/dist/hooks/group/useCreateGroup.js +2 -3
  54. package/dist/hooks/init/useCanLoadChatData.d.ts +7 -0
  55. package/dist/hooks/init/useCanLoadChatData.d.ts.map +1 -0
  56. package/dist/hooks/init/useCanLoadChatData.js +15 -0
  57. package/dist/hooks/message/useCanPinMessage.d.ts +2 -0
  58. package/dist/hooks/message/useCanPinMessage.d.ts.map +1 -0
  59. package/dist/hooks/message/useCanPinMessage.js +29 -0
  60. package/dist/hooks/message/useMessage.d.ts.map +1 -1
  61. package/dist/hooks/message/useMessage.js +3 -5
  62. package/dist/hooks/message/useUnreadMessageIndicator.d.ts.map +1 -1
  63. package/dist/hooks/message/useUnreadMessageIndicator.js +3 -5
  64. package/dist/hooks/session/useAgentSupportThreadJoin.d.ts +1 -0
  65. package/dist/hooks/session/useAgentSupportThreadJoin.d.ts.map +1 -1
  66. package/dist/hooks/session/useAgentSupportThreadJoin.js +13 -2
  67. package/dist/hooks/session/useSessionLatestPreview.d.ts +4 -1
  68. package/dist/hooks/session/useSessionLatestPreview.d.ts.map +1 -1
  69. package/dist/hooks/session/useSessionLatestPreview.js +4 -1
  70. package/dist/locales/vi/boot.json +3 -2
  71. package/dist/locales/vi/common.json +1 -0
  72. package/dist/screens/conversation-detail/index.d.ts.map +1 -1
  73. package/dist/screens/conversation-detail/index.js +3 -1
  74. package/dist/screens/desk-message/DeskWorkspace.d.ts +3 -1
  75. package/dist/screens/desk-message/DeskWorkspace.d.ts.map +1 -1
  76. package/dist/screens/desk-message/DeskWorkspace.js +4 -4
  77. package/dist/screens/desk-message/index.d.ts +2 -0
  78. package/dist/screens/desk-message/index.d.ts.map +1 -1
  79. package/dist/screens/desk-message/index.js +26 -8
  80. package/dist/store/boot.d.ts +15 -0
  81. package/dist/store/boot.d.ts.map +1 -1
  82. package/dist/store/boot.js +20 -2
  83. package/dist/styles/global.css +1 -1
  84. package/package.json +1 -1
  85. package/dist/components/conversation-inbox/contact-search/PublicGroupAvatar.d.ts +0 -9
  86. package/dist/components/conversation-inbox/contact-search/PublicGroupAvatar.d.ts.map +0 -1
  87. package/dist/components/conversation-inbox/contact-search/PublicGroupAvatar.js +0 -19
  88. package/dist/components/group/GroupCompositeAvatar.d.ts +0 -11
  89. package/dist/components/group/GroupCompositeAvatar.d.ts.map +0 -1
  90. package/dist/components/group/GroupCompositeAvatar.js +0 -87
@@ -1 +1 @@
1
- {"version":3,"file":"GroupThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/GroupThreadInfo.tsx"],"names":[],"mappings":"AA0BA,MAAM,WAAW,qBAAqB;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AASD,QAAA,MAAM,eAAe,GAAI,qBAAqB,oBAAoB,mDAqrBjE,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"GroupThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/GroupThreadInfo.tsx"],"names":[],"mappings":"AA0BA,MAAM,WAAW,qBAAqB;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AASD,QAAA,MAAM,eAAe,GAAI,qBAAqB,oBAAoB,mDAirBjE,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -20,7 +20,7 @@ import AdminPermissionModal from "../../components/group/AdminPermissionModal";
20
20
  import MemberPermissionPanel from "../../components/group/MemberPermissionPanel";
21
21
  import DisableSendPermissionConfirm from "../../components/group/DisableSendPermissionConfirm";
22
22
  import InviteLinkPanel from "../../components/group/InviteLinkPanel";
23
- import GroupCompositeAvatar from "../../components/group/GroupCompositeAvatar";
23
+ import GroupAvatar from "../../components/group/GroupAvatar";
24
24
  import GroupPanelHeader from "../../components/group/GroupPanelHeader";
25
25
  import GroupInfoBodyCrm from "./GroupInfoBodyCrm";
26
26
  const GroupThreadInfo = ({ onClose, config }) => {
@@ -256,7 +256,7 @@ const GroupThreadInfo = ({ onClose, config }) => {
256
256
  if (!groupID) {
257
257
  return null;
258
258
  }
259
- return (_jsxs("div", { className: "h-full w-full bg-white flex flex-col relative overflow-hidden", "data-cp-name": "GroupThreadInfo", children: [isCrm ? (_jsxs(_Fragment, { children: [_jsx(GroupPanelHeader, { title: t("group_info_title"), onClose: onClose }), _jsx(GroupInfoBodyCrm, { groupID: groupID, groupName: groupDisplayName, faceURL: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.faceURL, ownerUserID: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.ownerUserID, introduction: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction, memberCount: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.memberCount, isPublic: (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.visibility) === GroupVisibility.Public, canEditAvatar: permissions.updateGroupAvatar, canEditName: permissions.updateGroupName, canEditDescription: permissions.updateGroupDescription, canConfigPermissions: permissions.configGroupPermission, showInviteLink: isInviteLinkFeatureEnabled && !isViewOnlyMember, showAllowSend: Boolean(config === null || config === void 0 ? void 0 : config.showAllowSendMessage) && isOwner, isOwner: isOwner, isAdminOrOwner: isAdminOrOwner, isViewOnlyMember: isViewOnlyMember, notifyEnabled: notifyEnabled, isTogglingNotification: isTogglingNotification, allowSendMessage: allowSendMessage, isTogglingSendMessage: isTogglingSendMessage, isAvatarUploading: isAvatarUploading, isSaving: isSavingGroupInfo, editingField: editingField, nameValue: nameValue, descriptionValue: descriptionValue, avatarInputRef: avatarInputRef, onNameChange: setNameValue, onDescriptionChange: setDescriptionValue, onStartEdit: (field) => (field === "name" ? startEditName() : startEditDescription()), onCancelEdit: (field) => (field === "name" ? cancelEditName() : cancelEditDescription()), onSaveEdit: (field) => (field === "name" ? saveName() : saveDescription()), onAvatarClick: handleAvatarClick, onAvatarSelect: handleAvatarSelect, onToggleNotification: handleToggleNotification, onToggleSendMessage: handleToggleSendMessage, onOpenRow: setActiveRow, onConfirm: setConfirmAction })] })) : (_jsxs(_Fragment, { 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: groupDisplayName, 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: groupDisplayName }), 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-[var(--dc-color-primary-hover)] text-[var(--dc-color-primary-hover)] 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-[var(--dc-color-primary-hover)] text-white text-sm font-medium disabled:bg-gray-100 disabled:text-gray-300", disabled: isSavingGroupInfo ||
259
+ return (_jsxs("div", { className: "h-full w-full bg-white flex flex-col relative overflow-hidden", "data-cp-name": "GroupThreadInfo", children: [isCrm ? (_jsxs(_Fragment, { children: [_jsx(GroupPanelHeader, { title: t("group_info_title"), onClose: onClose }), _jsx(GroupInfoBodyCrm, { groupName: groupDisplayName, faceURL: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.faceURL, introduction: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction, memberCount: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.memberCount, isPublic: (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.visibility) === GroupVisibility.Public, canEditAvatar: permissions.updateGroupAvatar, canEditName: permissions.updateGroupName, canEditDescription: permissions.updateGroupDescription, canConfigPermissions: permissions.configGroupPermission, showInviteLink: isInviteLinkFeatureEnabled && !isViewOnlyMember, showAllowSend: Boolean(config === null || config === void 0 ? void 0 : config.showAllowSendMessage) && isOwner, isOwner: isOwner, isAdminOrOwner: isAdminOrOwner, isViewOnlyMember: isViewOnlyMember, notifyEnabled: notifyEnabled, isTogglingNotification: isTogglingNotification, allowSendMessage: allowSendMessage, isTogglingSendMessage: isTogglingSendMessage, isAvatarUploading: isAvatarUploading, isSaving: isSavingGroupInfo, editingField: editingField, nameValue: nameValue, descriptionValue: descriptionValue, avatarInputRef: avatarInputRef, onNameChange: setNameValue, onDescriptionChange: setDescriptionValue, onStartEdit: (field) => (field === "name" ? startEditName() : startEditDescription()), onCancelEdit: (field) => (field === "name" ? cancelEditName() : cancelEditDescription()), onSaveEdit: (field) => (field === "name" ? saveName() : saveDescription()), onAvatarClick: handleAvatarClick, onAvatarSelect: handleAvatarSelect, onToggleNotification: handleToggleNotification, onToggleSendMessage: handleToggleSendMessage, onOpenRow: setActiveRow, onConfirm: setConfirmAction })] })) : (_jsxs(_Fragment, { 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(GroupAvatar, { faceURL: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.faceURL, groupName: groupDisplayName, 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: groupDisplayName }), 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-[var(--dc-color-primary-hover)] text-[var(--dc-color-primary-hover)] 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-[var(--dc-color-primary-hover)] text-white text-sm font-medium disabled:bg-gray-100 disabled:text-gray-300", disabled: isSavingGroupInfo ||
260
260
  !nameValue.trim() ||
261
261
  nameValue.trim() === ((_c = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName) !== null && _c !== void 0 ? _c : ""), onClick: saveName, children: t("save") })] })] }) })), _jsx("div", { className: "px-4 pb-4", children: editingField === "description" ? (_jsxs("div", { className: "flex flex-col gap-3 bg-[#f5f5f5] rounded-lg p-3", children: [_jsxs("span", { className: "flex items-center gap-2 text-sm font-medium text-gray-900", children: [_jsx(Icon, { icon: "info-circle-o", size: 18, className: "text-gray-500" }), t("description")] }), _jsx(Input.TextArea, { ref: descriptionInputRef, value: descriptionValue, onChange: (event) => setDescriptionValue(event.target.value), placeholder: t("enter_group_description"), autoSize: { minRows: 1, maxRows: 4 }, maxLength: 500, disabled: isSavingGroupInfo, className: "bg-white" }), _jsxs("span", { className: "self-end text-xs text-gray-400", children: [descriptionValue.length, " / 500"] }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg border border-[var(--dc-color-primary-hover)] text-[var(--dc-color-primary-hover)] 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-[var(--dc-color-primary-hover)] text-white text-sm font-medium disabled:bg-gray-100 disabled:text-gray-300", disabled: isSavingGroupInfo ||
262
262
  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" })] })] })), isInviteLinkFeatureEnabled && !isViewOnlyMember && (_jsx("div", { className: "mx-4 mt-3 rounded-lg overflow-hidden bg-[#f5f5f5]", children: _jsxs("button", { type: "button", className: "w-full flex items-center justify-between px-3 py-3 text-left text-sm", onClick: () => setActiveRow("invite-link"), children: [_jsxs("span", { className: "flex items-center gap-3", children: [_jsx(Icon, { icon: "link-alt-o", size: 18 }), t("invite_via_link")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }) })), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 mb-4 rounded-lg overflow-hidden", children: [isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("delete"), children: [_jsx(Icon, { icon: "trash-o", size: 18 }), t("delete_conversation")] })), !isViewOnlyMember && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("leave"), children: [_jsx(Icon, { icon: "logout-o", size: 18 }), t("leave_group")] })), isOwner && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("disband"), children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("disband_group")] }))] })] })] })), _jsx(ConfirmModal, { open: confirmAction === "leave", onOk: handleConfirmLeave, onCancel: () => setConfirmAction(null), title: t("leave_group_confirm_title"), okText: t("leave_group"), cancelText: t(isCrm ? "action_cancel" : "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(isCrm ? "action_cancel" : "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(isCrm ? "action_cancel" : "cancel"), okType: "danger", confirmLoading: isDeleting, children: _jsx("p", { children: t("delete_conversation_confirm_message") }) }), _jsx(DisableSendPermissionConfirm, { open: confirmAction === "mute-all", title: t("disable_send_permission_title_all_members"), onConfirm: () => {
@@ -5,5 +5,6 @@ export declare const images: {
5
5
  muteNotificationWarning: string;
6
6
  searchEmpty: string;
7
7
  stickerInvalid: string;
8
+ defaultAvatarGroup: string;
8
9
  };
9
10
  //# sourceMappingURL=images.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/constants/images.ts"],"names":[],"mappings":"AAwBA,eAAO,MAAM,MAAM;;;;;;;CAOlB,CAAC"}
1
+ {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/constants/images.ts"],"names":[],"mappings":"AA8BA,eAAO,MAAM,MAAM;;;;;;;;CAQlB,CAAC"}
@@ -5,6 +5,8 @@ const emptyResources = new URL("../assets/images/emptyResources.png", import.met
5
5
  const muteNotificationWarning = new URL("../assets/images/muteNotificationWarning.png", import.meta.url).href;
6
6
  const searchEmpty = new URL("../assets/images/searchEmpty.png", import.meta.url).href;
7
7
  const stickerInvalid = new URL("../assets/images/stickerInvalid.png", import.meta.url).href;
8
+ // Default avatar for a group without a photo (DROPPII-30928).
9
+ const defaultAvatarGroup = new URL("../assets/images/defaultAvatarGroup.png", import.meta.url).href;
8
10
  export const images = {
9
11
  conversationBg,
10
12
  imageFailed,
@@ -12,4 +14,5 @@ export const images = {
12
14
  muteNotificationWarning,
13
15
  searchEmpty,
14
16
  stickerInvalid,
17
+ defaultAvatarGroup,
15
18
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useResolveConversation.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useResolveConversation.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,gBAAgB,EAIjB,MAAM,8BAA8B,CAAC;AAgBtC,MAAM,WAAW,4BAA4B;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsDD,eAAO,MAAM,wBAAwB,GACnC,OAAO,MAAM,KACZ,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAWjC,CAAC;AA+DF,eAAO,MAAM,sBAAsB,GAAI,wDAMpC,4BAA4B;;;CA8J9B,CAAC"}
1
+ {"version":3,"file":"useResolveConversation.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useResolveConversation.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,gBAAgB,EAIjB,MAAM,8BAA8B,CAAC;AAetC,MAAM,WAAW,4BAA4B;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsDD,eAAO,MAAM,wBAAwB,GACnC,OAAO,MAAM,KACZ,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAWjC,CAAC;AA+DF,eAAO,MAAM,sBAAsB,GAAI,wDAMpC,4BAA4B;;;CA2J9B,CAAC"}
@@ -4,8 +4,7 @@ import { GroupStatus, GroupVisibility, SessionType, } from "@droppii-org/wasm-cl
4
4
  import { DChatSDK } from "../../constants/sdk";
5
5
  import { apiInstance } from "../../services/api";
6
6
  import { ENDPOINTS } from "../../services/routes";
7
- import { ConnectStatus, SyncStatus } from "../../types/chat";
8
- import { useChatContext } from "../../context/ChatContext";
7
+ import { useCanLoadChatData } from "../../hooks/init/useCanLoadChatData";
9
8
  import useConversationStore from "../../store/conversation";
10
9
  import { getGroupIdFromConversationId, getPeerUserIdFromConversationId, resolveConversationBotId, } from "../../utils/conversation";
11
10
  import useAuthStore from "../../store/auth";
@@ -119,9 +118,7 @@ export const useResolveConversation = ({ conversationId, userId, groupId, botId,
119
118
  // conversationData looks truthy; only a rename to something else (a newer
120
119
  // resolve superseding this one) or an explicit reject below clears it.
121
120
  const pendingTargetRef = useRef(null);
122
- const { connectStatus, syncStatus } = useChatContext();
123
- const isSdkReady = connectStatus === ConnectStatus.Connected &&
124
- syncStatus !== SyncStatus.Loading;
121
+ const isSdkReady = useCanLoadChatData();
125
122
  const conversationData = useConversationStore((state) => state.conversationData);
126
123
  const removedFromGroupModalOpen = useConversationStore((state) => state.removedFromGroupModalOpen);
127
124
  const setConversationData = useConversationStore((state) => state.setConversationData);
@@ -1 +1 @@
1
- {"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AAiDA,eAAO,MAAM,cAAc,YAmnB1B,CAAC"}
1
+ {"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AAiDA,eAAO,MAAM,cAAc,YAqnB1B,CAAC"}
@@ -234,7 +234,9 @@ export const useGlobalEvent = () => {
234
234
  }
235
235
  updateConnectStatus(ConnectStatus.Disconnected);
236
236
  };
237
+ // data is `reinstalled`: true when this device starts from an empty local DB.
237
238
  const syncStartHandler = ({ data }) => {
239
+ useBootStore.getState().markSyncStarted(data === true || data === "true");
238
240
  updateSyncStatus(SyncStatus.Loading);
239
241
  };
240
242
  const syncProgressHandler = ({ data }) => {
@@ -1 +1 @@
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;AAEjE,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;CA4DtE,CAAC"}
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;AAEjE,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;CA2DtE,CAAC"}
@@ -56,9 +56,8 @@ export const useCreateGroup = () => {
56
56
  firstMemberName: firstMember === null || firstMember === void 0 ? void 0 : firstMember.displayName,
57
57
  remainingCount: restMembers.length,
58
58
  });
59
- // AC-Default2: no picked avatar → leave faceURL empty. UI renders a
60
- // composite of the owner's + first-joined member's avatars instead of
61
- // a static placeholder (see GroupCompositeAvatar).
59
+ // No picked avatar → leave faceURL empty; GroupAvatar shows the default
60
+ // group image (DROPPII-30928).
62
61
  const resolvedFaceURL = faceURL || "";
63
62
  try {
64
63
  const { data: group } = await DChatSDK.createGroup({
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Whether threads can be opened and their history read: connected and not mid-sync. A fresh
3
+ * device's first sync counts as done once its conversations are stored, since history then
4
+ * loads on demand while the latest message of each conversation is still being pulled.
5
+ */
6
+ export declare const useCanLoadChatData: () => boolean;
7
+ //# sourceMappingURL=useCanLoadChatData.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCanLoadChatData.d.ts","sourceRoot":"","sources":["../../../src/hooks/init/useCanLoadChatData.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,eAO9B,CAAC"}
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { useChatContext } from "../../context/ChatContext";
3
+ import useBootStore, { selectIsSyncingInBackground } from "../../store/boot";
4
+ import { ConnectStatus, SyncStatus } from "../../types/chat";
5
+ /**
6
+ * Whether threads can be opened and their history read: connected and not mid-sync. A fresh
7
+ * device's first sync counts as done once its conversations are stored, since history then
8
+ * loads on demand while the latest message of each conversation is still being pulled.
9
+ */
10
+ export const useCanLoadChatData = () => {
11
+ const { connectStatus, syncStatus } = useChatContext();
12
+ const isSyncingInBackground = useBootStore(selectIsSyncingInBackground);
13
+ return (connectStatus === ConnectStatus.Connected &&
14
+ (syncStatus !== SyncStatus.Loading || isSyncingInBackground));
15
+ };
@@ -0,0 +1,2 @@
1
+ export declare const useCanPinMessage: () => boolean;
2
+ //# sourceMappingURL=useCanPinMessage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCanPinMessage.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/useCanPinMessage.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,gBAAgB,eA2B5B,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { useChatContext } from "../../context/ChatContext";
2
+ import { useGroupPermission } from "../../hooks/group/useGroupPermission";
3
+ import { isSupportThreadJoinedHere } from "../../hooks/session/useAgentSupportThreadJoin";
4
+ import { useIsJoinedGroup } from "../../hooks/session/useIsJoinedGroup";
5
+ import useAuthStore from "../../store/auth";
6
+ import useConversationStore from "../../store/conversation";
7
+ import { isGroupChat, isSupportSessionAgentView } from "../../utils/conversation";
8
+ // Pin rule of DChatConversationDetail, shared by its toolbox and pinned panel.
9
+ // Desk's MessageList keeps its own rule, which also checks group membership.
10
+ export const useCanPinMessage = () => {
11
+ const { user, allowPinMessageWithoutPermission } = useChatContext();
12
+ const authUserID = useAuthStore((state) => state.userID);
13
+ const conversation = useConversationStore((state) => state.conversationData);
14
+ const permissions = useGroupPermission(conversation === null || conversation === void 0 ? void 0 : conversation.groupID);
15
+ const isGroup = conversation ? isGroupChat(conversation) : false;
16
+ // An agent on someone else's support thread manages pins only once joined.
17
+ const isAgentSupportThread = !!conversation &&
18
+ !isGroup &&
19
+ isSupportSessionAgentView(conversation, (user === null || user === void 0 ? void 0 : user.userID) || authUserID);
20
+ const { isJoined } = useIsJoinedGroup(isAgentSupportThread ? conversation === null || conversation === void 0 ? void 0 : conversation.groupID : undefined);
21
+ if (isAgentSupportThread) {
22
+ return (!conversation.groupID ||
23
+ isJoined === true ||
24
+ isSupportThreadJoinedHere(conversation.conversationID));
25
+ }
26
+ // Some hosts run pin without their backend returning PIN_MESSAGE yet, so
27
+ // the permission check would hide a feature that is live for them.
28
+ return (!isGroup || permissions.pinMessage || !!allowPinMessageWithoutPermission);
29
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"useMessage.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/useMessage.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,WAAW,EACX,WAAW,EAEZ,MAAM,8BAA8B,CAAC;AAmBtC,eAAO,MAAM,kBAAkB,eAkB9B,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,SAAS,WAAW,EACpB,OAAO,OAAO,EACd,eAAe;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5E,eAAe,MAAM,KACpB,OA4BF,CAAC;AAEF,eAAO,MAAM,UAAU,GACrB,iBAAiB,MAAM,EACvB,oBAAoB,MAAM,EAC1B,iBAAc,EACd,uBAAqB,EAGrB,yBAAyB,MAAM,IAAI;;;;;qBAgBd,WAAW,EAAE;;;;;;qBAAb,WAAW,EAAE;;;;;;;CAoZnC,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,SAAS,WAAW,SACpB,CAAC;AAChC,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,SACpB,CAAC;AAClC,eAAO,MAAM,gBAAgB,GAAI,aAAa,MAAM,SACf,CAAC;AAEtC,eAAO,MAAM,kBAAkB,GAC7B,aAAa,WAAW,EAAE,EAC1B,SAAS,WAAW,EACpB,WAAW,MAAM,GAAG,MAAM,EAC1B,YAAY,CAAC,SAAS,EAAE,WAAW,KAAK,OAAO,KAC9C,WAAW,GAAG,SAqBhB,CAAC;AAMF,eAAO,MAAM,oBAAoB,GAC/B,aAAa,WAAW,EAAE,EAC1B,SAAS,WAAW,EACpB,WAAW,MAAM,GAAG,MAAM,KACzB,WAAW,GAAG,SAQhB,CAAC;AAmBF,eAAO,MAAM,mBAAmB,GAC9B,SAAS,WAAW,EAAE,EACtB,SAAS,WAAW,EAAE,kBAgBvB,CAAC"}
1
+ {"version":3,"file":"useMessage.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/useMessage.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,WAAW,EACX,WAAW,EAEZ,MAAM,8BAA8B,CAAC;AAkBtC,eAAO,MAAM,kBAAkB,eAkB9B,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,SAAS,WAAW,EACpB,OAAO,OAAO,EACd,eAAe;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5E,eAAe,MAAM,KACpB,OA4BF,CAAC;AAEF,eAAO,MAAM,UAAU,GACrB,iBAAiB,MAAM,EACvB,oBAAoB,MAAM,EAC1B,iBAAc,EACd,uBAAqB,EAGrB,yBAAyB,MAAM,IAAI;;;;;qBAed,WAAW,EAAE;;;;;;qBAAb,WAAW,EAAE;;;;;;;CAoZnC,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,SAAS,WAAW,SACpB,CAAC;AAChC,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,SACpB,CAAC;AAClC,eAAO,MAAM,gBAAgB,GAAI,aAAa,MAAM,SACf,CAAC;AAEtC,eAAO,MAAM,kBAAkB,GAC7B,aAAa,WAAW,EAAE,EAC1B,SAAS,WAAW,EACpB,WAAW,MAAM,GAAG,MAAM,EAC1B,YAAY,CAAC,SAAS,EAAE,WAAW,KAAK,OAAO,KAC9C,WAAW,GAAG,SAqBhB,CAAC;AAMF,eAAO,MAAM,oBAAoB,GAC/B,aAAa,WAAW,EAAE,EAC1B,SAAS,WAAW,EACpB,WAAW,MAAM,GAAG,MAAM,KACzB,WAAW,GAAG,SAQhB,CAAC;AAmBF,eAAO,MAAM,mBAAmB,GAC9B,SAAS,WAAW,EAAE,EACtB,SAAS,WAAW,EAAE,kBAgBvB,CAAC"}
@@ -7,9 +7,8 @@ import { useTranslation } from "react-i18next";
7
7
  import { useLatest, useRequest } from "ahooks";
8
8
  import emitter, { emit } from "../../utils/events";
9
9
  import isEmpty from "lodash/isEmpty";
10
- import { useChatContext } from "../../context/ChatContext";
11
10
  import useAuthStore from "../../store/auth";
12
- import { ConnectStatus, SyncStatus } from "../../types/chat";
11
+ import { useCanLoadChatData } from "../../hooks/init/useCanLoadChatData";
13
12
  import { GROUP_NOTIFICATION_MESSAGE_TYPES, isGroupNotificationMessageType, isSessionEventMessage, isSystemLogMessageType } from "../../utils/messageLog";
14
13
  import { isEventCustomMessage, isRenderableCustomMessage, } from "../../utils/customMessage";
15
14
  import { isGroupConversation } from "../../utils/imCommon";
@@ -60,12 +59,11 @@ export const useMessage = (conversationId, searchClientMsgID, enabled = true, us
60
59
  // Host-supplied, threaded down from the screen props the same way
61
60
  // onCopyMessageLink is. Omit it and the SDK raises its own antd toast.
62
61
  onSearchMessageRevoked) => {
63
- const { connectStatus, syncStatus } = useChatContext();
62
+ const canLoadChatData = useCanLoadChatData();
64
63
  const { t } = useTranslation();
65
64
  const userID = useAuthStore((state) => state.userID);
66
65
  const canLoad = enabled &&
67
- connectStatus === ConnectStatus.Connected &&
68
- syncStatus !== SyncStatus.Loading &&
66
+ canLoadChatData &&
69
67
  !!conversationId &&
70
68
  !!userID;
71
69
  const [loadState, setLoadState] = useState({
@@ -1 +1 @@
1
- {"version":3,"file":"useUnreadMessageIndicator.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/useUnreadMessageIndicator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAUhE,eAAO,MAAM,8BAA8B,OAAO,CAAC;AAoEnD,UAAU,+BAA+B;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,WAAW,EAAE,CAAC;CAC5B;AAED;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GAAI,qDAIvC,+BAA+B;;;;;;CA8FjC,CAAC"}
1
+ {"version":3,"file":"useUnreadMessageIndicator.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/useUnreadMessageIndicator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAShE,eAAO,MAAM,8BAA8B,OAAO,CAAC;AAoEnD,UAAU,+BAA+B;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,WAAW,EAAE,CAAC;CAC5B;AAED;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GAAI,qDAIvC,+BAA+B;;;;;;CA6FjC,CAAC"}
@@ -2,10 +2,9 @@
2
2
  import { useCallback, useEffect, useRef, useState } from "react";
3
3
  import { useLatest } from "ahooks";
4
4
  import { DChatSDK } from "../../constants/sdk";
5
- import { useChatContext } from "../../context/ChatContext";
6
5
  import useAuthStore from "../../store/auth";
7
6
  import useConversationStore from "../../store/conversation";
8
- import { ConnectStatus, SyncStatus } from "../../types/chat";
7
+ import { useCanLoadChatData } from "../../hooks/init/useCanLoadChatData";
9
8
  // Mark-as-read waits for this lookup, so a call that never settles must not
10
9
  // leave the thread unread.
11
10
  export const FIRST_UNREAD_LOOKUP_TIMEOUT_MS = 5000;
@@ -61,13 +60,12 @@ const isOlderThan = (message, boundary) => message.seq && boundary.seq
61
60
  */
62
61
  export const useUnreadMessageIndicator = ({ conversationId = "", searchClientMsgID, messageList, }) => {
63
62
  var _a;
64
- const { connectStatus, syncStatus } = useChatContext();
63
+ const canLoadChatData = useCanLoadChatData();
65
64
  const userID = useAuthStore((state) => state.userID);
66
65
  const storedConversationId = useConversationStore((state) => { var _a; return (_a = state.conversationData) === null || _a === void 0 ? void 0 : _a.conversationID; });
67
66
  // Waiting for the store to hold this conversation also waits for its open
68
67
  // target, which some hosts set a moment after the selected id.
69
- const canLookup = connectStatus === ConnectStatus.Connected &&
70
- syncStatus !== SyncStatus.Loading &&
68
+ const canLookup = canLoadChatData &&
71
69
  !!conversationId &&
72
70
  !!userID &&
73
71
  storedConversationId === conversationId;
@@ -1,4 +1,5 @@
1
1
  import { type ConversationItem } from "@droppii-org/wasm-client-sdk";
2
+ export declare const isSupportThreadJoinedHere: (conversationId?: string) => boolean;
2
3
  /**
3
4
  * CRM's join gate for the thread detail: an agent viewing a support thread
4
5
  * they aren't a member of can't send until they join. Opt-in — only a host
@@ -1 +1 @@
1
- {"version":3,"file":"useAgentSupportThreadJoin.d.ts","sourceRoot":"","sources":["../../../src/hooks/session/useAgentSupportThreadJoin.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,gBAAgB,EAGtB,MAAM,8BAA8B,CAAC;AAYtC;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GACpC,eAAe,gBAAgB,GAAG,IAAI,EACtC,aAAa,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC;;;;;CAuE1D,CAAC"}
1
+ {"version":3,"file":"useAgentSupportThreadJoin.d.ts","sourceRoot":"","sources":["../../../src/hooks/session/useAgentSupportThreadJoin.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,gBAAgB,EAGtB,MAAM,8BAA8B,CAAC;AAatC,eAAO,MAAM,yBAAyB,GAAI,iBAAiB,MAAM,YACF,CAAC;AAEhE;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GACpC,eAAe,gBAAgB,GAAG,IAAI,EACtC,aAAa,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC;;;;;CA8E1D,CAAC"}
@@ -1,9 +1,11 @@
1
1
  "use client";
2
2
  import { useCallback, useEffect, useState } from "react";
3
3
  import { message } from "antd";
4
+ import { useQueryClient } from "@tanstack/react-query";
4
5
  import { useTranslation } from "react-i18next";
5
6
  import { CbEvents, } from "@droppii-org/wasm-client-sdk";
6
7
  import { DChatSDK } from "../../constants/sdk";
8
+ import { QUERY_KEYS } from "../../services/query";
7
9
  import useAuthStore from "../../store/auth";
8
10
  import { PeerType } from "../../types/conversation";
9
11
  import { isSupportSessionAgentView } from "../../utils/conversation";
@@ -12,6 +14,7 @@ import { useIsJoinedGroup } from "./useIsJoinedGroup";
12
14
  // check right after still says "not a member". Joins made in this page
13
15
  // session are trusted instead — kept outside React so a remount keeps them.
14
16
  const joinedConversationIds = new Set();
17
+ export const isSupportThreadJoinedHere = (conversationId) => !!conversationId && joinedConversationIds.has(conversationId);
15
18
  /**
16
19
  * CRM's join gate for the thread detail: an agent viewing a support thread
17
20
  * they aren't a member of can't send until they join. Opt-in — only a host
@@ -19,6 +22,7 @@ const joinedConversationIds = new Set();
19
22
  */
20
23
  export const useAgentSupportThreadJoin = (conversation, joinThread) => {
21
24
  const { t } = useTranslation();
25
+ const queryClient = useQueryClient();
22
26
  const userID = useAuthStore((state) => state.userID);
23
27
  const isCrm = useAuthStore((state) => state.isCrm);
24
28
  const isAgentSupportThread = !!joinThread &&
@@ -68,8 +72,15 @@ export const useAgentSupportThreadJoin = (conversation, joinThread) => {
68
72
  joinedConversationIds.add(conversationId);
69
73
  setJoinCount((count) => count + 1);
70
74
  message.success(t("join_group_success"));
71
- }, [conversationId, joinThread, t]);
72
- const isJoinedHere = !!conversationId && joinedConversationIds.has(conversationId);
75
+ // Re-renders other readers of this membership (pin rights); a join on a
76
+ // cached group syncs as an update, so no join event would refetch it.
77
+ if (groupId) {
78
+ const queryKey = [QUERY_KEYS.IS_JOINED_GROUP, groupId];
79
+ await queryClient.cancelQueries({ queryKey });
80
+ queryClient.setQueryData(queryKey, true);
81
+ }
82
+ }, [conversationId, groupId, joinThread, queryClient, t]);
83
+ const isJoinedHere = isSupportThreadJoinedHere(conversationId);
73
84
  const isGated = !!groupId && !isJoinedHere;
74
85
  return {
75
86
  isCheckingMembership: isGated && isJoined === null && !isError,
@@ -7,5 +7,8 @@ export interface ConversationPreviewSource {
7
7
  conversationId: string;
8
8
  conversation?: ConversationItem;
9
9
  }
10
- export declare const useSessionLatestPreview: (sessions: ConversationPreviewSource[]) => Map<string, LatestPreview>;
10
+ export declare const useSessionLatestPreview: (sessions: ConversationPreviewSource[]) => {
11
+ previews: Map<string, LatestPreview>;
12
+ isLoading: boolean;
13
+ };
11
14
  //# sourceMappingURL=useSessionLatestPreview.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSessionLatestPreview.d.ts","sourceRoot":"","sources":["../../../src/hooks/session/useSessionLatestPreview.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAyB,MAAM,8BAA8B,CAAC;AASvF,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAKD,MAAM,WAAW,yBAAyB;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAkBD,eAAO,MAAM,uBAAuB,GAClC,UAAU,yBAAyB,EAAE,+BAyEtC,CAAC"}
1
+ {"version":3,"file":"useSessionLatestPreview.d.ts","sourceRoot":"","sources":["../../../src/hooks/session/useSessionLatestPreview.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAyB,MAAM,8BAA8B,CAAC;AASvF,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAKD,MAAM,WAAW,yBAAyB;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAkBD,eAAO,MAAM,uBAAuB,GAClC,UAAU,yBAAyB,EAAE;;;CA8EtC,CAAC"}
@@ -54,7 +54,9 @@ export const useSessionLatestPreview = (sessions) => {
54
54
  emitter.off("GROUP_MEMBERS_CHANGED", groupMembersChangedHandler);
55
55
  };
56
56
  }, [sessions, queryClient]);
57
- return useMemo(() => {
57
+ // A preview is loading only while its query fetches for the first time.
58
+ const isLoading = results.some((result) => result.isLoading);
59
+ const previews = useMemo(() => {
58
60
  const map = new Map();
59
61
  sessions.forEach((session, index) => {
60
62
  var _a, _b, _c;
@@ -77,4 +79,5 @@ export const useSessionLatestPreview = (sessions) => {
77
79
  });
78
80
  return map;
79
81
  }, [sessions, results]);
82
+ return { previews, isLoading };
80
83
  };
@@ -12,13 +12,14 @@
12
12
  "sync": "Đồng bộ tin nhắn"
13
13
  },
14
14
  "slow": "Mạng đang chậm hơn bình thường. Livechat vẫn đang tải, bạn không cần làm gì thêm.",
15
- "error_hint": "Kiểm tra kết nối mạng rồi bấm Thử lại. Nếu vẫn lỗi, báo bộ phận kỹ thuật: dừng ở bước {{step}}."
15
+ "error_hint": "Kiểm tra kết nối mạng rồi bấm Thử lại. Nếu vẫn lỗi, báo bộ phận kỹ thuật: dừng ở bước {{step}}.",
16
+ "background_sync": "Đang đồng bộ các cuộc trò chuyện còn lại"
16
17
  },
17
18
  "inbox": {
18
19
  "title": "Đang tải tin nhắn",
19
20
  "engine": "Đang chuẩn bị hộp thư…",
20
21
  "connect": "Đang kết nối…",
21
- "sync": "Đang đồng bộ…",
22
+ "sync": "Vui lòng chờ trong giây lát…",
22
23
  "slow": "Lần đầu mở có thể mất một chút thời gian. Những lần sau sẽ nhanh hơn.",
23
24
  "error_title": "Chưa tải được tin nhắn",
24
25
  "error_hint": "Kiểm tra kết nối mạng rồi thử lại."
@@ -74,6 +74,7 @@
74
74
  "context_menu_view_original": "Xem tin nhắn gốc",
75
75
  "pinned_panel_title": "Tin nhắn đã ghim",
76
76
  "pinned_panel_hint": "Nhấn giữ tin nhắn để chọn \"Bỏ ghim\" hoặc \"Xem tin nhắn gốc\"",
77
+ "pinned_panel_hint_view_only": "Nhấn giữ tin nhắn để chọn \"Xem tin nhắn gốc\"",
77
78
  "pin_message_success": "Đã ghim tin nhắn",
78
79
  "pin_message_failed": "Ghim tin nhắn thất bại",
79
80
  "unpin_message_success": "Đã bỏ ghim tin nhắn",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/conversation-detail/index.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAWrF,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,6CAA6C,CAAC;AAQrD,MAAM,WAAW,4BACf,SAAQ,4BAA4B,EAClC,4BAA4B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,kGAAkG;IAClG,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;CACrC;AAED,QAAA,MAAM,uBAAuB,GAAI,8IAc9B,4BAA4B,mDA4Q9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/conversation-detail/index.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAYrF,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,6CAA6C,CAAC;AAQrD,MAAM,WAAW,4BACf,SAAQ,4BAA4B,EAClC,4BAA4B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,kGAAkG;IAClG,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;CACrC;AAED,QAAA,MAAM,uBAAuB,GAAI,8IAc9B,4BAA4B,mDA6Q9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
@@ -28,6 +28,7 @@ import { ConversationMessageList, ConversationDetailHeader, ConversationDetailIn
28
28
  import TypingIndicatorBar from "../../components/conversation-detail/TypingIndicatorBar";
29
29
  import { BotButtonMessage, BotInactiveNotice, BotMenuSheet, BotStartButton, BotSuggest, CsSessionConfirmModal, } from "../../components/conversation-detail/bot";
30
30
  import { useBotFlow } from "../../hooks/conversation/useBotFlow";
31
+ import { useCanPinMessage } from "../../hooks/message/useCanPinMessage";
31
32
  import { BOT_TYPE } from "../../constants/botFlow";
32
33
  import { useResolveConversation, } from "../../hooks/conversation/useResolveConversation";
33
34
  import { nextConsultantReturnTarget, useConsultantChatNavigation, } from "../../hooks/conversation/useConsultantChatNavigation";
@@ -45,6 +46,7 @@ const DChatConversationDetail = (_a) => {
45
46
  const conversation = useConversationStore((state) => state.conversationData);
46
47
  const authUserID = useAuthStore((state) => state.userID);
47
48
  const isViewOnlyMember = useConversationStore((state) => state.isViewOnlyMember);
49
+ const canPinMessage = useCanPinMessage();
48
50
  const clearSelectedConversation = useConversationStore((state) => state.clearSelectedConversation);
49
51
  const setPendingResolveTarget = useConversationStore((state) => state.setPendingResolveTarget);
50
52
  const { isResolving, error } = useResolveConversation({
@@ -139,6 +141,6 @@ const DChatConversationDetail = (_a) => {
139
141
  !botFlow.flowIsEnd &&
140
142
  botFlow.shouldShowButtons &&
141
143
  botFlow.currentButtonsBlock ? (_jsx(BotButtonMessage, { buttons: botFlow.currentButtonsBlock.data.buttons, disabled: botFlow.isProcessing, onSelect: botFlow.selectButton, onRestart: botFlow.restart })) : null;
142
- return (_jsxs("div", { "data-testid": "conversation-detail-root", className: clsx("relative flex h-full w-full flex-col overflow-hidden bg-white", className), children: [_jsx(ConversationDetailHeader, { conversation: conversation, onBack: handleBack, onProfileClick: openProfile }), _jsx(PinnedMessageBar, {}), _jsxs("div", { [COMPOSER_DISMISS_ZONE_ATTR]: "", className: "relative flex min-h-0 flex-1 flex-col", children: [children !== null && children !== void 0 ? children : (_jsx(ConversationMessageList, { bottomSlot: botButtonsSlot, onCopyMessageLink: onCopyMessageLink, onSearchMessageRevoked: onSearchMessageRevoked })), _jsx(TypingIndicatorBar, { conversationId: conversation.conversationID })] }), footer, isBotThread && (_jsxs(_Fragment, { children: [_jsx(BotMenuSheet, { open: isMenuOpen, items: botFlow.menuItems, disabled: botFlow.isProcessing, onClose: closeMenu, onSelect: botFlow.selectMenuOption }), _jsx(CsSessionConfirmModal, { open: botFlow.isCsConfirmOpen, loading: botFlow.isProcessing, onCancel: botFlow.cancelMenuOption, onConfirm: botFlow.confirmEndCsSession })] })), _jsx(ThreadProfilePanel, { open: isProfileOpen, conversation: conversation, onClose: closeProfile, onSelectResource: setResourceTab }), _jsx(MediaResourcePanel, { open: resourceTab !== null, initialTab: resourceTab !== null && resourceTab !== void 0 ? resourceTab : MediaResourceTabKey.Image, onClose: () => setResourceTab(null) })] }));
144
+ return (_jsxs("div", { "data-testid": "conversation-detail-root", className: clsx("relative flex h-full w-full flex-col overflow-hidden bg-white", className), children: [_jsx(ConversationDetailHeader, { conversation: conversation, onBack: handleBack, onProfileClick: openProfile }), _jsx(PinnedMessageBar, { canUnpin: canPinMessage }), _jsxs("div", { [COMPOSER_DISMISS_ZONE_ATTR]: "", className: "relative flex min-h-0 flex-1 flex-col", children: [children !== null && children !== void 0 ? children : (_jsx(ConversationMessageList, { bottomSlot: botButtonsSlot, onCopyMessageLink: onCopyMessageLink, onSearchMessageRevoked: onSearchMessageRevoked })), _jsx(TypingIndicatorBar, { conversationId: conversation.conversationID })] }), footer, isBotThread && (_jsxs(_Fragment, { children: [_jsx(BotMenuSheet, { open: isMenuOpen, items: botFlow.menuItems, disabled: botFlow.isProcessing, onClose: closeMenu, onSelect: botFlow.selectMenuOption }), _jsx(CsSessionConfirmModal, { open: botFlow.isCsConfirmOpen, loading: botFlow.isProcessing, onCancel: botFlow.cancelMenuOption, onConfirm: botFlow.confirmEndCsSession })] })), _jsx(ThreadProfilePanel, { open: isProfileOpen, conversation: conversation, onClose: closeProfile, onSelectResource: setResourceTab }), _jsx(MediaResourcePanel, { open: resourceTab !== null, initialTab: resourceTab !== null && resourceTab !== void 0 ? resourceTab : MediaResourceTabKey.Image, onClose: () => setResourceTab(null) })] }));
143
145
  };
144
146
  export default DChatConversationDetail;
@@ -11,8 +11,10 @@ interface DeskWorkspaceProps {
11
11
  isOffline?: boolean;
12
12
  /** Shown in the offline overlay: reconnect status, and Retry once the reconnect has failed. */
13
13
  offlineContent?: ReactNode;
14
+ /** First boot: covers the desk (locked like offline) while its first screen of data loads. */
15
+ bootOverlay?: ReactNode;
14
16
  }
15
17
  /** Bright Workspace: folder, list, chat and info as separate white panels on the page canvas. */
16
- declare const DeskWorkspace: ({ openCreateCannedModal, groupThreadInfoConfig, onCopyMessageLink, onSearchMessageRevoked, onOpenAppMenu, isOffline, offlineContent, }: DeskWorkspaceProps) => import("react/jsx-runtime").JSX.Element;
18
+ declare const DeskWorkspace: ({ openCreateCannedModal, groupThreadInfoConfig, onCopyMessageLink, onSearchMessageRevoked, onOpenAppMenu, isOffline, offlineContent, bootOverlay, }: DeskWorkspaceProps) => import("react/jsx-runtime").JSX.Element;
17
19
  export default DeskWorkspace;
18
20
  //# sourceMappingURL=DeskWorkspace.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DeskWorkspace.d.ts","sourceRoot":"","sources":["../../../src/screens/desk-message/DeskWorkspace.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAkB5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGxD,UAAU,kBAAkB;IAC1B,qBAAqB,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAChE,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IACpC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IACrD,4FAA4F;IAC5F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+FAA+F;IAC/F,cAAc,CAAC,EAAE,SAAS,CAAC;CAC5B;AAID,iGAAiG;AACjG,QAAA,MAAM,aAAa,GAAI,wIAQpB,kBAAkB,4CAiLpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"DeskWorkspace.d.ts","sourceRoot":"","sources":["../../../src/screens/desk-message/DeskWorkspace.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAqB5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGxD,UAAU,kBAAkB;IAC1B,qBAAqB,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAChE,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IACpC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IACrD,4FAA4F;IAC5F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+FAA+F;IAC/F,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,8FAA8F;IAC9F,WAAW,CAAC,EAAE,SAAS,CAAC;CACzB;AAID,iGAAiG;AACjG,QAAA,MAAM,aAAa,GAAI,qJASpB,kBAAkB,4CA2LpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -10,7 +10,7 @@ import { useMediaQuery } from "../../hooks/common/useMediaQuery";
10
10
  import { useSidePanel } from "../../hooks/common/useSidePanel";
11
11
  import { useVisualViewportHeight } from "../../hooks/common/useVisualViewportHeight";
12
12
  import { useViewportTier } from "../../hooks/common/useViewportTier";
13
- import { useDeskOfflineLock } from "../../hooks/conversation/useDeskOfflineLock";
13
+ import { DESK_OFFLINE_OVERLAY_ATTR, useDeskOfflineLock, } from "../../hooks/conversation/useDeskOfflineLock";
14
14
  import { useDeskThreadNavigation } from "../../hooks/conversation/useDeskThreadNavigation";
15
15
  import useSidePanelStore from "../../store/sidePanel";
16
16
  import useConversationStore from "../../store/conversation";
@@ -26,7 +26,7 @@ import DeskOfflineOverlay from "./DeskOfflineOverlay";
26
26
  import { PeerType } from "../../types/conversation";
27
27
  const minWidth = (px) => `(min-width: ${px}px)`;
28
28
  /** Bright Workspace: folder, list, chat and info as separate white panels on the page canvas. */
29
- const DeskWorkspace = ({ openCreateCannedModal, groupThreadInfoConfig, onCopyMessageLink, onSearchMessageRevoked, onOpenAppMenu, isOffline = false, offlineContent, }) => {
29
+ const DeskWorkspace = ({ openCreateCannedModal, groupThreadInfoConfig, onCopyMessageLink, onSearchMessageRevoked, onOpenAppMenu, isOffline = false, offlineContent, bootOverlay, }) => {
30
30
  const selectedConversationId = useConversationStore((state) => state.selectedConversationId);
31
31
  const searchClientMsgID = useConversationStore((state) => state.searchClientMsgID);
32
32
  const { isOpen: isInfoOpen, toggle: toggleInfo, close: closeInfo } = useSidePanel("info");
@@ -78,7 +78,7 @@ const DeskWorkspace = ({ openCreateCannedModal, groupThreadInfoConfig, onCopyMes
78
78
  useSidePanelStore.getState().closePanel();
79
79
  }, [isPhone, selectedConversationId]);
80
80
  const wasShowingThread = useRef(showThread);
81
- useDeskOfflineLock(workspaceEl, isOffline, () => {
81
+ useDeskOfflineLock(workspaceEl, isOffline || Boolean(bootOverlay), () => {
82
82
  var _a, _b;
83
83
  return isPhone
84
84
  ? showThread
@@ -121,6 +121,6 @@ const DeskWorkspace = ({ openCreateCannedModal, groupThreadInfoConfig, onCopyMes
121
121
  ? undefined
122
122
  : { width: isFolderInline ? LAYOUT_WIDTHS.list : LAYOUT_WIDTHS.listCompact }, children: [_jsx(DeskConversationList, { onOpenAppMenu: onOpenAppMenu }), !isFolderInline && _jsx(FolderOverlay, {})] }), _jsxs("div", { ref: chatPaneRef, className: isPhone
123
123
  ? `absolute inset-0 flex flex-col overflow-hidden bg-white ${showThread ? "" : "invisible"}`
124
- : "flex h-full flex-1 overflow-hidden rounded-xl bg-white shadow-sm", style: isPhone ? undefined : { minWidth: isDesktop ? LAYOUT_WIDTHS.chatMin : 0 }, children: [isPhone && !hasConversationData && (_jsx(PhoneThreadFallbackHeader, { onBack: backToList, onOpenAppMenu: onOpenAppMenu })), messageList] }), _jsx(ThreadInfo, { isOpen: isInfoOpen, onClose: closeInfo, groupConfig: groupThreadInfoConfig, supportPanelInline: isSupportPanelInline, drawerContainer: workspaceEl }), _jsx("div", { ref: setPanelSlotEl, className: "contents" }), _jsx(ShortcutHelpModal, { open: isHelpOpen, onClose: () => setIsHelpOpen(false) }), isOffline && (_jsx(DeskOfflineOverlay, { isPhone: isPhone, onOpenAppMenu: onOpenAppMenu, children: offlineContent }))] }));
124
+ : "flex h-full flex-1 overflow-hidden rounded-xl bg-white shadow-sm", style: isPhone ? undefined : { minWidth: isDesktop ? LAYOUT_WIDTHS.chatMin : 0 }, children: [isPhone && !hasConversationData && (_jsx(PhoneThreadFallbackHeader, { onBack: backToList, onOpenAppMenu: onOpenAppMenu })), messageList] }), _jsx(ThreadInfo, { isOpen: isInfoOpen, onClose: closeInfo, groupConfig: groupThreadInfoConfig, supportPanelInline: isSupportPanelInline, drawerContainer: workspaceEl }), _jsx("div", { ref: setPanelSlotEl, className: "contents" }), _jsx(ShortcutHelpModal, { open: isHelpOpen, onClose: () => setIsHelpOpen(false) }), bootOverlay ? (_jsx("div", { [DESK_OFFLINE_OVERLAY_ATTR]: "", "data-testid": "desk-boot-overlay", className: "absolute inset-0 z-[1000] flex", children: bootOverlay })) : (isOffline && (_jsx(DeskOfflineOverlay, { isPhone: isPhone, onOpenAppMenu: onOpenAppMenu, children: offlineContent })))] }));
125
125
  };
126
126
  export default DeskWorkspace;
@@ -1,5 +1,7 @@
1
1
  import { GroupThreadInfoConfig } from "../../components/thread/GroupThreadInfo";
2
2
  import type { CannedCreateRequest } from "../../types/chat";
3
+ /** Longest the boot screen waits for the desk's first page of conversations once it can load. */
4
+ export declare const FIRST_SCREEN_MAX_WAIT_MS = 10000;
3
5
  interface DChatDeskMessageProps {
4
6
  openCreateCannedModal?: (request?: CannedCreateRequest) => void;
5
7
  groupThreadInfoConfig?: GroupThreadInfoConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/desk-message/index.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAK5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAIxD,UAAU,qBAAqB;IAC7B,qBAAqB,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAChE,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,kGAAkG;IAClG,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IACpC,+GAA+G;IAC/G,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;CACtD;AAWD,QAAA,MAAM,gBAAgB,GAAI,OAAO,qBAAqB,4CA4CrD,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/desk-message/index.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAM5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGxD,iGAAiG;AACjG,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C,UAAU,qBAAqB;IAC7B,qBAAqB,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAChE,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,kGAAkG;IAClG,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IACpC,+GAA+G;IAC/G,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;CACtD;AAWD,QAAA,MAAM,gBAAgB,GAAI,OAAO,qBAAqB,4CA4DrD,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -6,10 +6,13 @@ import { useChatContext } from "../../context/ChatContext";
6
6
  import { ConnectStatus } from "../../types/chat";
7
7
  import { QUERY_KEYS } from "../../services/query";
8
8
  import { useChatBootPhase } from "../../hooks/init/useChatBootPhase";
9
+ import useBootStore, { selectHasConversations } from "../../store/boot";
9
10
  import DeskWorkspace from "./DeskWorkspace";
10
11
  import DeskWorkspaceSkeleton from "./DeskWorkspaceSkeleton";
11
12
  import DeskBootCard from "./DeskBootCard";
12
13
  const DESK_SLOW_AFTER_MS = 15000;
14
+ /** Longest the boot screen waits for the desk's first page of conversations once it can load. */
15
+ export const FIRST_SCREEN_MAX_WAIT_MS = 10000;
13
16
  // Lists behind the desk; they refetch after a reconnect because events were missed while offline.
14
17
  const DESK_QUERY_KEYS = [
15
18
  QUERY_KEYS.GET_TEAM_SESSIONS,
@@ -23,13 +26,26 @@ const DChatDeskMessage = (props) => {
23
26
  const { connectStatus } = useChatContext();
24
27
  const queryClient = useQueryClient();
25
28
  const isConnected = connectStatus === ConnectStatus.Connected;
26
- // After the first full boot the desk stays mounted, so a reconnect keeps scroll, search and panels.
27
- const [hasBooted, setHasBooted] = useState(boot.phase === "ready");
29
+ const hasConversations = useBootStore(selectHasConversations);
30
+ const isFirstScreenReady = useBootStore((state) => state.isFirstScreenReady);
31
+ // The desk mounts once the local DB holds the conversations; a fresh device's sync then
32
+ // finishes in the background while the first page loads on demand under the boot screen.
33
+ const canMountDesk = isConnected && hasConversations;
34
+ const [isFirstScreenTimedOut, setIsFirstScreenTimedOut] = useState(false);
35
+ const isBooted = canMountDesk && (isFirstScreenReady || isFirstScreenTimedOut);
36
+ // After the first screen has shown, the desk stays mounted, so a reconnect keeps scroll, search and panels.
37
+ const [hasBooted, setHasBooted] = useState(isBooted);
28
38
  const wasDropped = useRef(false);
29
39
  useEffect(() => {
30
- if (boot.phase === "ready")
40
+ if (isBooted)
31
41
  setHasBooted(true);
32
- }, [boot.phase]);
42
+ }, [isBooted]);
43
+ useEffect(() => {
44
+ if (hasBooted || !canMountDesk || isFirstScreenReady)
45
+ return;
46
+ const timer = setTimeout(() => setIsFirstScreenTimedOut(true), FIRST_SCREEN_MAX_WAIT_MS);
47
+ return () => clearTimeout(timer);
48
+ }, [hasBooted, canMountDesk, isFirstScreenReady]);
33
49
  useEffect(() => {
34
50
  if (!hasBooted)
35
51
  return;
@@ -47,9 +63,11 @@ const DChatDeskMessage = (props) => {
47
63
  },
48
64
  });
49
65
  }, [hasBooted, isConnected, queryClient]);
50
- if (!hasBooted && boot.phase !== "ready") {
51
- return (_jsx(DeskWorkspaceSkeleton, { children: _jsx(DeskBootCard, { boot: boot }) }));
52
- }
53
- return (_jsx(DeskWorkspace, Object.assign({}, props, { isOffline: !isConnected, offlineContent: isConnected ? undefined : _jsx(DeskBootCard, { boot: boot }) })));
66
+ const showDesk = hasBooted || isBooted;
67
+ // An incremental sync can end before the first page is in; keep the sync step on screen till then.
68
+ const bootScreen = (_jsx(DeskWorkspaceSkeleton, { children: _jsx(DeskBootCard, { boot: boot.phase === "ready" ? Object.assign(Object.assign({}, boot), { phase: "sync" }) : boot }) }));
69
+ if (!showDesk && !canMountDesk)
70
+ return bootScreen;
71
+ return (_jsx(DeskWorkspace, Object.assign({}, props, { isOffline: !isConnected, offlineContent: isConnected ? undefined : _jsx(DeskBootCard, { boot: boot }), bootOverlay: showDesk ? undefined : bootScreen })));
54
72
  };
55
73
  export default DChatDeskMessage;