@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,108 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { format, isValid } from "date-fns";
3
+ import { notFoundValue } from "@zenify-omni/chat-js/constants/common.js";
4
+ import { AttributeFieldType } from "@zenify-omni/chat-js/constants/setting.js";
5
+ import PreviewListFiles from "../components/custom/field-file-upload/preview-list-file.js";
6
+ import PreviewListImage from "../components/custom/field-file-upload/preview-list-image.js";
7
+ import { formatLinkToPreviewImage } from "../components/custom/field-file-upload/image-upload.js";
8
+ import { getFirstURL } from "@zenify-omni/chat-js/utils/string.js";
9
+ import { CUSTOM_FIELD_SORT_RE } from "@zenify-omni/chat-js/constants/sortable-fields.js";
10
+ import { makeSortableHeader } from "../components/custom/sortable-header.js";
11
+ import { maskData } from "@zenify-omni/chat-js/utils/data.js";
12
+ function buildExtraFieldColumns(fields, options = {}) {
13
+ const {
14
+ enableLinkDetection = true,
15
+ getRowValue = (row, key) => row.original[key],
16
+ sortable,
17
+ maskSensitiveData = false
18
+ } = options;
19
+ if (!fields || fields.length === 0) return [];
20
+ return fields.map((field) => ({
21
+ accessorKey: field.key,
22
+ size: 150,
23
+ header: sortable?.useSortBinding && CUSTOM_FIELD_SORT_RE.test(field.key) ? makeSortableHeader(field.title, field.key, sortable.useSortBinding) : () => /* @__PURE__ */ jsx("div", { children: field.title }),
24
+ cell: ({ row }) => {
25
+ let value = getRowValue(row, field.key);
26
+ if (Array.isArray(value) && ![AttributeFieldType.Image, AttributeFieldType.File].includes(
27
+ field.type
28
+ )) {
29
+ if (!value.length) {
30
+ value = notFoundValue;
31
+ } else if ([
32
+ AttributeFieldType.MultiSelect,
33
+ AttributeFieldType.CheckBox
34
+ ].includes(field.type)) {
35
+ value = value.map((item) => item?.label ?? item).join(", ");
36
+ } else {
37
+ value = value.join(", ");
38
+ }
39
+ } else if (value) {
40
+ if (field.type === "date") {
41
+ value = isValid(new Date(value)) ? format(new Date(value), "dd/MM/yyyy HH:mm") : notFoundValue;
42
+ }
43
+ } else {
44
+ value = notFoundValue;
45
+ }
46
+ if (maskSensitiveData && [
47
+ AttributeFieldType.Phone,
48
+ AttributeFieldType.Text,
49
+ AttributeFieldType.Textarea
50
+ ].includes(field.type) && typeof value === "string") {
51
+ value = maskData(value);
52
+ }
53
+ if (enableLinkDetection && (field.type === "text" || field.type === "textarea")) {
54
+ const link = getFirstURL(value);
55
+ if (link) {
56
+ const linkIndex = value.indexOf(link);
57
+ const before = value.slice(0, linkIndex);
58
+ const after = value.slice(linkIndex + link.length);
59
+ return /* @__PURE__ */ jsxs("div", { children: [
60
+ before,
61
+ /* @__PURE__ */ jsx(
62
+ "span",
63
+ {
64
+ className: "cursor-pointer truncate text-primary hover:underline",
65
+ onClick: (e) => {
66
+ e.stopPropagation();
67
+ window.open(link, "_blank", "noopener,noreferrer");
68
+ },
69
+ children: link
70
+ }
71
+ ),
72
+ after
73
+ ] });
74
+ }
75
+ }
76
+ if ([AttributeFieldType.Image].includes(field.type) && Array.isArray(value)) {
77
+ return /* @__PURE__ */ jsx(
78
+ PreviewListImage,
79
+ {
80
+ className: "flex items-center",
81
+ classNameImage: "w-8 h-8",
82
+ maxImageShowing: 2,
83
+ isRemoveImages: false,
84
+ showImageName: false,
85
+ images: formatLinkToPreviewImage(value),
86
+ classNameRemaining: "text-sm"
87
+ }
88
+ );
89
+ }
90
+ if ([AttributeFieldType.File].includes(field.type) && Array.isArray(value)) {
91
+ return /* @__PURE__ */ jsx(
92
+ PreviewListFiles,
93
+ {
94
+ classNameItem: "border-none bg-transparent !pl-0",
95
+ maxFileShowing: 1,
96
+ isRemoveFiles: false,
97
+ isDownloadFiles: false,
98
+ datas: formatLinkToPreviewImage(value)
99
+ }
100
+ );
101
+ }
102
+ return value;
103
+ }
104
+ }));
105
+ }
106
+ export {
107
+ buildExtraFieldColumns
108
+ };
package/utils/file.js ADDED
@@ -0,0 +1,316 @@
1
+ import i18n from "@zenify-omni/chat-js/lib/i18n/index.js";
2
+ import { getPresignedUrl } from "@zenify-omni/chat-js/api/file.js";
3
+ import axios from "axios";
4
+ import { AttributeFieldType } from "@zenify-omni/chat-js/constants/setting.js";
5
+ import { isEmpty } from "lodash-es";
6
+ import {
7
+ isZalo,
8
+ isZaloPersonal,
9
+ isFacebook,
10
+ isInstagram
11
+ } from "../components/chat-route/util.js";
12
+ import {
13
+ ACCEPTED_FILE_TYPES_PERSONAL_ZALO_WITH_VIDEO,
14
+ ACCEPTED_VIDEO_TYPES_PERSONAL_ZALO,
15
+ ACCEPTED_FILE_TYPES_ZALO,
16
+ ACCEPTED_FILE_TYPES_DEFAULT,
17
+ ACCEPTED_FILE_TYPES_IMAGES,
18
+ ACCEPTED_FILE_TYPES_IMAGES_ZALO_OA,
19
+ DEFAULT_FILE_SIZE,
20
+ MAXIMUM_FILE_SIZE_PERSONAL_ZALO,
21
+ MAXIMUM_IMAGE_SIZE_PERSONAL_ZALO,
22
+ MAXIMUM_IMAGE_INPUT_SIZE_ZALO_OA,
23
+ MAXIMUM_IMAGE_INPUT_SIZE_PERSONAL_ZALO,
24
+ IMAGE_INPUT_SIZE_MULTIPLIER,
25
+ MAXIMUM_FILE_SIZE_ZALO,
26
+ MAXIMUM_IMAGE_SIZE_FACEBOOK,
27
+ DEFAULT_IMAGE_SIZE,
28
+ ACCEPTED_FILE_TYPES_CUSTOM,
29
+ DEFAULT_FILE_SIZE_CHATTING
30
+ } from "@zenify-omni/chat-js/constants/file-upload.js";
31
+ function formatBytes(bytes, decimals = 2) {
32
+ if (bytes === 0) return "0 Bytes";
33
+ const k = 1024;
34
+ const dm = decimals < 0 ? 0 : decimals;
35
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
36
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
37
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
38
+ }
39
+ function checkFileSizeLarger(size, compareSize = 5 * 1024 * 1024) {
40
+ return size > compareSize;
41
+ }
42
+ function normalizeFileName(fileName) {
43
+ if (!fileName) return fileName;
44
+ let decoded = fileName;
45
+ try {
46
+ let prev;
47
+ do {
48
+ prev = decoded;
49
+ decoded = decodeURIComponent(decoded);
50
+ } while (decoded !== prev && /%[0-9A-Fa-f]{2}/.test(prev));
51
+ } catch {
52
+ decoded = fileName;
53
+ }
54
+ const lastDot = decoded.lastIndexOf(".");
55
+ const name = lastDot > 0 ? decoded.slice(0, lastDot) : decoded;
56
+ const ext = lastDot > 0 ? decoded.slice(lastDot) : "";
57
+ const cleanName = name.replace(/[#?%&+/\\:*"<>|=@;,]/g, "").replace(/\s+/g, "_").replace(/_+/g, "_").replace(/^[_.]+|[_.]+$/g, "") || "file";
58
+ return cleanName + ext;
59
+ }
60
+ async function uploadFileToCloudflare(file, params) {
61
+ try {
62
+ const safeFileName = normalizeFileName(file.name);
63
+ const { data } = await getPresignedUrl(safeFileName, params);
64
+ const { presignedUrl, link } = data;
65
+ if (!presignedUrl) {
66
+ throw new Error(
67
+ i18n.t("common:toast.uploadFailed", {
68
+ defaultValue: "Kh\xF4ng th\u1EC3 upload file"
69
+ })
70
+ );
71
+ }
72
+ await axios.put(presignedUrl, file, {
73
+ headers: {
74
+ "Content-Type": file.type || "application/octet-stream"
75
+ }
76
+ });
77
+ return link;
78
+ } catch (error) {
79
+ throw new Error(
80
+ error && error.message || i18n.t("common:toast.errorOccurred", { defaultValue: "\u0110\xE3 x\u1EA3y ra l\u1ED7i" })
81
+ );
82
+ }
83
+ }
84
+ const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
85
+ function isImage(contentType, mimeType) {
86
+ const ext = (mimeType || "").toLowerCase();
87
+ const subType = (contentType || "").split("/")[1];
88
+ if (subType === "x-www-form-urlencoded") {
89
+ return IMAGE_EXTENSIONS.includes(ext);
90
+ }
91
+ if (contentType && contentType.startsWith("image/")) return true;
92
+ return IMAGE_EXTENSIONS.includes(ext);
93
+ }
94
+ const VIDEO_EXTENSIONS = ACCEPTED_VIDEO_TYPES_PERSONAL_ZALO.map(
95
+ (ext) => ext.replace(".", "")
96
+ );
97
+ function isVideo(contentType, mimeType) {
98
+ const ext = (mimeType || "").toLowerCase();
99
+ if (VIDEO_EXTENSIONS.includes(ext)) return true;
100
+ return Boolean(contentType) && contentType.startsWith("video/");
101
+ }
102
+ const VIDEO_MIME_TYPES = {
103
+ mp4: "video/mp4",
104
+ mov: "video/quicktime",
105
+ avi: "video/x-msvideo",
106
+ wmv: "video/x-ms-wmv",
107
+ webm: "video/webm",
108
+ ogg: "video/ogg"
109
+ };
110
+ function getVideoMimeType(source) {
111
+ const ext = getFileExtension((source || "").split(/[?#]/)[0]);
112
+ return VIDEO_MIME_TYPES[ext] || "video/mp4";
113
+ }
114
+ async function buildFilePayloadExtraField({
115
+ extraFields,
116
+ extraFieldForm
117
+ }) {
118
+ try {
119
+ if (isEmpty(extraFieldForm) || isEmpty(extraFields)) return {};
120
+ const promises = Object.keys(extraFieldForm).map(async (key) => {
121
+ const field = extraFields.find((f) => f.key === key);
122
+ if (!field) return null;
123
+ let value = extraFieldForm[key];
124
+ if ([AttributeFieldType.Image, AttributeFieldType.File].includes(field.type)) {
125
+ if (Array.isArray(value)) {
126
+ const uploadPromises = value.map(async (file) => {
127
+ if (file?.file?.size) {
128
+ const fileUrl = await uploadFileToCloudflare(file.file, {
129
+ type: "import",
130
+ content_type: file.file.type
131
+ });
132
+ return { preview: fileUrl };
133
+ } else {
134
+ return {
135
+ preview: typeof file === "string" ? file : file?.preview
136
+ };
137
+ }
138
+ });
139
+ const uploadedUrls = await Promise.all(uploadPromises);
140
+ value = uploadedUrls.map((e) => e.preview);
141
+ }
142
+ }
143
+ return { key, value };
144
+ });
145
+ const resolved = await Promise.all(promises);
146
+ const result = {};
147
+ resolved.forEach((item) => {
148
+ if (item) result[item.key] = item.value;
149
+ });
150
+ return result;
151
+ } catch (error) {
152
+ throw new Error(
153
+ error && error.message || i18n.t("common:toast.errorOccurred", { defaultValue: "\u0110\xE3 x\u1EA3y ra l\u1ED7i" })
154
+ );
155
+ }
156
+ }
157
+ function validateFileSize(file, chatRoom) {
158
+ const fileExtension = getFileExtension(file?.name);
159
+ const isImageFile2 = isImage(file?.type, fileExtension);
160
+ let compareSize = DEFAULT_FILE_SIZE;
161
+ if (isImageFile2) {
162
+ compareSize = getAcceptImageSize(chatRoom);
163
+ } else {
164
+ compareSize = DEFAULT_FILE_SIZE_CHATTING;
165
+ if (isZaloPersonal(chatRoom)) {
166
+ compareSize = MAXIMUM_FILE_SIZE_PERSONAL_ZALO;
167
+ }
168
+ if (isZalo(chatRoom)) {
169
+ compareSize = DEFAULT_FILE_SIZE;
170
+ }
171
+ }
172
+ return [
173
+ !checkFileSizeLarger(file.size, compareSize),
174
+ i18n.t("common:message.channelFileSizeLimit", {
175
+ channel: chatRoom?.source || "chat",
176
+ size: compareSize / 1024 / 1024,
177
+ defaultValue: "V\u1EDBi k\xEAnh {channel} ch\u1EC9 \u0111\xE1p \u1EE9ng file d\u01B0\u1EDBi {size} MB"
178
+ })
179
+ ];
180
+ }
181
+ const getFileExtension = (fileName) => fileName?.split(".").pop()?.toLowerCase() || "";
182
+ function getAcceptFileType(chatRoom) {
183
+ if (!chatRoom) return "";
184
+ if (isZaloPersonal(chatRoom)) {
185
+ return ACCEPTED_FILE_TYPES_PERSONAL_ZALO_WITH_VIDEO.join(",");
186
+ }
187
+ if (isZalo(chatRoom)) {
188
+ return ACCEPTED_FILE_TYPES_ZALO.join(",");
189
+ }
190
+ return ACCEPTED_FILE_TYPES_CUSTOM.join(",");
191
+ }
192
+ function getAcceptFileSize(chatRoom) {
193
+ if (!chatRoom) return "";
194
+ if (isZaloPersonal(chatRoom)) {
195
+ return MAXIMUM_FILE_SIZE_PERSONAL_ZALO;
196
+ }
197
+ if (isZalo(chatRoom)) {
198
+ return DEFAULT_FILE_SIZE;
199
+ }
200
+ return DEFAULT_FILE_SIZE_CHATTING;
201
+ }
202
+ function getAcceptedImageTypes(chatRoom) {
203
+ return isZalo(chatRoom) ? ACCEPTED_FILE_TYPES_IMAGES_ZALO_OA : ACCEPTED_FILE_TYPES_IMAGES;
204
+ }
205
+ function getAcceptImageType(chatRoom) {
206
+ if (!chatRoom) return "";
207
+ return getAcceptedImageTypes(chatRoom).join(",");
208
+ }
209
+ const isGifFile = (file) => {
210
+ const ext = getFileExtension(file?.name);
211
+ return file?.type === "image/gif" || ext === "gif";
212
+ };
213
+ function needsImageCompression(file, chatRoom) {
214
+ const ext = getFileExtension(file?.name);
215
+ if (!isImage(file?.type, ext)) return false;
216
+ if (isGifFile(file)) return false;
217
+ if (isZalo(chatRoom)) return true;
218
+ return file?.size > getAcceptImageSize(chatRoom);
219
+ }
220
+ function shouldValidateImageInputSize(file) {
221
+ const ext = getFileExtension(file?.name);
222
+ if (!isImage(file?.type, ext)) return false;
223
+ if (isGifFile(file)) return false;
224
+ return true;
225
+ }
226
+ function getImageInputMaxSize(chatRoom) {
227
+ if (isZaloPersonal(chatRoom)) return MAXIMUM_IMAGE_INPUT_SIZE_PERSONAL_ZALO;
228
+ if (isZalo(chatRoom)) return MAXIMUM_IMAGE_INPUT_SIZE_ZALO_OA;
229
+ return getAcceptImageSize(chatRoom) * IMAGE_INPUT_SIZE_MULTIPLIER;
230
+ }
231
+ function validateImageInputSize(file, chatRoom) {
232
+ const maxSize = getImageInputMaxSize(chatRoom);
233
+ if (file?.size > maxSize) {
234
+ return [
235
+ false,
236
+ i18n.t("common:message.imageTooLarge", {
237
+ size: formatBytes(maxSize),
238
+ defaultValue: "\u1EA2nh c\xF3 dung l\u01B0\u1EE3ng v\u01B0\u1EE3t qu\xE1 gi\u1EDBi h\u1EA1n cho ph\xE9p (T\u1ED1i \u0111a {size})"
239
+ })
240
+ ];
241
+ }
242
+ return [true, ""];
243
+ }
244
+ function getAcceptImageSize(chatRoom) {
245
+ if (!chatRoom) return DEFAULT_IMAGE_SIZE;
246
+ if (isZaloPersonal(chatRoom)) {
247
+ return MAXIMUM_IMAGE_SIZE_PERSONAL_ZALO;
248
+ }
249
+ if (isZalo(chatRoom)) {
250
+ return MAXIMUM_FILE_SIZE_ZALO;
251
+ }
252
+ if (isFacebook(chatRoom) || isInstagram(chatRoom)) {
253
+ return MAXIMUM_IMAGE_SIZE_FACEBOOK;
254
+ }
255
+ return DEFAULT_IMAGE_SIZE;
256
+ }
257
+ function validateFileExtension(file, chatRoom) {
258
+ const fileExtension = getFileExtension(file?.name);
259
+ const isImageFile2 = isImage(file?.type, fileExtension);
260
+ if (isImageFile2) {
261
+ const acceptedImageTypes = getAcceptedImageTypes(chatRoom);
262
+ return [
263
+ acceptedImageTypes.includes(`.${fileExtension}`),
264
+ i18n.t("common:message.invalidImageFormat", {
265
+ types: acceptedImageTypes.join(", "),
266
+ defaultValue: "\u0110\u1ECBnh d\u1EA1ng h\xECnh \u1EA3nh kh\xF4ng h\u1EE3p l\u1EC7. H\u1ED7 tr\u1EE3: {types}"
267
+ })
268
+ ];
269
+ } else {
270
+ const acceptFileType = getAcceptFileType(chatRoom);
271
+ if (acceptFileType === ACCEPTED_FILE_TYPES_DEFAULT) {
272
+ return [true, ""];
273
+ }
274
+ const extensions = acceptFileType.split(",");
275
+ return [
276
+ extensions.includes(`.${fileExtension}`),
277
+ i18n.t("common:message.invalidFileFormat", {
278
+ types: extensions.join(", "),
279
+ defaultValue: "\u0110\u1ECBnh d\u1EA1ng file kh\xF4ng h\u1EE3p l\u1EC7. H\u1ED7 tr\u1EE3: {types}"
280
+ })
281
+ ];
282
+ }
283
+ }
284
+ const isImageFile = (link) => {
285
+ const imageExtensions = ["jpg", "jpeg", "png", "gif", "bmp", "webp"];
286
+ const fileExtension = getFileExtension(link);
287
+ return imageExtensions.includes(fileExtension);
288
+ };
289
+ const isVideoFile = (link) => {
290
+ const videoExtensions = ["mp4", "mov", "avi", "webm", "mkv"];
291
+ const fileExtension = getFileExtension(link);
292
+ return videoExtensions.includes(fileExtension);
293
+ };
294
+ export {
295
+ buildFilePayloadExtraField,
296
+ checkFileSizeLarger,
297
+ formatBytes,
298
+ getAcceptFileSize,
299
+ getAcceptFileType,
300
+ getAcceptImageSize,
301
+ getAcceptImageType,
302
+ getFileExtension,
303
+ getImageInputMaxSize,
304
+ getVideoMimeType,
305
+ isImage,
306
+ isImageFile,
307
+ isVideo,
308
+ isVideoFile,
309
+ needsImageCompression,
310
+ normalizeFileName,
311
+ shouldValidateImageInputSize,
312
+ uploadFileToCloudflare,
313
+ validateFileExtension,
314
+ validateFileSize,
315
+ validateImageInputSize
316
+ };
@@ -0,0 +1,12 @@
1
+ function htmlToPlainText(value = "") {
2
+ const stripped = String(value).replace(/<br\s*\/?>/gi, "\n").replace(/<\/p\s*>/gi, "\n").replace(/<[^>]*>/g, "").replace(/\n{3,}/g, "\n\n").trim();
3
+ try {
4
+ const doc = new DOMParser().parseFromString(stripped, "text/html");
5
+ return doc.body.textContent ?? stripped;
6
+ } catch {
7
+ return stripped;
8
+ }
9
+ }
10
+ export {
11
+ htmlToPlainText
12
+ };
@@ -0,0 +1,71 @@
1
+ import i18n from "@zenify-omni/chat-js/lib/i18n/index.js";
2
+ import { showToast } from "../components/custom/showToast.js";
3
+ import useAppStatusStore from "../store/app-status-store.js";
4
+ import { toast as toastSonner } from "sonner";
5
+ const stripHtmlTags = (html) => {
6
+ if (!html || typeof html !== "string") return html;
7
+ return html.replace(/<\/?[^>]+(>|$)/g, " ").replace(/\s+/g, " ").trim();
8
+ };
9
+ const truncateText = (text, maxLength = 150) => {
10
+ if (!text || typeof text !== "string") return text;
11
+ return text.length > maxLength ? text.substring(0, maxLength) + "..." : text;
12
+ };
13
+ const isSupported = () => typeof window !== "undefined" && "Notification" in window;
14
+ const getPermissionStatus = () => {
15
+ if (!isSupported()) {
16
+ return "not supported";
17
+ }
18
+ return Notification.permission;
19
+ };
20
+ const createNotification = (title, options = {}) => {
21
+ if (!isSupported()) {
22
+ console.log("Notifications are not available in this environment");
23
+ return null;
24
+ }
25
+ if (getPermissionStatus() !== "granted") {
26
+ console.log("The user hasn't granted permission to send push notifications");
27
+ return null;
28
+ }
29
+ const notification = new Notification(title, options);
30
+ if (options.onclick) {
31
+ notification.addEventListener("click", options.onclick);
32
+ }
33
+ return notification;
34
+ };
35
+ const activeToasts = /* @__PURE__ */ new Map();
36
+ const pushNotification = (title = i18n.t("common:nav.notification", { defaultValue: "Th\xF4ng b\xE1o" }), config = {}, type = "info") => {
37
+ if (config?.description) {
38
+ config.description = truncateText(stripHtmlTags(config.description));
39
+ }
40
+ const isAppVisible = useAppStatusStore.getState().isVisible;
41
+ if (!isAppVisible) {
42
+ const body = config?.content || config?.description || "";
43
+ const cleanBody = truncateText(stripHtmlTags(body));
44
+ createNotification(title, {
45
+ body: cleanBody,
46
+ onclick: () => {
47
+ window.focus();
48
+ if (config.action && config.action?.onClick) {
49
+ config.action?.onClick();
50
+ } else if (config.onClick) {
51
+ config.onClick();
52
+ }
53
+ }
54
+ });
55
+ }
56
+ if (config?.notificationKey) {
57
+ const previousToast = activeToasts.get(config.notificationKey);
58
+ if (previousToast) {
59
+ toastSonner.dismiss(previousToast);
60
+ }
61
+ }
62
+ const toast = showToast(title, type, config);
63
+ if (config?.notificationKey && toast) {
64
+ activeToasts.set(config.notificationKey, toast);
65
+ }
66
+ return toast;
67
+ };
68
+ export {
69
+ createNotification,
70
+ pushNotification
71
+ };
@@ -0,0 +1,64 @@
1
+ import emojiSprite__asset from "../assets/images/emoji.webp";
2
+ import emojiMap from "../components/chat-route/chat-box/emojiMap.js";
3
+ import { assetUrl } from "../assets/asset-url.js";
4
+ const emojiSprite = assetUrl(emojiSprite__asset);
5
+ const sortedCodes = Object.keys(emojiMap).sort((a, b) => b.length - a.length);
6
+ const EMOJI_PATTERN = "(?:" + sortedCodes.map((c) => c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|") + ")";
7
+ const LOWER_TO_CODE = new Map(sortedCodes.map((c) => [c.toLowerCase(), c]));
8
+ function replaceSegment(text) {
9
+ const regex = new RegExp(EMOJI_PATTERN, "gi");
10
+ const candidates = [];
11
+ let match;
12
+ while ((match = regex.exec(text)) !== null) {
13
+ const code = LOWER_TO_CODE.get(match[0].toLowerCase());
14
+ if (!code) continue;
15
+ candidates.push({
16
+ start: match.index,
17
+ end: match.index + match[0].length,
18
+ code
19
+ });
20
+ }
21
+ if (candidates.length === 0) return text;
22
+ const candidateStartSet = new Set(candidates.map((m) => m.start));
23
+ const candidateEndSet = new Set(candidates.map((m) => m.end));
24
+ let result = "";
25
+ let lastEnd = 0;
26
+ for (const { start, end, code } of candidates) {
27
+ const beforeOk = start === 0 || /\s/.test(text[start - 1]) || candidateEndSet.has(start);
28
+ const afterOk = end === text.length || /\s/.test(text[end]) || candidateStartSet.has(end);
29
+ if (!beforeOk || !afterOk) continue;
30
+ result += text.slice(lastEnd, start);
31
+ result += `<span class="zalo-emoji" data-emoji-code="${code}"></span>`;
32
+ lastEnd = end;
33
+ }
34
+ result += text.slice(lastEnd);
35
+ return result;
36
+ }
37
+ function replaceZaloSprite(html) {
38
+ if (!html) return html;
39
+ return html.split(/(<[^>]*>)/).map((part, i) => i % 2 !== 0 ? part : replaceSegment(part)).join("");
40
+ }
41
+ function applyZaloEmojiStyles(container, size = 20) {
42
+ if (!container) return;
43
+ container.querySelectorAll(".zalo-emoji[data-emoji-code]").forEach((el) => {
44
+ const code = el.getAttribute("data-emoji-code");
45
+ const pos = emojiMap[code];
46
+ if (!pos) return;
47
+ el.style.cssText = [
48
+ "display:inline-block",
49
+ `width:${size}px`,
50
+ `height:${size}px`,
51
+ `background-image:url(${emojiSprite})`,
52
+ "background-size:5100%",
53
+ "background-repeat:no-repeat",
54
+ `background-position:${pos.x} ${pos.y}`,
55
+ "margin:0 1px -3px",
56
+ "vertical-align:middle",
57
+ "flex-shrink:0"
58
+ ].join(";");
59
+ });
60
+ }
61
+ export {
62
+ applyZaloEmojiStyles,
63
+ replaceZaloSprite
64
+ };
@@ -0,0 +1,17 @@
1
+ import i18n from "@zenify-omni/chat-js/lib/i18n/index.js";
2
+ import { toast } from "sonner";
3
+ function showBeError(err, fallback = i18n.t("common:toast.errorOccurred", {
4
+ defaultValue: "\u0110\xE3 x\u1EA3y ra l\u1ED7i"
5
+ })) {
6
+ const message = err?.message || fallback;
7
+ const errors = err?.data?.errors;
8
+ if (Array.isArray(errors) && errors.length) {
9
+ const details = errors.map((e) => e?.msg || e?.message || e).filter(Boolean).slice(0, 5).join("\n");
10
+ toast.error(message, { description: details });
11
+ return;
12
+ }
13
+ toast.error(message);
14
+ }
15
+ export {
16
+ showBeError as default
17
+ };
@@ -0,0 +1,101 @@
1
+ import { createTypingSocket } from "@zenify-omni/chat-js/socket/index.js";
2
+ import { getUser } from "@zenify-omni/chat-js/utils/auth.js";
3
+ import { ChatMessageKey } from "@zenify-omni/chat-js/constants/chat-message.js";
4
+ import useChatStore from "../store/chat-store.js";
5
+ import { PRE_LOGOUT_EVENT } from "@zenify-omni/chat-js/constants/events.js";
6
+ import { env } from "../config/env.js";
7
+ import { onModuleDispose } from "../config/hmr.js";
8
+ import { registerChatTransport } from "@zenify-omni/chat-js/transport-registry.js";
9
+ let typingSocket = null;
10
+ const getBaseUrl = () => {
11
+ try {
12
+ return new URL(env.CHAT_URL).origin;
13
+ } catch {
14
+ return env.CHAT_URL;
15
+ }
16
+ };
17
+ const onUpdateChatting = ({
18
+ room_id,
19
+ agent_id,
20
+ agent_name,
21
+ agent_avatar,
22
+ is_typing
23
+ }) => {
24
+ const currentUser = getUser();
25
+ if (!currentUser) return;
26
+ if (agent_id === currentUser.agent_id) return;
27
+ const chatStore = useChatStore.getState();
28
+ if (is_typing) {
29
+ chatStore.setTypingUser(room_id, agent_id, agent_name, agent_avatar);
30
+ } else {
31
+ chatStore.clearTypingUser(room_id, agent_id);
32
+ }
33
+ };
34
+ const typingTimers = /* @__PURE__ */ new Map();
35
+ const onUpdateChattingWithTimer = (payload) => {
36
+ const { room_id, agent_id, is_typing } = payload;
37
+ const currentUser = getUser();
38
+ if (!currentUser) return;
39
+ if (agent_id === currentUser.agent_id) return;
40
+ const timerKey = `${room_id}:${agent_id}`;
41
+ clearTimeout(typingTimers.get(timerKey));
42
+ if (is_typing) {
43
+ onUpdateChatting(payload);
44
+ typingTimers.set(
45
+ timerKey,
46
+ setTimeout(() => {
47
+ useChatStore.getState().clearTypingUser(room_id, agent_id);
48
+ typingTimers.delete(timerKey);
49
+ }, 5e3)
50
+ );
51
+ } else {
52
+ onUpdateChatting(payload);
53
+ typingTimers.delete(timerKey);
54
+ }
55
+ };
56
+ const connectTypingSocket = () => {
57
+ if (typingSocket) return;
58
+ const user = getUser();
59
+ if (!user) return;
60
+ typingSocket = createTypingSocket(getBaseUrl(), user);
61
+ typingSocket.on(ChatMessageKey.UPDATE_TYPING, onUpdateChattingWithTimer);
62
+ };
63
+ const disconnectTypingSocket = () => {
64
+ if (!typingSocket) return;
65
+ typingSocket.off(ChatMessageKey.UPDATE_TYPING, onUpdateChattingWithTimer);
66
+ typingSocket.disconnect();
67
+ typingSocket = null;
68
+ typingTimers.forEach((timer) => clearTimeout(timer));
69
+ typingTimers.clear();
70
+ };
71
+ const emitTyping = (roomId, isTyping) => {
72
+ if (!typingSocket || !roomId) return;
73
+ const user = getUser();
74
+ if (!user) return;
75
+ typingSocket.emit(ChatMessageKey.UPDATE_TYPING, {
76
+ room_id: roomId,
77
+ agent_id: user.agent_id,
78
+ agent_name: user.name,
79
+ agent_avatar: user.avatar,
80
+ is_typing: isTyping
81
+ });
82
+ };
83
+ let __preLogoutBound = false;
84
+ if (!__preLogoutBound) {
85
+ document.addEventListener(PRE_LOGOUT_EVENT, disconnectTypingSocket);
86
+ __preLogoutBound = true;
87
+ }
88
+ onModuleDispose(
89
+ () => document.removeEventListener(PRE_LOGOUT_EVENT, disconnectTypingSocket)
90
+ );
91
+ registerChatTransport({
92
+ name: "typing",
93
+ connect: connectTypingSocket,
94
+ disconnect: disconnectTypingSocket,
95
+ methods: { connectTypingSocket, disconnectTypingSocket, emitTyping }
96
+ });
97
+ export {
98
+ connectTypingSocket,
99
+ disconnectTypingSocket,
100
+ emitTyping
101
+ };