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