@sonordev/site-kit 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/README.md +376 -0
  2. package/dist/SetupWizard-Cki06kB0.d.mts +12 -0
  3. package/dist/SetupWizard-Cki06kB0.d.ts +12 -0
  4. package/dist/analytics/index.d.mts +93 -0
  5. package/dist/analytics/index.d.ts +93 -0
  6. package/dist/analytics/index.js +89 -0
  7. package/dist/analytics/index.js.map +1 -0
  8. package/dist/analytics/index.mjs +71 -0
  9. package/dist/analytics/index.mjs.map +1 -0
  10. package/dist/api-CWtoFJCO.d.mts +137 -0
  11. package/dist/api-CWtoFJCO.d.ts +137 -0
  12. package/dist/blog/index.d.mts +305 -0
  13. package/dist/blog/index.d.ts +305 -0
  14. package/dist/blog/index.js +1578 -0
  15. package/dist/blog/index.js.map +1 -0
  16. package/dist/blog/index.mjs +1562 -0
  17. package/dist/blog/index.mjs.map +1 -0
  18. package/dist/blog/server.d.mts +229 -0
  19. package/dist/blog/server.d.ts +229 -0
  20. package/dist/blog/server.js +692 -0
  21. package/dist/blog/server.js.map +1 -0
  22. package/dist/blog/server.mjs +666 -0
  23. package/dist/blog/server.mjs.map +1 -0
  24. package/dist/chunk-24277A3Q.mjs +968 -0
  25. package/dist/chunk-24277A3Q.mjs.map +1 -0
  26. package/dist/chunk-373TK6TZ.js +321 -0
  27. package/dist/chunk-373TK6TZ.js.map +1 -0
  28. package/dist/chunk-3MYZS6PD.js +30 -0
  29. package/dist/chunk-3MYZS6PD.js.map +1 -0
  30. package/dist/chunk-43GBM4SX.js +283 -0
  31. package/dist/chunk-43GBM4SX.js.map +1 -0
  32. package/dist/chunk-4XPGGLVP.mjs +53 -0
  33. package/dist/chunk-4XPGGLVP.mjs.map +1 -0
  34. package/dist/chunk-622GAQP5.js +2008 -0
  35. package/dist/chunk-622GAQP5.js.map +1 -0
  36. package/dist/chunk-6BIPAKL4.mjs +28 -0
  37. package/dist/chunk-6BIPAKL4.mjs.map +1 -0
  38. package/dist/chunk-6ZCISNAB.mjs +343 -0
  39. package/dist/chunk-6ZCISNAB.mjs.map +1 -0
  40. package/dist/chunk-72MQFHYJ.js +1429 -0
  41. package/dist/chunk-72MQFHYJ.js.map +1 -0
  42. package/dist/chunk-7557OTHW.js +62 -0
  43. package/dist/chunk-7557OTHW.js.map +1 -0
  44. package/dist/chunk-7FUV73JZ.js +981 -0
  45. package/dist/chunk-7FUV73JZ.js.map +1 -0
  46. package/dist/chunk-7RF6PVHA.mjs +324 -0
  47. package/dist/chunk-7RF6PVHA.mjs.map +1 -0
  48. package/dist/chunk-7RYCHO6D.mjs +134 -0
  49. package/dist/chunk-7RYCHO6D.mjs.map +1 -0
  50. package/dist/chunk-7UKPRW25.mjs +1999 -0
  51. package/dist/chunk-7UKPRW25.mjs.map +1 -0
  52. package/dist/chunk-7URAOG2M.js +14864 -0
  53. package/dist/chunk-7URAOG2M.js.map +1 -0
  54. package/dist/chunk-AFAO3TGS.mjs +810 -0
  55. package/dist/chunk-AFAO3TGS.mjs.map +1 -0
  56. package/dist/chunk-BYLIU6XG.js +343 -0
  57. package/dist/chunk-BYLIU6XG.js.map +1 -0
  58. package/dist/chunk-D63MUKZ6.mjs +4423 -0
  59. package/dist/chunk-D63MUKZ6.mjs.map +1 -0
  60. package/dist/chunk-DDKW2FNA.js +390 -0
  61. package/dist/chunk-DDKW2FNA.js.map +1 -0
  62. package/dist/chunk-DQYMKR27.mjs +341 -0
  63. package/dist/chunk-DQYMKR27.mjs.map +1 -0
  64. package/dist/chunk-DW5UJKHH.js +221 -0
  65. package/dist/chunk-DW5UJKHH.js.map +1 -0
  66. package/dist/chunk-EEZCR6E6.js +50 -0
  67. package/dist/chunk-EEZCR6E6.js.map +1 -0
  68. package/dist/chunk-GCJXQ4AG.mjs +59 -0
  69. package/dist/chunk-GCJXQ4AG.mjs.map +1 -0
  70. package/dist/chunk-JGNQK2G6.mjs +14845 -0
  71. package/dist/chunk-JGNQK2G6.mjs.map +1 -0
  72. package/dist/chunk-JTLOJLWQ.mjs +563 -0
  73. package/dist/chunk-JTLOJLWQ.mjs.map +1 -0
  74. package/dist/chunk-K23A4G76.mjs +202 -0
  75. package/dist/chunk-K23A4G76.mjs.map +1 -0
  76. package/dist/chunk-KKU3K7RG.js +336 -0
  77. package/dist/chunk-KKU3K7RG.js.map +1 -0
  78. package/dist/chunk-KUGMH4ZF.js +571 -0
  79. package/dist/chunk-KUGMH4ZF.js.map +1 -0
  80. package/dist/chunk-LBVWVP72.js +110 -0
  81. package/dist/chunk-LBVWVP72.js.map +1 -0
  82. package/dist/chunk-LIVWLY2P.js +138 -0
  83. package/dist/chunk-LIVWLY2P.js.map +1 -0
  84. package/dist/chunk-M2T6R7BA.mjs +1003 -0
  85. package/dist/chunk-M2T6R7BA.mjs.map +1 -0
  86. package/dist/chunk-MV3QN7PW.mjs +47 -0
  87. package/dist/chunk-MV3QN7PW.mjs.map +1 -0
  88. package/dist/chunk-OB7E654K.js +72 -0
  89. package/dist/chunk-OB7E654K.js.map +1 -0
  90. package/dist/chunk-OIIKTGRL.mjs +380 -0
  91. package/dist/chunk-OIIKTGRL.mjs.map +1 -0
  92. package/dist/chunk-P3UWIUJS.mjs +1427 -0
  93. package/dist/chunk-P3UWIUJS.mjs.map +1 -0
  94. package/dist/chunk-PKN27UMH.mjs +136 -0
  95. package/dist/chunk-PKN27UMH.mjs.map +1 -0
  96. package/dist/chunk-QXV4667R.mjs +105 -0
  97. package/dist/chunk-QXV4667R.mjs.map +1 -0
  98. package/dist/chunk-S7FRYNSU.mjs +315 -0
  99. package/dist/chunk-S7FRYNSU.mjs.map +1 -0
  100. package/dist/chunk-TFLQX7K7.mjs +68 -0
  101. package/dist/chunk-TFLQX7K7.mjs.map +1 -0
  102. package/dist/chunk-UWE5PCYJ.mjs +279 -0
  103. package/dist/chunk-UWE5PCYJ.mjs.map +1 -0
  104. package/dist/chunk-UYFDNX2F.js +4469 -0
  105. package/dist/chunk-UYFDNX2F.js.map +1 -0
  106. package/dist/chunk-W4PALSGM.js +350 -0
  107. package/dist/chunk-W4PALSGM.js.map +1 -0
  108. package/dist/chunk-WECQ6KOB.js +1008 -0
  109. package/dist/chunk-WECQ6KOB.js.map +1 -0
  110. package/dist/chunk-XQQWI6WB.js +814 -0
  111. package/dist/chunk-XQQWI6WB.js.map +1 -0
  112. package/dist/chunk-XZJOZJB6.js +140 -0
  113. package/dist/chunk-XZJOZJB6.js.map +1 -0
  114. package/dist/chunk-ZSMWDLMK.js +63 -0
  115. package/dist/chunk-ZSMWDLMK.js.map +1 -0
  116. package/dist/cli/index.js +37243 -0
  117. package/dist/cli/index.js.map +1 -0
  118. package/dist/cli/index.mjs +37209 -0
  119. package/dist/cli/index.mjs.map +1 -0
  120. package/dist/commerce/index.d.mts +170 -0
  121. package/dist/commerce/index.d.ts +170 -0
  122. package/dist/commerce/index.js +174 -0
  123. package/dist/commerce/index.js.map +1 -0
  124. package/dist/commerce/index.mjs +5 -0
  125. package/dist/commerce/index.mjs.map +1 -0
  126. package/dist/commerce/server.d.mts +107 -0
  127. package/dist/commerce/server.d.ts +107 -0
  128. package/dist/commerce/server.js +187 -0
  129. package/dist/commerce/server.js.map +1 -0
  130. package/dist/commerce/server.mjs +177 -0
  131. package/dist/commerce/server.mjs.map +1 -0
  132. package/dist/config/index.d.mts +43 -0
  133. package/dist/config/index.d.ts +43 -0
  134. package/dist/config/index.js +66 -0
  135. package/dist/config/index.js.map +1 -0
  136. package/dist/config/index.mjs +64 -0
  137. package/dist/config/index.mjs.map +1 -0
  138. package/dist/engage/index.d.mts +33 -0
  139. package/dist/engage/index.d.ts +33 -0
  140. package/dist/engage/index.js +22 -0
  141. package/dist/engage/index.js.map +1 -0
  142. package/dist/engage/index.mjs +5 -0
  143. package/dist/engage/index.mjs.map +1 -0
  144. package/dist/forms/index.d.mts +437 -0
  145. package/dist/forms/index.d.ts +437 -0
  146. package/dist/forms/index.js +1168 -0
  147. package/dist/forms/index.js.map +1 -0
  148. package/dist/forms/index.mjs +1142 -0
  149. package/dist/forms/index.mjs.map +1 -0
  150. package/dist/generators-2XKQMPKH.mjs +4 -0
  151. package/dist/generators-2XKQMPKH.mjs.map +1 -0
  152. package/dist/generators-DTMO36DV.js +33 -0
  153. package/dist/generators-DTMO36DV.js.map +1 -0
  154. package/dist/images/index.d.mts +4 -0
  155. package/dist/images/index.d.ts +4 -0
  156. package/dist/images/index.js +46 -0
  157. package/dist/images/index.js.map +1 -0
  158. package/dist/images/index.mjs +5 -0
  159. package/dist/images/index.mjs.map +1 -0
  160. package/dist/images/server.d.mts +69 -0
  161. package/dist/images/server.d.ts +69 -0
  162. package/dist/images/server.js +21 -0
  163. package/dist/images/server.js.map +1 -0
  164. package/dist/images/server.mjs +4 -0
  165. package/dist/images/server.mjs.map +1 -0
  166. package/dist/index.d.mts +846 -0
  167. package/dist/index.d.ts +846 -0
  168. package/dist/index.js +2623 -0
  169. package/dist/index.js.map +1 -0
  170. package/dist/index.mjs +2416 -0
  171. package/dist/index.mjs.map +1 -0
  172. package/dist/layout/index.d.mts +53 -0
  173. package/dist/layout/index.d.ts +53 -0
  174. package/dist/layout/index.js +187 -0
  175. package/dist/layout/index.js.map +1 -0
  176. package/dist/layout/index.mjs +185 -0
  177. package/dist/layout/index.mjs.map +1 -0
  178. package/dist/llms/index.d.mts +448 -0
  179. package/dist/llms/index.d.ts +448 -0
  180. package/dist/llms/index.js +581 -0
  181. package/dist/llms/index.js.map +1 -0
  182. package/dist/llms/index.mjs +529 -0
  183. package/dist/llms/index.mjs.map +1 -0
  184. package/dist/manifest/index.d.mts +62 -0
  185. package/dist/manifest/index.d.ts +62 -0
  186. package/dist/manifest/index.js +85 -0
  187. package/dist/manifest/index.js.map +1 -0
  188. package/dist/manifest/index.mjs +83 -0
  189. package/dist/manifest/index.mjs.map +1 -0
  190. package/dist/middleware/index.d.mts +63 -0
  191. package/dist/middleware/index.d.ts +63 -0
  192. package/dist/middleware/index.js +54 -0
  193. package/dist/middleware/index.js.map +1 -0
  194. package/dist/middleware/index.mjs +51 -0
  195. package/dist/middleware/index.mjs.map +1 -0
  196. package/dist/migrator-2MQHOFDQ.mjs +4 -0
  197. package/dist/migrator-2MQHOFDQ.mjs.map +1 -0
  198. package/dist/migrator-THJCF6MZ.js +37 -0
  199. package/dist/migrator-THJCF6MZ.js.map +1 -0
  200. package/dist/redirects/index.d.mts +78 -0
  201. package/dist/redirects/index.d.ts +78 -0
  202. package/dist/redirects/index.js +26 -0
  203. package/dist/redirects/index.js.map +1 -0
  204. package/dist/redirects/index.mjs +5 -0
  205. package/dist/redirects/index.mjs.map +1 -0
  206. package/dist/reputation/index.d.mts +57 -0
  207. package/dist/reputation/index.d.ts +57 -0
  208. package/dist/reputation/index.js +21 -0
  209. package/dist/reputation/index.js.map +1 -0
  210. package/dist/reputation/index.mjs +4 -0
  211. package/dist/reputation/index.mjs.map +1 -0
  212. package/dist/robots/index.d.mts +38 -0
  213. package/dist/robots/index.d.ts +38 -0
  214. package/dist/robots/index.js +52 -0
  215. package/dist/robots/index.js.map +1 -0
  216. package/dist/robots/index.mjs +50 -0
  217. package/dist/robots/index.mjs.map +1 -0
  218. package/dist/routing-B5XS-6_W.d.mts +118 -0
  219. package/dist/routing-DZYzyDHw.d.ts +118 -0
  220. package/dist/scanner-GAF5PO5F.js +53 -0
  221. package/dist/scanner-GAF5PO5F.js.map +1 -0
  222. package/dist/scanner-LKJKW7IT.mjs +4 -0
  223. package/dist/scanner-LKJKW7IT.mjs.map +1 -0
  224. package/dist/securityHeaders-nwZ6nP4g.d.mts +24 -0
  225. package/dist/securityHeaders-nwZ6nP4g.d.ts +24 -0
  226. package/dist/seo/index.d.mts +600 -0
  227. package/dist/seo/index.d.ts +600 -0
  228. package/dist/seo/index.js +883 -0
  229. package/dist/seo/index.js.map +1 -0
  230. package/dist/seo/index.mjs +773 -0
  231. package/dist/seo/index.mjs.map +1 -0
  232. package/dist/seo/register-sitemap-cli.js +151 -0
  233. package/dist/seo/register-sitemap-cli.js.map +1 -0
  234. package/dist/seo/register-sitemap-cli.mjs +144 -0
  235. package/dist/seo/register-sitemap-cli.mjs.map +1 -0
  236. package/dist/seo/server.d.mts +107 -0
  237. package/dist/seo/server.d.ts +107 -0
  238. package/dist/seo/server.js +207 -0
  239. package/dist/seo/server.js.map +1 -0
  240. package/dist/seo/server.mjs +186 -0
  241. package/dist/seo/server.mjs.map +1 -0
  242. package/dist/server-api-EWXKOQZA.mjs +4 -0
  243. package/dist/server-api-EWXKOQZA.mjs.map +1 -0
  244. package/dist/server-api-GJPNRYUP.js +81 -0
  245. package/dist/server-api-GJPNRYUP.js.map +1 -0
  246. package/dist/setup/client.d.mts +60 -0
  247. package/dist/setup/client.d.ts +60 -0
  248. package/dist/setup/client.js +31 -0
  249. package/dist/setup/client.js.map +1 -0
  250. package/dist/setup/client.mjs +6 -0
  251. package/dist/setup/client.mjs.map +1 -0
  252. package/dist/setup/index.d.mts +5 -0
  253. package/dist/setup/index.d.ts +5 -0
  254. package/dist/setup/index.js +35 -0
  255. package/dist/setup/index.js.map +1 -0
  256. package/dist/setup/index.mjs +6 -0
  257. package/dist/setup/index.mjs.map +1 -0
  258. package/dist/setup/server.d.mts +14 -0
  259. package/dist/setup/server.d.ts +14 -0
  260. package/dist/setup/server.js +13 -0
  261. package/dist/setup/server.js.map +1 -0
  262. package/dist/setup/server.mjs +4 -0
  263. package/dist/setup/server.mjs.map +1 -0
  264. package/dist/site-config/index.d.mts +24 -0
  265. package/dist/site-config/index.d.ts +24 -0
  266. package/dist/site-config/index.js +17 -0
  267. package/dist/site-config/index.js.map +1 -0
  268. package/dist/site-config/index.mjs +4 -0
  269. package/dist/site-config/index.mjs.map +1 -0
  270. package/dist/sitemap/index.d.mts +96 -0
  271. package/dist/sitemap/index.d.ts +96 -0
  272. package/dist/sitemap/index.js +288 -0
  273. package/dist/sitemap/index.js.map +1 -0
  274. package/dist/sitemap/index.mjs +285 -0
  275. package/dist/sitemap/index.mjs.map +1 -0
  276. package/dist/socket-loader-J26QHHOB.js +16 -0
  277. package/dist/socket-loader-J26QHHOB.js.map +1 -0
  278. package/dist/socket-loader-R7S2YJ2J.mjs +14 -0
  279. package/dist/socket-loader-R7S2YJ2J.mjs.map +1 -0
  280. package/dist/types-0dmq3k20.d.mts +168 -0
  281. package/dist/types-0dmq3k20.d.ts +168 -0
  282. package/dist/types-Blb2QNkV.d.mts +263 -0
  283. package/dist/types-Blb2QNkV.d.ts +263 -0
  284. package/dist/types-BnCwwUX3.d.mts +250 -0
  285. package/dist/types-BnCwwUX3.d.ts +250 -0
  286. package/dist/types-CGlnp43R.d.mts +312 -0
  287. package/dist/types-CGlnp43R.d.ts +312 -0
  288. package/dist/types-D08004rU.d.mts +179 -0
  289. package/dist/types-D08004rU.d.ts +179 -0
  290. package/dist/types-DNSYU7qI.d.mts +127 -0
  291. package/dist/types-DNSYU7qI.d.ts +127 -0
  292. package/dist/types-KZP_VWZp.d.mts +266 -0
  293. package/dist/types-KZP_VWZp.d.ts +266 -0
  294. package/dist/useEventModal-BVTx69XE.d.mts +274 -0
  295. package/dist/useEventModal-Dx1dItTJ.d.ts +274 -0
  296. package/dist/web-vitals-444RLW3B.js +252 -0
  297. package/dist/web-vitals-444RLW3B.js.map +1 -0
  298. package/dist/web-vitals-KPICZIEF.mjs +241 -0
  299. package/dist/web-vitals-KPICZIEF.mjs.map +1 -0
  300. package/package.json +192 -0
@@ -0,0 +1,1999 @@
1
+ 'use client';
2
+ import React2, { useState, useRef, useEffect, useCallback, createElement } from 'react';
3
+ import ReactMarkdown from 'react-markdown';
4
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
+ import { usePathname } from 'next/navigation';
6
+
7
+ // src/engage/ChatWidget.tsx
8
+ function getApiConfig() {
9
+ const isDev = typeof process !== "undefined" && process.env?.NODE_ENV === "development";
10
+ const defaultApiUrl = isDev && process.env?.NEXT_PUBLIC_UPTRADE_API_URL ? process.env.NEXT_PUBLIC_UPTRADE_API_URL : "https://api.uptrademedia.com";
11
+ const defaultSignalUrl = isDev && process.env?.NEXT_PUBLIC_SIGNAL_API_URL ? process.env.NEXT_PUBLIC_SIGNAL_API_URL : "https://signal.uptrademedia.com";
12
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || defaultApiUrl : defaultApiUrl;
13
+ const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
14
+ const signalUrl = typeof window !== "undefined" ? window.__SITE_KIT_SIGNAL_URL__ || defaultSignalUrl : defaultSignalUrl;
15
+ return { apiUrl, apiKey, signalUrl };
16
+ }
17
+ function generateVisitorId() {
18
+ const stored = typeof localStorage !== "undefined" ? localStorage.getItem("engage_visitor_id") : null;
19
+ if (stored) return stored;
20
+ const id = `visitor_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
21
+ if (typeof localStorage !== "undefined") localStorage.setItem("engage_visitor_id", id);
22
+ return id;
23
+ }
24
+ function linkifyUrls(text) {
25
+ return text.replace(
26
+ /(^|[^(\]])(https?:\/\/[^\s<>"{}|\\^`[\])]+)/g,
27
+ (_, before, url) => `${before}[${url}](${url})`
28
+ );
29
+ }
30
+ function userRequestedHandoff(text) {
31
+ const lower = text.toLowerCase().trim();
32
+ const patterns = [
33
+ /\b(talk|speak)\s+(to|with)\s+(a\s+)?(person|human|agent|representative|someone)\b/,
34
+ /\b(real|live)\s+(person|human|agent|support)\b/,
35
+ /\bconnect\s+(me\s+)?(with|to)\s+(a\s+)?(person|human|agent)\b/,
36
+ /\b(need|want)\s+(to\s+)?(talk|speak)\s+(to|with)\s+(someone|a\s+person)\b/,
37
+ /\bhuman\s+(support|agent|help)\b/,
38
+ /\b(talk|speak)\s+to\s+(a\s+)?person\b/,
39
+ /\bget\s+(a\s+)?(person|human)\b/
40
+ ];
41
+ return patterns.some((p) => p.test(lower));
42
+ }
43
+ function adjustColor(hex, amount) {
44
+ const num = parseInt(hex.replace("#", ""), 16);
45
+ const r = Math.min(255, Math.max(0, (num >> 16) + amount));
46
+ const g = Math.min(255, Math.max(0, (num >> 8 & 255) + amount));
47
+ const b = Math.min(255, Math.max(0, (num & 255) + amount));
48
+ return `#${(1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1)}`;
49
+ }
50
+ function isLightColor(hex) {
51
+ const num = parseInt(hex.replace("#", ""), 16);
52
+ const r = num >> 16;
53
+ const g = num >> 8 & 255;
54
+ const b = num & 255;
55
+ return (r * 299 + g * 587 + b * 114) / 1e3 > 160;
56
+ }
57
+ function ChatWidget({ projectId: propProjectId, config, apiUrl: propApiUrl, signalUrl: propSignalUrl }) {
58
+ const [resolvedProjectId, setResolvedProjectId] = useState(propProjectId || null);
59
+ const projectId = propProjectId || resolvedProjectId || "";
60
+ const [isOpen, setIsOpen] = useState(false);
61
+ const [messages, setMessages] = useState([]);
62
+ const [inputValue, setInputValue] = useState("");
63
+ const [isLoading, setIsLoading] = useState(false);
64
+ const [availability, setAvailability] = useState(null);
65
+ const [sessionId, setSessionId] = useState(null);
66
+ const [visitorId] = useState(generateVisitorId);
67
+ const [agentTyping, setAgentTyping] = useState(false);
68
+ const [showOfflineForm, setShowOfflineForm] = useState(false);
69
+ const [offlineForm, setOfflineForm] = useState({ name: "", email: "", phone: "", message: "" });
70
+ const [offlineSubmitted, setOfflineSubmitted] = useState(false);
71
+ const [connectionStatus, setConnectionStatus] = useState("disconnected");
72
+ const [widgetConfig, setWidgetConfig] = useState(null);
73
+ const [handoffOfflinePrompt, setHandoffOfflinePrompt] = useState(null);
74
+ const [pendingFiles, setPendingFiles] = useState([]);
75
+ const [lastFailedSend, setLastFailedSend] = useState(null);
76
+ const [showWelcome, setShowWelcome] = useState(true);
77
+ const [checkingAvailability, setCheckingAvailability] = useState(false);
78
+ const [checkingHandoff, setCheckingHandoff] = useState(false);
79
+ useRef(null);
80
+ const pendingInitialMessageRef = useRef(null);
81
+ const apiKeyMissingWarnedRef = useRef(false);
82
+ const siteContextRef = useRef(null);
83
+ function ensureApiKey() {
84
+ const { apiKey } = getApiConfig();
85
+ if (apiKey) return apiKey;
86
+ if (!apiKeyMissingWarnedRef.current) {
87
+ apiKeyMissingWarnedRef.current = true;
88
+ console.warn("[ChatWidget] API key is required for Engage widget. Set __SITE_KIT_API_KEY__ or pass apiKey via SiteKitProvider.");
89
+ }
90
+ return null;
91
+ }
92
+ const messagesEndRef = useRef(null);
93
+ const inputRef = useRef(null);
94
+ const socketRef = useRef(null);
95
+ const pollingIntervalRef = useRef(null);
96
+ const position = config?.position || widgetConfig?.position || "bottom-right";
97
+ const primaryColor = widgetConfig?.brand_primary || config?.buttonColor || "#00afab";
98
+ const businessName = widgetConfig?.project_name || widgetConfig?.business_info?.name || "Chat with us";
99
+ const logoUrl = widgetConfig?.logo_url || null;
100
+ const welcomeEnabled = widgetConfig?.welcome_screen_enabled !== false;
101
+ const quickActions = Array.isArray(widgetConfig?.welcome_quick_actions) ? widgetConfig.welcome_quick_actions : [];
102
+ const showPoweredBy = widgetConfig?.show_powered_by !== false;
103
+ const welcomeMessage = widgetConfig?.initial_message ?? widgetConfig?.welcome_message ?? config?.welcomeMessage ?? "Hi! How can I help you today?";
104
+ const offlineHeading = widgetConfig?.offline_heading ?? "No agents available right now";
105
+ const offlineSubheading = handoffOfflinePrompt ?? widgetConfig?.offline_subheading ?? widgetConfig?.form_description ?? widgetConfig?.offline_message ?? config?.offlineMessage ?? "Leave us a message and we'll get back to you!";
106
+ const baseUrl = propApiUrl || getApiConfig().apiUrl;
107
+ const signalUrl = propSignalUrl || getApiConfig().signalUrl;
108
+ useEffect(() => {
109
+ if (propProjectId || resolvedProjectId) return;
110
+ const { apiKey } = getApiConfig();
111
+ if (!apiKey) return;
112
+ let cancelled = false;
113
+ fetch(`${baseUrl}/api/public/seo/project-info`, {
114
+ headers: { "x-api-key": apiKey }
115
+ }).then((res) => res.ok ? res.json() : null).then((data) => {
116
+ if (cancelled || !data?.valid || !data?.project_id) return;
117
+ setResolvedProjectId(data.project_id);
118
+ }).catch(() => {
119
+ });
120
+ return () => {
121
+ cancelled = true;
122
+ };
123
+ }, [propProjectId, resolvedProjectId, baseUrl]);
124
+ const fetchWidgetConfig = useCallback(async () => {
125
+ if (!projectId) return;
126
+ const apiKey = ensureApiKey();
127
+ if (!apiKey) return;
128
+ try {
129
+ const response = await fetch(`${baseUrl}/engage/widget/config?projectId=${projectId}`, {
130
+ headers: { "x-api-key": apiKey }
131
+ });
132
+ if (response.ok) {
133
+ const { data } = await response.json();
134
+ setWidgetConfig(data ?? null);
135
+ }
136
+ } catch (error) {
137
+ if (process.env.NODE_ENV === "development") {
138
+ console.warn("[ChatWidget] Config fetch failed:", error instanceof Error ? error.message : error);
139
+ }
140
+ }
141
+ }, [projectId, baseUrl]);
142
+ const checkAvailability = useCallback(async () => {
143
+ if (!projectId) return null;
144
+ const apiKey = ensureApiKey();
145
+ if (!apiKey) return null;
146
+ try {
147
+ const response = await fetch(`${baseUrl}/engage/widget/availability?projectId=${projectId}`, {
148
+ headers: { "x-api-key": apiKey }
149
+ });
150
+ if (response.ok) {
151
+ const { data } = await response.json();
152
+ setAvailability(data);
153
+ return data;
154
+ }
155
+ } catch (error) {
156
+ if (process.env.NODE_ENV === "development") {
157
+ console.warn("[ChatWidget] Availability check failed:", error instanceof Error ? error.message : error);
158
+ }
159
+ }
160
+ return null;
161
+ }, [projectId, baseUrl]);
162
+ const initSession = useCallback(async () => {
163
+ const apiKey = ensureApiKey();
164
+ if (!apiKey) return null;
165
+ try {
166
+ const response = await fetch(`${baseUrl}/engage/widget/session`, {
167
+ method: "POST",
168
+ headers: { "Content-Type": "application/json", "x-api-key": apiKey },
169
+ body: JSON.stringify({
170
+ projectId,
171
+ visitorId,
172
+ sourceUrl: typeof window !== "undefined" ? window.location.href : "",
173
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : ""
174
+ })
175
+ });
176
+ if (response.ok) {
177
+ const { data } = await response.json();
178
+ const session = data.session ?? data;
179
+ const sid = session?.id ?? session?.session_id ?? data.id ?? data.session_id;
180
+ const signalEnabled = session?.chat_mode === "ai" || session?.chat_mode === "hybrid";
181
+ setSessionId(sid);
182
+ const messages2 = session?.messages ?? data.messages ?? [];
183
+ if (messages2.length > 0) {
184
+ setMessages(
185
+ messages2.map((m) => ({
186
+ id: m.id,
187
+ role: m.role === "visitor" ? "user" : m.role,
188
+ content: m.content,
189
+ timestamp: new Date(m.created_at),
190
+ agentName: m.sender_name
191
+ }))
192
+ );
193
+ }
194
+ return { id: sid, signalEnabled };
195
+ }
196
+ } catch (error) {
197
+ console.error("[ChatWidget] Session init failed:", error);
198
+ }
199
+ return null;
200
+ }, [projectId, visitorId, baseUrl]);
201
+ const fetchSiteContext = useCallback(async () => {
202
+ if (siteContextRef.current) return siteContextRef.current;
203
+ try {
204
+ const base = typeof window !== "undefined" ? window.location.origin : "";
205
+ const res = await fetch(`${base}/llms.txt`, { cache: "no-store" });
206
+ if (res.ok) {
207
+ const text = await res.text();
208
+ if (text?.trim()) siteContextRef.current = text.trim();
209
+ }
210
+ } catch {
211
+ }
212
+ return siteContextRef.current;
213
+ }, []);
214
+ const sendToSignalApi = useCallback(
215
+ async (content, conversationId) => {
216
+ const apiKey = ensureApiKey();
217
+ if (!apiKey) throw new Error("API key required for chat");
218
+ const url = `${signalUrl.replace(/\/$/, "")}/echo/public/chat`;
219
+ const siteContext = await fetchSiteContext();
220
+ const body = {
221
+ message: content,
222
+ visitorId,
223
+ ...conversationId ? { conversationId } : {},
224
+ pageUrl: typeof window !== "undefined" ? window.location.href : void 0,
225
+ ...siteContext ? { siteContext } : {}
226
+ };
227
+ const res = await fetch(url, {
228
+ method: "POST",
229
+ headers: {
230
+ "Content-Type": "application/json",
231
+ "x-api-key": apiKey
232
+ },
233
+ body: JSON.stringify(body)
234
+ });
235
+ const json = await res.json();
236
+ if (!res.ok) {
237
+ throw new Error(json?.error?.message || json?.message || `Signal API error: ${res.status}`);
238
+ }
239
+ const data = json?.data ?? json;
240
+ const aiContent = data?.content ?? data?.response ?? data?.message ?? "I'm sorry, I couldn't process that.";
241
+ const suggestions = data?.suggestions;
242
+ const offerHandoff = data?.offerHandoff === true || data?.offer_handoff === true;
243
+ const newConversationId = data?.conversationId ?? conversationId;
244
+ return { content: aiContent, suggestions, offerHandoff, conversationId: newConversationId };
245
+ },
246
+ [signalUrl, visitorId, fetchSiteContext]
247
+ );
248
+ const handleSocketMessage = useCallback((data) => {
249
+ switch (data.type || data.event) {
250
+ case "message": {
251
+ const role = data.role === "visitor" ? "user" : data.role === "ai" ? "assistant" : data.role;
252
+ const newMessage = {
253
+ id: data.id || `msg-${Date.now()}`,
254
+ role,
255
+ content: data.content ?? "",
256
+ timestamp: /* @__PURE__ */ new Date(),
257
+ agentName: data.agentName,
258
+ ...data.attachments?.length ? { attachments: data.attachments } : {},
259
+ ...data.suggestions?.length ? { suggestions: data.suggestions } : {}
260
+ };
261
+ setMessages((prev) => [...prev, newMessage]);
262
+ if (role === "assistant" || role === "agent") {
263
+ setAgentTyping(false);
264
+ setIsLoading(false);
265
+ }
266
+ break;
267
+ }
268
+ case "agent:joined":
269
+ setMessages((prev) => [
270
+ ...prev,
271
+ {
272
+ id: `system-${Date.now()}`,
273
+ role: "system",
274
+ content: data.agentName ? `${data.agentName} has joined the chat.` : "An agent has joined the chat.",
275
+ timestamp: /* @__PURE__ */ new Date()
276
+ }
277
+ ]);
278
+ break;
279
+ case "typing":
280
+ setAgentTyping(data.isTyping);
281
+ break;
282
+ case "handoff:initiated":
283
+ setMessages((prev) => [
284
+ ...prev,
285
+ { id: `system-${Date.now()}`, role: "system", content: data.message || "Connecting you with a team member...", timestamp: /* @__PURE__ */ new Date() }
286
+ ]);
287
+ break;
288
+ case "chat:closed":
289
+ setMessages((prev) => [
290
+ ...prev,
291
+ { id: `system-${Date.now()}`, role: "system", content: data.message || "This chat has been closed.", timestamp: /* @__PURE__ */ new Date() }
292
+ ]);
293
+ break;
294
+ }
295
+ }, []);
296
+ const connectSocket = useCallback(
297
+ async (currentSessionId) => {
298
+ if (socketRef.current?.connected) return;
299
+ if (socketRef.current) {
300
+ socketRef.current.disconnect();
301
+ socketRef.current = null;
302
+ }
303
+ const { createSocket } = await import('./socket-loader-R7S2YJ2J.mjs');
304
+ const namespaceUrl = `${baseUrl.replace(/\/$/, "")}/engage/chat`;
305
+ const socket = await createSocket(namespaceUrl, {
306
+ query: { projectId, visitorId, sessionId: currentSessionId },
307
+ transports: ["websocket", "polling"],
308
+ // Auto-reconnect config
309
+ reconnection: true,
310
+ reconnectionAttempts: 10,
311
+ reconnectionDelay: 1e3,
312
+ reconnectionDelayMax: 1e4,
313
+ timeout: 15e3
314
+ });
315
+ socket.on("connect", () => {
316
+ setConnectionStatus("connected");
317
+ console.log("[ChatWidget] Socket.io connected");
318
+ if (currentSessionId) {
319
+ const apiKey = ensureApiKey();
320
+ if (apiKey) {
321
+ fetch(`${baseUrl}/engage/widget/messages?sessionId=${currentSessionId}`, {
322
+ headers: { "x-api-key": apiKey }
323
+ }).then((res) => res.ok ? res.json() : null).then((json) => {
324
+ const data = json?.data ?? json;
325
+ if (Array.isArray(data) && data.length) {
326
+ setMessages((prev) => {
327
+ const byId = new Map(prev.map((m) => [m.id, m]));
328
+ data.forEach(
329
+ (m) => byId.set(m.id, {
330
+ id: m.id,
331
+ role: m.role === "visitor" ? "user" : m.role,
332
+ content: m.content,
333
+ timestamp: new Date(m.created_at),
334
+ agentName: m.sender_name,
335
+ attachments: m.attachments
336
+ })
337
+ );
338
+ return Array.from(byId.values()).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
339
+ });
340
+ }
341
+ }).catch((err) => console.warn("[ChatWidget] Refetch messages on reconnect failed", err));
342
+ }
343
+ }
344
+ if (lastFailedSend) {
345
+ socket.emit("visitor:message", {
346
+ content: lastFailedSend.content,
347
+ attachments: lastFailedSend.attachments?.length ? lastFailedSend.attachments : void 0
348
+ });
349
+ setLastFailedSend(null);
350
+ setMessages((prev) => prev.filter((m) => !m.sendFailed));
351
+ setIsLoading(true);
352
+ }
353
+ });
354
+ socket.on("message", (data) => handleSocketMessage({ type: "message", ...data }));
355
+ socket.on("agent:joined", (data) => handleSocketMessage({ type: "agent:joined", ...data }));
356
+ socket.on("typing", (data) => handleSocketMessage({ type: "typing", ...data }));
357
+ socket.on("handoff:initiated", (data) => handleSocketMessage({ type: "handoff:initiated", ...data }));
358
+ socket.on("chat:closed", (data) => handleSocketMessage({ type: "chat:closed", ...data }));
359
+ socket.on("disconnect", (reason) => {
360
+ setConnectionStatus("disconnected");
361
+ console.log("[ChatWidget] Socket disconnected:", reason);
362
+ });
363
+ socket.on("reconnect_attempt", (attempt) => {
364
+ setConnectionStatus("connecting");
365
+ console.log(`[ChatWidget] Reconnect attempt #${attempt}`);
366
+ });
367
+ socket.on("reconnect_failed", () => {
368
+ console.warn("[ChatWidget] All reconnect attempts failed, falling back to polling");
369
+ if (isOpen && currentSessionId) startPolling(currentSessionId);
370
+ });
371
+ socket.on("connect_error", (err) => {
372
+ console.error("[ChatWidget] Socket connect error:", err?.message || err, { baseUrl: namespaceUrl, projectId, visitorId });
373
+ setConnectionStatus("connecting");
374
+ });
375
+ socketRef.current = socket;
376
+ },
377
+ [projectId, visitorId, baseUrl, isOpen, handleSocketMessage, lastFailedSend]
378
+ );
379
+ const startPolling = useCallback(
380
+ (currentSessionId) => {
381
+ if (pollingIntervalRef.current) return;
382
+ pollingIntervalRef.current = setInterval(async () => {
383
+ try {
384
+ const apiKey = ensureApiKey();
385
+ if (!apiKey) return;
386
+ const response = await fetch(`${baseUrl}/engage/widget/messages?sessionId=${currentSessionId}`, {
387
+ headers: { "x-api-key": apiKey }
388
+ });
389
+ if (response.ok) {
390
+ const { data } = await response.json();
391
+ setMessages((prev) => {
392
+ const existingIds = new Set(prev.map((m) => m.id));
393
+ const newMessages = data.filter((m) => !existingIds.has(m.id));
394
+ if (newMessages.length > 0) {
395
+ return [
396
+ ...prev,
397
+ ...newMessages.map((m) => ({
398
+ id: m.id,
399
+ role: m.role === "visitor" ? "user" : m.role,
400
+ content: m.content,
401
+ timestamp: new Date(m.created_at),
402
+ agentName: m.sender_name
403
+ }))
404
+ ];
405
+ }
406
+ return prev;
407
+ });
408
+ }
409
+ } catch (error) {
410
+ console.error("[ChatWidget] Polling failed:", error);
411
+ }
412
+ }, 3e3);
413
+ },
414
+ [baseUrl]
415
+ );
416
+ useEffect(() => {
417
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
418
+ }, [messages, agentTyping]);
419
+ useEffect(() => {
420
+ if (isOpen && !showWelcome && inputRef.current) inputRef.current.focus();
421
+ }, [isOpen, showWelcome]);
422
+ useEffect(() => {
423
+ fetchWidgetConfig();
424
+ checkAvailability();
425
+ const interval = setInterval(checkAvailability, 6e4);
426
+ return () => {
427
+ clearInterval(interval);
428
+ if (socketRef.current) {
429
+ socketRef.current.disconnect();
430
+ socketRef.current = null;
431
+ }
432
+ };
433
+ }, [fetchWidgetConfig, checkAvailability]);
434
+ useEffect(() => {
435
+ if (isOpen && !showWelcome && !checkingAvailability && !showOfflineForm && !sessionId) {
436
+ initSession().then(async (result) => {
437
+ if (!result?.id) return;
438
+ const { id, signalEnabled } = result;
439
+ const pending = pendingInitialMessageRef.current;
440
+ if (pending) {
441
+ pendingInitialMessageRef.current = null;
442
+ }
443
+ if (signalEnabled && pending) {
444
+ setIsLoading(true);
445
+ setMessages((prev) => [
446
+ ...prev,
447
+ { id: `u-${Date.now()}`, role: "user", content: pending, timestamp: /* @__PURE__ */ new Date() }
448
+ ]);
449
+ try {
450
+ const { content: aiContent, suggestions, offerHandoff } = await sendToSignalApi(pending, id);
451
+ const userWantsHandoff = userRequestedHandoff(pending);
452
+ setMessages((prev) => [
453
+ ...prev,
454
+ {
455
+ id: `ai-${Date.now()}`,
456
+ role: "assistant",
457
+ content: aiContent,
458
+ timestamp: /* @__PURE__ */ new Date(),
459
+ ...suggestions?.length ? { suggestions } : {},
460
+ ...offerHandoff || userWantsHandoff ? { offerHandoff: true } : {}
461
+ }
462
+ ]);
463
+ } catch (err) {
464
+ console.error("[ChatWidget] Signal API error:", err);
465
+ setMessages((prev) => [
466
+ ...prev,
467
+ {
468
+ id: `err-${Date.now()}`,
469
+ role: "assistant",
470
+ content: "I apologize, but I encountered an error. Would you like to speak with a team member?",
471
+ timestamp: /* @__PURE__ */ new Date(),
472
+ suggestions: ["Talk to a person", "Try again"],
473
+ offerHandoff: true
474
+ }
475
+ ]);
476
+ } finally {
477
+ setIsLoading(false);
478
+ }
479
+ }
480
+ setConnectionStatus("connecting");
481
+ await connectSocket(id);
482
+ if (!signalEnabled && pending) {
483
+ const waitForSocket = () => new Promise((resolve) => {
484
+ const check = (attempts = 0) => {
485
+ if (socketRef.current?.connected || attempts > 20) {
486
+ resolve();
487
+ return;
488
+ }
489
+ setTimeout(() => check(attempts + 1), 150);
490
+ };
491
+ check();
492
+ });
493
+ await waitForSocket();
494
+ if (socketRef.current?.connected) {
495
+ socketRef.current.emit("visitor:message", { content: pending });
496
+ }
497
+ }
498
+ });
499
+ }
500
+ return () => {
501
+ if (pollingIntervalRef.current) clearInterval(pollingIntervalRef.current);
502
+ };
503
+ }, [isOpen, showWelcome, checkingAvailability, showOfflineForm, sessionId, initSession, connectSocket, sendToSignalApi]);
504
+ const handleToggle = useCallback(() => {
505
+ setIsOpen((prev) => !prev);
506
+ }, []);
507
+ const startChat = useCallback(
508
+ async (initialMessage) => {
509
+ setShowWelcome(false);
510
+ const beginChatSession = () => {
511
+ setMessages([{ id: "welcome", role: "assistant", content: welcomeMessage, timestamp: /* @__PURE__ */ new Date() }]);
512
+ if (initialMessage) {
513
+ pendingInitialMessageRef.current = initialMessage;
514
+ const userMsg = { id: `user-${Date.now()}`, role: "user", content: initialMessage, timestamp: /* @__PURE__ */ new Date() };
515
+ setMessages((prev) => [...prev, userMsg]);
516
+ setIsLoading(true);
517
+ }
518
+ };
519
+ if (widgetConfig?.signal_enabled) {
520
+ beginChatSession();
521
+ return;
522
+ }
523
+ setCheckingAvailability(true);
524
+ const firstCheck = await checkAvailability();
525
+ if (firstCheck?.available && firstCheck.agentsOnline > 0) {
526
+ setCheckingAvailability(false);
527
+ beginChatSession();
528
+ return;
529
+ }
530
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
531
+ const secondCheck = await checkAvailability();
532
+ setCheckingAvailability(false);
533
+ if (secondCheck?.available && secondCheck.agentsOnline > 0) {
534
+ beginChatSession();
535
+ } else {
536
+ setShowOfflineForm(true);
537
+ }
538
+ },
539
+ [widgetConfig?.signal_enabled, welcomeMessage, checkAvailability]
540
+ );
541
+ const uploadWidgetFile = useCallback(
542
+ async (file) => {
543
+ if (!sessionId || !visitorId) return null;
544
+ const apiKey = ensureApiKey();
545
+ if (!apiKey) return null;
546
+ const form = new FormData();
547
+ form.append("file", file);
548
+ form.append("sessionId", sessionId);
549
+ form.append("visitorId", visitorId);
550
+ const res = await fetch(`${baseUrl}/engage/widget/upload`, {
551
+ method: "POST",
552
+ headers: { "x-api-key": apiKey },
553
+ body: form
554
+ });
555
+ if (!res.ok) throw new Error("Upload failed");
556
+ const json = await res.json();
557
+ const d = json?.data ?? json;
558
+ return d?.url ? { name: d.name ?? file.name, url: d.url, size: d.size, mimeType: d.mimeType } : null;
559
+ },
560
+ [sessionId, visitorId, baseUrl]
561
+ );
562
+ const handleSubmit = useCallback(
563
+ async (e) => {
564
+ e.preventDefault();
565
+ const hasText = !!inputValue.trim();
566
+ const hasFiles = pendingFiles.length > 0;
567
+ if (!hasText && !hasFiles || isLoading) return;
568
+ const content = inputValue.trim() || "";
569
+ let attachments = [];
570
+ if (pendingFiles.length) {
571
+ try {
572
+ for (const file of pendingFiles) {
573
+ const att = await uploadWidgetFile(file);
574
+ if (att) attachments.push(att);
575
+ }
576
+ setPendingFiles([]);
577
+ } catch (err) {
578
+ console.error("[ChatWidget] File upload failed", err);
579
+ setMessages((prev) => [...prev, { id: `err-${Date.now()}`, role: "assistant", content: "Failed to upload file. Please try again.", timestamp: /* @__PURE__ */ new Date() }]);
580
+ return;
581
+ }
582
+ }
583
+ const userMessage = {
584
+ id: `user-${Date.now()}`,
585
+ role: "user",
586
+ content,
587
+ timestamp: /* @__PURE__ */ new Date(),
588
+ ...attachments.length ? { attachments } : {}
589
+ };
590
+ setMessages((prev) => [...prev, userMessage]);
591
+ setInputValue("");
592
+ setIsLoading(true);
593
+ if (widgetConfig?.signal_enabled && hasText) {
594
+ try {
595
+ const { content: aiContent, suggestions, offerHandoff } = await sendToSignalApi(content, sessionId);
596
+ const lastUserContent = messages.filter((m) => m.role === "user").slice(-1)[0]?.content ?? "";
597
+ setMessages((prev) => [
598
+ ...prev,
599
+ {
600
+ id: `ai-${Date.now()}`,
601
+ role: "assistant",
602
+ content: aiContent,
603
+ timestamp: /* @__PURE__ */ new Date(),
604
+ ...suggestions?.length ? { suggestions } : {},
605
+ ...offerHandoff || userRequestedHandoff(lastUserContent) ? { offerHandoff: true } : {}
606
+ }
607
+ ]);
608
+ } catch (err) {
609
+ console.error("[ChatWidget] Signal API error:", err);
610
+ setMessages((prev) => [
611
+ ...prev,
612
+ {
613
+ id: `err-${Date.now()}`,
614
+ role: "assistant",
615
+ content: "I apologize, but I encountered an error. Would you like to speak with a team member?",
616
+ timestamp: /* @__PURE__ */ new Date(),
617
+ suggestions: ["Talk to a person", "Try again"],
618
+ offerHandoff: true
619
+ }
620
+ ]);
621
+ } finally {
622
+ setIsLoading(false);
623
+ }
624
+ return;
625
+ }
626
+ const socket = socketRef.current;
627
+ if (socket?.connected) {
628
+ socket.emit("visitor:message", { content: userMessage.content, attachments: attachments.length ? attachments : void 0 });
629
+ setLastFailedSend(null);
630
+ return;
631
+ }
632
+ setLastFailedSend({ content: userMessage.content, attachments });
633
+ setTimeout(() => {
634
+ if (!socketRef.current?.connected) {
635
+ setMessages((prev) => [
636
+ ...prev,
637
+ { id: `error-${Date.now()}`, role: "system", content: "Reconnecting...", timestamp: /* @__PURE__ */ new Date(), sendFailed: true }
638
+ ]);
639
+ setIsLoading(false);
640
+ }
641
+ }, 3e3);
642
+ },
643
+ [inputValue, isLoading, pendingFiles, uploadWidgetFile, widgetConfig?.signal_enabled, sendToSignalApi, sessionId]
644
+ );
645
+ const retryFailedSend = useCallback(() => {
646
+ if (!lastFailedSend || !sessionId) return;
647
+ const socket = socketRef.current;
648
+ if (socket?.connected) {
649
+ socket.emit("visitor:message", { content: lastFailedSend.content, attachments: lastFailedSend.attachments?.length ? lastFailedSend.attachments : void 0 });
650
+ setLastFailedSend(null);
651
+ setMessages((prev) => prev.filter((m) => !m.sendFailed));
652
+ setIsLoading(true);
653
+ } else {
654
+ connectSocket(sessionId);
655
+ }
656
+ }, [lastFailedSend, sessionId, connectSocket]);
657
+ const requestHandoff = useCallback(async () => {
658
+ if (!sessionId) return;
659
+ const apiKey = ensureApiKey();
660
+ if (!apiKey) return;
661
+ setCheckingHandoff(true);
662
+ try {
663
+ const firstAvail = await checkAvailability();
664
+ if (firstAvail?.agentsOnline && firstAvail.agentsOnline > 0) {
665
+ setCheckingHandoff(false);
666
+ await fetch(`${baseUrl}/engage/widget/handoff`, {
667
+ method: "POST",
668
+ headers: { "Content-Type": "application/json", "x-api-key": apiKey },
669
+ body: JSON.stringify({ sessionId })
670
+ });
671
+ setMessages((prev) => [
672
+ ...prev,
673
+ { id: `handoff-${Date.now()}`, role: "system", content: "Connecting you with a team member. Please hold on!", timestamp: /* @__PURE__ */ new Date() }
674
+ ]);
675
+ return;
676
+ }
677
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
678
+ const secondAvail = await checkAvailability();
679
+ setCheckingHandoff(false);
680
+ if (secondAvail?.agentsOnline && secondAvail.agentsOnline > 0) {
681
+ await fetch(`${baseUrl}/engage/widget/handoff`, {
682
+ method: "POST",
683
+ headers: { "Content-Type": "application/json", "x-api-key": apiKey },
684
+ body: JSON.stringify({ sessionId })
685
+ });
686
+ setMessages((prev) => [
687
+ ...prev,
688
+ { id: `handoff-${Date.now()}`, role: "system", content: "Connecting you with a team member. Please hold on!", timestamp: /* @__PURE__ */ new Date() }
689
+ ]);
690
+ } else {
691
+ setHandoffOfflinePrompt(widgetConfig?.offline_subheading ?? "Nobody is online right now. Leave your details and we'll get back to you.");
692
+ setShowOfflineForm(true);
693
+ setMessages((prev) => [
694
+ ...prev,
695
+ { id: `handoff-offline-${Date.now()}`, role: "system", content: offlineHeading, timestamp: /* @__PURE__ */ new Date() }
696
+ ]);
697
+ }
698
+ } catch (error) {
699
+ setCheckingHandoff(false);
700
+ console.error("[ChatWidget] Handoff request failed:", error);
701
+ }
702
+ }, [sessionId, baseUrl, projectId, widgetConfig, offlineHeading, checkAvailability]);
703
+ const [offlineError, setOfflineError] = useState(null);
704
+ const handleOfflineSubmit = useCallback(
705
+ async (e) => {
706
+ e.preventDefault();
707
+ if (!offlineForm.name || !offlineForm.email || !offlineForm.message) return;
708
+ const apiKey = ensureApiKey();
709
+ if (!apiKey) return;
710
+ setIsLoading(true);
711
+ setOfflineError(null);
712
+ try {
713
+ const response = await fetch(`${baseUrl}/engage/widget/offline-form`, {
714
+ method: "POST",
715
+ headers: { "Content-Type": "application/json", "x-api-key": apiKey },
716
+ body: JSON.stringify({
717
+ projectId,
718
+ visitorId,
719
+ ...offlineForm,
720
+ pageUrl: typeof window !== "undefined" ? window.location.href : "",
721
+ ...widgetConfig?.offlineFormSlug && { formSlug: widgetConfig.offlineFormSlug }
722
+ })
723
+ });
724
+ if (response.ok) {
725
+ setOfflineSubmitted(true);
726
+ } else {
727
+ const errorBody = await response.text().catch(() => "");
728
+ console.error(`[ChatWidget] Offline form returned ${response.status}:`, errorBody);
729
+ setOfflineError("Something went wrong. Please try again.");
730
+ }
731
+ } catch (error) {
732
+ console.error("[ChatWidget] Offline form submission failed:", error);
733
+ setOfflineError("Unable to send message. Please check your connection and try again.");
734
+ } finally {
735
+ setIsLoading(false);
736
+ }
737
+ },
738
+ [offlineForm, projectId, visitorId, baseUrl, widgetConfig]
739
+ );
740
+ const handleKeyDown = useCallback((e) => {
741
+ if (e.key === "Enter" && !e.shiftKey) {
742
+ e.preventDefault();
743
+ handleSubmit(e);
744
+ }
745
+ }, [handleSubmit]);
746
+ const handleTyping = useCallback(() => {
747
+ const socket = socketRef.current;
748
+ if (socket?.connected) {
749
+ socket.emit("visitor:typing", { isTyping: true });
750
+ setTimeout(() => {
751
+ if (socketRef.current?.connected) socketRef.current.emit("visitor:typing", { isTyping: false });
752
+ }, 2e3);
753
+ }
754
+ }, []);
755
+ const statusLabel = (() => {
756
+ if (showOfflineForm) return null;
757
+ if (checkingAvailability) return { dot: "#f59e0b", text: "Checking for a team member..." };
758
+ if (widgetConfig?.signal_enabled) return { dot: null, text: "Echo Intelligent Site Assistant" };
759
+ if (availability && availability.agentsOnline > 0) return { dot: "#22c55e", text: "Online" };
760
+ return { dot: "#9ca3af", text: "We'll respond soon" };
761
+ })();
762
+ const ChatButton = /* @__PURE__ */ jsx(
763
+ "button",
764
+ {
765
+ onClick: handleToggle,
766
+ "aria-label": isOpen ? "Close chat" : "Open chat",
767
+ style: {
768
+ position: "fixed",
769
+ [position === "bottom-left" ? "left" : "right"]: 20,
770
+ bottom: 20,
771
+ width: 60,
772
+ height: 60,
773
+ borderRadius: "50%",
774
+ backgroundColor: primaryColor,
775
+ border: "none",
776
+ display: "flex",
777
+ alignItems: "center",
778
+ justifyContent: "center",
779
+ cursor: "pointer",
780
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.25)",
781
+ transition: "transform 0.2s, box-shadow 0.2s",
782
+ zIndex: 9999
783
+ },
784
+ onMouseEnter: (e) => {
785
+ e.currentTarget.style.transform = "scale(1.05)";
786
+ e.currentTarget.style.boxShadow = "0 6px 16px rgba(0, 0, 0, 0.3)";
787
+ },
788
+ onMouseLeave: (e) => {
789
+ e.currentTarget.style.transform = "scale(1)";
790
+ e.currentTarget.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.25)";
791
+ },
792
+ children: isOpen ? /* @__PURE__ */ jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
793
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
794
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
795
+ ] }) : /* @__PURE__ */ jsx("svg", { width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) })
796
+ }
797
+ );
798
+ const Header = /* @__PURE__ */ jsxs(
799
+ "div",
800
+ {
801
+ style: {
802
+ padding: "16px 20px",
803
+ background: `linear-gradient(135deg, ${primaryColor}, ${adjustColor(primaryColor, -25)})`,
804
+ color: isLightColor(primaryColor) ? "#1a1a1a" : "white",
805
+ display: "flex",
806
+ alignItems: "center",
807
+ gap: 12
808
+ },
809
+ children: [
810
+ /* @__PURE__ */ jsx(
811
+ "div",
812
+ {
813
+ style: {
814
+ width: 40,
815
+ height: 40,
816
+ borderRadius: "50%",
817
+ backgroundColor: "rgba(255,255,255,0.2)",
818
+ display: "flex",
819
+ alignItems: "center",
820
+ justifyContent: "center",
821
+ overflow: "hidden",
822
+ flexShrink: 0
823
+ },
824
+ children: logoUrl ? /* @__PURE__ */ jsx("img", { src: logoUrl, alt: "", style: { width: 28, height: 28, objectFit: "contain", filter: isLightColor(primaryColor) ? "none" : "brightness(0) invert(1)" } }) : /* @__PURE__ */ jsx("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) })
825
+ }
826
+ ),
827
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
828
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 600, fontSize: 16, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: showOfflineForm ? offlineHeading : businessName }),
829
+ statusLabel && /* @__PURE__ */ jsxs("div", { style: { fontSize: 13, opacity: 0.9, display: "flex", alignItems: "center", gap: 6, marginTop: 2 }, children: [
830
+ statusLabel.dot && /* @__PURE__ */ jsx("span", { style: { width: 8, height: 8, borderRadius: "50%", backgroundColor: statusLabel.dot, flexShrink: 0 } }),
831
+ statusLabel.text
832
+ ] })
833
+ ] }),
834
+ connectionStatus === "connecting" && !showWelcome && !showOfflineForm && /* @__PURE__ */ jsxs("div", { style: { fontSize: 11, opacity: 0.7, display: "flex", alignItems: "center", gap: 4 }, children: [
835
+ /* @__PURE__ */ jsx("span", { style: { animation: "chatDot 1s infinite ease-in-out" }, children: "\u25CF" }),
836
+ "Connecting"
837
+ ] })
838
+ ]
839
+ }
840
+ );
841
+ const WelcomeScreen = /* @__PURE__ */ jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", padding: 20, gap: 16 }, children: [
842
+ /* @__PURE__ */ jsx("div", { style: { textAlign: "center", paddingTop: 8 }, children: /* @__PURE__ */ jsx("div", { style: { fontSize: 15, color: "#374151", lineHeight: 1.5 }, children: welcomeMessage }) }),
843
+ quickActions.length > 0 && /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: quickActions.map((action, i) => /* @__PURE__ */ jsx(
844
+ "button",
845
+ {
846
+ type: "button",
847
+ onClick: () => startChat(action),
848
+ style: {
849
+ padding: "12px 16px",
850
+ borderRadius: 12,
851
+ border: `1px solid ${primaryColor}33`,
852
+ backgroundColor: `${primaryColor}08`,
853
+ color: primaryColor,
854
+ fontSize: 14,
855
+ cursor: "pointer",
856
+ textAlign: "left",
857
+ transition: "background-color 0.15s, border-color 0.15s",
858
+ lineHeight: 1.4
859
+ },
860
+ onMouseEnter: (e) => {
861
+ e.currentTarget.style.backgroundColor = `${primaryColor}15`;
862
+ e.currentTarget.style.borderColor = `${primaryColor}55`;
863
+ },
864
+ onMouseLeave: (e) => {
865
+ e.currentTarget.style.backgroundColor = `${primaryColor}08`;
866
+ e.currentTarget.style.borderColor = `${primaryColor}33`;
867
+ },
868
+ children: action
869
+ },
870
+ i
871
+ )) }),
872
+ /* @__PURE__ */ jsx(
873
+ "button",
874
+ {
875
+ type: "button",
876
+ onClick: () => startChat(),
877
+ style: {
878
+ padding: "12px 20px",
879
+ borderRadius: 12,
880
+ border: "none",
881
+ backgroundColor: primaryColor,
882
+ color: isLightColor(primaryColor) ? "#1a1a1a" : "white",
883
+ fontSize: 14,
884
+ fontWeight: 600,
885
+ cursor: "pointer",
886
+ marginTop: "auto"
887
+ },
888
+ children: "Start a conversation"
889
+ }
890
+ ),
891
+ widgetConfig?.business_info?.phone && /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", fontSize: 13, color: "#6b7280" }, children: [
892
+ "Or call us at",
893
+ " ",
894
+ /* @__PURE__ */ jsx("a", { href: `tel:${widgetConfig.business_info.phone}`, style: { color: primaryColor, textDecoration: "none", fontWeight: 500 }, children: widgetConfig.business_info.phone })
895
+ ] })
896
+ ] });
897
+ const OfflineFormView = /* @__PURE__ */ jsx("div", { style: { padding: 20, flex: 1, overflowY: "auto" }, children: offlineSubmitted ? /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", padding: 20 }, children: [
898
+ /* @__PURE__ */ jsx("div", { style: { width: 56, height: 56, borderRadius: "50%", backgroundColor: `${primaryColor}15`, display: "flex", alignItems: "center", justifyContent: "center", margin: "0 auto 16px" }, children: /* @__PURE__ */ jsx("svg", { width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: primaryColor, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) }) }),
899
+ /* @__PURE__ */ jsx("h3", { style: { margin: "0 0 8px", fontSize: 18, fontWeight: 600, color: "#111827" }, children: "Message Sent!" }),
900
+ /* @__PURE__ */ jsx("p", { style: { margin: 0, color: "#6b7280", fontSize: 14 }, children: offlineSubheading })
901
+ ] }) : /* @__PURE__ */ jsxs("form", { onSubmit: handleOfflineSubmit, children: [
902
+ /* @__PURE__ */ jsx("p", { style: { margin: "0 0 16px", color: "#6b7280", fontSize: 14 }, children: offlineSubheading }),
903
+ [
904
+ { name: "name", type: "text", placeholder: "Your name *", required: true },
905
+ { name: "email", type: "email", placeholder: "Your email *", required: true },
906
+ { name: "phone", type: "tel", placeholder: "Phone (optional)", required: false }
907
+ ].map((field) => /* @__PURE__ */ jsx("div", { style: { marginBottom: 12 }, children: /* @__PURE__ */ jsx(
908
+ "input",
909
+ {
910
+ type: field.type,
911
+ placeholder: field.placeholder,
912
+ value: offlineForm[field.name],
913
+ onChange: (e) => setOfflineForm((prev) => ({ ...prev, [field.name]: e.target.value })),
914
+ required: field.required,
915
+ style: { width: "100%", padding: "10px 12px", borderRadius: 8, border: "1px solid #e5e7eb", fontSize: 14, outline: "none", boxSizing: "border-box" },
916
+ onFocus: (e) => e.currentTarget.style.borderColor = primaryColor,
917
+ onBlur: (e) => e.currentTarget.style.borderColor = "#e5e7eb"
918
+ }
919
+ ) }, field.name)),
920
+ /* @__PURE__ */ jsx("div", { style: { marginBottom: 16 }, children: /* @__PURE__ */ jsx(
921
+ "textarea",
922
+ {
923
+ placeholder: "How can we help? *",
924
+ value: offlineForm.message,
925
+ onChange: (e) => setOfflineForm((prev) => ({ ...prev, message: e.target.value })),
926
+ required: true,
927
+ rows: 4,
928
+ style: { width: "100%", padding: "10px 12px", borderRadius: 8, border: "1px solid #e5e7eb", fontSize: 14, outline: "none", resize: "vertical", boxSizing: "border-box" },
929
+ onFocus: (e) => e.currentTarget.style.borderColor = primaryColor,
930
+ onBlur: (e) => e.currentTarget.style.borderColor = "#e5e7eb"
931
+ }
932
+ ) }),
933
+ offlineError && /* @__PURE__ */ jsx("div", { style: { padding: "8px 12px", borderRadius: 8, backgroundColor: "#fef2f2", border: "1px solid #fecaca", color: "#dc2626", fontSize: 13, marginBottom: 12 }, children: offlineError }),
934
+ /* @__PURE__ */ jsx(
935
+ "button",
936
+ {
937
+ type: "submit",
938
+ disabled: isLoading,
939
+ style: {
940
+ width: "100%",
941
+ padding: "12px",
942
+ borderRadius: 8,
943
+ border: "none",
944
+ backgroundColor: primaryColor,
945
+ color: isLightColor(primaryColor) ? "#1a1a1a" : "white",
946
+ fontSize: 14,
947
+ fontWeight: 600,
948
+ cursor: isLoading ? "wait" : "pointer",
949
+ opacity: isLoading ? 0.7 : 1
950
+ },
951
+ children: isLoading ? "Sending..." : "Send Message"
952
+ }
953
+ )
954
+ ] }) });
955
+ const CheckingScreen = /* @__PURE__ */ jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", padding: 32, gap: 20, textAlign: "center" }, children: [
956
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: 64, height: 64 }, children: [
957
+ /* @__PURE__ */ jsx(
958
+ "div",
959
+ {
960
+ style: {
961
+ position: "absolute",
962
+ inset: 0,
963
+ borderRadius: "50%",
964
+ backgroundColor: `${primaryColor}15`,
965
+ animation: "checkPulse 2s infinite ease-out"
966
+ }
967
+ }
968
+ ),
969
+ /* @__PURE__ */ jsx(
970
+ "div",
971
+ {
972
+ style: {
973
+ position: "absolute",
974
+ inset: 8,
975
+ borderRadius: "50%",
976
+ backgroundColor: `${primaryColor}25`,
977
+ animation: "checkPulse 2s infinite ease-out 0.4s"
978
+ }
979
+ }
980
+ ),
981
+ /* @__PURE__ */ jsx(
982
+ "div",
983
+ {
984
+ style: {
985
+ position: "absolute",
986
+ inset: 16,
987
+ borderRadius: "50%",
988
+ backgroundColor: primaryColor,
989
+ display: "flex",
990
+ alignItems: "center",
991
+ justifyContent: "center"
992
+ },
993
+ children: /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: isLightColor(primaryColor) ? "#1a1a1a" : "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
994
+ /* @__PURE__ */ jsx("path", { d: "M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" }),
995
+ /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "7", r: "4" }),
996
+ /* @__PURE__ */ jsx("line", { x1: "20", y1: "8", x2: "20", y2: "14" }),
997
+ /* @__PURE__ */ jsx("line", { x1: "23", y1: "11", x2: "17", y2: "11" })
998
+ ] })
999
+ }
1000
+ )
1001
+ ] }),
1002
+ /* @__PURE__ */ jsxs("div", { children: [
1003
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 16, fontWeight: 600, color: "#111827", marginBottom: 6 }, children: checkingHandoff ? "Looking for online support" : "Checking for a team member" }),
1004
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 14, color: "#6b7280", lineHeight: 1.5 }, children: [
1005
+ "One moment please",
1006
+ /* @__PURE__ */ jsx("span", { style: { display: "inline-flex", width: 20 }, children: /* @__PURE__ */ jsx("span", { style: { animation: "checkDots 1.5s infinite steps(4, end)" }, children: "..." }) })
1007
+ ] })
1008
+ ] }),
1009
+ /* @__PURE__ */ jsx("div", { style: { width: "80%", height: 3, backgroundColor: "#e5e7eb", borderRadius: 2, overflow: "hidden" }, children: /* @__PURE__ */ jsx(
1010
+ "div",
1011
+ {
1012
+ style: {
1013
+ width: "100%",
1014
+ height: "100%",
1015
+ backgroundColor: primaryColor,
1016
+ borderRadius: 2,
1017
+ animation: "checkProgress 5s linear forwards",
1018
+ transformOrigin: "left"
1019
+ }
1020
+ }
1021
+ ) }),
1022
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#9ca3af" }, children: "This usually takes just a few seconds" })
1023
+ ] });
1024
+ const MessagesView = /* @__PURE__ */ jsxs(Fragment, { children: [
1025
+ /* @__PURE__ */ jsxs(
1026
+ "div",
1027
+ {
1028
+ style: { flex: 1, overflowY: "auto", padding: 16, display: "flex", flexDirection: "column", gap: 12, backgroundColor: "#f9fafb" },
1029
+ children: [
1030
+ messages.map((message) => {
1031
+ const isAssistant = message.role === "assistant" || message.role === "agent";
1032
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: message.role === "user" ? "flex-end" : "flex-start" }, children: /* @__PURE__ */ jsxs(
1033
+ "div",
1034
+ {
1035
+ style: {
1036
+ maxWidth: "80%",
1037
+ padding: message.role === "system" ? "8px 12px" : isAssistant ? "12px 16px" : "10px 14px",
1038
+ borderRadius: message.role === "user" ? "16px 16px 4px 16px" : message.role === "system" ? "8px" : "16px 16px 16px 4px",
1039
+ backgroundColor: message.role === "user" ? primaryColor : message.role === "system" ? "#e5e7eb" : "#ffffff",
1040
+ color: message.role === "user" ? isLightColor(primaryColor) ? "#1a1a1a" : "white" : message.role === "system" ? "#6b7280" : "#111827",
1041
+ boxShadow: message.role === "system" ? "none" : "0 1px 2px rgba(0,0,0,0.08)",
1042
+ fontSize: message.role === "system" ? 13 : 14,
1043
+ fontStyle: message.role === "system" ? "italic" : "normal",
1044
+ lineHeight: isAssistant ? 1.6 : 1.5,
1045
+ whiteSpace: isAssistant ? "normal" : "pre-wrap",
1046
+ wordBreak: "break-word",
1047
+ textAlign: isAssistant ? "left" : void 0,
1048
+ borderLeft: isAssistant ? `3px solid ${primaryColor}33` : void 0
1049
+ },
1050
+ children: [
1051
+ message.agentName && message.role === "agent" && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, opacity: 0.6, marginBottom: 4 }, children: message.agentName }),
1052
+ isAssistant ? /* @__PURE__ */ jsx("div", { className: "chatMessageContent", children: /* @__PURE__ */ jsx(
1053
+ ReactMarkdown,
1054
+ {
1055
+ components: {
1056
+ p: ({ children }) => /* @__PURE__ */ jsx("p", { style: { margin: "0 0 8px", lineHeight: 1.6 }, children }),
1057
+ strong: ({ children }) => /* @__PURE__ */ jsx("strong", { style: { fontWeight: 600 }, children }),
1058
+ ul: ({ children }) => /* @__PURE__ */ jsx("ul", { style: { margin: "4px 0 8px", paddingLeft: 20 }, children }),
1059
+ ol: ({ children }) => /* @__PURE__ */ jsx("ol", { style: { margin: "4px 0 8px", paddingLeft: 20 }, children }),
1060
+ li: ({ children }) => /* @__PURE__ */ jsx("li", { style: { marginBottom: 4 }, children }),
1061
+ h3: ({ children }) => /* @__PURE__ */ jsx("h3", { style: { margin: "12px 0 6px", fontSize: 15, fontWeight: 600 }, children }),
1062
+ h4: ({ children }) => /* @__PURE__ */ jsx("h4", { style: { margin: "8px 0 4px", fontSize: 14, fontWeight: 600 }, children }),
1063
+ a: ({ href, children }) => /* @__PURE__ */ jsx(
1064
+ "a",
1065
+ {
1066
+ href,
1067
+ target: "_blank",
1068
+ rel: "noopener noreferrer",
1069
+ style: {
1070
+ display: "inline-block",
1071
+ marginTop: 6,
1072
+ marginRight: 6,
1073
+ padding: "8px 14px",
1074
+ borderRadius: 8,
1075
+ backgroundColor: `${primaryColor}15`,
1076
+ color: primaryColor,
1077
+ fontWeight: 600,
1078
+ fontSize: 13,
1079
+ textDecoration: "none",
1080
+ border: `1px solid ${primaryColor}40`,
1081
+ boxShadow: "0 1px 2px rgba(0,0,0,0.06)",
1082
+ transition: "background-color 0.15s, transform 0.1s"
1083
+ },
1084
+ onMouseOver: (e) => {
1085
+ e.currentTarget.style.backgroundColor = `${primaryColor}25`;
1086
+ e.currentTarget.style.transform = "translateY(-1px)";
1087
+ },
1088
+ onMouseOut: (e) => {
1089
+ e.currentTarget.style.backgroundColor = `${primaryColor}15`;
1090
+ e.currentTarget.style.transform = "translateY(0)";
1091
+ },
1092
+ children
1093
+ }
1094
+ )
1095
+ },
1096
+ children: linkifyUrls(message.content)
1097
+ }
1098
+ ) }) : message.content,
1099
+ message.attachments?.length ? /* @__PURE__ */ jsx("div", { style: { marginTop: 8, display: "flex", flexDirection: "column", gap: 6 }, children: message.attachments.map(
1100
+ (att, i) => att.mimeType?.startsWith("image/") ? /* @__PURE__ */ jsx("a", { href: att.url, target: "_blank", rel: "noopener noreferrer", style: { display: "block" }, children: /* @__PURE__ */ jsx("img", { src: att.url, alt: att.name, style: { maxWidth: "100%", maxHeight: 200, borderRadius: 8, objectFit: "contain" } }) }, i) : /* @__PURE__ */ jsxs("a", { href: att.url, target: "_blank", rel: "noopener noreferrer", style: { fontSize: 13, wordBreak: "break-all", display: "flex", alignItems: "center", gap: 6 }, children: [
1101
+ /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) }),
1102
+ att.name
1103
+ ] }, i)
1104
+ ) }) : null,
1105
+ message.suggestions?.length ? /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, marginTop: 8 }, children: message.suggestions.map((s, i) => /* @__PURE__ */ jsx(
1106
+ "button",
1107
+ {
1108
+ type: "button",
1109
+ onClick: () => {
1110
+ if (/talk\s+to\s+(a\s+)?person/i.test(s)) {
1111
+ requestHandoff();
1112
+ } else {
1113
+ setInputValue(s);
1114
+ inputRef.current?.focus();
1115
+ }
1116
+ },
1117
+ style: {
1118
+ padding: "6px 12px",
1119
+ borderRadius: 16,
1120
+ border: `1px solid ${primaryColor}`,
1121
+ backgroundColor: `${primaryColor}10`,
1122
+ color: primaryColor,
1123
+ fontSize: 13,
1124
+ cursor: "pointer"
1125
+ },
1126
+ children: s
1127
+ },
1128
+ i
1129
+ )) }) : null,
1130
+ message.sendFailed && lastFailedSend && /* @__PURE__ */ jsx("div", { style: { marginTop: 8 }, children: /* @__PURE__ */ jsx(
1131
+ "button",
1132
+ {
1133
+ type: "button",
1134
+ onClick: retryFailedSend,
1135
+ style: { padding: "6px 12px", borderRadius: 6, border: "1px solid #ef4444", backgroundColor: "#fef2f2", color: "#dc2626", fontSize: 13, cursor: "pointer" },
1136
+ children: "Retry send"
1137
+ }
1138
+ ) }),
1139
+ widgetConfig?.signal_enabled && message.role === "assistant" && widgetConfig?.handoff_enabled !== false && message.offerHandoff && !message.suggestions?.some((s) => /talk\s+to\s+(a\s+)?person/i.test(s)) && message.id === messages.filter((m) => m.role === "assistant").slice(-1)[0]?.id && /* @__PURE__ */ jsx(
1140
+ "button",
1141
+ {
1142
+ onClick: requestHandoff,
1143
+ style: {
1144
+ display: "inline-block",
1145
+ marginTop: 8,
1146
+ padding: "6px 12px",
1147
+ borderRadius: 6,
1148
+ border: `1px solid ${primaryColor}`,
1149
+ backgroundColor: "transparent",
1150
+ color: primaryColor,
1151
+ fontSize: 13,
1152
+ cursor: "pointer"
1153
+ },
1154
+ children: "Talk to a person"
1155
+ }
1156
+ )
1157
+ ]
1158
+ }
1159
+ ) }, message.id);
1160
+ }),
1161
+ (isLoading || agentTyping) && /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "flex-start" }, children: /* @__PURE__ */ jsxs(
1162
+ "div",
1163
+ {
1164
+ style: {
1165
+ padding: "10px 14px",
1166
+ borderRadius: "16px 16px 16px 4px",
1167
+ backgroundColor: "#ffffff",
1168
+ boxShadow: "0 1px 2px rgba(0,0,0,0.08)",
1169
+ display: "flex",
1170
+ gap: 4,
1171
+ color: "#9ca3af"
1172
+ },
1173
+ children: [
1174
+ /* @__PURE__ */ jsx("span", { style: { animation: "chatDot 1.4s infinite ease-in-out", animationDelay: "0s" }, children: "\u25CF" }),
1175
+ /* @__PURE__ */ jsx("span", { style: { animation: "chatDot 1.4s infinite ease-in-out", animationDelay: "0.2s" }, children: "\u25CF" }),
1176
+ /* @__PURE__ */ jsx("span", { style: { animation: "chatDot 1.4s infinite ease-in-out", animationDelay: "0.4s" }, children: "\u25CF" })
1177
+ ]
1178
+ }
1179
+ ) }),
1180
+ /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
1181
+ ]
1182
+ }
1183
+ ),
1184
+ lastFailedSend && /* @__PURE__ */ jsxs("div", { style: { padding: "8px 12px", backgroundColor: "#fef2f2", borderTop: "1px solid #fecaca", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 }, children: [
1185
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 13, color: "#dc2626" }, children: "Failed to send" }),
1186
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: retryFailedSend, style: { padding: "4px 10px", borderRadius: 6, border: "1px solid #dc2626", background: "#fff", color: "#dc2626", fontSize: 12, cursor: "pointer" }, children: "Retry" })
1187
+ ] }),
1188
+ /* @__PURE__ */ jsx("form", { onSubmit: handleSubmit, style: { padding: 12, borderTop: "1px solid #e5e7eb", backgroundColor: "#ffffff" }, children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
1189
+ /* @__PURE__ */ jsx(
1190
+ "input",
1191
+ {
1192
+ ref: inputRef,
1193
+ type: "text",
1194
+ value: inputValue,
1195
+ onChange: (e) => {
1196
+ setInputValue(e.target.value);
1197
+ handleTyping();
1198
+ },
1199
+ onKeyDown: handleKeyDown,
1200
+ placeholder: "Type a message...",
1201
+ disabled: isLoading,
1202
+ style: {
1203
+ flex: 1,
1204
+ padding: "10px 14px",
1205
+ borderRadius: 24,
1206
+ border: "1px solid #e5e7eb",
1207
+ fontSize: 14,
1208
+ outline: "none",
1209
+ transition: "border-color 0.2s"
1210
+ },
1211
+ onFocus: (e) => e.currentTarget.style.borderColor = primaryColor,
1212
+ onBlur: (e) => e.currentTarget.style.borderColor = "#e5e7eb"
1213
+ }
1214
+ ),
1215
+ /* @__PURE__ */ jsx(
1216
+ "button",
1217
+ {
1218
+ type: "submit",
1219
+ disabled: !inputValue.trim() || isLoading,
1220
+ style: {
1221
+ width: 40,
1222
+ height: 40,
1223
+ borderRadius: "50%",
1224
+ border: "none",
1225
+ backgroundColor: (inputValue.trim() || pendingFiles.length) && !isLoading ? primaryColor : "#e5e7eb",
1226
+ color: (inputValue.trim() || pendingFiles.length) && !isLoading && isLightColor(primaryColor) ? "#1a1a1a" : "white",
1227
+ cursor: inputValue.trim() && !isLoading ? "pointer" : "not-allowed",
1228
+ display: "flex",
1229
+ alignItems: "center",
1230
+ justifyContent: "center",
1231
+ transition: "background-color 0.2s",
1232
+ flexShrink: 0
1233
+ },
1234
+ children: /* @__PURE__ */ jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" }) })
1235
+ }
1236
+ )
1237
+ ] }) })
1238
+ ] });
1239
+ const ChatPopup = isOpen && /* @__PURE__ */ jsxs(
1240
+ "div",
1241
+ {
1242
+ style: {
1243
+ position: "fixed",
1244
+ [position === "bottom-left" ? "left" : "right"]: 20,
1245
+ bottom: 90,
1246
+ width: 380,
1247
+ maxWidth: "calc(100vw - 40px)",
1248
+ height: 520,
1249
+ maxHeight: "calc(100vh - 120px)",
1250
+ backgroundColor: "#ffffff",
1251
+ borderRadius: 16,
1252
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.2)",
1253
+ display: "flex",
1254
+ flexDirection: "column",
1255
+ overflow: "hidden",
1256
+ zIndex: 9998,
1257
+ animation: "chatSlideUp 0.3s ease-out"
1258
+ },
1259
+ children: [
1260
+ Header,
1261
+ checkingAvailability || checkingHandoff ? CheckingScreen : showOfflineForm ? OfflineFormView : showWelcome && welcomeEnabled && messages.length === 0 ? WelcomeScreen : MessagesView,
1262
+ showPoweredBy && /* @__PURE__ */ jsxs("div", { style: { padding: "6px 0", textAlign: "center", fontSize: 11, color: "#9ca3af", backgroundColor: "#ffffff", borderTop: "1px solid #f3f4f6" }, children: [
1263
+ "Powered by",
1264
+ " ",
1265
+ /* @__PURE__ */ jsx("a", { href: "https://uptrademedia.com", target: "_blank", rel: "noopener noreferrer", style: { color: "#6b7280", textDecoration: "none", fontWeight: 500 }, children: "Sonor" })
1266
+ ] }),
1267
+ /* @__PURE__ */ jsx("style", { children: `
1268
+ @keyframes chatSlideUp {
1269
+ from { opacity: 0; transform: translateY(20px); }
1270
+ to { opacity: 1; transform: translateY(0); }
1271
+ }
1272
+ @keyframes chatDot {
1273
+ 0%, 80%, 100% { opacity: 0.3; }
1274
+ 40% { opacity: 1; }
1275
+ }
1276
+ @keyframes checkPulse {
1277
+ 0% { transform: scale(1); opacity: 1; }
1278
+ 100% { transform: scale(1.8); opacity: 0; }
1279
+ }
1280
+ @keyframes checkDots {
1281
+ 0% { content: ''; }
1282
+ 25% { content: '.'; }
1283
+ 50% { content: '..'; }
1284
+ 75% { content: '...'; }
1285
+ }
1286
+ @keyframes checkProgress {
1287
+ from { transform: scaleX(0); }
1288
+ to { transform: scaleX(1); }
1289
+ }
1290
+ .chatMessageContent p:first-child { margin-top: 0; }
1291
+ .chatMessageContent p:last-child { margin-bottom: 0; }
1292
+ ` })
1293
+ ]
1294
+ }
1295
+ );
1296
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1297
+ ChatPopup,
1298
+ ChatButton
1299
+ ] });
1300
+ }
1301
+ function handleAction(action, node, onClose, onAction) {
1302
+ if (!action?.action || action.action === "none") return;
1303
+ if (onAction) {
1304
+ onAction(action, node);
1305
+ }
1306
+ switch (action.action) {
1307
+ case "link":
1308
+ if (action.url) {
1309
+ if (action.newTab) {
1310
+ window.open(action.url, "_blank", "noopener,noreferrer");
1311
+ } else {
1312
+ window.location.href = action.url;
1313
+ }
1314
+ }
1315
+ break;
1316
+ case "scroll":
1317
+ if (action.target) {
1318
+ const element = document.querySelector(action.target);
1319
+ element?.scrollIntoView({ behavior: "smooth" });
1320
+ }
1321
+ break;
1322
+ case "close":
1323
+ onClose?.();
1324
+ break;
1325
+ case "copy":
1326
+ if (action.text) {
1327
+ navigator.clipboard.writeText(action.text).catch(console.error);
1328
+ }
1329
+ break;
1330
+ case "share":
1331
+ if (navigator.share) {
1332
+ navigator.share({
1333
+ title: document.title,
1334
+ url: window.location.href
1335
+ }).catch(console.error);
1336
+ }
1337
+ break;
1338
+ case "download":
1339
+ if (action.url) {
1340
+ const a = document.createElement("a");
1341
+ a.href = action.url;
1342
+ a.download = "";
1343
+ a.click();
1344
+ }
1345
+ break;
1346
+ }
1347
+ }
1348
+ var animationKeyframes = `
1349
+ @keyframes engageFadeIn { from { opacity: 0; } to { opacity: 1; } }
1350
+ @keyframes engageFadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
1351
+ @keyframes engageFadeInDown { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } }
1352
+ @keyframes engageSlideInLeft { from { opacity: 0; transform: translateX(-20px); } to { opacity: 1; transform: translateX(0); } }
1353
+ @keyframes engageSlideInRight { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: translateX(0); } }
1354
+ @keyframes engageSlideInUp { from { opacity: 0; transform: translateY(100%); } to { opacity: 1; transform: translateY(0); } }
1355
+ @keyframes engageScaleIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
1356
+ @keyframes engageBounceIn { 0% { opacity: 0; transform: scale(0.3); } 50% { transform: scale(1.05); } 70% { transform: scale(0.9); } 100% { opacity: 1; transform: scale(1); } }
1357
+ `;
1358
+ function getAnimationStyle(animation, delay, duration) {
1359
+ if (!animation || animation === "none") return {};
1360
+ const animationMap = {
1361
+ fadeIn: "engageFadeIn",
1362
+ fadeInUp: "engageFadeInUp",
1363
+ fadeInDown: "engageFadeInDown",
1364
+ slideInLeft: "engageSlideInLeft",
1365
+ slideInRight: "engageSlideInRight",
1366
+ slideInUp: "engageSlideInUp",
1367
+ scaleIn: "engageScaleIn",
1368
+ bounceIn: "engageBounceIn"
1369
+ };
1370
+ const animationName = animationMap[animation];
1371
+ if (!animationName) return {};
1372
+ return {
1373
+ animation: `${animationName} ${duration || 300}ms ease-out ${delay || 0}ms forwards`,
1374
+ opacity: 0
1375
+ // Start invisible, animation will show it
1376
+ };
1377
+ }
1378
+ function NodeRenderer({ node, onClose, onAction, context }) {
1379
+ const [isHovered, setIsHovered] = useState(false);
1380
+ const { type, props = {}, style = {}, children } = node;
1381
+ const computedStyle = { ...style };
1382
+ if (props.animation) {
1383
+ Object.assign(computedStyle, getAnimationStyle(
1384
+ props.animation,
1385
+ props.animationDelay,
1386
+ props.animationDuration
1387
+ ));
1388
+ }
1389
+ if (isHovered) {
1390
+ if (props.hoverScale && props.hoverScale !== "none") {
1391
+ computedStyle.transform = `scale(${props.hoverScale})`;
1392
+ computedStyle.transition = "transform 0.2s ease";
1393
+ }
1394
+ if (props.hoverShadow && props.hoverShadow !== "none") {
1395
+ const shadowMap = {
1396
+ sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
1397
+ md: "0 4px 6px -1px rgb(0 0 0 / 0.1)",
1398
+ lg: "0 10px 15px -3px rgb(0 0 0 / 0.1)"
1399
+ };
1400
+ computedStyle.boxShadow = shadowMap[props.hoverShadow];
1401
+ }
1402
+ }
1403
+ const eventHandlers = {
1404
+ onMouseEnter: () => setIsHovered(true),
1405
+ onMouseLeave: () => setIsHovered(false)
1406
+ };
1407
+ if (props.onClick) {
1408
+ eventHandlers.onClick = (e) => {
1409
+ e.preventDefault();
1410
+ handleAction(props.onClick, node, onClose, onAction);
1411
+ };
1412
+ computedStyle.cursor = "pointer";
1413
+ }
1414
+ const renderedChildren = children?.map((child) => /* @__PURE__ */ jsx(
1415
+ NodeRenderer,
1416
+ {
1417
+ node: child,
1418
+ onClose,
1419
+ onAction,
1420
+ context
1421
+ },
1422
+ child.id
1423
+ ));
1424
+ switch (type) {
1425
+ case "Container":
1426
+ case "Box":
1427
+ case "Section":
1428
+ return /* @__PURE__ */ jsx("div", { style: computedStyle, ...eventHandlers, children: renderedChildren });
1429
+ case "Text":
1430
+ return /* @__PURE__ */ jsx("p", { style: computedStyle, ...eventHandlers, children: resolveText(props.text, context) });
1431
+ case "Heading":
1432
+ const HeadingTag = `h${props.level || 2}`;
1433
+ return createElement(
1434
+ HeadingTag,
1435
+ { style: computedStyle, ...eventHandlers },
1436
+ resolveText(props.text, context)
1437
+ );
1438
+ case "Button":
1439
+ case "BookingButton":
1440
+ case "BuyNow":
1441
+ case "AddToCart":
1442
+ case "EventRSVP":
1443
+ return /* @__PURE__ */ jsx(
1444
+ "button",
1445
+ {
1446
+ style: computedStyle,
1447
+ ...eventHandlers,
1448
+ type: "button",
1449
+ children: resolveText(props.text || props.label, context) || "Button"
1450
+ }
1451
+ );
1452
+ case "Image":
1453
+ return /* @__PURE__ */ jsx(
1454
+ "img",
1455
+ {
1456
+ src: props.src,
1457
+ alt: props.alt || "",
1458
+ style: computedStyle,
1459
+ ...eventHandlers
1460
+ }
1461
+ );
1462
+ case "Link":
1463
+ return /* @__PURE__ */ jsx(
1464
+ "a",
1465
+ {
1466
+ href: props.href || "#",
1467
+ target: props.newTab ? "_blank" : void 0,
1468
+ rel: props.newTab ? "noopener noreferrer" : void 0,
1469
+ style: computedStyle,
1470
+ ...eventHandlers,
1471
+ children: resolveText(props.text, context) || renderedChildren
1472
+ }
1473
+ );
1474
+ case "Input":
1475
+ return /* @__PURE__ */ jsx(
1476
+ "input",
1477
+ {
1478
+ type: props.inputType || "text",
1479
+ placeholder: props.placeholder,
1480
+ style: computedStyle,
1481
+ ...eventHandlers
1482
+ }
1483
+ );
1484
+ case "Divider":
1485
+ return /* @__PURE__ */ jsx("hr", { style: { border: "none", borderTop: "1px solid #e5e7eb", ...computedStyle } });
1486
+ case "Spacer":
1487
+ return /* @__PURE__ */ jsx("div", { style: { ...computedStyle, width: props.width, height: props.height } });
1488
+ case "Icon":
1489
+ return /* @__PURE__ */ jsx("span", { style: computedStyle, ...eventHandlers, children: props.icon || "\u2605" });
1490
+ case "FormEmbed":
1491
+ return /* @__PURE__ */ jsx(
1492
+ "div",
1493
+ {
1494
+ style: computedStyle,
1495
+ "data-engage-form": props.form_id,
1496
+ ...eventHandlers,
1497
+ children: /* @__PURE__ */ jsxs("div", { style: { padding: "16px", background: "#f5f5f5", borderRadius: "8px", textAlign: "center" }, children: [
1498
+ "Form: ",
1499
+ props.form_id || "Not configured"
1500
+ ] })
1501
+ }
1502
+ );
1503
+ case "ProductCard":
1504
+ case "EventCard":
1505
+ return /* @__PURE__ */ jsx(
1506
+ "div",
1507
+ {
1508
+ style: computedStyle,
1509
+ "data-engage-offering": props.offering_id,
1510
+ ...eventHandlers,
1511
+ children: renderedChildren
1512
+ }
1513
+ );
1514
+ default:
1515
+ console.warn(`[DesignRenderer] Unknown node type: ${type}`);
1516
+ return /* @__PURE__ */ jsx("div", { style: computedStyle, ...eventHandlers, children: renderedChildren });
1517
+ }
1518
+ }
1519
+ function resolveText(text, context) {
1520
+ if (!text) return "";
1521
+ if (!context) return text;
1522
+ return text.replace(/\{\{(\w+(?:\.\w+)*)\}\}/g, (match, path) => {
1523
+ const value = getNestedValue(context, path);
1524
+ return value !== void 0 ? String(value) : match;
1525
+ });
1526
+ }
1527
+ function getNestedValue(obj, path) {
1528
+ return path.split(".").reduce((acc, key) => acc?.[key], obj);
1529
+ }
1530
+ function DesignRenderer({
1531
+ design,
1532
+ onClose,
1533
+ onAction,
1534
+ context
1535
+ }) {
1536
+ React2.useEffect(() => {
1537
+ if (typeof document === "undefined") return;
1538
+ const styleId = "engage-design-renderer-animations";
1539
+ if (!document.getElementById(styleId)) {
1540
+ const styleSheet = document.createElement("style");
1541
+ styleSheet.id = styleId;
1542
+ styleSheet.textContent = animationKeyframes;
1543
+ document.head.appendChild(styleSheet);
1544
+ }
1545
+ }, []);
1546
+ const rootStyle = {
1547
+ ...design.style
1548
+ };
1549
+ return /* @__PURE__ */ jsx("div", { style: rootStyle, "data-engage-design": design.id, children: design.children?.map((node) => /* @__PURE__ */ jsx(
1550
+ NodeRenderer,
1551
+ {
1552
+ node,
1553
+ onClose,
1554
+ onAction,
1555
+ context
1556
+ },
1557
+ node.id
1558
+ )) });
1559
+ }
1560
+ function getApiConfig2() {
1561
+ const isDev = typeof process !== "undefined" && process.env?.NODE_ENV === "development";
1562
+ const defaultApiUrl = isDev && process.env?.NEXT_PUBLIC_UPTRADE_API_URL ? process.env.NEXT_PUBLIC_UPTRADE_API_URL : "https://api.uptrademedia.com";
1563
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || defaultApiUrl : defaultApiUrl;
1564
+ const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
1565
+ return { apiUrl, apiKey };
1566
+ }
1567
+ function EngageWidget({
1568
+ apiUrl: propApiUrl,
1569
+ apiKey: propApiKey,
1570
+ projectId: propProjectId,
1571
+ position = "bottom-right",
1572
+ zIndex = 9999,
1573
+ chatEnabled = true,
1574
+ debug = false
1575
+ }) {
1576
+ const pathname = usePathname();
1577
+ const [elements, setElements] = useState([]);
1578
+ const [activeElements, setActiveElements] = useState([]);
1579
+ const [dismissedElements, setDismissedElements] = useState(/* @__PURE__ */ new Set());
1580
+ const [chatReady, setChatReady] = useState(false);
1581
+ useEffect(() => {
1582
+ async function loadElements() {
1583
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
1584
+ const apiUrl = propApiUrl || globalApiUrl;
1585
+ const apiKey = propApiKey || globalApiKey;
1586
+ if (!apiKey) {
1587
+ if (debug) console.warn("[Engage] No API key configured");
1588
+ return;
1589
+ }
1590
+ let visitorId = typeof localStorage !== "undefined" ? localStorage.getItem("_uptrade_vid") : null;
1591
+ if (!visitorId) {
1592
+ visitorId = crypto.randomUUID();
1593
+ if (typeof localStorage !== "undefined") localStorage.setItem("_uptrade_vid", visitorId);
1594
+ }
1595
+ const url = typeof window !== "undefined" ? window.location.href : "";
1596
+ try {
1597
+ const response = await fetch(
1598
+ `${apiUrl.replace(/\/$/, "")}/engage/widget/elements?url=${encodeURIComponent(url)}&visitorId=${encodeURIComponent(visitorId)}`,
1599
+ { headers: { "x-api-key": apiKey } }
1600
+ );
1601
+ if (!response.ok) {
1602
+ if (debug) console.error("[Engage] Error loading elements:", response.statusText);
1603
+ return;
1604
+ }
1605
+ const json = await response.json();
1606
+ const elementsList = json.data?.elements ?? json.elements ?? [];
1607
+ if (debug) console.log("[Engage] Loaded elements:", elementsList);
1608
+ setElements(elementsList);
1609
+ } catch (error) {
1610
+ if (debug) console.error("[Engage] Error loading elements:", error);
1611
+ }
1612
+ }
1613
+ loadElements();
1614
+ }, [propApiUrl, propApiKey, debug]);
1615
+ useEffect(() => {
1616
+ if (!chatEnabled) return;
1617
+ const id = typeof requestIdleCallback !== "undefined" ? requestIdleCallback(() => setChatReady(true), { timeout: 3e3 }) : setTimeout(() => setChatReady(true), 1);
1618
+ return () => {
1619
+ typeof cancelIdleCallback !== "undefined" ? cancelIdleCallback(id) : clearTimeout(id);
1620
+ };
1621
+ }, [chatEnabled]);
1622
+ useEffect(() => {
1623
+ if (!elements.length) return;
1624
+ const checkElement = (element) => {
1625
+ if (dismissedElements.has(element.id)) return false;
1626
+ if (element.targeting?.pages) {
1627
+ const { include, exclude } = element.targeting.pages;
1628
+ if (exclude?.some((p) => matchPath(pathname, p))) return false;
1629
+ if (include && !include.some((p) => matchPath(pathname, p))) return false;
1630
+ }
1631
+ if (element.targeting?.devices) {
1632
+ const device = getDeviceType();
1633
+ if (!element.targeting.devices.includes(device)) return false;
1634
+ }
1635
+ if (element.trigger?.frequency) {
1636
+ const { type, days } = element.trigger.frequency;
1637
+ const key = `_engage_${element.id}`;
1638
+ if (type === "once") {
1639
+ if (localStorage.getItem(key)) return false;
1640
+ } else if (type === "once-per-session") {
1641
+ if (sessionStorage.getItem(key)) return false;
1642
+ } else if (type === "every-n-days" && days) {
1643
+ const lastShown = localStorage.getItem(key);
1644
+ if (lastShown) {
1645
+ const elapsed = Date.now() - parseInt(lastShown, 10);
1646
+ if (elapsed < days * 24 * 60 * 60 * 1e3) return false;
1647
+ }
1648
+ }
1649
+ }
1650
+ return true;
1651
+ };
1652
+ const eligible = elements.filter(checkElement);
1653
+ if (debug) console.log("[Engage] Eligible elements:", eligible);
1654
+ eligible.forEach((element) => {
1655
+ const trigger = element.trigger;
1656
+ if (trigger?.type === "immediate" || !trigger?.type) {
1657
+ setActiveElements((prev) => [...prev, element.id]);
1658
+ } else if (trigger?.type === "delay" && trigger.delay) {
1659
+ setTimeout(() => {
1660
+ setActiveElements((prev) => [...prev, element.id]);
1661
+ }, trigger.delay * 1e3);
1662
+ } else if (trigger?.type === "exit-intent") {
1663
+ const handleMouseLeave = (e) => {
1664
+ if (e.clientY < 10) {
1665
+ setActiveElements((prev) => [...prev, element.id]);
1666
+ document.removeEventListener("mouseleave", handleMouseLeave);
1667
+ }
1668
+ };
1669
+ document.addEventListener("mouseleave", handleMouseLeave);
1670
+ } else if (trigger?.type === "scroll" && trigger.scrollPercentage) {
1671
+ const handleScroll = () => {
1672
+ const scrollPercent = window.scrollY / (document.body.scrollHeight - window.innerHeight) * 100;
1673
+ if (scrollPercent >= (trigger.scrollPercentage || 50)) {
1674
+ setActiveElements((prev) => [...prev, element.id]);
1675
+ window.removeEventListener("scroll", handleScroll);
1676
+ }
1677
+ };
1678
+ window.addEventListener("scroll", handleScroll);
1679
+ }
1680
+ });
1681
+ }, [elements, pathname, dismissedElements, debug]);
1682
+ const handleDismiss = useCallback((elementId) => {
1683
+ setDismissedElements((prev) => /* @__PURE__ */ new Set([...prev, elementId]));
1684
+ setActiveElements((prev) => prev.filter((id) => id !== elementId));
1685
+ const element = elements.find((e) => e.id === elementId);
1686
+ if (element?.trigger?.frequency) {
1687
+ const key = `_engage_${elementId}`;
1688
+ if (element.trigger.frequency.type === "once-per-session") {
1689
+ sessionStorage.setItem(key, "true");
1690
+ } else {
1691
+ localStorage.setItem(key, Date.now().toString());
1692
+ }
1693
+ }
1694
+ }, [elements]);
1695
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1696
+ activeElements.map((elementId) => {
1697
+ const element = elements.find((e) => e.id === elementId);
1698
+ if (!element) return null;
1699
+ const { apiUrl: globalApiUrl, apiKey: globalApiKey } = getApiConfig2();
1700
+ return /* @__PURE__ */ jsx(
1701
+ EngageElementRenderer,
1702
+ {
1703
+ element,
1704
+ onDismiss: () => handleDismiss(element.id),
1705
+ zIndex,
1706
+ apiUrl: propApiUrl || globalApiUrl,
1707
+ apiKey: propApiKey || globalApiKey
1708
+ },
1709
+ element.id
1710
+ );
1711
+ }),
1712
+ chatEnabled && chatReady && /* @__PURE__ */ jsx(
1713
+ ChatWidget,
1714
+ {
1715
+ projectId: propProjectId ?? void 0,
1716
+ config: {
1717
+ position,
1718
+ buttonColor: "#00afab"
1719
+ // Default teal, can be customized later
1720
+ }
1721
+ }
1722
+ )
1723
+ ] });
1724
+ }
1725
+ function trackEngageEvent(apiUrl, apiKey, endpoint, data) {
1726
+ if (!apiKey) return;
1727
+ const visitorId = typeof localStorage !== "undefined" ? localStorage.getItem("_uptrade_vid") : null;
1728
+ fetch(`${apiUrl.replace(/\/$/, "")}/engage/widget/${endpoint}`, {
1729
+ method: "POST",
1730
+ headers: { "Content-Type": "application/json", "x-api-key": apiKey },
1731
+ body: JSON.stringify({ ...data, visitorId, pageUrl: window.location.href }),
1732
+ keepalive: true
1733
+ }).catch(() => {
1734
+ });
1735
+ }
1736
+ function handleEngageAction(action, _node, element, apiUrl, apiKey) {
1737
+ trackEngageEvent(apiUrl, apiKey, "click", {
1738
+ elementId: element.id,
1739
+ action: action.action
1740
+ });
1741
+ switch (action.action) {
1742
+ case "navigate":
1743
+ if (action.url) window.location.href = action.url;
1744
+ break;
1745
+ case "open-url":
1746
+ if (action.url) window.open(action.url, "_blank");
1747
+ break;
1748
+ case "submit-form":
1749
+ case "submit_form":
1750
+ window.dispatchEvent(new CustomEvent("engage:submit-form", {
1751
+ detail: { formId: action.formId || action.url, elementId: element.id }
1752
+ }));
1753
+ break;
1754
+ case "track-conversion":
1755
+ window.dispatchEvent(new CustomEvent("engage:conversion", {
1756
+ detail: { action, elementId: element.id }
1757
+ }));
1758
+ break;
1759
+ default:
1760
+ window.dispatchEvent(new CustomEvent("engage:action", {
1761
+ detail: { action, elementId: element.id }
1762
+ }));
1763
+ break;
1764
+ }
1765
+ }
1766
+ function EngageElementRenderer({
1767
+ element,
1768
+ onDismiss,
1769
+ zIndex,
1770
+ apiUrl,
1771
+ apiKey
1772
+ }) {
1773
+ const containerRef = useRef(null);
1774
+ const impressionTrackedRef = useRef(false);
1775
+ useEffect(() => {
1776
+ const el = containerRef.current;
1777
+ if (!el || impressionTrackedRef.current) return;
1778
+ const observer = new IntersectionObserver(
1779
+ ([entry]) => {
1780
+ if (entry.isIntersecting && !impressionTrackedRef.current) {
1781
+ impressionTrackedRef.current = true;
1782
+ trackEngageEvent(apiUrl, apiKey, "impression", { elementId: element.id });
1783
+ observer.disconnect();
1784
+ }
1785
+ },
1786
+ { threshold: 0.5 }
1787
+ );
1788
+ observer.observe(el);
1789
+ return () => observer.disconnect();
1790
+ }, [apiUrl, apiKey, element.id]);
1791
+ const onAction = useCallback(
1792
+ (action, node) => {
1793
+ handleEngageAction(action, node, element, apiUrl, apiKey);
1794
+ },
1795
+ [element, apiUrl, apiKey]
1796
+ );
1797
+ if (element.design_json) {
1798
+ if (element.type === "popup") {
1799
+ return /* @__PURE__ */ jsx(
1800
+ "div",
1801
+ {
1802
+ ref: containerRef,
1803
+ "data-engage-element": element.id,
1804
+ style: {
1805
+ position: "fixed",
1806
+ top: 0,
1807
+ left: 0,
1808
+ right: 0,
1809
+ bottom: 0,
1810
+ backgroundColor: "rgba(0,0,0,0.5)",
1811
+ display: "flex",
1812
+ alignItems: "center",
1813
+ justifyContent: "center",
1814
+ zIndex
1815
+ },
1816
+ onClick: onDismiss,
1817
+ children: /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
1818
+ DesignRenderer,
1819
+ {
1820
+ design: element.design_json,
1821
+ onClose: onDismiss,
1822
+ onAction
1823
+ }
1824
+ ) })
1825
+ }
1826
+ );
1827
+ }
1828
+ if (element.type === "bar") {
1829
+ return /* @__PURE__ */ jsx(
1830
+ "div",
1831
+ {
1832
+ ref: containerRef,
1833
+ "data-engage-element": element.id,
1834
+ style: {
1835
+ position: "fixed",
1836
+ top: 0,
1837
+ left: 0,
1838
+ right: 0,
1839
+ zIndex
1840
+ },
1841
+ children: /* @__PURE__ */ jsx(
1842
+ DesignRenderer,
1843
+ {
1844
+ design: element.design_json,
1845
+ onClose: onDismiss,
1846
+ onAction
1847
+ }
1848
+ )
1849
+ }
1850
+ );
1851
+ }
1852
+ if (element.type === "nudge" || element.type === "slide-in") {
1853
+ const position = element.config?.position || "bottom-right";
1854
+ const positionStyles = {
1855
+ "bottom-right": { bottom: 20, right: 20 },
1856
+ "bottom-left": { bottom: 20, left: 20 },
1857
+ "top-right": { top: 20, right: 20 },
1858
+ "top-left": { top: 20, left: 20 }
1859
+ };
1860
+ return /* @__PURE__ */ jsx(
1861
+ "div",
1862
+ {
1863
+ ref: containerRef,
1864
+ "data-engage-element": element.id,
1865
+ style: {
1866
+ position: "fixed",
1867
+ zIndex,
1868
+ ...positionStyles[position]
1869
+ },
1870
+ children: /* @__PURE__ */ jsx(
1871
+ DesignRenderer,
1872
+ {
1873
+ design: element.design_json,
1874
+ onClose: onDismiss,
1875
+ onAction
1876
+ }
1877
+ )
1878
+ }
1879
+ );
1880
+ }
1881
+ return /* @__PURE__ */ jsx("div", { ref: containerRef, "data-engage-element": element.id, children: /* @__PURE__ */ jsx(
1882
+ DesignRenderer,
1883
+ {
1884
+ design: element.design_json,
1885
+ onClose: onDismiss,
1886
+ onAction
1887
+ }
1888
+ ) });
1889
+ }
1890
+ if (element.type === "popup") {
1891
+ return /* @__PURE__ */ jsx(
1892
+ "div",
1893
+ {
1894
+ style: {
1895
+ position: "fixed",
1896
+ top: 0,
1897
+ left: 0,
1898
+ right: 0,
1899
+ bottom: 0,
1900
+ backgroundColor: "rgba(0,0,0,0.5)",
1901
+ display: "flex",
1902
+ alignItems: "center",
1903
+ justifyContent: "center",
1904
+ zIndex
1905
+ },
1906
+ onClick: onDismiss,
1907
+ children: /* @__PURE__ */ jsxs(
1908
+ "div",
1909
+ {
1910
+ style: {
1911
+ backgroundColor: "white",
1912
+ padding: 24,
1913
+ borderRadius: 8,
1914
+ maxWidth: 500,
1915
+ width: "90%"
1916
+ },
1917
+ onClick: (e) => e.stopPropagation(),
1918
+ children: [
1919
+ /* @__PURE__ */ jsx(
1920
+ "button",
1921
+ {
1922
+ onClick: onDismiss,
1923
+ style: {
1924
+ position: "absolute",
1925
+ top: 8,
1926
+ right: 8,
1927
+ background: "none",
1928
+ border: "none",
1929
+ fontSize: 24,
1930
+ cursor: "pointer"
1931
+ },
1932
+ children: "\xD7"
1933
+ }
1934
+ ),
1935
+ element.config?.title && /* @__PURE__ */ jsx("h2", { children: element.config.title }),
1936
+ element.config?.message && /* @__PURE__ */ jsx("p", { children: element.config.message })
1937
+ ]
1938
+ }
1939
+ )
1940
+ }
1941
+ );
1942
+ }
1943
+ if (element.type === "bar") {
1944
+ return /* @__PURE__ */ jsxs(
1945
+ "div",
1946
+ {
1947
+ style: {
1948
+ position: "fixed",
1949
+ top: 0,
1950
+ left: 0,
1951
+ right: 0,
1952
+ backgroundColor: element.config?.backgroundColor || "#3b82f6",
1953
+ color: element.config?.textColor || "white",
1954
+ padding: "12px 24px",
1955
+ display: "flex",
1956
+ alignItems: "center",
1957
+ justifyContent: "center",
1958
+ gap: 16,
1959
+ zIndex
1960
+ },
1961
+ children: [
1962
+ /* @__PURE__ */ jsx("span", { children: element.config?.message }),
1963
+ /* @__PURE__ */ jsx(
1964
+ "button",
1965
+ {
1966
+ onClick: onDismiss,
1967
+ style: {
1968
+ background: "none",
1969
+ border: "none",
1970
+ color: "inherit",
1971
+ fontSize: 20,
1972
+ cursor: "pointer"
1973
+ },
1974
+ children: "\xD7"
1975
+ }
1976
+ )
1977
+ ]
1978
+ }
1979
+ );
1980
+ }
1981
+ return null;
1982
+ }
1983
+ function matchPath(pathname, pattern) {
1984
+ if (pattern.endsWith("*")) {
1985
+ return pathname.startsWith(pattern.slice(0, -1));
1986
+ }
1987
+ return pathname === pattern;
1988
+ }
1989
+ function getDeviceType() {
1990
+ if (typeof window === "undefined") return "desktop";
1991
+ const ua = navigator.userAgent;
1992
+ if (/tablet|ipad|playbook|silk/i.test(ua)) return "tablet";
1993
+ if (/mobile|iphone|ipod|android|blackberry|opera mini|iemobile/i.test(ua)) return "mobile";
1994
+ return "desktop";
1995
+ }
1996
+
1997
+ export { ChatWidget, DesignRenderer, EngageWidget };
1998
+ //# sourceMappingURL=chunk-7UKPRW25.mjs.map
1999
+ //# sourceMappingURL=chunk-7UKPRW25.mjs.map