@zenify-omni/chat-ui-react-js 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (403) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +92 -0
  3. package/assets/asset-url.js +6 -0
  4. package/assets/icons/chatter_agent.js +27 -0
  5. package/assets/icons/chatter_chatbot.js +43 -0
  6. package/assets/icons/chatter_contact.js +36 -0
  7. package/assets/icons/email_icon.png +0 -0
  8. package/assets/icons/emoji/angry.svg +56 -0
  9. package/assets/icons/emoji/haha.svg +20 -0
  10. package/assets/icons/emoji/like.svg +10 -0
  11. package/assets/icons/emoji/like_out_line.png +0 -0
  12. package/assets/icons/emoji/love.svg +10 -0
  13. package/assets/icons/emoji/sad.svg +46 -0
  14. package/assets/icons/emoji/wow.svg +51 -0
  15. package/assets/icons/instagram.js +29 -0
  16. package/assets/icons/not_found.png +0 -0
  17. package/assets/icons/not_found_template.svg +9 -0
  18. package/assets/icons/phone_icon.png +0 -0
  19. package/assets/icons/shopee.js +17 -0
  20. package/assets/icons/sms_icon.png +0 -0
  21. package/assets/icons/tiktok.js +17 -0
  22. package/assets/icons/whatsapp.js +28 -0
  23. package/assets/icons/zalo.js +17 -0
  24. package/assets/icons/zalo_icon.png +0 -0
  25. package/assets/icons/zns_icon.png +0 -0
  26. package/assets/images/emoji.webp +0 -0
  27. package/assets/images/error.png +0 -0
  28. package/assets/images/menu.png +0 -0
  29. package/assets/images/money.png +0 -0
  30. package/assets/images/success.png +0 -0
  31. package/assets/public-urls.js +78 -0
  32. package/components/campaign-detail-route/select/group.js +38 -0
  33. package/components/campaign-detail-route/select/multi-groups.js +43 -0
  34. package/components/chat-route/chat-box/ads-context-card.js +143 -0
  35. package/components/chat-route/chat-box/chat-drop-file.js +18 -0
  36. package/components/chat-route/chat-box/chat-feedback/chat-insight.js +214 -0
  37. package/components/chat-route/chat-box/chat-feedback/index.js +127 -0
  38. package/components/chat-route/chat-box/chat-header-actions-inline.js +1055 -0
  39. package/components/chat-route/chat-box/chat-header.js +465 -0
  40. package/components/chat-route/chat-box/chat-input.js +1164 -0
  41. package/components/chat-route/chat-box/chat-message-action.js +399 -0
  42. package/components/chat-route/chat-box/chat-message-list.js +995 -0
  43. package/components/chat-route/chat-box/chat-message.js +2099 -0
  44. package/components/chat-route/chat-box/chat-reply-preview.js +317 -0
  45. package/components/chat-route/chat-box/common-groups-modal.js +430 -0
  46. package/components/chat-route/chat-box/constants.js +29 -0
  47. package/components/chat-route/chat-box/contact-card/index.js +87 -0
  48. package/components/chat-route/chat-box/edit-customer-alias-dialog.js +197 -0
  49. package/components/chat-route/chat-box/emoji-picker-combined.js +91 -0
  50. package/components/chat-route/chat-box/emoji-sprite.js +45 -0
  51. package/components/chat-route/chat-box/emojiMap.js +92 -0
  52. package/components/chat-route/chat-box/facebook-reconnect-notice.js +49 -0
  53. package/components/chat-route/chat-box/forward-message-modal.js +659 -0
  54. package/components/chat-route/chat-box/index.js +165 -0
  55. package/components/chat-route/chat-box/message-time.js +44 -0
  56. package/components/chat-route/chat-box/message-viewers.js +114 -0
  57. package/components/chat-route/chat-box/pasted-file-preview.js +138 -0
  58. package/components/chat-route/chat-box/pinned-messages.js +255 -0
  59. package/components/chat-route/chat-box/react-message/react-preview.js +83 -0
  60. package/components/chat-route/chat-box/react-message/reaction-button.js +200 -0
  61. package/components/chat-route/chat-box/right-side/chat-history.js +142 -0
  62. package/components/chat-route/chat-box/right-side/chat-tag-selector.js +232 -0
  63. package/components/chat-route/chat-box/right-side/create-personal-tag-modal.js +458 -0
  64. package/components/chat-route/chat-box/right-side/customer/customer-info-item.js +60 -0
  65. package/components/chat-route/chat-box/right-side/in-room-dialog.js +225 -0
  66. package/components/chat-route/chat-box/right-side/zalo-group/add-friend-dialog.js +154 -0
  67. package/components/chat-route/chat-box/right-side/zalo-group/send-message-dialog.js +165 -0
  68. package/components/chat-route/chat-box/send-contact-card-modal.js +102 -0
  69. package/components/chat-route/chat-box/send-contact-card.js +80 -0
  70. package/components/chat-route/chat-box/text-content.js +400 -0
  71. package/components/chat-route/chat-box/use-facebook-page-invalid.js +22 -0
  72. package/components/chat-route/chat-box/zalo-emoji-decoration.js +98 -0
  73. package/components/chat-route/chat-box/zalo-emoji-picker.js +74 -0
  74. package/components/chat-route/chat-box/zalo-personal-editor.js +590 -0
  75. package/components/chat-route/chat-box/zalo-reminder/create-zalo-reminder.js +868 -0
  76. package/components/chat-route/chat-box/zalo-reminder/use-send-zalo-reminder.js +98 -0
  77. package/components/chat-route/chat-box/zalo-reminder/zalo-reminder-content.js +294 -0
  78. package/components/chat-route/chat-box/zalo-rtf-utils.js +422 -0
  79. package/components/chat-route/chat-list/action-chat-item.js +116 -0
  80. package/components/chat-route/chat-list/assign-agent-dialog.js +170 -0
  81. package/components/chat-route/chat-list/assign-tag-dialog.js +242 -0
  82. package/components/chat-route/chat-list/chat-item-tags.js +105 -0
  83. package/components/chat-route/chat-list/chat-item.js +749 -0
  84. package/components/chat-route/chat-list/chat-list-item.js +370 -0
  85. package/components/chat-route/chat-list/chat-list-selection-bar.js +426 -0
  86. package/components/chat-route/chat-list/chat-search-preview.js +48 -0
  87. package/components/chat-route/chat-list/chat-sort-menu.js +89 -0
  88. package/components/chat-route/chat-list/edit-filter-modal.js +468 -0
  89. package/components/chat-route/chat-list/filter-chat-sources.js +222 -0
  90. package/components/chat-route/chat-list/filter-chat.js +501 -0
  91. package/components/chat-route/chat-list/filter-criteria-rows.js +845 -0
  92. package/components/chat-route/chat-list/filter-group-select.js +66 -0
  93. package/components/chat-route/chat-list/filter-pin-menu.js +165 -0
  94. package/components/chat-route/chat-list/friend-request/list-friend-request.js +287 -0
  95. package/components/chat-route/chat-list/index.js +243 -0
  96. package/components/chat-route/chat-list/inline-search-results.js +178 -0
  97. package/components/chat-route/chat-list/post-ids-field.js +104 -0
  98. package/components/chat-route/chat-list/quick-filter-bar.js +391 -0
  99. package/components/chat-route/chat-list/save-filter-popover.js +258 -0
  100. package/components/chat-route/chat-list/search-messages/index.js +225 -0
  101. package/components/chat-route/chat-list/search-result-header.js +51 -0
  102. package/components/chat-route/chat-list/util.js +604 -0
  103. package/components/chat-route/chat-toolbar.js +108 -0
  104. package/components/chat-route/hooks/use-reassign.js +111 -0
  105. package/components/chat-route/shared/close-conversation-dialog.js +111 -0
  106. package/components/chat-route/shared/force-transfer-dialog.js +109 -0
  107. package/components/chat-route/shared/in-queue-confirm-dialog.js +40 -0
  108. package/components/chat-route/util.js +146 -0
  109. package/components/chat-route/zalo/modal-action-zalo.js +76 -0
  110. package/components/chat-route/zalo/modal-add-friend-zalo.js +277 -0
  111. package/components/custom/SafeHTML.js +45 -0
  112. package/components/custom/audio-player.js +81 -0
  113. package/components/custom/avatar.js +95 -0
  114. package/components/custom/between-date-picker/index.js +86 -0
  115. package/components/custom/button-close.js +28 -0
  116. package/components/custom/button.js +80 -0
  117. package/components/custom/color-picker.js +81 -0
  118. package/components/custom/combobox.js +168 -0
  119. package/components/custom/custom-dialog.js +44 -0
  120. package/components/custom/custom-select.js +256 -0
  121. package/components/custom/cycle-value-helpers.js +11 -0
  122. package/components/custom/cycle-value.js +93 -0
  123. package/components/custom/date-time-picker/components/date-time-group.js +44 -0
  124. package/components/custom/date-time-picker/components/date-time-input.js +383 -0
  125. package/components/custom/date-time-picker/components/date-time-unit-input.js +138 -0
  126. package/components/custom/date-time-picker/components/month-year-picker.js +232 -0
  127. package/components/custom/date-time-picker/hooks/use-merge-refs.js +18 -0
  128. package/components/custom/date-time-picker/hooks/use-scroll-to-center.js +18 -0
  129. package/components/custom/date-time-picker/index.js +348 -0
  130. package/components/custom/field-file-upload/files-upload.js +87 -0
  131. package/components/custom/field-file-upload/image-upload.js +115 -0
  132. package/components/custom/field-file-upload/preview-images-modal.js +284 -0
  133. package/components/custom/field-file-upload/preview-list-file.js +134 -0
  134. package/components/custom/field-file-upload/preview-list-image.js +108 -0
  135. package/components/custom/field-file-upload/upload-custom.js +222 -0
  136. package/components/custom/field-generator.js +639 -0
  137. package/components/custom/field-layout-popover.js +304 -0
  138. package/components/custom/file-preview-modal.js +351 -0
  139. package/components/custom/html-edtior.js +100 -0
  140. package/components/custom/icon.js +49 -0
  141. package/components/custom/infinite-scroll-combobox.js +253 -0
  142. package/components/custom/inline-edit-popover.js +90 -0
  143. package/components/custom/inputs/calendar-input.js +0 -0
  144. package/components/custom/inputs/chip-input.js +136 -0
  145. package/components/custom/inputs/file-input.js +71 -0
  146. package/components/custom/inputs/mask-data/mask-data-input.js +80 -0
  147. package/components/custom/inputs/mask-data/mask-data-select.js +78 -0
  148. package/components/custom/inputs/mask-data/mask-data-textarea.js +75 -0
  149. package/components/custom/inputs/mask-data/reconcile-masked-edit.js +20 -0
  150. package/components/custom/jxl-decode.js +34 -0
  151. package/components/custom/jxl-image.js +68 -0
  152. package/components/custom/jxl-worker.js +12 -0
  153. package/components/custom/label.js +55 -0
  154. package/components/custom/layout.js +30 -0
  155. package/components/custom/multi-select-search-cc.js +179 -0
  156. package/components/custom/multiple-select-setting-tags.js +404 -0
  157. package/components/custom/multiple-selector.js +821 -0
  158. package/components/custom/pagination.js +95 -0
  159. package/components/custom/popup-confirm.js +120 -0
  160. package/components/custom/previews/facebook/facebook-appointment-booking-preview.js +83 -0
  161. package/components/custom/previews/facebook/facebook-audio-preview.js +130 -0
  162. package/components/custom/previews/facebook/facebook-event-preview.js +72 -0
  163. package/components/custom/previews/facebook/facebook-file-preview.js +95 -0
  164. package/components/custom/previews/facebook/facebook-group-preview.js +103 -0
  165. package/components/custom/previews/facebook/facebook-image-preview.js +81 -0
  166. package/components/custom/previews/facebook/facebook-page-preview.js +85 -0
  167. package/components/custom/previews/facebook/facebook-post-preview.js +77 -0
  168. package/components/custom/previews/facebook/facebook-reel-preview.js +83 -0
  169. package/components/custom/previews/facebook/facebook-sticker-preview.js +23 -0
  170. package/components/custom/previews/facebook/facebook-video-preview.js +114 -0
  171. package/components/custom/previews/facebook/index.js +24 -0
  172. package/components/custom/previews/file-preview.js +747 -0
  173. package/components/custom/previews/image-preview.js +288 -0
  174. package/components/custom/previews/instagram/index.js +4 -0
  175. package/components/custom/previews/instagram/instagram-share-post-preview.js +77 -0
  176. package/components/custom/previews/link-preview.js +166 -0
  177. package/components/custom/previews/product-preview.js +334 -0
  178. package/components/custom/previews/tiktok-order-preview.js +278 -0
  179. package/components/custom/previews/tiktok-product-preview.js +321 -0
  180. package/components/custom/previews/tiktok-voucher-preview.js +151 -0
  181. package/components/custom/previews/whatsapp/index.js +10 -0
  182. package/components/custom/previews/whatsapp/whatsapp-contact-card-preview.js +150 -0
  183. package/components/custom/previews/whatsapp/whatsapp-order-preview.js +38 -0
  184. package/components/custom/previews/whatsapp/whatsapp-poll-preview.js +29 -0
  185. package/components/custom/previews/whatsapp/whatsapp-product-preview.js +29 -0
  186. package/components/custom/retry-image.js +44 -0
  187. package/components/custom/search-input.js +26 -0
  188. package/components/custom/separator-with-text.js +14 -0
  189. package/components/custom/showToast.js +21 -0
  190. package/components/custom/sortable-header.js +62 -0
  191. package/components/custom/table-resize-line.js +92 -0
  192. package/components/custom/time-picker/index.js +73 -0
  193. package/components/custom/time-picker/time-picker-input.js +159 -0
  194. package/components/custom/time-picker/time-picker-utils.js +152 -0
  195. package/components/custom/viewers/audio-player.js +192 -0
  196. package/components/custom/viewers/code-viewer.js +140 -0
  197. package/components/custom/viewers/csv-viewer.js +174 -0
  198. package/components/custom/viewers/docx-viewer.js +145 -0
  199. package/components/custom/viewers/image-viewer.js +104 -0
  200. package/components/custom/viewers/markdown-viewer.js +124 -0
  201. package/components/custom/viewers/pdf-viewer.js +333 -0
  202. package/components/custom/viewers/spreadsheet-viewer.js +232 -0
  203. package/components/custom/viewers/unsupported-viewer.js +85 -0
  204. package/components/custom/viewers/video-player.js +241 -0
  205. package/components/customers-route/change-owner-modal.js +430 -0
  206. package/components/customers-route/contact-note-button.js +212 -0
  207. package/components/customers-route/customer-source.js +37 -0
  208. package/components/customers-route/table/columns.js +675 -0
  209. package/components/customers-route/table/edit-fields.js +125 -0
  210. package/components/customers-route/table/editable-cell.js +218 -0
  211. package/components/customers-route/table/note-cell.js +77 -0
  212. package/components/dialogs/invite-agent.js +677 -0
  213. package/components/error-boundary.js +53 -0
  214. package/components/leads-route/duplicate-phone-warning-toast.js +96 -0
  215. package/components/leads-route/table/columns.js +523 -0
  216. package/components/leads-route/table/contact-cell-name.js +82 -0
  217. package/components/leads-route/table/edit-fields.js +142 -0
  218. package/components/leads-route/table/editable-cell.js +228 -0
  219. package/components/org-tree/group-name-display.js +36 -0
  220. package/components/org-tree/group-tree-multi-select.js +208 -0
  221. package/components/org-tree/group-tree-select.js +220 -0
  222. package/components/posts-route/dialog-select-posts.js +284 -0
  223. package/components/reports-route/agents/multi-select-agents.js +55 -0
  224. package/components/reports-route/agents/multi-select-search-agents.js +43 -0
  225. package/components/settings-route/chat-layout/detail/constants.js +353 -0
  226. package/components/settings-route/chat-layout/detail/form.js +43 -0
  227. package/components/settings-route/chat-timeout/channel-multi-select.js +119 -0
  228. package/components/settings-route/multi-channel/facebook/hooks/use-facebook-sdk.js +3 -0
  229. package/components/settings-route/object-rule/util.js +79 -0
  230. package/components/settings-route/routing/settings-dialog.js +561 -0
  231. package/components/settings-route/switchboard/queue-new/constant.js +9 -0
  232. package/components/shared/linked-customer-fields/LinkedCustomerFields.js +91 -0
  233. package/components/shared/linked-customer-fields/use-linked-customer-fields.js +123 -0
  234. package/components/smartqc-route/qc-form/controls/multi-select-search-agents.js +50 -0
  235. package/components/ui/alert-dialog.js +112 -0
  236. package/components/ui/avatar.js +44 -0
  237. package/components/ui/badge.js +54 -0
  238. package/components/ui/button.js +48 -0
  239. package/components/ui/calendar.js +70 -0
  240. package/components/ui/card.js +58 -0
  241. package/components/ui/carousel.js +238 -0
  242. package/components/ui/checkbox.js +27 -0
  243. package/components/ui/command.js +111 -0
  244. package/components/ui/context-menu.js +154 -0
  245. package/components/ui/dialog.js +104 -0
  246. package/components/ui/dropdown-menu.js +169 -0
  247. package/components/ui/form.js +104 -0
  248. package/components/ui/input.js +115 -0
  249. package/components/ui/label.js +20 -0
  250. package/components/ui/popover.js +29 -0
  251. package/components/ui/radio-group.js +53 -0
  252. package/components/ui/scroll-area.js +38 -0
  253. package/components/ui/select.js +147 -0
  254. package/components/ui/separator.js +24 -0
  255. package/components/ui/sheet.js +113 -0
  256. package/components/ui/skeleton.js +17 -0
  257. package/components/ui/slider.js +26 -0
  258. package/components/ui/sonner.js +76 -0
  259. package/components/ui/switch.js +54 -0
  260. package/components/ui/table-v2.js +269 -0
  261. package/components/ui/table.js +245 -0
  262. package/components/ui/tabs.js +89 -0
  263. package/components/ui/textarea.js +33 -0
  264. package/components/ui/tooltip.js +40 -0
  265. package/components/workflow-editor/nodes/ticket/picker-field.js +233 -0
  266. package/components/zalo-login-qr.js +237 -0
  267. package/components/zalo-login-status-modal.js +45 -0
  268. package/config/env.js +27 -0
  269. package/config/hmr.js +8 -0
  270. package/config/navigation.js +54 -0
  271. package/config/runtime.js +46 -0
  272. package/config/theme.js +274 -0
  273. package/constants/brand-colors.js +32 -0
  274. package/constants/chat-icon.js +34 -0
  275. package/dist/chat.css +1 -0
  276. package/exceptions/throw-exception.js +15 -0
  277. package/hooks/use-chat-attachments.js +106 -0
  278. package/hooks/use-chat-configs.js +62 -0
  279. package/hooks/use-chat-customer.js +33 -0
  280. package/hooks/use-chat-display-config-sync.js +72 -0
  281. package/hooks/use-chat-permission.js +68 -0
  282. package/hooks/use-chat-room-detail.js +44 -0
  283. package/hooks/use-chat-selection.js +26 -0
  284. package/hooks/use-chat-sort.js +69 -0
  285. package/hooks/use-chat-surface.js +8 -0
  286. package/hooks/use-common-filter-options.js +70 -0
  287. package/hooks/use-crm-organization-filter-options.js +0 -0
  288. package/hooks/use-crm-organizations-data.js +23 -0
  289. package/hooks/use-debounce.js +14 -0
  290. package/hooks/use-dynamic-fields.js +7 -0
  291. package/hooks/use-facebook-notice.js +24 -0
  292. package/hooks/use-feature-flags.js +6 -0
  293. package/hooks/use-field-layout-override.js +46 -0
  294. package/hooks/use-file-upload.js +119 -0
  295. package/hooks/use-file-viewer.js +93 -0
  296. package/hooks/use-frozen-columns.js +29 -0
  297. package/hooks/use-horizontal-scroll.js +41 -0
  298. package/hooks/use-image-preview-queue.js +101 -0
  299. package/hooks/use-inline-editable-cell.js +97 -0
  300. package/hooks/use-intersection-observer.js +24 -0
  301. package/hooks/use-interval.js +20 -0
  302. package/hooks/use-latest-message-id.js +13 -0
  303. package/hooks/use-lead-filter-options.js +0 -0
  304. package/hooks/use-logout-listener.js +43 -0
  305. package/hooks/use-org-tree.js +43 -0
  306. package/hooks/use-permission.js +33 -0
  307. package/hooks/use-quick-filters.js +175 -0
  308. package/hooks/use-resizable-table.js +0 -0
  309. package/hooks/use-setting-common.js +389 -0
  310. package/hooks/use-settings.js +41 -0
  311. package/hooks/use-sort-bindings.js +24 -0
  312. package/hooks/use-table-display-config.js +0 -0
  313. package/hooks/use-tenant-config.js +99 -0
  314. package/hooks/use-update-user-config.js +150 -0
  315. package/hooks/use-zalo-connection.js +57 -0
  316. package/hooks/use-zalo-mention-list.js +31 -0
  317. package/index.css +624 -0
  318. package/index.js +100 -0
  319. package/lib/deal-name.js +27 -0
  320. package/lib/i18n/react.js +7 -0
  321. package/lib/i18n/use-language.js +0 -0
  322. package/lib/query-client.js +34 -0
  323. package/lib/utils.js +25 -0
  324. package/package.json +108 -0
  325. package/provider/chat-context.js +9 -0
  326. package/provider/chat-provider.js +177 -0
  327. package/provider/chat-room-context.js +9 -0
  328. package/provider/chat-room-hooks.js +8 -0
  329. package/provider/chat-room-provider.js +84 -0
  330. package/provider/hooks.js +22 -0
  331. package/provider/index.js +42 -0
  332. package/public/contact-card-user.svg +25 -0
  333. package/public/images/icons/facebook.svg +1 -0
  334. package/public/images/icons/instagram.svg +1 -0
  335. package/public/images/icons/shopee.svg +1 -0
  336. package/public/images/icons/whatsapp.svg +1 -0
  337. package/public/images/icons/zalo.png +0 -0
  338. package/public/images/images.jpg +0 -0
  339. package/public/images/ticket_sources/api.png +0 -0
  340. package/public/images/ticket_sources/email.png +0 -0
  341. package/public/images/ticket_sources/facebook.png +0 -0
  342. package/public/images/ticket_sources/hand.png +0 -0
  343. package/public/images/ticket_sources/instagram.png +0 -0
  344. package/public/images/ticket_sources/livechat.png +0 -0
  345. package/public/images/ticket_sources/shopee.svg +1 -0
  346. package/public/images/ticket_sources/sms.png +0 -0
  347. package/public/images/ticket_sources/telephone-call-in.png +0 -0
  348. package/public/images/ticket_sources/telephone-call-out.png +0 -0
  349. package/public/images/ticket_sources/tiktok.png +0 -0
  350. package/public/images/ticket_sources/tiktok_shop.png +0 -0
  351. package/public/images/ticket_sources/whatsapp.png +0 -0
  352. package/public/images/ticket_sources/whatsapp.svg +1 -0
  353. package/public/images/ticket_sources/zalo.png +0 -0
  354. package/public/placeholder.svg +13 -0
  355. package/store/app-status-store.js +17 -0
  356. package/store/chat-display-config-store.js +182 -0
  357. package/store/chat-display-config.resolvers.js +88 -0
  358. package/store/chat-store-helpers.js +62 -0
  359. package/store/chat-store.js +931 -0
  360. package/store/crm-organization-list-filter-store.js +126 -0
  361. package/store/customer-list-filter-store.js +139 -0
  362. package/store/devtools.js +9 -0
  363. package/store/duplicate-phone-warning-store.js +18 -0
  364. package/store/entity-extract-store.js +72 -0
  365. package/store/file-preview-store.js +27 -0
  366. package/store/filter-store.js +289 -0
  367. package/store/index.js +22 -0
  368. package/store/knowledge-crawl-store.js +53 -0
  369. package/store/lead-list-filter-store.js +142 -0
  370. package/store/list-pagination-state.js +44 -0
  371. package/store/lumi-learn-from-chat-store.js +69 -0
  372. package/store/merge-suggestion-store.js +27 -0
  373. package/store/notification-store.js +63 -0
  374. package/store/preview-image-store.js +22 -0
  375. package/store/settings-store/multi-channel/email-list-store.js +20 -0
  376. package/store/settings-store/multi-channel/facebook-list-store.js +22 -0
  377. package/store/settings-store/multi-channel/google-list-store.js +20 -0
  378. package/store/settings-store/multi-channel/instagram-list-store.js +20 -0
  379. package/store/settings-store/multi-channel/misa-list-store.js +20 -0
  380. package/store/settings-store/multi-channel/nhanhvn-list-store.js +20 -0
  381. package/store/settings-store/multi-channel/shopee-list-store.js +20 -0
  382. package/store/settings-store/multi-channel/shopee-order-list-store.js +20 -0
  383. package/store/settings-store/multi-channel/sms-list-store.js +20 -0
  384. package/store/settings-store/multi-channel/tiktok-business-list-store.js +20 -0
  385. package/store/settings-store/multi-channel/tiktok-list-store.js +20 -0
  386. package/store/settings-store/multi-channel/zalo-list-store.js +22 -0
  387. package/store/settings-store/multi-channel/zalo-user-list-store.js +25 -0
  388. package/store/smart-qc-store/smart-qc-list-filter-store.js +70 -0
  389. package/store/tenant-config-store.js +21 -0
  390. package/store/user-detail-store.js +17 -0
  391. package/store/user-status-store.js +33 -0
  392. package/store/workflow-store.js +24 -0
  393. package/store/workspace-store.js +32 -0
  394. package/tailwind-preset.js +129 -0
  395. package/utils/chat-actions.js +414 -0
  396. package/utils/chat_socket.js +1225 -0
  397. package/utils/extra-columns.js +108 -0
  398. package/utils/file.js +316 -0
  399. package/utils/html-to-plain-text.js +12 -0
  400. package/utils/notification.js +71 -0
  401. package/utils/replaceZaloSprite.js +64 -0
  402. package/utils/show-be-error.js +17 -0
  403. package/utils/typing_socket.js +101 -0
@@ -0,0 +1,1164 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { Button } from "../../custom/button.js";
3
+ import {
4
+ ImagePlus,
5
+ Paperclip,
6
+ CalendarIcon,
7
+ UserRoundPlus,
8
+ Users,
9
+ ALargeSmall,
10
+ Smile,
11
+ IdCard
12
+ } from "lucide-react";
13
+ import {
14
+ TooltipProvider,
15
+ Tooltip,
16
+ TooltipTrigger,
17
+ TooltipContent
18
+ } from "../../ui/tooltip.js";
19
+ import {
20
+ useState,
21
+ useRef,
22
+ useCallback,
23
+ useEffect,
24
+ useMemo,
25
+ lazy,
26
+ Suspense
27
+ } from "react";
28
+ import { createPortal } from "react-dom";
29
+ const EmojiPickerCombined = lazy(
30
+ () => import("./emoji-picker-combined.js").then((m) => ({
31
+ default: m.EmojiPickerCombined
32
+ }))
33
+ );
34
+ import FileInput from "../../custom/inputs/file-input.js";
35
+ import { Textarea } from "../../ui/textarea.js";
36
+ import { Checkbox } from "../../ui/checkbox.js";
37
+ import { sendMessage } from "../../../utils/chat_socket.js";
38
+ import { emitTyping } from "../../../utils/typing_socket.js";
39
+ import {
40
+ getAcceptFileSize,
41
+ getAcceptFileType,
42
+ getAcceptImageType,
43
+ getImageInputMaxSize,
44
+ getFileExtension,
45
+ isImage,
46
+ isVideo,
47
+ shouldValidateImageInputSize,
48
+ uploadFileToCloudflare,
49
+ validateFileExtension,
50
+ validateFileSize,
51
+ validateImageInputSize
52
+ } from "../../../utils/file.js";
53
+ import useChatStore from "../../../store/chat-store.js";
54
+ import { throwException } from "../../../exceptions/throw-exception.js";
55
+ import { useGetZaloAllocatedList } from "../../../hooks/use-setting-common.js";
56
+ import { cn, isEnableFBHumanTag } from "../../../lib/utils.js";
57
+ import { SourceDetailTicket } from "@zenify-omni/chat-js/constants/chat-source.js";
58
+ import {
59
+ CreateZaloReminder
60
+ } from "./zalo-reminder/create-zalo-reminder.js";
61
+ import {
62
+ useCheckAccessByPZaloConnection,
63
+ useSendZaloReminder
64
+ } from "./zalo-reminder/use-send-zalo-reminder.js";
65
+ import { getUser } from "@zenify-omni/chat-js/utils/auth.js";
66
+ import {
67
+ showErrorToast,
68
+ showWarningToast
69
+ } from "../../ui/sonner.js";
70
+ import useModelAddZalo from "../zalo/modal-add-friend-zalo.js";
71
+ import {
72
+ TypeModelAddZalo
73
+ } from "../zalo/modal-add-friend-zalo.js";
74
+ import ModalAddZalo from "../zalo/modal-action-zalo.js";
75
+ import { CHATTER_TYPES } from "./constants.js";
76
+ import {
77
+ isFacebook,
78
+ isZalo,
79
+ isZaloGroup,
80
+ isZaloPersonal,
81
+ isZaloGeneralChannel,
82
+ isTiktok
83
+ } from "../util.js";
84
+ import PastedFilePreview from "./pasted-file-preview.js";
85
+ import { useImagePreviewQueue } from "../../../hooks/use-image-preview-queue.js";
86
+ import { CommonGroupsModal } from "./common-groups-modal.js";
87
+ import { SendContactCardModal } from "./send-contact-card-modal.js";
88
+ import { ChatReplyPreview } from "./chat-reply-preview.js";
89
+ import { sendZaloPersonalMessageWithFiles } from "@zenify-omni/chat-js/api/chat.js";
90
+ import { v4 as uuidv4 } from "uuid";
91
+ import { syncShopeeMessages } from "@zenify-omni/chat-js/api/shopee.js";
92
+ import { ZaloPersonalEditor } from "./zalo-personal-editor.js";
93
+ import { toast } from "sonner";
94
+ import { useZaloMentionList } from "../../../hooks/use-zalo-mention-list.js";
95
+ import { useTranslation } from "react-i18next";
96
+ import i18n from "@zenify-omni/chat-js/lib/i18n/index.js";
97
+ const EMPTY = {};
98
+ function ChatInput({ isChatAllowed: isChatAllowedProp, className, ...rest }) {
99
+ const isChatAllowedStore = useChatStore((state) => state.isChatAllowed);
100
+ const isChatAllowed = isChatAllowedProp ?? isChatAllowedStore;
101
+ if (!isChatAllowed) return null;
102
+ return /* @__PURE__ */ jsx("div", { className: cn("flex-shrink-0 border-t border-t-border bg-card", className), children: /* @__PURE__ */ jsx(ChatInputInner, { ...rest }) });
103
+ }
104
+ function ChatInputInner({
105
+ isAddFriendZalo,
106
+ ticketId,
107
+ chatRoom = {},
108
+ requester,
109
+ pastedFiles = [],
110
+ setPastedFiles,
111
+ showPreview
112
+ }) {
113
+ const { t } = useTranslation();
114
+ const user = getUser();
115
+ const replySelected = useChatStore((state) => state.replySelected);
116
+ const setReplySelected = useChatStore((state) => state.setReplySelected);
117
+ const setDraftMessages = useChatStore((state) => state.setDraftMessages);
118
+ const clearDraftMessage = useChatStore((state) => state.clearDraftMessage);
119
+ const [message, setMessage] = useState("");
120
+ const messageRef = useRef(message);
121
+ const [isReminderDialogOpen, setIsReminderDialogOpen] = useState(false);
122
+ const [isPastedFilesSending, setIsPastedFilesSending] = useState(false);
123
+ const [isCommonGroupsOpen, setIsCommonGroupsOpen] = useState(false);
124
+ const [isSendContactCardOpen, setIsSendContactCardOpen] = useState(false);
125
+ const [isToolbarVisible, setIsToolbarVisible] = useState(false);
126
+ const [isEmojiPickerOpen, setIsEmojiPickerOpen] = useState(false);
127
+ const [emojiPickerPos, setEmojiPickerPos] = useState({ top: 0, left: 0 });
128
+ const emojiButtonRef = useRef(null);
129
+ const emojiPickerRef = useRef(null);
130
+ const handleToggleEmojiPicker = useCallback(() => {
131
+ if (!isEmojiPickerOpen && emojiButtonRef.current) {
132
+ const rect = emojiButtonRef.current.getBoundingClientRect();
133
+ setEmojiPickerPos({ top: rect.top, left: rect.left });
134
+ }
135
+ setIsEmojiPickerOpen((v) => !v);
136
+ }, [isEmojiPickerOpen]);
137
+ useEffect(() => {
138
+ if (!isEmojiPickerOpen) return;
139
+ const handleClickOutside = (e) => {
140
+ if (emojiPickerRef.current && !emojiPickerRef.current.contains(e.target) && emojiButtonRef.current && !emojiButtonRef.current.contains(e.target)) {
141
+ setIsEmojiPickerOpen(false);
142
+ }
143
+ };
144
+ document.addEventListener("mousedown", handleClickOutside);
145
+ return () => document.removeEventListener("mousedown", handleClickOutside);
146
+ }, [isEmojiPickerOpen]);
147
+ const lastCustomerMsgTime = useChatStore((state) => {
148
+ const messages = state.ticketChatMessages[ticketId] || [];
149
+ const customerTypes = [CHATTER_TYPES.CONTACT, CHATTER_TYPES.CUSTOMER];
150
+ for (let i = messages.length - 1; i >= 0; i--) {
151
+ const msg = messages[i];
152
+ if (customerTypes.includes(msg.chatter_type) && msg.created_at) {
153
+ return new Date(msg.created_at).getTime();
154
+ }
155
+ }
156
+ return null;
157
+ });
158
+ const isLastMsgOver24h = useMemo(() => {
159
+ if (!lastCustomerMsgTime) return false;
160
+ return Date.now() - lastCustomerMsgTime > 24 * 60 * 60 * 1e3;
161
+ }, [lastCustomerMsgTime]);
162
+ const isHumanTagEnabled = isFacebook(chatRoom) && isEnableFBHumanTag();
163
+ const [isHumanTag, setIsHumanTag] = useState(false);
164
+ useEffect(() => {
165
+ setIsHumanTag(isHumanTagEnabled && isLastMsgOver24h);
166
+ }, [isHumanTagEnabled, isLastMsgOver24h]);
167
+ const fileInputRef = useRef(null);
168
+ const imageInputRef = useRef(null);
169
+ const typingTimerRef = useRef(null);
170
+ const textareaRef = useRef(null);
171
+ const tiptapRef = useRef(null);
172
+ const chatInputContainerRef = useRef(null);
173
+ useEffect(() => {
174
+ setReplySelected(null);
175
+ }, [setReplySelected, ticketId]);
176
+ useEffect(() => {
177
+ setIsToolbarVisible(false);
178
+ }, [ticketId]);
179
+ useEffect(() => {
180
+ messageRef.current = message;
181
+ }, [message]);
182
+ useEffect(() => {
183
+ const currentTicketId = ticketId;
184
+ return () => {
185
+ const text = messageRef.current;
186
+ if (text?.trim()) {
187
+ useChatStore.getState().setDraftMessages(currentTicketId, text);
188
+ } else {
189
+ useChatStore.getState().clearDraftMessage(currentTicketId);
190
+ }
191
+ };
192
+ }, [ticketId]);
193
+ useEffect(() => {
194
+ const draft = useChatStore.getState().draftMessages[ticketId];
195
+ if (draft) {
196
+ setMessage(draft);
197
+ messageRef.current = draft;
198
+ setTimeout(() => {
199
+ tiptapRef.current?.setContent(draft, []);
200
+ }, 50);
201
+ } else {
202
+ setMessage("");
203
+ messageRef.current = "";
204
+ }
205
+ }, [ticketId]);
206
+ useEffect(() => {
207
+ if (message !== messageRef.current) return;
208
+ const timer = setTimeout(() => {
209
+ if (message.trim()) {
210
+ setDraftMessages(ticketId, message);
211
+ } else {
212
+ clearDraftMessage(ticketId);
213
+ }
214
+ }, 300);
215
+ return () => clearTimeout(timer);
216
+ }, [message, ticketId, setDraftMessages, clearDraftMessage]);
217
+ const handleTyping = useCallback(() => {
218
+ if (!chatRoom?._id) return;
219
+ emitTyping(chatRoom._id, true);
220
+ clearTimeout(typingTimerRef.current);
221
+ typingTimerRef.current = setTimeout(() => {
222
+ emitTyping(chatRoom._id, false);
223
+ }, 3e3);
224
+ }, [chatRoom?._id]);
225
+ const handleEmojiSelect = useCallback(
226
+ (emojiOrCode) => {
227
+ const text = typeof emojiOrCode === "string" ? emojiOrCode : emojiOrCode.native ?? emojiOrCode.id;
228
+ if (isZaloPersonal(chatRoom)) {
229
+ tiptapRef.current?.insertText(text);
230
+ } else {
231
+ const el = textareaRef.current;
232
+ if (el) {
233
+ const start = el.selectionStart;
234
+ const end = el.selectionEnd;
235
+ const newVal = message.slice(0, start) + text + message.slice(end);
236
+ setMessage(newVal);
237
+ setTimeout(() => {
238
+ el.focus();
239
+ el.setSelectionRange(start + text.length, start + text.length);
240
+ }, 0);
241
+ } else {
242
+ setMessage((prev) => prev + text);
243
+ }
244
+ }
245
+ },
246
+ [chatRoom, message]
247
+ );
248
+ const clearFileInput = useCallback((inputRef) => {
249
+ if (inputRef?.current) {
250
+ inputRef.current.value = "";
251
+ }
252
+ }, []);
253
+ const uploadFile = useCallback(async (file) => {
254
+ try {
255
+ const contentType = file.type || "application/octet-stream";
256
+ const data = await uploadFileToCloudflare(file, {
257
+ content_type: contentType
258
+ });
259
+ return data || null;
260
+ } catch (error) {
261
+ throwException(error);
262
+ return null;
263
+ }
264
+ }, []);
265
+ const handleSendMessage = useCallback(
266
+ (msg, type = "text", fileName = null) => {
267
+ let content = msg?.trim();
268
+ if (!content) return;
269
+ const options = {
270
+ is_personal_zalo: chatRoom?.is_personal_zalo
271
+ };
272
+ if (isHumanTag) {
273
+ options.tag = "HUMAN_AGENT";
274
+ }
275
+ clearTimeout(typingTimerRef.current);
276
+ emitTyping(chatRoom?._id, false);
277
+ sendMessage(chatRoom, content, type, options, fileName);
278
+ setMessage("");
279
+ clearDraftMessage(ticketId);
280
+ setReplySelected(null);
281
+ if (isHumanTag) {
282
+ setIsHumanTag(false);
283
+ }
284
+ },
285
+ [chatRoom, isHumanTag, setReplySelected, clearDraftMessage, ticketId]
286
+ );
287
+ const handleSendZaloMessage = (text, styles, mentionData) => {
288
+ if (!text?.trim()) return;
289
+ const options = {};
290
+ if (styles?.length > 0) options.styles = styles;
291
+ if (mentionData) options._mentionData = mentionData;
292
+ clearTimeout(typingTimerRef.current);
293
+ emitTyping(chatRoom?._id, false);
294
+ sendMessage(chatRoom, text, "text", options, null);
295
+ clearDraftMessage(ticketId);
296
+ setReplySelected(null);
297
+ };
298
+ const { addFilesToPreview } = useImagePreviewQueue(chatRoom, setPastedFiles);
299
+ const handleSelectImage = useCallback(
300
+ (files) => {
301
+ const valid = [];
302
+ for (const file of Array.from(files)) {
303
+ const [ok, message2] = shouldValidateImageInputSize(file) ? validateImageInputSize(file, chatRoom) : validateFileSize(file, chatRoom);
304
+ if (!ok) {
305
+ showWarningToast(message2);
306
+ clearFileInput(imageInputRef);
307
+ return;
308
+ }
309
+ valid.push(file);
310
+ }
311
+ addFilesToPreview(valid);
312
+ clearFileInput(imageInputRef);
313
+ },
314
+ [chatRoom, addFilesToPreview, clearFileInput]
315
+ );
316
+ const handleSelectFile = useCallback(
317
+ (files) => {
318
+ addFilesToPreview(files);
319
+ clearFileInput(fileInputRef);
320
+ },
321
+ [addFilesToPreview, clearFileInput]
322
+ );
323
+ const { handleSendReminderRequest } = useSendZaloReminder(chatRoom);
324
+ const { success: isPersonalZaloConnection, _refetch } = useCheckAccessByPZaloConnection({ ticketId, userId: user?._id });
325
+ const handleOpenReminderDialog = useCallback(() => {
326
+ _refetch().then(() => {
327
+ if (!isPersonalZaloConnection) {
328
+ showErrorToast(
329
+ i18n.t("chat:message.noPersonalZaloLinked", {
330
+ defaultValue: "B\u1EA1n ch\u01B0a li\xEAn k\u1EBFt Zalo c\xE1 nh\xE2n"
331
+ })
332
+ );
333
+ } else {
334
+ setIsReminderDialogOpen(true);
335
+ }
336
+ }).catch(() => {
337
+ showErrorToast(
338
+ i18n.t("chat:message.noPersonalZaloLinked", {
339
+ defaultValue: "B\u1EA1n ch\u01B0a li\xEAn k\u1EBFt Zalo c\xE1 nh\xE2n"
340
+ })
341
+ );
342
+ });
343
+ }, [isPersonalZaloConnection, _refetch]);
344
+ const handlePasteMessage = (e) => {
345
+ const items = e.clipboardData.items;
346
+ const collected = [];
347
+ let fileValid = true;
348
+ let errorMessage = "";
349
+ for (let i = 0; i < items.length; i++) {
350
+ const entry = items[i].webkitGetAsEntry?.();
351
+ if (entry?.isDirectory) continue;
352
+ const item = items[i];
353
+ if (item.kind !== "file") continue;
354
+ const file = item.getAsFile?.();
355
+ if (!file) continue;
356
+ if (shouldValidateImageInputSize(file)) {
357
+ ;
358
+ [fileValid, errorMessage] = validateImageInputSize(file, chatRoom);
359
+ } else {
360
+ ;
361
+ [fileValid, errorMessage] = validateFileSize(file, chatRoom);
362
+ }
363
+ if (!fileValid) break;
364
+ [fileValid, errorMessage] = validateFileExtension(file, chatRoom);
365
+ if (!fileValid) break;
366
+ collected.push(file);
367
+ }
368
+ if (!fileValid) {
369
+ showWarningToast(errorMessage);
370
+ } else if (collected.length > 0) {
371
+ addFilesToPreview(collected);
372
+ }
373
+ };
374
+ const removeFile = (fileId) => {
375
+ setPastedFiles?.((prev) => {
376
+ const updated = prev.filter((f) => f.id !== fileId);
377
+ const removed = prev.find((f) => f.id === fileId);
378
+ if (removed) {
379
+ URL.revokeObjectURL(removed.preview);
380
+ }
381
+ return updated;
382
+ });
383
+ };
384
+ const sendPastedFilesImpl = useCallback(async () => {
385
+ const sendableFiles = pastedFiles.filter(
386
+ (f) => !f.compressionError && !f.isCompressing
387
+ );
388
+ const skippedFiles = pastedFiles.filter(
389
+ (f) => f.compressionError || f.isCompressing
390
+ );
391
+ const revokeSkippedPreviews = () => skippedFiles.forEach((f) => URL.revokeObjectURL(f.preview));
392
+ if (skippedFiles.length > 0) {
393
+ showWarningToast(
394
+ i18n.t("chat:message.imagesSkippedLimit", {
395
+ length: skippedFiles.length,
396
+ defaultValue: "{length} \u1EA3nh b\u1ECB b\u1ECF qua do ch\u01B0a t\u1ED1i \u01B0u xong ho\u1EB7c v\u01B0\u1EE3t gi\u1EDBi h\u1EA1n."
397
+ })
398
+ );
399
+ }
400
+ if (sendableFiles.length === 0) {
401
+ return;
402
+ }
403
+ if (isZaloPersonal(chatRoom)) {
404
+ const sourceId = chatRoom?.source_id;
405
+ const userZaloId = chatRoom?.source_room_id;
406
+ const { text: zaloText } = tiptapRef.current?.getData() ?? { text: "" };
407
+ const isGroup = isZaloGroup(chatRoom);
408
+ const BATCH_SIZE = 4;
409
+ const chunkArray = (arr, size) => {
410
+ const chunks = [];
411
+ for (let i = 0; i < arr.length; i += size) {
412
+ chunks.push(arr.slice(i, i + size));
413
+ }
414
+ return chunks;
415
+ };
416
+ const prepareBatch = async (batch, contentType, text = "") => {
417
+ const clientMsgId = uuidv4();
418
+ const settled = await Promise.allSettled(
419
+ batch.map(async (pastedFile) => {
420
+ if (pastedFile.isTemplateUrl) {
421
+ return { url: pastedFile.url, file: null };
422
+ }
423
+ try {
424
+ const url = await uploadFile(pastedFile.file);
425
+ return {
426
+ url,
427
+ file: pastedFile.file
428
+ };
429
+ } catch (err) {
430
+ throwException(err);
431
+ } finally {
432
+ URL.revokeObjectURL(pastedFile.preview);
433
+ }
434
+ })
435
+ );
436
+ const validResults = settled.filter((r) => r.status === "fulfilled" && r.value?.url).map((r) => r.value);
437
+ if (!validResults.length) return null;
438
+ const validUrls = validResults.map((r) => r.url);
439
+ const validFiles = validResults.map((r) => r.file).filter(Boolean);
440
+ const combinedUrl = validUrls.join("||");
441
+ const fileName = contentType === "image" ? null : validResults[0]?.file?.name ?? null;
442
+ const content = contentType === "image" ? {
443
+ type: "image",
444
+ text,
445
+ src: combinedUrl
446
+ } : {
447
+ type: contentType,
448
+ src: combinedUrl,
449
+ filename: fileName
450
+ };
451
+ useChatStore.getState().addNewMessage(chatRoom._id, {
452
+ _id: clientMsgId,
453
+ client_msg_id: clientMsgId,
454
+ room_id: chatRoom._id,
455
+ chatter_id: user?._id,
456
+ chatter_name: user?.name,
457
+ chatter_type: "agent",
458
+ source: chatRoom.source,
459
+ session_id: chatRoom.session?._id,
460
+ session: chatRoom.session,
461
+ content,
462
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
463
+ status: "pending"
464
+ });
465
+ return {
466
+ clientMsgId,
467
+ sourceId,
468
+ userZaloId,
469
+ isGroup,
470
+ text,
471
+ roomId: chatRoom._id,
472
+ sessionId: chatRoom.session?._id,
473
+ chatterId: user?._id,
474
+ chatterName: user?.name,
475
+ chatterAvatar: user?.avatar,
476
+ files: validFiles,
477
+ content
478
+ };
479
+ };
480
+ const callBatchApi = async ({ clientMsgId, ...apiParams }) => {
481
+ try {
482
+ const { data: body } = await sendZaloPersonalMessageWithFiles({
483
+ clientMsgId,
484
+ ...apiParams
485
+ });
486
+ if (body?.success) {
487
+ useChatStore.getState().updateMessageByClientMsgId(chatRoom._id, clientMsgId, {
488
+ _id: body.data.chat_message_id,
489
+ status: "success"
490
+ });
491
+ } else {
492
+ useChatStore.getState().updateMessageByClientMsgId(chatRoom._id, clientMsgId, {
493
+ _id: body.data.chat_message_id,
494
+ status: "failed"
495
+ });
496
+ }
497
+ } catch {
498
+ useChatStore.getState().updateMessageByClientMsgId(chatRoom._id, clientMsgId, {
499
+ status: "failed"
500
+ });
501
+ }
502
+ };
503
+ const templateEntries = sendableFiles.filter((f) => f.isTemplateUrl);
504
+ const regularEntries = sendableFiles.filter((f) => !f.isTemplateUrl);
505
+ const imageFiles = regularEntries.filter(
506
+ (f) => isImage(f.type, getFileExtension(f.name))
507
+ );
508
+ const videoFiles = regularEntries.filter(
509
+ (f) => !isImage(f.type, getFileExtension(f.name)) && isVideo(f.type, getFileExtension(f.name))
510
+ );
511
+ const otherFiles = regularEntries.filter(
512
+ (f) => !isImage(f.type, getFileExtension(f.name)) && !isVideo(f.type, getFileExtension(f.name))
513
+ );
514
+ const imageBatches = chunkArray(imageFiles, BATCH_SIZE);
515
+ const apiDescriptors = [];
516
+ const sendTextSeparately = !(regularEntries.length === 1 && imageFiles.length === 1);
517
+ if (sendTextSeparately && zaloText?.trim()) {
518
+ handleSendMessage(zaloText, "text");
519
+ }
520
+ let textSent = sendTextSeparately;
521
+ for (const batch of imageBatches) {
522
+ const descriptor = await prepareBatch(
523
+ batch,
524
+ "image",
525
+ textSent ? "" : zaloText
526
+ );
527
+ if (descriptor) {
528
+ apiDescriptors.push(descriptor);
529
+ textSent = true;
530
+ }
531
+ }
532
+ const singleFileEntries = [
533
+ ...videoFiles.map((file) => ({ file, contentType: "video" })),
534
+ ...otherFiles.map((file) => ({ file, contentType: "file" }))
535
+ ];
536
+ for (const { file, contentType } of singleFileEntries) {
537
+ const descriptor = await prepareBatch([file], contentType, "");
538
+ if (descriptor) {
539
+ apiDescriptors.push(descriptor);
540
+ }
541
+ }
542
+ if (templateEntries.length > 0) {
543
+ if (!textSent && zaloText?.trim()) {
544
+ handleSendMessage(zaloText, "text");
545
+ textSent = true;
546
+ }
547
+ templateEntries.forEach((f) => {
548
+ const ext = getFileExtension(f.name);
549
+ let msgType = "file";
550
+ if (isImage(f.type, ext)) msgType = "image";
551
+ else if (isVideo(f.type, ext)) msgType = "video";
552
+ const fileName = msgType === "file" ? null : f.name;
553
+ handleSendMessage(f.url, msgType, fileName);
554
+ });
555
+ }
556
+ if (!apiDescriptors.length && !templateEntries.length) {
557
+ return;
558
+ }
559
+ tiptapRef.current?.clear();
560
+ clearDraftMessage(ticketId);
561
+ revokeSkippedPreviews();
562
+ setPastedFiles?.([]);
563
+ (async () => {
564
+ for (const descriptor of apiDescriptors) {
565
+ await callBatchApi(descriptor);
566
+ }
567
+ })();
568
+ return;
569
+ } else {
570
+ const files = [];
571
+ const images = [];
572
+ for (const pastedFile of sendableFiles) {
573
+ if (pastedFile.isTemplateUrl) {
574
+ if (isImage(pastedFile.type, getFileExtension(pastedFile.name))) {
575
+ images.push(pastedFile.url);
576
+ } else {
577
+ files.push({ url: pastedFile.url, name: pastedFile.name });
578
+ }
579
+ continue;
580
+ }
581
+ try {
582
+ const uploadedUrl = await uploadFile(pastedFile.file);
583
+ if (uploadedUrl) {
584
+ if (isImage(pastedFile.type, getFileExtension(pastedFile.name))) {
585
+ images.push(uploadedUrl);
586
+ } else {
587
+ files.push({ url: uploadedUrl, name: pastedFile.name });
588
+ }
589
+ }
590
+ } catch (error) {
591
+ throwException(error);
592
+ } finally {
593
+ URL.revokeObjectURL(pastedFile.preview);
594
+ }
595
+ }
596
+ if (images.length > 0) {
597
+ if (isZalo(chatRoom)) {
598
+ images.forEach((url) => handleSendMessage(url, "image"));
599
+ } else {
600
+ handleSendMessage(images.join("||"), "image");
601
+ }
602
+ }
603
+ files.forEach(({ url, name }) => handleSendMessage(url, "file", name));
604
+ const text = textareaRef.current?.value?.trim();
605
+ if (text) {
606
+ handleSendMessage(text, "text");
607
+ }
608
+ revokeSkippedPreviews();
609
+ setPastedFiles?.([]);
610
+ }
611
+ }, [
612
+ chatRoom,
613
+ setPastedFiles,
614
+ user?.name,
615
+ user?._id,
616
+ user?.avatar,
617
+ pastedFiles,
618
+ clearDraftMessage,
619
+ ticketId,
620
+ uploadFile,
621
+ handleSendMessage
622
+ ]);
623
+ const sendingRoomIdsRef = useRef(/* @__PURE__ */ new Set());
624
+ const currentRoomIdRef = useRef(chatRoom?._id);
625
+ useEffect(() => {
626
+ currentRoomIdRef.current = chatRoom?._id;
627
+ setIsPastedFilesSending(sendingRoomIdsRef.current.has(chatRoom?._id));
628
+ }, [chatRoom?._id]);
629
+ const handleSendPastedFiles = useCallback(async () => {
630
+ const roomId = chatRoom?._id;
631
+ const inflightRooms = sendingRoomIdsRef.current;
632
+ if (inflightRooms.has(roomId)) return;
633
+ inflightRooms.add(roomId);
634
+ setIsPastedFilesSending(true);
635
+ try {
636
+ await sendPastedFilesImpl();
637
+ } finally {
638
+ inflightRooms.delete(roomId);
639
+ if (currentRoomIdRef.current === roomId) setIsPastedFilesSending(false);
640
+ }
641
+ }, [sendPastedFilesImpl, chatRoom?._id]);
642
+ const clearAllFiles = () => {
643
+ pastedFiles.forEach((f) => URL.revokeObjectURL(f.preview));
644
+ setPastedFiles?.([]);
645
+ };
646
+ const handleSendPastedFilesRef = useRef(handleSendPastedFiles);
647
+ useEffect(() => {
648
+ handleSendPastedFilesRef.current = handleSendPastedFiles;
649
+ }, [handleSendPastedFiles]);
650
+ useEffect(() => {
651
+ const container = chatInputContainerRef.current;
652
+ if (!pastedFiles?.length || !container) return;
653
+ const onKeyDown = (e) => {
654
+ if (e.key !== "Enter" || e.shiftKey) return;
655
+ e.preventDefault();
656
+ e.stopPropagation();
657
+ handleSendPastedFilesRef.current();
658
+ };
659
+ container.addEventListener("keydown", onKeyDown, true);
660
+ return () => container.removeEventListener("keydown", onKeyDown, true);
661
+ }, [pastedFiles?.length]);
662
+ const [isMentionTriggered, setIsMentionTriggered] = useState(false);
663
+ useEffect(() => {
664
+ setIsMentionTriggered(false);
665
+ }, [chatRoom?._id]);
666
+ useEffect(() => {
667
+ return () => {
668
+ clearTimeout(typingTimerRef.current);
669
+ if (chatRoom?._id) emitTyping(chatRoom._id, false);
670
+ };
671
+ }, [chatRoom?._id]);
672
+ const typingUsers = useChatStore(
673
+ (state) => state.typingUsers[chatRoom?._id] ?? EMPTY
674
+ );
675
+ const typingUserList = Object.values(typingUsers);
676
+ const typingLabel = typingUserList.length > 0 ? t("chat:status.typing", {
677
+ defaultValue: "{name} \u0111ang nh\u1EADp",
678
+ name: `${typingUserList.slice(0, 3).map((u) => u.name).join(", ")}${typingUserList.length > 3 ? ` +${typingUserList.length - 3}` : ""}`
679
+ }) : null;
680
+ const mentionList = useZaloMentionList(chatRoom, {
681
+ enabled: isMentionTriggered
682
+ });
683
+ const isShowGroupModal = (() => {
684
+ if (isZaloGroup(chatRoom)) return false;
685
+ if (isZaloPersonal(chatRoom) && !isZaloGeneralChannel(chatRoom) && chatRoom?.source_id === user?.zalo_id)
686
+ return true;
687
+ return isZaloGeneralChannel(chatRoom);
688
+ })();
689
+ return /* @__PURE__ */ jsxs(
690
+ "div",
691
+ {
692
+ ref: chatInputContainerRef,
693
+ className: cn(
694
+ "relative flex w-full flex-none flex-col",
695
+ isZaloPersonal(chatRoom) && isToolbarVisible || replySelected ? "min-h-[248px]" : "min-h-[188px]"
696
+ ),
697
+ children: [
698
+ showPreview && pastedFiles?.length > 0 && /* @__PURE__ */ jsx(
699
+ PastedFilePreview,
700
+ {
701
+ pastedFiles,
702
+ onRemove: removeFile,
703
+ onRemoveAll: clearAllFiles,
704
+ onSendPastedFiles: handleSendPastedFiles,
705
+ isSending: isPastedFilesSending
706
+ }
707
+ ),
708
+ /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col", children: /* @__PURE__ */ jsxs("div", { className: "flex h-full w-full flex-1 flex-col overflow-hidden rounded-md border-input", children: [
709
+ /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-1 flex-col", children: [
710
+ replySelected && /* @__PURE__ */ jsx(ChatReplyPreview, { isHideReplyPreview: true, ticketId }),
711
+ isHumanTagEnabled && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-2 border-b border-b-border px-3 py-1.5", children: /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-1.5", children: [
712
+ /* @__PURE__ */ jsx(
713
+ Checkbox,
714
+ {
715
+ checked: isHumanTag,
716
+ onCheckedChange: setIsHumanTag
717
+ }
718
+ ),
719
+ /* @__PURE__ */ jsx("span", { className: "select-none text-xs text-muted-foreground", children: t("chat:action.sendWithHumanAgentTag", {
720
+ defaultValue: "G\u1EEDi tin nh\u1EAFn v\u1EDBi Human Agent tag"
721
+ }) })
722
+ ] }) }),
723
+ /* @__PURE__ */ jsx("div", { className: "relative flex h-full w-full flex-1", children: isZaloPersonal(chatRoom) ? /* @__PURE__ */ jsxs(Fragment, { children: [
724
+ /* @__PURE__ */ jsx(
725
+ ZaloPersonalEditor,
726
+ {
727
+ ref: tiptapRef,
728
+ mentionList: isZaloGroup(chatRoom) ? mentionList : null,
729
+ onMentionTrigger: isZaloGroup(chatRoom) ? () => setIsMentionTriggered(true) : void 0,
730
+ onSend: handleSendZaloMessage,
731
+ onPaste: handlePasteMessage,
732
+ onUpdate: (text) => {
733
+ messageRef.current = text;
734
+ if (text.trim()) {
735
+ setDraftMessages(ticketId, text);
736
+ } else {
737
+ clearDraftMessage(ticketId);
738
+ }
739
+ handleTyping();
740
+ },
741
+ showToolbar: isToolbarVisible
742
+ }
743
+ ),
744
+ typingLabel && /* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute bottom-2 left-3 flex items-center gap-1.5 duration-300 animate-in fade-in", children: [
745
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: typingLabel }),
746
+ /* @__PURE__ */ jsx(
747
+ "span",
748
+ {
749
+ className: "h-1.5 w-1.5 rounded-full bg-muted-foreground/60",
750
+ style: {
751
+ animation: "typing-bounce 1.2s infinite ease-in-out",
752
+ animationDelay: "0ms"
753
+ }
754
+ }
755
+ ),
756
+ /* @__PURE__ */ jsx(
757
+ "span",
758
+ {
759
+ className: "h-1.5 w-1.5 rounded-full bg-muted-foreground/60",
760
+ style: {
761
+ animation: "typing-bounce 1.2s infinite ease-in-out",
762
+ animationDelay: "200ms"
763
+ }
764
+ }
765
+ ),
766
+ /* @__PURE__ */ jsx(
767
+ "span",
768
+ {
769
+ className: "h-1.5 w-1.5 rounded-full bg-muted-foreground/60",
770
+ style: {
771
+ animation: "typing-bounce 1.2s infinite ease-in-out",
772
+ animationDelay: "400ms"
773
+ }
774
+ }
775
+ )
776
+ ] })
777
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
778
+ /* @__PURE__ */ jsx(
779
+ Textarea,
780
+ {
781
+ ref: textareaRef,
782
+ id: "textarea-chat-input",
783
+ rows: 4,
784
+ value: message,
785
+ onFocus: () => {
786
+ if (chatRoom?.source === SourceDetailTicket.SHOPEE) {
787
+ const messages = useChatStore.getState().ticketChatMessages[ticketId] || [];
788
+ const oldestMsg = messages[0];
789
+ syncShopeeMessages({
790
+ roomId: ticketId,
791
+ pageId: chatRoom?.source_id,
792
+ shopeeConversationId: chatRoom?.source_room_id,
793
+ oldestMessageTimestamp: oldestMsg?.created_at
794
+ }).catch((error) => {
795
+ console.error(error);
796
+ });
797
+ }
798
+ },
799
+ onPaste: handlePasteMessage,
800
+ onChange: (e) => {
801
+ const val = e.target.value;
802
+ setMessage(val);
803
+ handleTyping();
804
+ },
805
+ onKeyDown: (e) => {
806
+ if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
807
+ e.preventDefault();
808
+ handleSendMessage(message);
809
+ }
810
+ },
811
+ placeholder: t("chat:placeholder.typeMessage", {
812
+ defaultValue: "Nh\u1EADp tin nh\u1EAFn"
813
+ }),
814
+ className: "flex-1 resize-none rounded-md !border-none p-3 !shadow-none focus-visible:ring-0"
815
+ }
816
+ ),
817
+ typingLabel && !message && /* @__PURE__ */ jsxs("div", { className: "pointer-events-none absolute bottom-2 left-3 flex items-center gap-1.5 duration-300 animate-in fade-in", children: [
818
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: typingLabel }),
819
+ /* @__PURE__ */ jsx(
820
+ "span",
821
+ {
822
+ className: "h-1.5 w-1.5 rounded-full bg-muted-foreground/60",
823
+ style: {
824
+ animation: "typing-bounce 1.2s infinite ease-in-out",
825
+ animationDelay: "0ms"
826
+ }
827
+ }
828
+ ),
829
+ /* @__PURE__ */ jsx(
830
+ "span",
831
+ {
832
+ className: "h-1.5 w-1.5 rounded-full bg-muted-foreground/60",
833
+ style: {
834
+ animation: "typing-bounce 1.2s infinite ease-in-out",
835
+ animationDelay: "200ms"
836
+ }
837
+ }
838
+ ),
839
+ /* @__PURE__ */ jsx(
840
+ "span",
841
+ {
842
+ className: "h-1.5 w-1.5 rounded-full bg-muted-foreground/60",
843
+ style: {
844
+ animation: "typing-bounce 1.2s infinite ease-in-out",
845
+ animationDelay: "400ms"
846
+ }
847
+ }
848
+ )
849
+ ] })
850
+ ] }) })
851
+ ] }),
852
+ /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-shrink-0 items-center gap-1 px-3 py-2", children: [
853
+ !isTiktok(chatRoom) && /* @__PURE__ */ jsxs(Tooltip, { children: [
854
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
855
+ Button,
856
+ {
857
+ size: "sm",
858
+ type: "button",
859
+ variant: "ghost",
860
+ className: "h-8 w-8 p-0",
861
+ children: [
862
+ /* @__PURE__ */ jsx(
863
+ "label",
864
+ {
865
+ htmlFor: "file-upload",
866
+ className: "flex cursor-pointer items-center justify-center",
867
+ children: /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4 stroke-muted-foreground" })
868
+ }
869
+ ),
870
+ /* @__PURE__ */ jsx(
871
+ FileInput,
872
+ {
873
+ ref: fileInputRef,
874
+ id: "file-upload",
875
+ className: "hidden",
876
+ multiple: true,
877
+ onChange: handleSelectFile,
878
+ acceptType: getAcceptFileType(chatRoom),
879
+ maxFileSize: getAcceptFileSize(chatRoom)
880
+ }
881
+ )
882
+ ]
883
+ }
884
+ ) }),
885
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", children: t("chat:label.file", { defaultValue: "T\u1EC7p" }) })
886
+ ] }),
887
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
888
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
889
+ Button,
890
+ {
891
+ size: "sm",
892
+ type: "button",
893
+ variant: "ghost",
894
+ className: "h-8 w-8 p-0",
895
+ children: [
896
+ /* @__PURE__ */ jsx(
897
+ "label",
898
+ {
899
+ htmlFor: "image-upload",
900
+ className: "flex cursor-pointer items-center justify-center",
901
+ children: /* @__PURE__ */ jsx(ImagePlus, { className: "h-4 w-4 stroke-muted-foreground" })
902
+ }
903
+ ),
904
+ /* @__PURE__ */ jsx(
905
+ FileInput,
906
+ {
907
+ ref: imageInputRef,
908
+ id: "image-upload",
909
+ className: "hidden",
910
+ multiple: true,
911
+ acceptType: getAcceptImageType(chatRoom),
912
+ maxFileSize: getImageInputMaxSize(chatRoom),
913
+ onChange: handleSelectImage
914
+ }
915
+ )
916
+ ]
917
+ }
918
+ ) }),
919
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", children: t("chat:label.image", { defaultValue: "\u1EA2nh" }) })
920
+ ] }),
921
+ /* @__PURE__ */ jsxs(Fragment, { children: [
922
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
923
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
924
+ Button,
925
+ {
926
+ ref: emojiButtonRef,
927
+ size: "sm",
928
+ type: "button",
929
+ variant: "ghost",
930
+ className: cn(
931
+ "h-8 w-8 p-0",
932
+ isEmojiPickerOpen && "bg-accent text-accent-foreground"
933
+ ),
934
+ onClick: handleToggleEmojiPicker,
935
+ children: /* @__PURE__ */ jsx(Smile, { className: "h-4 w-4 stroke-muted-foreground" })
936
+ }
937
+ ) }),
938
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", children: t("chat:label.emotion", { defaultValue: "C\u1EA3m x\xFAc" }) })
939
+ ] }),
940
+ isEmojiPickerOpen && createPortal(
941
+ /* @__PURE__ */ jsx(
942
+ "div",
943
+ {
944
+ ref: emojiPickerRef,
945
+ style: {
946
+ position: "fixed",
947
+ top: emojiPickerPos.top,
948
+ left: emojiPickerPos.left,
949
+ transform: "translateY(-100%)",
950
+ zIndex: 9999
951
+ },
952
+ children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
953
+ EmojiPickerCombined,
954
+ {
955
+ onSelect: handleEmojiSelect,
956
+ showZaloTab: isZaloPersonal(chatRoom)
957
+ }
958
+ ) })
959
+ }
960
+ ),
961
+ document.body
962
+ )
963
+ ] }),
964
+ isZaloPersonal(chatRoom) && /* @__PURE__ */ jsxs(Tooltip, { children: [
965
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
966
+ Button,
967
+ {
968
+ size: "sm",
969
+ type: "button",
970
+ variant: "ghost",
971
+ className: "h-8 w-8 p-0",
972
+ onClick: handleOpenReminderDialog,
973
+ children: /* @__PURE__ */ jsx(CalendarIcon, { className: "h-4 w-4 stroke-muted-foreground" })
974
+ }
975
+ ) }),
976
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", children: t("chat:field.appointment", { defaultValue: "L\u1ECBch h\u1EB9n" }) })
977
+ ] }),
978
+ user?.has_permission_login_zalo_qr && isAddFriendZalo && isZaloPersonal(chatRoom) && !isZaloGroup(chatRoom) && /* @__PURE__ */ jsx(BtnAddFriend, { contact: requester, chat_room: chatRoom }),
979
+ isShowGroupModal && /* @__PURE__ */ jsxs(Tooltip, { children: [
980
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
981
+ Button,
982
+ {
983
+ size: "sm",
984
+ type: "button",
985
+ variant: "ghost",
986
+ className: "h-8 w-8 p-0",
987
+ onClick: () => setIsCommonGroupsOpen(true),
988
+ children: /* @__PURE__ */ jsx(Users, { className: "h-4 w-4 stroke-muted-foreground" })
989
+ }
990
+ ) }),
991
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", children: t("chat:field.commonGroup", {
992
+ defaultValue: "Nh\xF3m chung"
993
+ }) })
994
+ ] }),
995
+ isZaloPersonal(chatRoom) && /* @__PURE__ */ jsxs(Tooltip, { children: [
996
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
997
+ Button,
998
+ {
999
+ size: "sm",
1000
+ type: "button",
1001
+ variant: "ghost",
1002
+ className: "h-8 w-8 p-0",
1003
+ onClick: () => setIsSendContactCardOpen(true),
1004
+ children: /* @__PURE__ */ jsx(IdCard, { className: "h-4 w-4 stroke-muted-foreground" })
1005
+ }
1006
+ ) }),
1007
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", children: i18n.t("chat:label.guiDanhThiep", {
1008
+ defaultValue: "G\u1EEDi danh thi\u1EBFp"
1009
+ }) })
1010
+ ] }),
1011
+ isZaloPersonal(chatRoom) && /* @__PURE__ */ jsxs(Tooltip, { children: [
1012
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1013
+ Button,
1014
+ {
1015
+ size: "sm",
1016
+ type: "button",
1017
+ variant: "ghost",
1018
+ className: cn(
1019
+ "h-8 w-8 p-0",
1020
+ isToolbarVisible && "bg-accent text-accent-foreground"
1021
+ ),
1022
+ onClick: () => setIsToolbarVisible((v) => !v),
1023
+ children: /* @__PURE__ */ jsx(ALargeSmall, { className: "h-4 w-4 stroke-muted-foreground" })
1024
+ }
1025
+ ) }),
1026
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", children: "Format" })
1027
+ ] }),
1028
+ isReminderDialogOpen && /* @__PURE__ */ jsx(
1029
+ CreateZaloReminder,
1030
+ {
1031
+ ticket: chatRoom,
1032
+ requester,
1033
+ reminderData: null,
1034
+ open: isReminderDialogOpen,
1035
+ onOpenChange: setIsReminderDialogOpen,
1036
+ onSubmit: handleSendReminderRequest
1037
+ }
1038
+ ),
1039
+ isCommonGroupsOpen && /* @__PURE__ */ jsx(
1040
+ CommonGroupsModal,
1041
+ {
1042
+ open: isCommonGroupsOpen,
1043
+ onOpenChange: setIsCommonGroupsOpen,
1044
+ zaloId: chatRoom?.source_id,
1045
+ customerZaloId: chatRoom?.source_room_id,
1046
+ customerName: requester?.name || requester?.zalo_name,
1047
+ customerAvatar: requester?.avatar,
1048
+ customerCrmType: requester?.crm_type
1049
+ }
1050
+ ),
1051
+ isSendContactCardOpen && /* @__PURE__ */ jsx(
1052
+ SendContactCardModal,
1053
+ {
1054
+ open: isSendContactCardOpen,
1055
+ onOpenChange: setIsSendContactCardOpen,
1056
+ zaloId: chatRoom?.source_id,
1057
+ chatRoom
1058
+ }
1059
+ ),
1060
+ /* @__PURE__ */ jsx("div", { className: "ml-auto flex items-center gap-2", children: /* @__PURE__ */ jsx(
1061
+ Button,
1062
+ {
1063
+ size: "sm",
1064
+ className: "h-8 px-4",
1065
+ onClick: () => {
1066
+ if (isZaloPersonal(chatRoom)) {
1067
+ const data = tiptapRef.current?.getData();
1068
+ if (data?.text?.trim()) {
1069
+ handleSendZaloMessage(
1070
+ data.text,
1071
+ data.styles,
1072
+ data.mentionData
1073
+ );
1074
+ tiptapRef.current?.clear();
1075
+ }
1076
+ } else {
1077
+ handleSendMessage(message);
1078
+ }
1079
+ },
1080
+ children: t("common:action.send", { defaultValue: "G\u1EEDi" })
1081
+ }
1082
+ ) })
1083
+ ] }) })
1084
+ ] }) })
1085
+ ]
1086
+ }
1087
+ );
1088
+ }
1089
+ const BtnAddFriend = ({ contact, chat_room }) => {
1090
+ const { t } = useTranslation();
1091
+ const user = getUser();
1092
+ const [loadingSendMess, setLoadingSendMess] = useState(false);
1093
+ const [loadingZalo, setLoadingZalo] = useState(false);
1094
+ const { data: zaloAllocatedList } = useGetZaloAllocatedList(user);
1095
+ const {
1096
+ zaloPop,
1097
+ setZaloPop,
1098
+ textareaRef,
1099
+ handleSubmit,
1100
+ getTitle,
1101
+ getTitleButon,
1102
+ handleFindUserZalo
1103
+ } = useModelAddZalo({
1104
+ contact,
1105
+ ticket: chat_room,
1106
+ setLoadingZalo,
1107
+ setLoadingSendMess
1108
+ });
1109
+ const checkZaloLimit = () => {
1110
+ if (zaloAllocatedList) {
1111
+ const zaloInfo = zaloAllocatedList.find(
1112
+ (e) => e.zalo_id === chat_room?.source_id
1113
+ );
1114
+ if (!zaloInfo || zaloInfo.sent_friend_requests >= zaloInfo.limit_send_friend_request) {
1115
+ toast.error(
1116
+ i18n.t("chat:message.dailyFriendRequestLimitReached", {
1117
+ defaultValue: "T\xE0i kho\u1EA3n \u0111\xE3 \u0111\u1EA1t \u0111\u1EBFn gi\u1EDBi h\u1EA1n k\u1EBFt b\u1EA1n trong ng\xE0y"
1118
+ })
1119
+ );
1120
+ return false;
1121
+ }
1122
+ }
1123
+ return true;
1124
+ };
1125
+ const handleSubmitRequest = () => {
1126
+ if (!checkZaloLimit()) return;
1127
+ handleFindUserZalo(null, TypeModelAddZalo.AddFriendInChat, {
1128
+ personal_zalo_id: chat_room.source_room_id,
1129
+ page_id: chat_room?.source_id
1130
+ }).then(() => {
1131
+ });
1132
+ };
1133
+ return /* @__PURE__ */ jsxs("div", { className: "inline-block w-fit", children: [
1134
+ /* @__PURE__ */ jsx(
1135
+ ModalAddZalo,
1136
+ {
1137
+ isLoading: loadingSendMess,
1138
+ zaloPop,
1139
+ setZaloPop,
1140
+ getTitle,
1141
+ textareaRef,
1142
+ getTitleButon,
1143
+ handleSubmit
1144
+ }
1145
+ ),
1146
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
1147
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
1148
+ Button,
1149
+ {
1150
+ disabled: loadingZalo,
1151
+ variant: "ghost",
1152
+ size: "sm",
1153
+ className: "h-8 w-8 p-0",
1154
+ onClick: handleSubmitRequest,
1155
+ children: /* @__PURE__ */ jsx(UserRoundPlus, { className: "h-4 w-4 stroke-muted-foreground" })
1156
+ }
1157
+ ) }),
1158
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", children: t("chat:action.addFriend", { defaultValue: "K\u1EBFt b\u1EA1n" }) })
1159
+ ] })
1160
+ ] });
1161
+ };
1162
+ export {
1163
+ ChatInput
1164
+ };