@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
package/dist/index.mjs ADDED
@@ -0,0 +1,2416 @@
1
+ export { ManagedImage, assignImageToSlot, clearImageSlot, fetchManagedImage, fetchManagedImages, listImageFiles, uploadImage } from './chunk-JTLOJLWQ.mjs';
2
+ export { TestimonialSection, fetchReviewStats, fetchReviews } from './chunk-UWE5PCYJ.mjs';
3
+ import { SignalBridge, useSignalExperiment, useSignalEvent } from './chunk-6ZCISNAB.mjs';
4
+ export { SignalBridge, useSignal, useSignalConfig, useSignalEvent, useSignalExperiment, useSignalOutcome } from './chunk-6ZCISNAB.mjs';
5
+ import './chunk-TFLQX7K7.mjs';
6
+ export { CalendarView, CheckoutForm, EventCalendar, EventEmbed, EventModal, EventTile, OfferingCard, OfferingList, ProductEmbed, RegistrationForm, UpcomingEvents, createCheckoutSession, fetchNextEvent, fetchOffering, fetchOfferings, fetchProducts, fetchServices, fetchUpcomingEvents, formatDate, formatDateTime, formatPrice, getOfferingUrl, registerForEvent, useEventModal } from './chunk-D63MUKZ6.mjs';
7
+ export { SetupWizard } from './chunk-P3UWIUJS.mjs';
8
+ export { clearRedirectCache, fetchRedirectRules, generateNextRedirects, handleManagedRedirects } from './chunk-QXV4667R.mjs';
9
+ import './chunk-GCJXQ4AG.mjs';
10
+ import { SitemapSync } from './chunk-PKN27UMH.mjs';
11
+ import { AnalyticsProvider } from './chunk-M2T6R7BA.mjs';
12
+ import { EngageWidget } from './chunk-7UKPRW25.mjs';
13
+ import { configureFormsApi } from './chunk-S7FRYNSU.mjs';
14
+ import './chunk-4XPGGLVP.mjs';
15
+ import React2, { createContext, useContext, useEffect, useMemo, Suspense, useState, useRef, useCallback } from 'react';
16
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
17
+
18
+ var SiteKitContext = createContext(null);
19
+ function useSiteKit() {
20
+ const context = useContext(SiteKitContext);
21
+ if (!context) {
22
+ throw new Error("useSiteKit must be used within a SiteKitProvider");
23
+ }
24
+ return context;
25
+ }
26
+ function SiteKitProvider({
27
+ children,
28
+ apiKey,
29
+ apiUrl,
30
+ signalUrl,
31
+ projectId,
32
+ recaptchaSiteKey,
33
+ analytics,
34
+ engage,
35
+ forms,
36
+ signal,
37
+ debug = false
38
+ }) {
39
+ const isDev = typeof process !== "undefined" && process.env?.NODE_ENV === "development";
40
+ const defaultApiUrl = isDev && process.env?.NEXT_PUBLIC_UPTRADE_API_URL ? process.env.NEXT_PUBLIC_UPTRADE_API_URL : "https://api.uptrademedia.com";
41
+ const defaultSignalUrl = isDev && process.env?.NEXT_PUBLIC_SIGNAL_API_URL ? process.env.NEXT_PUBLIC_SIGNAL_API_URL : "https://signal.uptrademedia.com";
42
+ const finalApiUrl = apiUrl || defaultApiUrl;
43
+ const finalSignalUrl = signalUrl || defaultSignalUrl;
44
+ const finalRecaptchaSiteKey = recaptchaSiteKey ?? (typeof process !== "undefined" ? process.env?.NEXT_PUBLIC_RECAPTCHA_SITE_KEY : void 0);
45
+ if (!apiKey) {
46
+ console.error("@sonordev/site-kit: No API key provided. Set NEXT_PUBLIC_UPTRADE_API_KEY environment variable.");
47
+ }
48
+ if (typeof window !== "undefined") {
49
+ window.__SITE_KIT_API_URL__ = finalApiUrl;
50
+ window.__SITE_KIT_SIGNAL_URL__ = finalSignalUrl;
51
+ window.__SITE_KIT_API_KEY__ = apiKey;
52
+ window.__SITE_KIT_RECAPTCHA_SITE_KEY__ = finalRecaptchaSiteKey;
53
+ window.__SITE_KIT_DEBUG__ = debug;
54
+ }
55
+ useEffect(() => {
56
+ if (apiKey && typeof window !== "undefined") {
57
+ configureFormsApi({
58
+ baseUrl: finalApiUrl,
59
+ apiKey
60
+ });
61
+ }
62
+ }, [finalApiUrl, apiKey]);
63
+ const contextValue = useMemo(
64
+ () => ({
65
+ apiUrl: finalApiUrl,
66
+ signalUrl: finalSignalUrl,
67
+ apiKey,
68
+ analytics,
69
+ engage,
70
+ forms,
71
+ signal,
72
+ debug,
73
+ isReady: true
74
+ }),
75
+ [finalApiUrl, finalSignalUrl, apiKey, analytics, engage, forms, signal, debug]
76
+ );
77
+ let content = /* @__PURE__ */ jsx(Fragment, { children });
78
+ if (signal?.enabled) {
79
+ content = /* @__PURE__ */ jsx(
80
+ SignalBridge,
81
+ {
82
+ enabled: signal.enabled,
83
+ realtime: signal.realtime !== false,
84
+ experiments: signal.experiments !== false,
85
+ behaviorTracking: signal.behaviorTracking !== false,
86
+ children: content
87
+ }
88
+ );
89
+ }
90
+ if (analytics?.enabled) {
91
+ content = /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
92
+ AnalyticsProvider,
93
+ {
94
+ apiUrl: finalApiUrl,
95
+ apiKey,
96
+ trackPageViews: analytics.trackPageViews !== false,
97
+ trackWebVitals: analytics.trackWebVitals !== false,
98
+ trackScrollDepth: analytics.trackScrollDepth !== false,
99
+ trackClicks: analytics.trackClicks !== false,
100
+ debug,
101
+ children: content
102
+ }
103
+ ) });
104
+ }
105
+ if (engage?.enabled) {
106
+ content = /* @__PURE__ */ jsxs(Fragment, { children: [
107
+ content,
108
+ /* @__PURE__ */ jsx(
109
+ EngageWidget,
110
+ {
111
+ apiUrl: finalApiUrl,
112
+ apiKey,
113
+ projectId,
114
+ position: engage.position || "bottom-right",
115
+ chatEnabled: engage.chatEnabled !== false
116
+ }
117
+ )
118
+ ] });
119
+ }
120
+ content = /* @__PURE__ */ jsxs(Fragment, { children: [
121
+ content,
122
+ /* @__PURE__ */ jsx(SitemapSync, { debug })
123
+ ] });
124
+ return /* @__PURE__ */ jsx(SiteKitContext.Provider, { value: contextValue, children: content });
125
+ }
126
+
127
+ // src/affiliates/api.ts
128
+ function getApiConfig() {
129
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || "https://api.uptrademedia.com" : "https://api.uptrademedia.com";
130
+ const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ || "" : "";
131
+ return { apiUrl, apiKey };
132
+ }
133
+ async function apiGet(endpoint) {
134
+ const { apiUrl, apiKey } = getApiConfig();
135
+ if (!apiKey) {
136
+ console.error("[Affiliates] No API key configured");
137
+ return null;
138
+ }
139
+ try {
140
+ const response = await fetch(`${apiUrl}${endpoint}`, {
141
+ method: "GET",
142
+ headers: {
143
+ "x-api-key": apiKey
144
+ }
145
+ });
146
+ if (!response.ok) {
147
+ console.error(`[Affiliates] API error: ${response.statusText}`);
148
+ return null;
149
+ }
150
+ return await response.json();
151
+ } catch (error) {
152
+ console.error("[Affiliates] Network error:", error);
153
+ return null;
154
+ }
155
+ }
156
+ async function fetchAffiliates(options = {}) {
157
+ const result = await apiGet(
158
+ "/api/public/affiliates"
159
+ );
160
+ return result?.affiliates || [];
161
+ }
162
+ function getTrackingUrl(affiliateId, offerId) {
163
+ const { apiUrl } = getApiConfig();
164
+ return `${apiUrl}/a/${affiliateId}/${offerId}`;
165
+ }
166
+ function useAffiliates() {
167
+ const [affiliates, setAffiliates] = useState([]);
168
+ const [isLoading, setIsLoading] = useState(true);
169
+ const [error, setError] = useState(null);
170
+ useEffect(() => {
171
+ async function load() {
172
+ try {
173
+ setIsLoading(true);
174
+ setError(null);
175
+ const data = await fetchAffiliates();
176
+ setAffiliates(data);
177
+ } catch (err) {
178
+ setError(err instanceof Error ? err.message : "Failed to load affiliates");
179
+ } finally {
180
+ setIsLoading(false);
181
+ }
182
+ }
183
+ load();
184
+ }, []);
185
+ return {
186
+ affiliates,
187
+ isLoading,
188
+ error,
189
+ getTrackingUrl
190
+ };
191
+ }
192
+ function AffiliateCard({
193
+ affiliate,
194
+ className = "",
195
+ renderLogo,
196
+ showOffers = false
197
+ }) {
198
+ const { getTrackingUrl: getTrackingUrl2 } = useAffiliates();
199
+ const defaultLogo = /* @__PURE__ */ jsx("div", { "data-affiliate-logo": true, className: "affiliate-logo", children: affiliate.logo_url ? /* @__PURE__ */ jsx(
200
+ "img",
201
+ {
202
+ src: affiliate.logo_url,
203
+ alt: affiliate.name,
204
+ loading: "lazy"
205
+ }
206
+ ) : /* @__PURE__ */ jsx("span", { children: affiliate.name.charAt(0) }) });
207
+ return /* @__PURE__ */ jsxs("div", { className, "data-affiliate-card": true, "data-affiliate-id": affiliate.id, children: [
208
+ renderLogo ? renderLogo(affiliate) : defaultLogo,
209
+ /* @__PURE__ */ jsx("div", { "data-affiliate-name": true, className: "affiliate-name", children: affiliate.name }),
210
+ affiliate.website_url && /* @__PURE__ */ jsx(
211
+ "a",
212
+ {
213
+ href: affiliate.website_url,
214
+ target: "_blank",
215
+ rel: "noopener noreferrer sponsored",
216
+ "data-affiliate-website": true,
217
+ className: "affiliate-website",
218
+ children: "Visit Website"
219
+ }
220
+ ),
221
+ showOffers && affiliate.offers && affiliate.offers.length > 0 && /* @__PURE__ */ jsx("div", { "data-affiliate-offers": true, className: "affiliate-offers", children: affiliate.offers.map((offer) => /* @__PURE__ */ jsx(
222
+ "a",
223
+ {
224
+ href: getTrackingUrl2(affiliate.id, offer.id),
225
+ target: "_blank",
226
+ rel: "noopener noreferrer sponsored",
227
+ "data-affiliate-offer": true,
228
+ "data-offer-id": offer.id,
229
+ className: "affiliate-offer",
230
+ children: offer.name
231
+ },
232
+ offer.id
233
+ )) })
234
+ ] });
235
+ }
236
+ function AffiliatesWidget({
237
+ className = "",
238
+ loadingComponent,
239
+ emptyComponent,
240
+ errorComponent,
241
+ showOffers = false,
242
+ renderAffiliate,
243
+ limit
244
+ }) {
245
+ const { affiliates, isLoading, error, getTrackingUrl: getTrackingUrl2 } = useAffiliates();
246
+ if (isLoading) {
247
+ return loadingComponent || /* @__PURE__ */ jsx("div", { "data-affiliates-loading": true, children: "Loading affiliates..." });
248
+ }
249
+ if (error) {
250
+ return errorComponent || /* @__PURE__ */ jsx("div", { "data-affiliates-error": true, children: error });
251
+ }
252
+ const displayAffiliates = limit ? affiliates.slice(0, limit) : affiliates;
253
+ if (displayAffiliates.length === 0) {
254
+ return emptyComponent || null;
255
+ }
256
+ return /* @__PURE__ */ jsx("div", { className, "data-affiliates-widget": true, children: displayAffiliates.map(
257
+ (affiliate) => renderAffiliate ? renderAffiliate(affiliate) : /* @__PURE__ */ jsx(
258
+ AffiliateCard,
259
+ {
260
+ affiliate,
261
+ showOffers
262
+ },
263
+ affiliate.id
264
+ )
265
+ ) });
266
+ }
267
+ function SignalExperiment({
268
+ experimentId,
269
+ variants,
270
+ fallback,
271
+ trackImpression = true,
272
+ children
273
+ }) {
274
+ const { assignment, variant, isControl } = useSignalExperiment(experimentId);
275
+ const trackEvent = useSignalEvent();
276
+ useEffect(() => {
277
+ if (trackImpression && variant) {
278
+ trackEvent({
279
+ event_type: "experiment",
280
+ event_name: "impression",
281
+ event_data: {
282
+ experiment_id: experimentId,
283
+ variant_key: variant
284
+ }
285
+ });
286
+ }
287
+ }, [experimentId, variant, trackImpression, trackEvent]);
288
+ if (children) {
289
+ return /* @__PURE__ */ jsx(Fragment, { children: children({ variant: variant || "control", isControl }) });
290
+ }
291
+ const selectedVariant = variant || "control";
292
+ const content = variants[selectedVariant];
293
+ if (content !== void 0) {
294
+ return /* @__PURE__ */ jsx(Fragment, { children: content });
295
+ }
296
+ if (fallback) {
297
+ return /* @__PURE__ */ jsx(Fragment, { children: fallback });
298
+ }
299
+ return /* @__PURE__ */ jsx(Fragment, { children: variants.control || null });
300
+ }
301
+ function useExperimentVariant(experimentId) {
302
+ return useSignalExperiment(experimentId);
303
+ }
304
+ function ExperimentConversion({
305
+ experimentId,
306
+ outcomeType = "click",
307
+ value,
308
+ children
309
+ }) {
310
+ const trackEvent = useSignalEvent();
311
+ const { variant } = useSignalExperiment(experimentId);
312
+ const handleInteraction = () => {
313
+ trackEvent({
314
+ event_type: "experiment",
315
+ event_name: "conversion",
316
+ event_data: {
317
+ experiment_id: experimentId,
318
+ variant_key: variant,
319
+ outcome_type: outcomeType,
320
+ value
321
+ }
322
+ });
323
+ };
324
+ return React2.cloneElement(children, {
325
+ onClick: (e) => {
326
+ handleInteraction();
327
+ if (children.props.onClick) {
328
+ children.props.onClick(e);
329
+ }
330
+ }
331
+ });
332
+ }
333
+ var styles = {
334
+ container: {
335
+ display: "flex",
336
+ flexDirection: "column",
337
+ height: "100vh",
338
+ maxWidth: "800px",
339
+ margin: "0 auto",
340
+ padding: "1rem",
341
+ fontFamily: "system-ui, -apple-system, sans-serif"
342
+ },
343
+ header: {
344
+ display: "flex",
345
+ alignItems: "center",
346
+ gap: "0.75rem",
347
+ padding: "1rem 0",
348
+ borderBottom: "1px solid #e5e7eb"
349
+ },
350
+ logo: {
351
+ width: "32px",
352
+ height: "32px",
353
+ borderRadius: "8px",
354
+ background: "linear-gradient(135deg, #3b82f6, #8b5cf6)",
355
+ display: "flex",
356
+ alignItems: "center",
357
+ justifyContent: "center",
358
+ color: "white",
359
+ fontWeight: "bold"
360
+ },
361
+ title: {
362
+ fontSize: "1.25rem",
363
+ fontWeight: "600",
364
+ color: "#111827"
365
+ },
366
+ subtitle: {
367
+ fontSize: "0.875rem",
368
+ color: "#6b7280"
369
+ },
370
+ messages: {
371
+ flex: 1,
372
+ overflowY: "auto",
373
+ padding: "1rem 0",
374
+ display: "flex",
375
+ flexDirection: "column",
376
+ gap: "1rem"
377
+ },
378
+ message: {
379
+ display: "flex",
380
+ gap: "0.75rem",
381
+ maxWidth: "85%"
382
+ },
383
+ messageAssistant: {
384
+ alignSelf: "flex-start"
385
+ },
386
+ messageUser: {
387
+ alignSelf: "flex-end",
388
+ flexDirection: "row-reverse"
389
+ },
390
+ avatar: {
391
+ width: "32px",
392
+ height: "32px",
393
+ borderRadius: "50%",
394
+ display: "flex",
395
+ alignItems: "center",
396
+ justifyContent: "center",
397
+ fontSize: "0.875rem",
398
+ flexShrink: 0
399
+ },
400
+ avatarAssistant: {
401
+ background: "linear-gradient(135deg, #3b82f6, #8b5cf6)",
402
+ color: "white"
403
+ },
404
+ avatarUser: {
405
+ background: "#e5e7eb",
406
+ color: "#374151"
407
+ },
408
+ bubble: {
409
+ padding: "0.75rem 1rem",
410
+ borderRadius: "1rem",
411
+ fontSize: "0.9375rem",
412
+ lineHeight: "1.5"
413
+ },
414
+ bubbleAssistant: {
415
+ background: "#f3f4f6",
416
+ color: "#111827",
417
+ borderTopLeftRadius: "4px"
418
+ },
419
+ bubbleUser: {
420
+ background: "#3b82f6",
421
+ color: "white",
422
+ borderTopRightRadius: "4px"
423
+ },
424
+ actions: {
425
+ display: "flex",
426
+ flexWrap: "wrap",
427
+ gap: "0.5rem",
428
+ marginTop: "0.75rem"
429
+ },
430
+ actionButton: {
431
+ padding: "0.5rem 1rem",
432
+ borderRadius: "0.5rem",
433
+ fontSize: "0.875rem",
434
+ fontWeight: "500",
435
+ cursor: "pointer",
436
+ border: "none",
437
+ transition: "all 0.15s"
438
+ },
439
+ actionPrimary: {
440
+ background: "#3b82f6",
441
+ color: "white"
442
+ },
443
+ actionSecondary: {
444
+ background: "#e5e7eb",
445
+ color: "#374151"
446
+ },
447
+ actionOutline: {
448
+ background: "transparent",
449
+ color: "#3b82f6",
450
+ border: "1px solid #3b82f6"
451
+ },
452
+ inputContainer: {
453
+ display: "flex",
454
+ gap: "0.5rem",
455
+ padding: "1rem 0",
456
+ borderTop: "1px solid #e5e7eb"
457
+ },
458
+ input: {
459
+ flex: 1,
460
+ padding: "0.75rem 1rem",
461
+ borderRadius: "0.75rem",
462
+ border: "1px solid #d1d5db",
463
+ fontSize: "0.9375rem",
464
+ outline: "none"
465
+ },
466
+ sendButton: {
467
+ padding: "0.75rem 1.5rem",
468
+ borderRadius: "0.75rem",
469
+ background: "#3b82f6",
470
+ color: "white",
471
+ border: "none",
472
+ fontWeight: "500",
473
+ cursor: "pointer"
474
+ },
475
+ typing: {
476
+ display: "flex",
477
+ gap: "4px",
478
+ padding: "0.75rem 1rem",
479
+ background: "#f3f4f6",
480
+ borderRadius: "1rem",
481
+ width: "fit-content"
482
+ },
483
+ typingDot: {
484
+ width: "8px",
485
+ height: "8px",
486
+ borderRadius: "50%",
487
+ background: "#9ca3af",
488
+ animation: "typing 1.4s infinite"
489
+ },
490
+ moduleCard: {
491
+ display: "flex",
492
+ alignItems: "center",
493
+ gap: "0.75rem",
494
+ padding: "0.75rem",
495
+ background: "white",
496
+ border: "1px solid #e5e7eb",
497
+ borderRadius: "0.5rem",
498
+ cursor: "pointer",
499
+ transition: "all 0.15s"
500
+ },
501
+ moduleCardSelected: {
502
+ borderColor: "#3b82f6",
503
+ background: "#eff6ff"
504
+ },
505
+ moduleIcon: {
506
+ fontSize: "1.5rem"
507
+ },
508
+ moduleInfo: {
509
+ flex: 1
510
+ },
511
+ moduleName: {
512
+ fontWeight: "500",
513
+ color: "#111827"
514
+ },
515
+ moduleDesc: {
516
+ fontSize: "0.75rem",
517
+ color: "#6b7280"
518
+ },
519
+ checkbox: {
520
+ width: "20px",
521
+ height: "20px",
522
+ accentColor: "#3b82f6"
523
+ }
524
+ };
525
+ var MODULES = [
526
+ { id: "analytics", name: "Analytics", icon: "\u{1F4CA}", description: "Page views, events, sessions, web vitals", recommended: true },
527
+ { id: "seo", name: "SEO", icon: "\u{1F50D}", description: "Managed FAQs, meta tags, schema markup", recommended: true },
528
+ { id: "forms", name: "Forms", icon: "\u{1F4DD}", description: "Sonor-managed forms with submissions", recommended: true },
529
+ { id: "engage", name: "Engage", icon: "\u{1F4AC}", description: "Live chat, popups, nudges, banners", recommended: false },
530
+ { id: "commerce", name: "Commerce", icon: "\u{1F6D2}", description: "Products, services, checkout", recommended: false },
531
+ { id: "signal", name: "Signal AI", icon: "\u{1F916}", description: "Autonomous optimization & A/B testing", recommended: false }
532
+ ];
533
+ function SetupAssistant({
534
+ apiUrl = "https://api.uptrademedia.com",
535
+ signalUrl = "https://signal.uptrademedia.com",
536
+ supabaseUrl,
537
+ supabaseKey,
538
+ projectId,
539
+ orgId,
540
+ authToken,
541
+ onComplete,
542
+ welcomeMessage
543
+ }) {
544
+ const [messages, setMessages] = useState([]);
545
+ const [input, setInput] = useState("");
546
+ const [isTyping, setIsTyping] = useState(false);
547
+ const [isExtracting, setIsExtracting] = useState(false);
548
+ const [state, setState] = useState({
549
+ step: "welcome",
550
+ isAuthenticated: !!authToken,
551
+ selectedModules: ["analytics", "seo", "forms"],
552
+ config: {},
553
+ errors: [],
554
+ context: {
555
+ flow: null,
556
+ step: "welcome",
557
+ project_id: projectId,
558
+ org_id: orgId
559
+ }
560
+ });
561
+ const messagesEndRef = useRef(null);
562
+ const inputRef = useRef(null);
563
+ const streamingMessageRef = useRef(null);
564
+ const sendToSignalStreaming = useCallback(async (message, context, onToken, onComplete2) => {
565
+ try {
566
+ const response = await fetch(`${signalUrl}/api/skills/setup/chat/stream`, {
567
+ method: "POST",
568
+ headers: {
569
+ "Content-Type": "application/json",
570
+ ...authToken ? { Authorization: `Bearer ${authToken}` } : {}
571
+ },
572
+ body: JSON.stringify({
573
+ message,
574
+ context,
575
+ project_id: projectId,
576
+ org_id: orgId
577
+ })
578
+ });
579
+ if (!response.ok) {
580
+ throw new Error("Signal API error");
581
+ }
582
+ const reader = response.body?.getReader();
583
+ if (!reader) throw new Error("No response body");
584
+ const decoder = new TextDecoder();
585
+ let buffer = "";
586
+ let actions = [];
587
+ let updatedContext = {};
588
+ while (true) {
589
+ const { done, value } = await reader.read();
590
+ if (done) break;
591
+ buffer += decoder.decode(value, { stream: true });
592
+ const lines = buffer.split("\n");
593
+ buffer = lines.pop() || "";
594
+ for (const line of lines) {
595
+ if (line.startsWith("data: ")) {
596
+ const data = line.slice(6);
597
+ if (data === "[DONE]") continue;
598
+ try {
599
+ const event = JSON.parse(data);
600
+ if (event.type === "token") {
601
+ onToken(event.content);
602
+ } else if (event.type === "actions") {
603
+ actions = event.actions;
604
+ } else if (event.type === "context") {
605
+ updatedContext = event.context;
606
+ }
607
+ } catch {
608
+ onToken(data);
609
+ }
610
+ }
611
+ }
612
+ }
613
+ onComplete2({ actions, updated_context: updatedContext });
614
+ } catch (error) {
615
+ console.error("Signal streaming error:", error);
616
+ const result = await sendToSignal(message, context);
617
+ onToken(result.response);
618
+ onComplete2({ actions: result.actions, updated_context: result.updated_context });
619
+ }
620
+ }, [signalUrl, authToken, projectId, orgId]);
621
+ const sendToSignal = useCallback(async (message, context) => {
622
+ try {
623
+ const response = await fetch(`${signalUrl}/api/skills/setup/chat`, {
624
+ method: "POST",
625
+ headers: {
626
+ "Content-Type": "application/json",
627
+ ...authToken ? { Authorization: `Bearer ${authToken}` } : {}
628
+ },
629
+ body: JSON.stringify({
630
+ message,
631
+ context,
632
+ project_id: projectId,
633
+ org_id: orgId
634
+ })
635
+ });
636
+ if (!response.ok) {
637
+ throw new Error("Signal API error");
638
+ }
639
+ return await response.json();
640
+ } catch (error) {
641
+ console.error("Signal API error:", error);
642
+ return {
643
+ response: "I had trouble connecting to Signal. Let me help you locally.",
644
+ actions: [
645
+ { id: "1", label: "Continue", action: "continue_local", variant: "primary" }
646
+ ]
647
+ };
648
+ }
649
+ }, [signalUrl, authToken, projectId, orgId]);
650
+ const extractBrandFromDomain = useCallback(async (domain) => {
651
+ setIsExtracting(true);
652
+ try {
653
+ const response = await fetch(`${apiUrl}/site-scrape/brand-only`, {
654
+ method: "POST",
655
+ headers: {
656
+ "Content-Type": "application/json",
657
+ ...authToken ? { Authorization: `Bearer ${authToken}` } : {}
658
+ },
659
+ body: JSON.stringify({ domain })
660
+ });
661
+ if (!response.ok) {
662
+ throw new Error("Brand extraction failed");
663
+ }
664
+ const data = await response.json();
665
+ return {
666
+ business_name: data.business_name,
667
+ tagline: data.tagline,
668
+ primary_color: data.primary_color,
669
+ secondary_color: data.secondary_color,
670
+ logo_url: data.logo_url,
671
+ phone_numbers: data.phone_numbers,
672
+ email_addresses: data.email_addresses,
673
+ social_profiles: data.social_profiles
674
+ };
675
+ } catch (error) {
676
+ console.error("Brand extraction error:", error);
677
+ return null;
678
+ } finally {
679
+ setIsExtracting(false);
680
+ }
681
+ }, [apiUrl, authToken]);
682
+ const addMessage = useCallback((message) => {
683
+ const newMessage = {
684
+ ...message,
685
+ id: crypto.randomUUID(),
686
+ timestamp: /* @__PURE__ */ new Date()
687
+ };
688
+ setMessages((prev) => [...prev, newMessage]);
689
+ return newMessage;
690
+ }, []);
691
+ useCallback((id, update) => {
692
+ setMessages((prev) => prev.map(
693
+ (m) => m.id === id ? { ...m, ...update } : m
694
+ ));
695
+ }, []);
696
+ const addStreamingMessage = useCallback(() => {
697
+ const id = crypto.randomUUID();
698
+ const newMessage = {
699
+ id,
700
+ role: "assistant",
701
+ content: "",
702
+ timestamp: /* @__PURE__ */ new Date(),
703
+ isStreaming: true
704
+ };
705
+ setMessages((prev) => [...prev, newMessage]);
706
+ streamingMessageRef.current = id;
707
+ return id;
708
+ }, []);
709
+ const appendToStreamingMessage = useCallback((token) => {
710
+ const id = streamingMessageRef.current;
711
+ if (!id) return;
712
+ setMessages((prev) => prev.map(
713
+ (m) => m.id === id ? { ...m, content: m.content + token } : m
714
+ ));
715
+ }, []);
716
+ const finalizeStreamingMessage = useCallback((actions) => {
717
+ const id = streamingMessageRef.current;
718
+ if (!id) return;
719
+ setMessages((prev) => prev.map(
720
+ (m) => m.id === id ? { ...m, isStreaming: false, actions } : m
721
+ ));
722
+ streamingMessageRef.current = null;
723
+ }, []);
724
+ const addAssistantMessage = useCallback((content, actions, component) => {
725
+ setIsTyping(true);
726
+ setTimeout(() => {
727
+ setIsTyping(false);
728
+ addMessage({ role: "assistant", content, actions, component });
729
+ }, 500 + Math.random() * 500);
730
+ }, [addMessage]);
731
+ const scrollToBottom = useCallback(() => {
732
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
733
+ }, []);
734
+ useEffect(() => {
735
+ scrollToBottom();
736
+ }, [messages, isTyping, scrollToBottom]);
737
+ useEffect(() => {
738
+ const welcome = welcomeMessage || `Hey! \u{1F44B} I'm Signal, your AI setup assistant.
739
+
740
+ I'll help you integrate Uptrade Site-Kit into your project. What are we working with today?`;
741
+ setTimeout(() => {
742
+ addAssistantMessage(welcome, [
743
+ { id: "1", label: "\u{1F195} New Site", action: "set_flow_new", variant: "primary" },
744
+ { id: "2", label: "\u{1F4C1} Existing Project", action: "set_flow_existing", variant: "secondary" },
745
+ { id: "3", label: "\u{1F504} Rebuild from Live Site", action: "set_flow_rebuild", variant: "outline" }
746
+ ]);
747
+ }, 500);
748
+ }, []);
749
+ const handleAction = useCallback(async (action, data) => {
750
+ switch (action) {
751
+ case "start":
752
+ addMessage({ role: "user", content: "Let's do it!" });
753
+ setState((prev) => ({ ...prev, step: "auth" }));
754
+ addAssistantMessage(
755
+ `Great! First, let's connect to your Sonor account.
756
+
757
+ You can sign in with your existing Sonor credentials:`,
758
+ [
759
+ { id: "1", label: "Sign in with Email", action: "auth_email", variant: "primary" },
760
+ { id: "2", label: "Sign in with Google", action: "auth_google", variant: "secondary" },
761
+ { id: "3", label: "I don't have an account", action: "auth_signup", variant: "outline" }
762
+ ]
763
+ );
764
+ break;
765
+ case "explain":
766
+ addMessage({ role: "user", content: "Tell me more first" });
767
+ addAssistantMessage(
768
+ `Uptrade Site-Kit is a lightweight package that connects your site to Sonor.
769
+
770
+ Here's what you can enable:
771
+
772
+ \u{1F4CA} **Analytics** - Track page views, events, and Core Web Vitals
773
+ \u{1F50D} **SEO** - Managed meta tags, FAQs, and schema markup
774
+ \u{1F4DD} **Forms** - Sonor-managed forms with spam protection
775
+ \u{1F4AC} **Engage** - Live chat, popups, and nudges
776
+ \u{1F6D2} **Commerce** - Products, services, and checkout
777
+ \u{1F916} **Signal AI** - Autonomous optimization and A/B testing
778
+
779
+ You only pay for what you use, and everything is managed from your Sonor dashboard.`,
780
+ [
781
+ { id: "1", label: "Sounds good, let's set it up", action: "start", variant: "primary" }
782
+ ]
783
+ );
784
+ break;
785
+ case "auth_email":
786
+ addMessage({ role: "user", content: "Sign in with Email" });
787
+ addAssistantMessage(
788
+ `What's your Sonor email address?`
789
+ );
790
+ setState((prev) => ({ ...prev, step: "auth" }));
791
+ setTimeout(() => inputRef.current?.focus(), 600);
792
+ break;
793
+ case "auth_google":
794
+ addMessage({ role: "user", content: "Sign in with Google" });
795
+ addAssistantMessage(
796
+ `Opening Google sign-in... (In a real implementation, this would trigger OAuth)`
797
+ );
798
+ setTimeout(() => {
799
+ setState((prev) => ({
800
+ ...prev,
801
+ step: "project",
802
+ isAuthenticated: true,
803
+ userEmail: "user@example.com"
804
+ }));
805
+ handleAuthSuccess("user@example.com");
806
+ }, 1500);
807
+ break;
808
+ case "select_project":
809
+ addMessage({ role: "user", content: `Selected: ${data?.name}` });
810
+ setState((prev) => ({
811
+ ...prev,
812
+ step: "modules",
813
+ selectedProject: data
814
+ }));
815
+ showModuleSelection();
816
+ break;
817
+ case "confirm_modules":
818
+ addMessage({ role: "user", content: `Selected ${state.selectedModules.length} modules` });
819
+ setState((prev) => ({ ...prev, step: "config" }));
820
+ showConfigGeneration();
821
+ break;
822
+ case "copy_code":
823
+ addMessage({ role: "user", content: "Copy integration code" });
824
+ addAssistantMessage(
825
+ `\u2705 Code copied to clipboard!
826
+
827
+ Paste this in your root layout file (e.g., \`app/layout.tsx\` or \`pages/_app.tsx\`).
828
+
829
+ Want me to help you verify the integration?`,
830
+ [
831
+ { id: "1", label: "Yes, verify my setup", action: "verify", variant: "primary" },
832
+ { id: "2", label: "I'm all set, thanks!", action: "complete", variant: "outline" }
833
+ ]
834
+ );
835
+ break;
836
+ case "verify":
837
+ addMessage({ role: "user", content: "Verify my setup" });
838
+ setState((prev) => ({ ...prev, step: "verify" }));
839
+ addAssistantMessage(
840
+ `To verify your setup, start your dev server and visit any page.
841
+
842
+ I'll check for:
843
+ \u2022 \u2713 SiteKitProvider is loading
844
+ \u2022 \u2713 API key is valid
845
+ \u2022 \u2713 Analytics events are sending
846
+ \u2022 \u2713 Modules are initializing
847
+
848
+ Run \`npm run dev\` and let me know when you're ready:`,
849
+ [
850
+ { id: "1", label: "My dev server is running", action: "check_connection", variant: "primary" }
851
+ ]
852
+ );
853
+ break;
854
+ case "check_connection":
855
+ addMessage({ role: "user", content: "My dev server is running" });
856
+ setIsTyping(true);
857
+ setTimeout(() => {
858
+ setIsTyping(false);
859
+ addAssistantMessage(
860
+ `\u{1F389} **Everything looks great!**
861
+
862
+ I detected your site at \`localhost:3000\` and verified:
863
+
864
+ \u2705 SiteKitProvider initialized
865
+ \u2705 API key authenticated
866
+ \u2705 Analytics tracking active
867
+ \u2705 SEO components ready
868
+
869
+ You're all set! Your data will start appearing in your Sonor dashboard within a few minutes.`,
870
+ [
871
+ { id: "1", label: "Open Sonor Dashboard", action: "open_dashboard", variant: "primary" },
872
+ { id: "2", label: "Enable Signal AI", action: "enable_signal", variant: "secondary" }
873
+ ]
874
+ );
875
+ setState((prev) => ({ ...prev, step: "complete" }));
876
+ }, 2e3);
877
+ break;
878
+ case "complete":
879
+ addMessage({ role: "user", content: "I'm all set, thanks!" });
880
+ addAssistantMessage(
881
+ `Awesome! \u{1F680}
882
+
883
+ Your site-kit integration is ready. Here's what happens next:
884
+
885
+ \u2022 Analytics data will appear in Sonor within ~5 minutes
886
+ \u2022 You can manage SEO, forms, and engage from the dashboard
887
+ \u2022 If you enabled Signal, it'll start learning from your traffic
888
+
889
+ Need help anytime? Just come back to \`/_uptrade/setup\` or ping us in Sonor.
890
+
891
+ Happy building! \u{1F3A8}`
892
+ );
893
+ onComplete?.(state);
894
+ break;
895
+ case "enable_signal":
896
+ addMessage({ role: "user", content: "Enable Signal AI" });
897
+ addAssistantMessage(
898
+ `Great choice! \u{1F916}
899
+
900
+ Signal AI will:
901
+ \u2022 Monitor your site for SEO issues
902
+ \u2022 Run A/B tests on CTAs and content
903
+ \u2022 Optimize popups and engagement timing
904
+ \u2022 Learn from user behavior to improve conversions
905
+
906
+ To enable Signal, add \`signal={{ enabled: true }}\` to your SiteKitProvider:
907
+
908
+ \`\`\`tsx
909
+ <SiteKitProvider
910
+ apiKey={process.env.NEXT_PUBLIC_UPTRADE_API_KEY!}
911
+ analytics={{ enabled: true }}
912
+ signal={{ enabled: true }} // Add this
913
+ >
914
+ \`\`\`
915
+
916
+ Signal requires the Business plan. Want me to check your plan?`,
917
+ [
918
+ { id: "1", label: "Check my plan", action: "check_plan", variant: "primary" },
919
+ { id: "2", label: "I'll do this later", action: "complete", variant: "outline" }
920
+ ]
921
+ );
922
+ break;
923
+ case "confirm_brand":
924
+ addMessage({ role: "user", content: "Brand info confirmed" });
925
+ setState((prev) => ({ ...prev, step: "modules" }));
926
+ showModuleSelectionWithRecommendations();
927
+ break;
928
+ case "edit_brand":
929
+ addMessage({ role: "user", content: "Edit brand info" });
930
+ addAssistantMessage(
931
+ `No problem! Let me know what to change:
932
+
933
+ \u2022 Business name
934
+ \u2022 Primary color (hex like #3b82f6)
935
+ \u2022 Tagline
936
+
937
+ Just type what you'd like to update.`
938
+ );
939
+ break;
940
+ case "manual_brand":
941
+ addMessage({ role: "user", content: "Enter brand manually" });
942
+ addAssistantMessage(
943
+ `Sure! What's your business name?`
944
+ );
945
+ setState((prev) => ({
946
+ ...prev,
947
+ context: { ...prev.context, step: "brand_manual" }
948
+ }));
949
+ break;
950
+ case "skip_brand":
951
+ addMessage({ role: "user", content: "Skip brand for now" });
952
+ setState((prev) => ({ ...prev, step: "modules" }));
953
+ showModuleSelection();
954
+ break;
955
+ case "extract_brand":
956
+ addMessage({ role: "user", content: "Extract from website" });
957
+ addAssistantMessage(
958
+ `What's the website URL? I'll extract the brand colors, name, and logo.`
959
+ );
960
+ break;
961
+ case "set_flow_new":
962
+ addMessage({ role: "user", content: "New Site" });
963
+ setState((prev) => ({
964
+ ...prev,
965
+ context: { ...prev.context, flow: "new", step: "brand" }
966
+ }));
967
+ addAssistantMessage(
968
+ `Great! For a new site, I'll help you:
969
+
970
+ 1. Set up your brand (colors, name)
971
+ 2. Choose which modules to enable
972
+ 3. Generate integration code
973
+
974
+ Do you have an existing website I can extract brand info from? Or would you prefer to enter it manually?`,
975
+ [
976
+ { id: "1", label: "Extract from website", action: "extract_brand", variant: "primary" },
977
+ { id: "2", label: "Enter manually", action: "manual_brand", variant: "outline" },
978
+ { id: "3", label: "Skip for now", action: "skip_brand", variant: "outline" }
979
+ ]
980
+ );
981
+ break;
982
+ case "set_flow_existing":
983
+ addMessage({ role: "user", content: "Existing Project" });
984
+ setState((prev) => ({
985
+ ...prev,
986
+ context: { ...prev.context, flow: "existing", step: "scan" }
987
+ }));
988
+ addAssistantMessage(
989
+ `For an existing project, I can scan your codebase to find:
990
+
991
+ \u2022 Forms to migrate (contact forms, newsletter signups)
992
+ \u2022 Chat widgets to replace (Intercom, Crisp, etc.)
993
+ \u2022 Metadata patterns to enhance
994
+ \u2022 Sitemap configuration
995
+
996
+ Run this in your project root:
997
+
998
+ \`\`\`bash
999
+ npx @sonordev/site-kit scan
1000
+ \`\`\`
1001
+
1002
+ Then paste the output here, or tell me about your project.`,
1003
+ [
1004
+ { id: "1", label: "I ran the scan", action: "show_scan_results", variant: "primary" },
1005
+ { id: "2", label: "Skip scan", action: "skip_scan", variant: "outline" }
1006
+ ]
1007
+ );
1008
+ break;
1009
+ case "set_flow_rebuild":
1010
+ addMessage({ role: "user", content: "Rebuild from Live Site" });
1011
+ setState((prev) => ({
1012
+ ...prev,
1013
+ context: { ...prev.context, flow: "rebuild", step: "scrape" }
1014
+ }));
1015
+ addAssistantMessage(
1016
+ `I'll help you rebuild with site-kit. Enter the live site URL and I'll:
1017
+
1018
+ \u2022 Extract brand colors and business info
1019
+ \u2022 Import FAQs with schema markup
1020
+ \u2022 Suggest redirect mappings
1021
+ \u2022 Generate copilot-instructions.md
1022
+
1023
+ What's the website URL?`
1024
+ );
1025
+ break;
1026
+ case "show_scan_results":
1027
+ addMessage({ role: "user", content: "I ran the scan" });
1028
+ addAssistantMessage(
1029
+ `Great! Paste the scan output here, or describe what you found.
1030
+
1031
+ I'll analyze the results and recommend a migration plan.`
1032
+ );
1033
+ break;
1034
+ case "skip_scan":
1035
+ addMessage({ role: "user", content: "Skip scan" });
1036
+ setState((prev) => ({ ...prev, step: "modules" }));
1037
+ showModuleSelection();
1038
+ break;
1039
+ case "generate_redirects":
1040
+ addMessage({ role: "user", content: "Generate redirects" });
1041
+ if (state.context.scrape_results?.routes) {
1042
+ addStreamingMessage();
1043
+ await sendToSignalStreaming(
1044
+ `Generate SEO-optimized redirects for these routes: ${JSON.stringify(state.context.scrape_results.routes)}`,
1045
+ state.context,
1046
+ (token) => appendToStreamingMessage(token),
1047
+ (result) => finalizeStreamingMessage(result.actions)
1048
+ );
1049
+ } else {
1050
+ addAssistantMessage(
1051
+ `I need a list of routes to generate redirects. Would you like to:
1052
+
1053
+ 1. Scrape a website for routes
1054
+ 2. Paste a list of URLs
1055
+
1056
+ What's easier?`,
1057
+ [
1058
+ { id: "1", label: "Scrape website", action: "set_flow_rebuild", variant: "primary" },
1059
+ { id: "2", label: "Paste URLs", action: "paste_urls", variant: "outline" }
1060
+ ]
1061
+ );
1062
+ }
1063
+ break;
1064
+ case "generate_copilot_instructions":
1065
+ addMessage({ role: "user", content: "Generate Copilot instructions" });
1066
+ addStreamingMessage();
1067
+ await sendToSignalStreaming(
1068
+ `Generate copilot-instructions.md for project with modules: ${state.selectedModules.join(", ")} and brand: ${state.context.brand?.business_name || "Unknown"}`,
1069
+ state.context,
1070
+ (token) => appendToStreamingMessage(token),
1071
+ (result) => {
1072
+ finalizeStreamingMessage([
1073
+ { id: "1", label: "\u{1F4CB} Copy to clipboard", action: "copy_copilot_instructions", variant: "primary" },
1074
+ { id: "2", label: "Continue", action: "complete", variant: "outline" }
1075
+ ]);
1076
+ }
1077
+ );
1078
+ break;
1079
+ case "verify_now":
1080
+ addMessage({ role: "user", content: "Verify integration" });
1081
+ verifyIntegration();
1082
+ break;
1083
+ }
1084
+ }, [addMessage, addAssistantMessage, addStreamingMessage, appendToStreamingMessage, finalizeStreamingMessage, sendToSignalStreaming, state, onComplete]);
1085
+ const handleAuthSuccess = useCallback((email) => {
1086
+ addAssistantMessage(
1087
+ `Welcome back, ${email.split("@")[0]}! \u{1F44B}
1088
+
1089
+ I found these projects in your account. Which one are we setting up?`,
1090
+ [
1091
+ { id: "1", label: "MyCompany.com", action: "select_project", variant: "secondary", data: { id: "1", name: "MyCompany.com", domain: "mycompany.com" } },
1092
+ { id: "2", label: "Blog Project", action: "select_project", variant: "secondary", data: { id: "2", name: "Blog Project", domain: "blog.mycompany.com" } },
1093
+ { id: "3", label: "+ Create new project", action: "create_project", variant: "outline" }
1094
+ ]
1095
+ );
1096
+ }, [addAssistantMessage]);
1097
+ const verifyIntegration = useCallback(async () => {
1098
+ setState((prev) => ({ ...prev, step: "verify" }));
1099
+ setIsTyping(true);
1100
+ try {
1101
+ const response = await fetch(`${signalUrl}/api/skills/setup/verify`, {
1102
+ method: "POST",
1103
+ headers: {
1104
+ "Content-Type": "application/json",
1105
+ ...authToken ? { Authorization: `Bearer ${authToken}` } : {}
1106
+ },
1107
+ body: JSON.stringify({
1108
+ project_id: projectId || state.context.project_id,
1109
+ dev_url: "http://localhost:3000"
1110
+ })
1111
+ });
1112
+ setIsTyping(false);
1113
+ if (!response.ok) {
1114
+ throw new Error("Verification failed");
1115
+ }
1116
+ const result = await response.json();
1117
+ if (result.overall_status === "success") {
1118
+ const checkList = result.checks.map(
1119
+ (c) => `${c.passed ? "\u2705" : "\u274C"} ${c.check.replace(/_/g, " ")}`
1120
+ ).join("\n");
1121
+ addAssistantMessage(
1122
+ `\u{1F389} **Integration Verified!**
1123
+
1124
+ ${checkList}
1125
+
1126
+ Your site-kit integration is working perfectly. Data will start appearing in Sonor shortly.`,
1127
+ [
1128
+ { id: "1", label: "Open Sonor Dashboard", action: "open_dashboard", variant: "primary" },
1129
+ { id: "2", label: "Generate Copilot Instructions", action: "generate_copilot_instructions", variant: "secondary" }
1130
+ ]
1131
+ );
1132
+ setState((prev) => ({ ...prev, step: "complete" }));
1133
+ } else {
1134
+ const issues = result.issues?.join("\n\u2022 ") || "Unknown issue";
1135
+ addAssistantMessage(
1136
+ `\u26A0\uFE0F **Verification Found Issues**
1137
+
1138
+ \u2022 ${issues}
1139
+
1140
+ Would you like help troubleshooting?`,
1141
+ [
1142
+ { id: "1", label: "Help me fix this", action: "troubleshoot", variant: "primary" },
1143
+ { id: "2", label: "Skip for now", action: "complete", variant: "outline" }
1144
+ ]
1145
+ );
1146
+ }
1147
+ } catch (error) {
1148
+ setIsTyping(false);
1149
+ addAssistantMessage(
1150
+ `I couldn't verify the integration automatically. Make sure your dev server is running.
1151
+
1152
+ You can manually verify by:
1153
+ 1. Opening your site in the browser
1154
+ 2. Checking the Network tab for requests to api.uptrademedia.com
1155
+ 3. Looking for analytics events in your Sonor dashboard`,
1156
+ [
1157
+ { id: "1", label: "Try again", action: "verify_now", variant: "primary" },
1158
+ { id: "2", label: "Continue anyway", action: "complete", variant: "outline" }
1159
+ ]
1160
+ );
1161
+ }
1162
+ }, [signalUrl, authToken, projectId, state.context.project_id, addAssistantMessage]);
1163
+ const showModuleSelectionWithRecommendations = useCallback(async () => {
1164
+ const { brand, business_type, scan_results } = state.context;
1165
+ if (brand || business_type) {
1166
+ try {
1167
+ const result = await sendToSignal(
1168
+ `Recommend modules for: ${brand?.business_name || "Unknown"}, type: ${business_type || "general"}`,
1169
+ { ...state.context, step: "recommend_modules" }
1170
+ );
1171
+ addAssistantMessage(
1172
+ result.response,
1173
+ void 0,
1174
+ /* @__PURE__ */ jsx(
1175
+ ModuleSelector,
1176
+ {
1177
+ modules: MODULES,
1178
+ selected: state.selectedModules,
1179
+ onChange: (modules) => setState((prev) => ({ ...prev, selectedModules: modules })),
1180
+ onConfirm: () => handleAction("confirm_modules")
1181
+ }
1182
+ )
1183
+ );
1184
+ return;
1185
+ } catch (error) {
1186
+ }
1187
+ }
1188
+ showModuleSelection();
1189
+ }, [state.context, state.selectedModules, sendToSignal, handleAction]);
1190
+ const showModuleSelection = useCallback(() => {
1191
+ addAssistantMessage(
1192
+ `Perfect! Now let's choose which features to enable.
1193
+
1194
+ I've pre-selected the essentials, but you can customize:`,
1195
+ void 0,
1196
+ /* @__PURE__ */ jsx(
1197
+ ModuleSelector,
1198
+ {
1199
+ modules: MODULES,
1200
+ selected: state.selectedModules,
1201
+ onChange: (modules) => setState((prev) => ({ ...prev, selectedModules: modules })),
1202
+ onConfirm: () => handleAction("confirm_modules")
1203
+ }
1204
+ )
1205
+ );
1206
+ }, [addAssistantMessage, state.selectedModules, handleAction]);
1207
+ const showConfigGeneration = useCallback(() => {
1208
+ const code = generateIntegrationCode(state);
1209
+ addAssistantMessage(
1210
+ `Here's your integration code! \u{1F389}
1211
+
1212
+ Add this to your root layout:`,
1213
+ [
1214
+ { id: "1", label: "\u{1F4CB} Copy Code", action: "copy_code", variant: "primary" }
1215
+ ],
1216
+ /* @__PURE__ */ jsx(CodeBlock, { code })
1217
+ );
1218
+ }, [addAssistantMessage, state]);
1219
+ const handleSubmit = useCallback(async (e) => {
1220
+ e.preventDefault();
1221
+ if (!input.trim()) return;
1222
+ const userInput = input.trim();
1223
+ setInput("");
1224
+ addMessage({ role: "user", content: userInput });
1225
+ if (state.step === "auth" && userInput.includes("@")) {
1226
+ setState((prev) => ({
1227
+ ...prev,
1228
+ isAuthenticated: true,
1229
+ userEmail: userInput,
1230
+ step: "project"
1231
+ }));
1232
+ addAssistantMessage(
1233
+ `Sending magic link to ${userInput}...
1234
+
1235
+ (In production, you'd receive an email. For now, I'll simulate a successful login.)`
1236
+ );
1237
+ setTimeout(() => handleAuthSuccess(userInput), 1500);
1238
+ } else if (userInput.toLowerCase().includes("http") || userInput.match(/^[\w.-]+\.[a-z]{2,}$/i)) {
1239
+ const domain = userInput.replace(/^https?:\/\//, "").split("/")[0];
1240
+ setIsTyping(true);
1241
+ addAssistantMessage(`Analyzing ${domain} to extract brand information...`);
1242
+ const brand = await extractBrandFromDomain(domain);
1243
+ setIsTyping(false);
1244
+ if (brand) {
1245
+ setState((prev) => ({
1246
+ ...prev,
1247
+ context: { ...prev.context, brand, domain }
1248
+ }));
1249
+ const brandSummary = [
1250
+ brand.business_name && `**Business:** ${brand.business_name}`,
1251
+ brand.tagline && `**Tagline:** ${brand.tagline}`,
1252
+ brand.primary_color && `**Primary Color:** ${brand.primary_color}`,
1253
+ brand.phone_numbers?.length && `**Phone:** ${brand.phone_numbers[0]}`
1254
+ ].filter(Boolean).join("\n");
1255
+ addAssistantMessage(
1256
+ `Found brand information:
1257
+
1258
+ ${brandSummary}
1259
+
1260
+ Does this look right?`,
1261
+ [
1262
+ { id: "1", label: "Yes, looks good!", action: "confirm_brand", variant: "primary" },
1263
+ { id: "2", label: "Edit brand info", action: "edit_brand", variant: "outline" }
1264
+ ]
1265
+ );
1266
+ } else {
1267
+ addAssistantMessage(
1268
+ `I couldn't extract brand info from ${domain}. You can enter it manually, or we can continue without it.`,
1269
+ [
1270
+ { id: "1", label: "Enter manually", action: "manual_brand", variant: "primary" },
1271
+ { id: "2", label: "Skip for now", action: "skip_brand", variant: "outline" }
1272
+ ]
1273
+ );
1274
+ }
1275
+ } else {
1276
+ addStreamingMessage();
1277
+ try {
1278
+ await sendToSignalStreaming(
1279
+ userInput,
1280
+ state.context,
1281
+ (token) => appendToStreamingMessage(token),
1282
+ (result) => {
1283
+ if (result.updated_context) {
1284
+ setState((prev) => ({
1285
+ ...prev,
1286
+ context: { ...prev.context, ...result.updated_context }
1287
+ }));
1288
+ }
1289
+ finalizeStreamingMessage(result.actions);
1290
+ }
1291
+ );
1292
+ } catch (error) {
1293
+ finalizeStreamingMessage();
1294
+ addAssistantMessage(
1295
+ `I understand you said: "${userInput}"
1296
+
1297
+ Let me help you with that. What would you like to do?`,
1298
+ [
1299
+ { id: "1", label: "Continue setup", action: "start", variant: "primary" },
1300
+ { id: "2", label: "Ask a question", action: "help", variant: "outline" }
1301
+ ]
1302
+ );
1303
+ }
1304
+ }
1305
+ }, [input, state.step, state.context, addMessage, addAssistantMessage, handleAuthSuccess, sendToSignalStreaming, appendToStreamingMessage, finalizeStreamingMessage, addStreamingMessage, extractBrandFromDomain]);
1306
+ return /* @__PURE__ */ jsxs("div", { style: styles.container, children: [
1307
+ /* @__PURE__ */ jsxs("div", { style: styles.header, children: [
1308
+ /* @__PURE__ */ jsx("div", { style: styles.logo, children: "U" }),
1309
+ /* @__PURE__ */ jsxs("div", { children: [
1310
+ /* @__PURE__ */ jsx("div", { style: styles.title, children: "Uptrade Setup" }),
1311
+ /* @__PURE__ */ jsx("div", { style: styles.subtitle, children: "Site-Kit Integration Wizard" })
1312
+ ] })
1313
+ ] }),
1314
+ /* @__PURE__ */ jsxs("div", { style: styles.messages, children: [
1315
+ messages.map((message) => /* @__PURE__ */ jsxs(
1316
+ "div",
1317
+ {
1318
+ style: {
1319
+ ...styles.message,
1320
+ ...message.role === "assistant" ? styles.messageAssistant : styles.messageUser
1321
+ },
1322
+ children: [
1323
+ /* @__PURE__ */ jsx(
1324
+ "div",
1325
+ {
1326
+ style: {
1327
+ ...styles.avatar,
1328
+ ...message.role === "assistant" ? styles.avatarAssistant : styles.avatarUser
1329
+ },
1330
+ children: message.role === "assistant" ? "\u2728" : "\u{1F464}"
1331
+ }
1332
+ ),
1333
+ /* @__PURE__ */ jsxs("div", { children: [
1334
+ /* @__PURE__ */ jsx(
1335
+ "div",
1336
+ {
1337
+ style: {
1338
+ ...styles.bubble,
1339
+ ...message.role === "assistant" ? styles.bubbleAssistant : styles.bubbleUser
1340
+ },
1341
+ children: message.content.split("\n").map((line, i) => /* @__PURE__ */ jsxs(React2.Fragment, { children: [
1342
+ line,
1343
+ i < message.content.split("\n").length - 1 && /* @__PURE__ */ jsx("br", {})
1344
+ ] }, i))
1345
+ }
1346
+ ),
1347
+ message.component,
1348
+ message.actions && /* @__PURE__ */ jsx("div", { style: styles.actions, children: message.actions.map((action) => /* @__PURE__ */ jsx(
1349
+ "button",
1350
+ {
1351
+ onClick: () => handleAction(action.action, action.data),
1352
+ style: {
1353
+ ...styles.actionButton,
1354
+ ...action.variant === "primary" ? styles.actionPrimary : action.variant === "outline" ? styles.actionOutline : styles.actionSecondary
1355
+ },
1356
+ children: action.label
1357
+ },
1358
+ action.id
1359
+ )) })
1360
+ ] })
1361
+ ]
1362
+ },
1363
+ message.id
1364
+ )),
1365
+ isTyping && /* @__PURE__ */ jsxs("div", { style: { ...styles.message, ...styles.messageAssistant }, children: [
1366
+ /* @__PURE__ */ jsx("div", { style: { ...styles.avatar, ...styles.avatarAssistant }, children: "\u2728" }),
1367
+ /* @__PURE__ */ jsxs("div", { style: styles.typing, children: [
1368
+ /* @__PURE__ */ jsx("div", { style: { ...styles.typingDot, animationDelay: "0s" } }),
1369
+ /* @__PURE__ */ jsx("div", { style: { ...styles.typingDot, animationDelay: "0.2s" } }),
1370
+ /* @__PURE__ */ jsx("div", { style: { ...styles.typingDot, animationDelay: "0.4s" } })
1371
+ ] })
1372
+ ] }),
1373
+ /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
1374
+ ] }),
1375
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, style: styles.inputContainer, children: [
1376
+ /* @__PURE__ */ jsx(
1377
+ "input",
1378
+ {
1379
+ ref: inputRef,
1380
+ type: "text",
1381
+ value: input,
1382
+ onChange: (e) => setInput(e.target.value),
1383
+ placeholder: "Type a message...",
1384
+ style: styles.input
1385
+ }
1386
+ ),
1387
+ /* @__PURE__ */ jsx("button", { type: "submit", style: styles.sendButton, children: "Send" })
1388
+ ] }),
1389
+ /* @__PURE__ */ jsx("style", { children: `
1390
+ @keyframes typing {
1391
+ 0%, 60%, 100% { transform: translateY(0); opacity: 0.5; }
1392
+ 30% { transform: translateY(-4px); opacity: 1; }
1393
+ }
1394
+ ` })
1395
+ ] });
1396
+ }
1397
+ function ModuleSelector({
1398
+ modules,
1399
+ selected,
1400
+ onChange,
1401
+ onConfirm
1402
+ }) {
1403
+ const toggle = (id) => {
1404
+ onChange(
1405
+ selected.includes(id) ? selected.filter((m) => m !== id) : [...selected, id]
1406
+ );
1407
+ };
1408
+ return /* @__PURE__ */ jsxs("div", { style: { marginTop: "0.75rem", display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
1409
+ modules.map((module) => /* @__PURE__ */ jsxs(
1410
+ "div",
1411
+ {
1412
+ onClick: () => toggle(module.id),
1413
+ style: {
1414
+ ...styles.moduleCard,
1415
+ ...selected.includes(module.id) ? styles.moduleCardSelected : {}
1416
+ },
1417
+ children: [
1418
+ /* @__PURE__ */ jsx("span", { style: styles.moduleIcon, children: module.icon }),
1419
+ /* @__PURE__ */ jsxs("div", { style: styles.moduleInfo, children: [
1420
+ /* @__PURE__ */ jsxs("div", { style: styles.moduleName, children: [
1421
+ module.name,
1422
+ module.recommended && /* @__PURE__ */ jsx("span", { style: { marginLeft: "0.5rem", fontSize: "0.625rem", background: "#dbeafe", color: "#1d4ed8", padding: "2px 6px", borderRadius: "4px" }, children: "Recommended" })
1423
+ ] }),
1424
+ /* @__PURE__ */ jsx("div", { style: styles.moduleDesc, children: module.description })
1425
+ ] }),
1426
+ /* @__PURE__ */ jsx(
1427
+ "input",
1428
+ {
1429
+ type: "checkbox",
1430
+ checked: selected.includes(module.id),
1431
+ onChange: () => toggle(module.id),
1432
+ style: styles.checkbox
1433
+ }
1434
+ )
1435
+ ]
1436
+ },
1437
+ module.id
1438
+ )),
1439
+ /* @__PURE__ */ jsxs(
1440
+ "button",
1441
+ {
1442
+ onClick: onConfirm,
1443
+ style: { ...styles.actionButton, ...styles.actionPrimary, marginTop: "0.5rem" },
1444
+ children: [
1445
+ "Continue with ",
1446
+ selected.length,
1447
+ " modules"
1448
+ ]
1449
+ }
1450
+ )
1451
+ ] });
1452
+ }
1453
+ function CodeBlock({ code }) {
1454
+ return /* @__PURE__ */ jsx("pre", { style: {
1455
+ marginTop: "0.75rem",
1456
+ padding: "1rem",
1457
+ background: "#1f2937",
1458
+ color: "#e5e7eb",
1459
+ borderRadius: "0.5rem",
1460
+ fontSize: "0.8125rem",
1461
+ overflowX: "auto",
1462
+ fontFamily: "monospace"
1463
+ }, children: code });
1464
+ }
1465
+ function generateIntegrationCode(state) {
1466
+ const { selectedModules } = state;
1467
+ const moduleConfigs = [];
1468
+ if (selectedModules.includes("analytics")) {
1469
+ moduleConfigs.push(` analytics={{ enabled: true }}`);
1470
+ }
1471
+ if (selectedModules.includes("engage")) {
1472
+ moduleConfigs.push(` engage={{ enabled: true }}`);
1473
+ }
1474
+ if (selectedModules.includes("forms")) {
1475
+ moduleConfigs.push(` forms={{ enabled: true }}`);
1476
+ }
1477
+ if (selectedModules.includes("signal")) {
1478
+ moduleConfigs.push(` signal={{ enabled: true, realtime: true }}`);
1479
+ }
1480
+ return `// app/layout.tsx (or pages/_app.tsx)
1481
+ import { SiteKitProvider } from '@sonordev/site-kit'
1482
+
1483
+ export default function RootLayout({ children }) {
1484
+ return (
1485
+ <html>
1486
+ <body>
1487
+ <SiteKitProvider
1488
+ apiKey={process.env.NEXT_PUBLIC_UPTRADE_API_KEY!}
1489
+ ${moduleConfigs.join("\n")}
1490
+ >
1491
+ {children}
1492
+ </SiteKitProvider>
1493
+ </body>
1494
+ </html>
1495
+ )
1496
+ }`;
1497
+ }
1498
+
1499
+ // src/sync/api.ts
1500
+ var DEFAULT_API_URL = "https://api.uptrademedia.com";
1501
+ function getApiConfig2() {
1502
+ const apiUrl = typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ || DEFAULT_API_URL : DEFAULT_API_URL;
1503
+ const apiKey = typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0;
1504
+ return { apiUrl, apiKey };
1505
+ }
1506
+ function buildHeaders(apiKey, isPost = false) {
1507
+ const headers = {};
1508
+ if (isPost) headers["Content-Type"] = "application/json";
1509
+ if (apiKey) headers["x-api-key"] = apiKey;
1510
+ return headers;
1511
+ }
1512
+ async function fetchBookingTypes(orgSlug, apiUrl, apiKey) {
1513
+ const cfg = getApiConfig2();
1514
+ const url = apiUrl || cfg.apiUrl;
1515
+ const key = apiKey || cfg.apiKey;
1516
+ const endpoint = key ? `${url}/sync/widget/types` : `${url}/sync/public/${orgSlug}/types`;
1517
+ const response = await fetch(endpoint, { headers: buildHeaders(key) });
1518
+ if (!response.ok) {
1519
+ const msg = await response.json().catch(() => ({ message: response.statusText }));
1520
+ const detail = msg && typeof msg.message === "string" ? msg.message : response.statusText;
1521
+ throw new Error(`Failed to fetch booking types: ${response.status} ${detail}`);
1522
+ }
1523
+ const data = await response.json();
1524
+ return data.types || [];
1525
+ }
1526
+ async function fetchBookingTypeDetails(typeSlug, orgSlug, apiUrl, apiKey) {
1527
+ const cfg = getApiConfig2();
1528
+ const url = apiUrl || cfg.apiUrl;
1529
+ const key = apiKey || cfg.apiKey;
1530
+ const endpoint = key ? `${url}/sync/widget/types/${typeSlug}` : `${url}/sync/public/${orgSlug}/types/${typeSlug}`;
1531
+ const response = await fetch(endpoint, { headers: buildHeaders(key) });
1532
+ if (!response.ok) {
1533
+ const msg = await response.json().catch(() => ({ message: response.statusText }));
1534
+ const detail = msg && typeof msg.message === "string" ? msg.message : response.statusText;
1535
+ throw new Error(`Failed to fetch booking type: ${response.status} ${detail}`);
1536
+ }
1537
+ return response.json();
1538
+ }
1539
+ async function fetchAvailability(typeSlug, date, orgSlug, apiUrl, apiKey, timezone, hostId) {
1540
+ const cfg = getApiConfig2();
1541
+ const url = apiUrl || cfg.apiUrl;
1542
+ const key = apiKey || cfg.apiKey;
1543
+ const params = new URLSearchParams({ date });
1544
+ if (timezone) params.append("timezone", timezone);
1545
+ if (hostId) params.append("hostId", hostId);
1546
+ const endpoint = key ? `${url}/sync/widget/availability/${typeSlug}?${params}` : `${url}/sync/public/${orgSlug}/availability/${typeSlug}?${params}`;
1547
+ const response = await fetch(endpoint, { headers: buildHeaders(key) });
1548
+ if (!response.ok) {
1549
+ const msg = await response.json().catch(() => ({ message: response.statusText }));
1550
+ let detail = msg && typeof msg.message === "string" ? String(msg.message).trim() : (response.statusText || "").trim();
1551
+ if (detail === String(response.status) || /^\d+$/.test(detail)) detail = "";
1552
+ const rest = detail ? `${response.status} ${detail}` : String(response.status);
1553
+ const fallback = response.status === 404 && !detail ? "No times available. In the Portal, check Sync \u2192 Booking Types (Schedule a Tour) has a host assigned and the project API key is set for this site." : null;
1554
+ throw new Error(fallback || `Failed to fetch availability: ${rest}`.trim());
1555
+ }
1556
+ const data = await response.json();
1557
+ const raw = data.slots || [];
1558
+ return raw.map((slot) => ({
1559
+ start: slot.startTime ?? slot.start,
1560
+ end: slot.endTime ?? slot.end,
1561
+ hostId: slot.hostId,
1562
+ available: slot.available !== false
1563
+ }));
1564
+ }
1565
+ async function createSlotHold(holdData, apiUrl, apiKey) {
1566
+ const cfg = getApiConfig2();
1567
+ const url = apiUrl || cfg.apiUrl;
1568
+ const key = apiKey || cfg.apiKey;
1569
+ const endpoint = key ? `${url}/sync/widget/hold` : `${url}/sync/public/hold`;
1570
+ const response = await fetch(endpoint, {
1571
+ method: "POST",
1572
+ headers: buildHeaders(key, true),
1573
+ body: JSON.stringify(holdData)
1574
+ });
1575
+ if (!response.ok) {
1576
+ throw new Error(`Failed to hold slot: ${response.statusText}`);
1577
+ }
1578
+ return response.json();
1579
+ }
1580
+ async function releaseSlotHold(holdId, apiUrl, apiKey) {
1581
+ const cfg = getApiConfig2();
1582
+ const url = apiUrl || cfg.apiUrl;
1583
+ const key = apiKey || cfg.apiKey;
1584
+ const endpoint = key ? `${url}/sync/widget/hold/${holdId}` : `${url}/sync/public/hold/${holdId}`;
1585
+ await fetch(endpoint, {
1586
+ method: "DELETE",
1587
+ headers: buildHeaders(key)
1588
+ });
1589
+ }
1590
+ async function createBooking(bookingData, apiUrl, apiKey) {
1591
+ const cfg = getApiConfig2();
1592
+ const url = apiUrl || cfg.apiUrl;
1593
+ const key = apiKey || cfg.apiKey;
1594
+ const endpoint = key ? `${url}/sync/widget/booking` : `${url}/sync/public/booking`;
1595
+ const response = await fetch(endpoint, {
1596
+ method: "POST",
1597
+ headers: buildHeaders(key, true),
1598
+ body: JSON.stringify(bookingData)
1599
+ });
1600
+ if (!response.ok) {
1601
+ const errorData = await response.json().catch(() => ({}));
1602
+ throw new Error(errorData.message || `Failed to create booking: ${response.statusText}`);
1603
+ }
1604
+ return response.json();
1605
+ }
1606
+ async function fetchAvailableDates(typeSlug, startDate, endDate, orgSlug, apiUrl, apiKey, timezone) {
1607
+ const availableDates = [];
1608
+ const start = new Date(startDate);
1609
+ const end = new Date(endDate);
1610
+ const current = new Date(start);
1611
+ while (current <= end) {
1612
+ const dateStr = current.toISOString().split("T")[0];
1613
+ try {
1614
+ const slots = await fetchAvailability(typeSlug, dateStr, orgSlug, apiUrl, apiKey, timezone);
1615
+ if (slots.some((s) => s.available)) {
1616
+ availableDates.push(dateStr);
1617
+ }
1618
+ } catch {
1619
+ }
1620
+ current.setDate(current.getDate() + 1);
1621
+ }
1622
+ return availableDates;
1623
+ }
1624
+ function detectTimezone() {
1625
+ try {
1626
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
1627
+ } catch {
1628
+ return "America/New_York";
1629
+ }
1630
+ }
1631
+ function formatTime(isoString, timezone) {
1632
+ const date = new Date(isoString);
1633
+ return date.toLocaleTimeString("en-US", {
1634
+ hour: "numeric",
1635
+ minute: "2-digit",
1636
+ hour12: true,
1637
+ timeZone: timezone
1638
+ });
1639
+ }
1640
+ function formatDate2(isoString, timezone) {
1641
+ const date = new Date(isoString);
1642
+ return date.toLocaleDateString("en-US", {
1643
+ weekday: "long",
1644
+ month: "long",
1645
+ day: "numeric",
1646
+ year: "numeric",
1647
+ timeZone: timezone
1648
+ });
1649
+ }
1650
+ function formatDuration(minutes) {
1651
+ if (minutes < 60) {
1652
+ return `${minutes} min`;
1653
+ }
1654
+ const hours = Math.floor(minutes / 60);
1655
+ const mins = minutes % 60;
1656
+ if (mins === 0) {
1657
+ return `${hours} hour${hours > 1 ? "s" : ""}`;
1658
+ }
1659
+ return `${hours}h ${mins}m`;
1660
+ }
1661
+ var DEFAULT_API_URL2 = "https://api.uptrademedia.com";
1662
+ var DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1663
+ var MONTHS = [
1664
+ "January",
1665
+ "February",
1666
+ "March",
1667
+ "April",
1668
+ "May",
1669
+ "June",
1670
+ "July",
1671
+ "August",
1672
+ "September",
1673
+ "October",
1674
+ "November",
1675
+ "December"
1676
+ ];
1677
+ function isSameDay(a, b) {
1678
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1679
+ }
1680
+ function isBeforeDay(a, b) {
1681
+ const ac = new Date(a.getFullYear(), a.getMonth(), a.getDate());
1682
+ const bc = new Date(b.getFullYear(), b.getMonth(), b.getDate());
1683
+ return ac < bc;
1684
+ }
1685
+ function calendarDays(year, month) {
1686
+ const first = new Date(year, month, 1);
1687
+ const last = new Date(year, month + 1, 0);
1688
+ const cells = [];
1689
+ for (let i = 0; i < first.getDay(); i++) cells.push(null);
1690
+ for (let d = 1; d <= last.getDate(); d++) cells.push(new Date(year, month, d));
1691
+ return cells;
1692
+ }
1693
+ function ChevronLeft() {
1694
+ return /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx("path", { d: "M12.5 15L7.5 10L12.5 5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
1695
+ }
1696
+ function ChevronRight() {
1697
+ return /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx("path", { d: "M7.5 5L12.5 10L7.5 15", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
1698
+ }
1699
+ function ClockIcon() {
1700
+ return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1701
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "6.5", stroke: "currentColor", strokeWidth: "1.2" }),
1702
+ /* @__PURE__ */ jsx("path", { d: "M8 4.5V8L10 10", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
1703
+ ] });
1704
+ }
1705
+ function GlobeIcon() {
1706
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1707
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "6.5", stroke: "currentColor", strokeWidth: "1.2" }),
1708
+ /* @__PURE__ */ jsx("path", { d: "M1.5 8H14.5", stroke: "currentColor", strokeWidth: "1.2" }),
1709
+ /* @__PURE__ */ jsx("path", { d: "M8 1.5C9.66 3.34 10.61 5.62 10.61 8C10.61 10.38 9.66 12.66 8 14.5C6.34 12.66 5.39 10.38 5.39 8C5.39 5.62 6.34 3.34 8 1.5Z", stroke: "currentColor", strokeWidth: "1.2" })
1710
+ ] });
1711
+ }
1712
+ function CheckCircleIcon() {
1713
+ return /* @__PURE__ */ jsxs("svg", { width: "56", height: "56", viewBox: "0 0 56 56", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1714
+ /* @__PURE__ */ jsx("circle", { cx: "28", cy: "28", r: "28", fill: "var(--bw-primary)" }),
1715
+ /* @__PURE__ */ jsx("path", { d: "M18 28.5L24.5 35L38 21.5", stroke: "white", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round" })
1716
+ ] });
1717
+ }
1718
+ function CalendarPlusIcon() {
1719
+ return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1720
+ /* @__PURE__ */ jsx("rect", { x: "1.5", y: "2.5", width: "13", height: "12", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }),
1721
+ /* @__PURE__ */ jsx("path", { d: "M1.5 6.5H14.5", stroke: "currentColor", strokeWidth: "1.2" }),
1722
+ /* @__PURE__ */ jsx("path", { d: "M5 1V4M11 1V4", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }),
1723
+ /* @__PURE__ */ jsx("path", { d: "M8 9V12M6.5 10.5H9.5", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" })
1724
+ ] });
1725
+ }
1726
+ function ArrowLeftIcon() {
1727
+ return /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx("path", { d: "M10 3L5 8L10 13", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
1728
+ }
1729
+ function SpinnerIcon() {
1730
+ return /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: "bw-spinner", children: [
1731
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "8", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeDasharray: "50.265", strokeDashoffset: "25", opacity: "0.3" }),
1732
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "8", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeDasharray: "50.265", strokeDashoffset: "37.7" })
1733
+ ] });
1734
+ }
1735
+ function BookingWidget({
1736
+ orgSlug,
1737
+ apiKey: propApiKey,
1738
+ apiUrl: propApiUrl,
1739
+ bookingTypeSlug,
1740
+ timezone: propTimezone,
1741
+ className = "",
1742
+ daysToShow = 60,
1743
+ onBookingComplete,
1744
+ onError,
1745
+ hideTypeSelector = false,
1746
+ styles: styles2 = {}
1747
+ }) {
1748
+ const apiKey = propApiKey || (typeof window !== "undefined" ? window.__SITE_KIT_API_KEY__ : void 0);
1749
+ const apiUrl = propApiUrl || (typeof window !== "undefined" ? window.__SITE_KIT_API_URL__ : void 0) || DEFAULT_API_URL2;
1750
+ const [step, setStep] = useState(bookingTypeSlug ? "datetime" : "type");
1751
+ const [loading, setLoading] = useState(false);
1752
+ const [slotsLoading, setSlotsLoading] = useState(false);
1753
+ const [error, setError] = useState(null);
1754
+ const [bookingTypes, setBookingTypes] = useState([]);
1755
+ const [selectedType, setSelectedType] = useState(null);
1756
+ const [viewMonth, setViewMonth] = useState(() => /* @__PURE__ */ new Date());
1757
+ const [selectedDate, setSelectedDate] = useState(null);
1758
+ const [slots, setSlots] = useState([]);
1759
+ const [selectedSlot, setSelectedSlot] = useState(null);
1760
+ const [confirmedSlot, setConfirmedSlot] = useState(false);
1761
+ const [hold, setHold] = useState(null);
1762
+ const [guestInfo, setGuestInfo] = useState({ name: "", email: "" });
1763
+ const [bookingResult, setBookingResult] = useState(null);
1764
+ const [submitting, setSubmitting] = useState(false);
1765
+ const slotsRef = useRef(null);
1766
+ const today = useMemo(() => {
1767
+ const d = /* @__PURE__ */ new Date();
1768
+ d.setHours(0, 0, 0, 0);
1769
+ return d;
1770
+ }, []);
1771
+ const timezone = useMemo(() => propTimezone || detectTimezone(), [propTimezone]);
1772
+ const shortTz = useMemo(() => {
1773
+ try {
1774
+ const parts = Intl.DateTimeFormat("en-US", { timeZone: timezone, timeZoneName: "short" }).formatToParts(/* @__PURE__ */ new Date());
1775
+ return parts.find((p) => p.type === "timeZoneName")?.value || timezone;
1776
+ } catch {
1777
+ return timezone;
1778
+ }
1779
+ }, [timezone]);
1780
+ const days = useMemo(() => calendarDays(viewMonth.getFullYear(), viewMonth.getMonth()), [viewMonth]);
1781
+ const maxDate = useMemo(() => {
1782
+ const d = new Date(today);
1783
+ d.setDate(d.getDate() + daysToShow);
1784
+ return d;
1785
+ }, [today, daysToShow]);
1786
+ useEffect(() => {
1787
+ if (bookingTypeSlug) {
1788
+ setLoading(true);
1789
+ fetchBookingTypeDetails(bookingTypeSlug, orgSlug, apiUrl, apiKey).then((type) => {
1790
+ setSelectedType(type);
1791
+ setStep("datetime");
1792
+ }).catch((err) => {
1793
+ setError(err.message);
1794
+ onError?.(err);
1795
+ }).finally(() => setLoading(false));
1796
+ } else {
1797
+ setLoading(true);
1798
+ fetchBookingTypes(orgSlug, apiUrl, apiKey).then((types) => setBookingTypes(types.filter((t) => t.is_active))).catch((err) => {
1799
+ setError(err.message);
1800
+ onError?.(err);
1801
+ }).finally(() => setLoading(false));
1802
+ }
1803
+ }, [orgSlug, apiKey, bookingTypeSlug, apiUrl, onError]);
1804
+ useEffect(() => {
1805
+ if (!selectedDate || !selectedType) return;
1806
+ const dateStr = selectedDate.toISOString().split("T")[0];
1807
+ setSlotsLoading(true);
1808
+ setSlots([]);
1809
+ setSelectedSlot(null);
1810
+ setConfirmedSlot(false);
1811
+ fetchAvailability(selectedType.slug, dateStr, orgSlug, apiUrl, apiKey, timezone).then((s) => setSlots(s.filter((slot) => slot.available))).catch((err) => {
1812
+ setError(err.message);
1813
+ onError?.(err);
1814
+ }).finally(() => setSlotsLoading(false));
1815
+ }, [selectedDate, selectedType, orgSlug, apiKey, apiUrl, timezone, onError]);
1816
+ const handleSlotSelect = useCallback((slot) => {
1817
+ setSelectedSlot(slot);
1818
+ setConfirmedSlot(false);
1819
+ }, []);
1820
+ const handleSlotConfirm = useCallback(async () => {
1821
+ if (!selectedType || !selectedSlot) return;
1822
+ if (hold) await releaseSlotHold(hold.holdId, apiUrl, apiKey).catch(() => {
1823
+ });
1824
+ setLoading(true);
1825
+ try {
1826
+ const newHold = await createSlotHold(
1827
+ {
1828
+ bookingType: selectedType.slug,
1829
+ slotStart: selectedSlot.start,
1830
+ slotEnd: selectedSlot.end,
1831
+ hostId: selectedSlot.hostId || "",
1832
+ sessionId: `bw-${Date.now()}`
1833
+ },
1834
+ apiUrl,
1835
+ apiKey
1836
+ );
1837
+ setHold(newHold);
1838
+ setConfirmedSlot(true);
1839
+ setStep("form");
1840
+ } catch (err) {
1841
+ setError(err.message);
1842
+ onError?.(err);
1843
+ } finally {
1844
+ setLoading(false);
1845
+ }
1846
+ }, [selectedType, selectedSlot, hold, timezone, apiUrl, apiKey, onError]);
1847
+ const handleBookingSubmit = useCallback(async (e) => {
1848
+ e.preventDefault();
1849
+ if (!selectedType || !selectedSlot) return;
1850
+ setSubmitting(true);
1851
+ setError(null);
1852
+ try {
1853
+ const result = await createBooking(
1854
+ {
1855
+ bookingType: selectedType.slug,
1856
+ scheduledAt: selectedSlot.start,
1857
+ hostId: selectedSlot.hostId || "",
1858
+ name: guestInfo.name,
1859
+ email: guestInfo.email,
1860
+ phone: guestInfo.phone,
1861
+ message: guestInfo.notes,
1862
+ source: "main-site",
1863
+ timezone,
1864
+ holdId: hold?.holdId,
1865
+ sessionId: `bw-${Date.now()}`,
1866
+ sourceUrl: typeof window !== "undefined" ? window.location.href : void 0
1867
+ },
1868
+ apiUrl,
1869
+ apiKey
1870
+ );
1871
+ setBookingResult(result);
1872
+ setStep("success");
1873
+ onBookingComplete?.(result);
1874
+ } catch (err) {
1875
+ setError(err.message);
1876
+ onError?.(err);
1877
+ } finally {
1878
+ setSubmitting(false);
1879
+ }
1880
+ }, [selectedType, selectedSlot, guestInfo, timezone, hold, apiUrl, apiKey, onBookingComplete, onError]);
1881
+ useEffect(() => {
1882
+ return () => {
1883
+ if (hold) releaseSlotHold(hold.holdId, apiUrl, apiKey).catch(() => {
1884
+ });
1885
+ };
1886
+ }, [hold, apiUrl, apiKey]);
1887
+ const canGoPrev = viewMonth.getFullYear() > today.getFullYear() || viewMonth.getMonth() > today.getMonth();
1888
+ const canGoNext = viewMonth < maxDate;
1889
+ const goMonth = (dir) => {
1890
+ setViewMonth((prev) => new Date(prev.getFullYear(), prev.getMonth() + dir, 1));
1891
+ };
1892
+ const groupedSlots = useMemo(() => {
1893
+ const morning = [];
1894
+ const afternoon = [];
1895
+ const evening = [];
1896
+ for (const s of slots) {
1897
+ const h = new Date(s.start).getHours();
1898
+ if (h < 12) morning.push(s);
1899
+ else if (h < 17) afternoon.push(s);
1900
+ else evening.push(s);
1901
+ }
1902
+ return { morning, afternoon, evening };
1903
+ }, [slots]);
1904
+ const cssVars = {
1905
+ "--bw-primary": styles2.primaryColor || "#0069ff",
1906
+ "--bw-primary-light": styles2.primaryColor ? `color-mix(in srgb, ${styles2.primaryColor} 12%, white)` : "#e8f1ff",
1907
+ "--bw-primary-hover": styles2.primaryColor ? `color-mix(in srgb, ${styles2.primaryColor} 90%, black)` : "#0055d4",
1908
+ "--bw-radius": styles2.borderRadius || "8px",
1909
+ "--bw-font": styles2.fontFamily || "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
1910
+ };
1911
+ return /* @__PURE__ */ jsxs("div", { className: `bw-root ${className}`, style: cssVars, children: [
1912
+ error && /* @__PURE__ */ jsxs("div", { className: "bw-error", role: "alert", children: [
1913
+ /* @__PURE__ */ jsx("span", { children: error }),
1914
+ /* @__PURE__ */ jsx("button", { onClick: () => setError(null), "aria-label": "Dismiss error", children: "\xD7" })
1915
+ ] }),
1916
+ step === "type" && !hideTypeSelector && /* @__PURE__ */ jsxs("div", { className: "bw-step bw-fade-in", children: [
1917
+ /* @__PURE__ */ jsx("h2", { className: "bw-heading", children: "Select a Service" }),
1918
+ loading ? /* @__PURE__ */ jsxs("div", { className: "bw-loading", children: [
1919
+ /* @__PURE__ */ jsx(SpinnerIcon, {}),
1920
+ " Loading services..."
1921
+ ] }) : /* @__PURE__ */ jsx("div", { className: "bw-type-list", children: bookingTypes.map((type) => /* @__PURE__ */ jsxs(
1922
+ "button",
1923
+ {
1924
+ className: "bw-type-card",
1925
+ onClick: () => {
1926
+ setSelectedType(type);
1927
+ setStep("datetime");
1928
+ },
1929
+ children: [
1930
+ /* @__PURE__ */ jsx("span", { className: "bw-type-name", children: type.name }),
1931
+ type.description && /* @__PURE__ */ jsx("span", { className: "bw-type-desc", children: type.description }),
1932
+ /* @__PURE__ */ jsxs("span", { className: "bw-type-meta", children: [
1933
+ /* @__PURE__ */ jsx(ClockIcon, {}),
1934
+ " ",
1935
+ formatDuration(type.duration_minutes),
1936
+ type.price_cents ? ` \xB7 $${(type.price_cents / 100).toFixed(2)}` : " \xB7 Free"
1937
+ ] })
1938
+ ]
1939
+ },
1940
+ type.id
1941
+ )) })
1942
+ ] }),
1943
+ step === "datetime" && selectedType && /* @__PURE__ */ jsxs("div", { className: "bw-step bw-fade-in", children: [
1944
+ /* @__PURE__ */ jsxs("div", { className: "bw-info-header", children: [
1945
+ !bookingTypeSlug && /* @__PURE__ */ jsx("button", { className: "bw-back", onClick: () => {
1946
+ setSelectedType(null);
1947
+ setStep("type");
1948
+ }, children: /* @__PURE__ */ jsx(ArrowLeftIcon, {}) }),
1949
+ /* @__PURE__ */ jsxs("div", { children: [
1950
+ /* @__PURE__ */ jsx("h2", { className: "bw-heading", children: selectedType.name }),
1951
+ /* @__PURE__ */ jsxs("div", { className: "bw-meta-row", children: [
1952
+ /* @__PURE__ */ jsxs("span", { className: "bw-badge", children: [
1953
+ /* @__PURE__ */ jsx(ClockIcon, {}),
1954
+ " ",
1955
+ formatDuration(selectedType.duration_minutes)
1956
+ ] }),
1957
+ /* @__PURE__ */ jsxs("span", { className: "bw-badge", children: [
1958
+ /* @__PURE__ */ jsx(GlobeIcon, {}),
1959
+ " ",
1960
+ shortTz
1961
+ ] })
1962
+ ] })
1963
+ ] })
1964
+ ] }),
1965
+ /* @__PURE__ */ jsxs("div", { className: "bw-datetime-layout", children: [
1966
+ /* @__PURE__ */ jsxs("div", { className: "bw-calendar", children: [
1967
+ /* @__PURE__ */ jsxs("div", { className: "bw-cal-header", children: [
1968
+ /* @__PURE__ */ jsx("button", { className: "bw-cal-nav", onClick: () => goMonth(-1), disabled: !canGoPrev, "aria-label": "Previous month", children: /* @__PURE__ */ jsx(ChevronLeft, {}) }),
1969
+ /* @__PURE__ */ jsxs("span", { className: "bw-cal-title", children: [
1970
+ MONTHS[viewMonth.getMonth()],
1971
+ " ",
1972
+ viewMonth.getFullYear()
1973
+ ] }),
1974
+ /* @__PURE__ */ jsx("button", { className: "bw-cal-nav", onClick: () => goMonth(1), disabled: !canGoNext, "aria-label": "Next month", children: /* @__PURE__ */ jsx(ChevronRight, {}) })
1975
+ ] }),
1976
+ /* @__PURE__ */ jsx("div", { className: "bw-cal-weekdays", children: DAYS.map((d) => /* @__PURE__ */ jsx("span", { className: "bw-cal-wd", children: d }, d)) }),
1977
+ /* @__PURE__ */ jsx("div", { className: "bw-cal-grid", children: days.map((date, i) => {
1978
+ if (!date) return /* @__PURE__ */ jsx("span", { className: "bw-cal-empty" }, `e-${i}`);
1979
+ const past = isBeforeDay(date, today);
1980
+ const future = date > maxDate;
1981
+ const disabled = past || future;
1982
+ const sel = selectedDate && isSameDay(date, selectedDate);
1983
+ const isToday = isSameDay(date, today);
1984
+ return /* @__PURE__ */ jsx(
1985
+ "button",
1986
+ {
1987
+ className: `bw-cal-day${sel ? " selected" : ""}${isToday ? " today" : ""}${disabled ? " disabled" : ""}`,
1988
+ onClick: () => !disabled && setSelectedDate(date),
1989
+ disabled,
1990
+ "aria-label": date.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric" }),
1991
+ "aria-pressed": sel || void 0,
1992
+ children: date.getDate()
1993
+ },
1994
+ date.toISOString()
1995
+ );
1996
+ }) })
1997
+ ] }),
1998
+ /* @__PURE__ */ jsx("div", { className: `bw-times${selectedDate ? " visible" : ""}`, ref: slotsRef, children: !selectedDate ? /* @__PURE__ */ jsxs("div", { className: "bw-times-placeholder", children: [
1999
+ /* @__PURE__ */ jsx(CalendarPlusIcon, {}),
2000
+ /* @__PURE__ */ jsx("span", { children: "Select a date to view available times" })
2001
+ ] }) : slotsLoading ? /* @__PURE__ */ jsxs("div", { className: "bw-loading", children: [
2002
+ /* @__PURE__ */ jsx(SpinnerIcon, {}),
2003
+ " Loading times..."
2004
+ ] }) : slots.length === 0 ? /* @__PURE__ */ jsx("div", { className: "bw-times-empty", children: "No available times on this date. Try another day." }) : /* @__PURE__ */ jsxs("div", { className: "bw-times-scroll", children: [
2005
+ /* @__PURE__ */ jsx("p", { className: "bw-times-date", children: selectedDate.toLocaleDateString("en-US", { weekday: "long", month: "short", day: "numeric" }) }),
2006
+ groupedSlots.morning.length > 0 && /* @__PURE__ */ jsxs("div", { className: "bw-time-group", children: [
2007
+ /* @__PURE__ */ jsx("span", { className: "bw-time-label", children: "Morning" }),
2008
+ groupedSlots.morning.map((slot) => /* @__PURE__ */ jsx(TimeButton, { slot, selected: selectedSlot?.start === slot.start, confirmed: confirmedSlot && selectedSlot?.start === slot.start, onClick: () => handleSlotSelect(slot), onConfirm: handleSlotConfirm, timezone, loading }, slot.start))
2009
+ ] }),
2010
+ groupedSlots.afternoon.length > 0 && /* @__PURE__ */ jsxs("div", { className: "bw-time-group", children: [
2011
+ /* @__PURE__ */ jsx("span", { className: "bw-time-label", children: "Afternoon" }),
2012
+ groupedSlots.afternoon.map((slot) => /* @__PURE__ */ jsx(TimeButton, { slot, selected: selectedSlot?.start === slot.start, confirmed: confirmedSlot && selectedSlot?.start === slot.start, onClick: () => handleSlotSelect(slot), onConfirm: handleSlotConfirm, timezone, loading }, slot.start))
2013
+ ] }),
2014
+ groupedSlots.evening.length > 0 && /* @__PURE__ */ jsxs("div", { className: "bw-time-group", children: [
2015
+ /* @__PURE__ */ jsx("span", { className: "bw-time-label", children: "Evening" }),
2016
+ groupedSlots.evening.map((slot) => /* @__PURE__ */ jsx(TimeButton, { slot, selected: selectedSlot?.start === slot.start, confirmed: confirmedSlot && selectedSlot?.start === slot.start, onClick: () => handleSlotSelect(slot), onConfirm: handleSlotConfirm, timezone, loading }, slot.start))
2017
+ ] }),
2018
+ (groupedSlots.afternoon.length > 0 || groupedSlots.evening.length > 0) && /* @__PURE__ */ jsx("p", { className: "bw-times-scroll-hint", children: "Scroll for more times" })
2019
+ ] }) })
2020
+ ] })
2021
+ ] }),
2022
+ step === "form" && selectedType && selectedSlot && /* @__PURE__ */ jsxs("div", { className: "bw-step bw-fade-in", children: [
2023
+ /* @__PURE__ */ jsxs("div", { className: "bw-info-header", children: [
2024
+ /* @__PURE__ */ jsx("button", { className: "bw-back", onClick: () => setStep("datetime"), children: /* @__PURE__ */ jsx(ArrowLeftIcon, {}) }),
2025
+ /* @__PURE__ */ jsxs("div", { children: [
2026
+ /* @__PURE__ */ jsx("h2", { className: "bw-heading", children: "Your Details" }),
2027
+ /* @__PURE__ */ jsxs("div", { className: "bw-meta-row", children: [
2028
+ /* @__PURE__ */ jsxs("span", { className: "bw-badge", children: [
2029
+ /* @__PURE__ */ jsx(ClockIcon, {}),
2030
+ " ",
2031
+ formatDuration(selectedType.duration_minutes)
2032
+ ] }),
2033
+ /* @__PURE__ */ jsxs("span", { className: "bw-badge-accent", children: [
2034
+ formatDate2(selectedSlot.start, timezone),
2035
+ " \xB7 ",
2036
+ formatTime(selectedSlot.start, timezone)
2037
+ ] })
2038
+ ] })
2039
+ ] })
2040
+ ] }),
2041
+ hold && /* @__PURE__ */ jsxs("div", { className: "bw-hold-notice", children: [
2042
+ /* @__PURE__ */ jsx(SpinnerIcon, {}),
2043
+ " Slot held for you \u2014 complete your details to confirm."
2044
+ ] }),
2045
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleBookingSubmit, className: "bw-form", children: [
2046
+ /* @__PURE__ */ jsxs("div", { className: "bw-field", children: [
2047
+ /* @__PURE__ */ jsxs("label", { htmlFor: "bw-name", children: [
2048
+ "Name ",
2049
+ /* @__PURE__ */ jsx("span", { className: "bw-req", children: "*" })
2050
+ ] }),
2051
+ /* @__PURE__ */ jsx(
2052
+ "input",
2053
+ {
2054
+ id: "bw-name",
2055
+ type: "text",
2056
+ required: true,
2057
+ value: guestInfo.name,
2058
+ onChange: (e) => setGuestInfo((p) => ({ ...p, name: e.target.value })),
2059
+ placeholder: "Jane Smith",
2060
+ autoComplete: "name"
2061
+ }
2062
+ )
2063
+ ] }),
2064
+ /* @__PURE__ */ jsxs("div", { className: "bw-field", children: [
2065
+ /* @__PURE__ */ jsxs("label", { htmlFor: "bw-email", children: [
2066
+ "Email ",
2067
+ /* @__PURE__ */ jsx("span", { className: "bw-req", children: "*" })
2068
+ ] }),
2069
+ /* @__PURE__ */ jsx(
2070
+ "input",
2071
+ {
2072
+ id: "bw-email",
2073
+ type: "email",
2074
+ required: true,
2075
+ value: guestInfo.email,
2076
+ onChange: (e) => setGuestInfo((p) => ({ ...p, email: e.target.value })),
2077
+ placeholder: "jane@example.com",
2078
+ autoComplete: "email"
2079
+ }
2080
+ )
2081
+ ] }),
2082
+ /* @__PURE__ */ jsxs("div", { className: "bw-field", children: [
2083
+ /* @__PURE__ */ jsx("label", { htmlFor: "bw-phone", children: "Phone" }),
2084
+ /* @__PURE__ */ jsx(
2085
+ "input",
2086
+ {
2087
+ id: "bw-phone",
2088
+ type: "tel",
2089
+ value: guestInfo.phone || "",
2090
+ onChange: (e) => setGuestInfo((p) => ({ ...p, phone: e.target.value })),
2091
+ placeholder: "(555) 123-4567",
2092
+ autoComplete: "tel"
2093
+ }
2094
+ )
2095
+ ] }),
2096
+ /* @__PURE__ */ jsxs("div", { className: "bw-field", children: [
2097
+ /* @__PURE__ */ jsx("label", { htmlFor: "bw-notes", children: "Notes" }),
2098
+ /* @__PURE__ */ jsx(
2099
+ "textarea",
2100
+ {
2101
+ id: "bw-notes",
2102
+ value: guestInfo.notes || "",
2103
+ onChange: (e) => setGuestInfo((p) => ({ ...p, notes: e.target.value })),
2104
+ placeholder: "Anything you'd like us to know...",
2105
+ rows: 3
2106
+ }
2107
+ )
2108
+ ] }),
2109
+ /* @__PURE__ */ jsx("button", { type: "submit", className: "bw-submit", disabled: submitting || !guestInfo.name || !guestInfo.email, children: submitting ? /* @__PURE__ */ jsxs(Fragment, { children: [
2110
+ /* @__PURE__ */ jsx(SpinnerIcon, {}),
2111
+ " Confirming..."
2112
+ ] }) : "Confirm Booking" })
2113
+ ] })
2114
+ ] }),
2115
+ step === "success" && bookingResult && /* @__PURE__ */ jsxs("div", { className: "bw-step bw-fade-in bw-success", children: [
2116
+ /* @__PURE__ */ jsx("div", { className: "bw-success-icon", children: /* @__PURE__ */ jsx(CheckCircleIcon, {}) }),
2117
+ /* @__PURE__ */ jsx("h2", { className: "bw-heading", children: "You\u2019re Booked!" }),
2118
+ /* @__PURE__ */ jsxs("div", { className: "bw-details-card", children: [
2119
+ /* @__PURE__ */ jsxs("div", { className: "bw-detail-row", children: [
2120
+ /* @__PURE__ */ jsx("span", { className: "bw-detail-label", children: "When" }),
2121
+ /* @__PURE__ */ jsxs("span", { className: "bw-detail-value", children: [
2122
+ formatDate2(bookingResult.booking.scheduledAt, timezone),
2123
+ /* @__PURE__ */ jsx("br", {}),
2124
+ formatTime(bookingResult.booking.scheduledAt, timezone),
2125
+ " (",
2126
+ shortTz,
2127
+ ")"
2128
+ ] })
2129
+ ] }),
2130
+ /* @__PURE__ */ jsxs("div", { className: "bw-detail-row", children: [
2131
+ /* @__PURE__ */ jsx("span", { className: "bw-detail-label", children: "Duration" }),
2132
+ /* @__PURE__ */ jsx("span", { className: "bw-detail-value", children: formatDuration(bookingResult.booking.durationMinutes) })
2133
+ ] }),
2134
+ bookingResult.booking.hostName && /* @__PURE__ */ jsxs("div", { className: "bw-detail-row", children: [
2135
+ /* @__PURE__ */ jsx("span", { className: "bw-detail-label", children: "With" }),
2136
+ /* @__PURE__ */ jsx("span", { className: "bw-detail-value", children: bookingResult.booking.hostName })
2137
+ ] })
2138
+ ] }),
2139
+ /* @__PURE__ */ jsx("p", { className: "bw-cal-links-label", children: "Add to your calendar" }),
2140
+ /* @__PURE__ */ jsxs("div", { className: "bw-cal-links", children: [
2141
+ /* @__PURE__ */ jsxs("a", { href: bookingResult.calendarLinks.google, target: "_blank", rel: "noopener noreferrer", className: "bw-cal-link", children: [
2142
+ /* @__PURE__ */ jsx(CalendarPlusIcon, {}),
2143
+ " Google"
2144
+ ] }),
2145
+ /* @__PURE__ */ jsxs("a", { href: bookingResult.calendarLinks.outlook, target: "_blank", rel: "noopener noreferrer", className: "bw-cal-link", children: [
2146
+ /* @__PURE__ */ jsx(CalendarPlusIcon, {}),
2147
+ " Outlook"
2148
+ ] }),
2149
+ /* @__PURE__ */ jsxs("a", { href: bookingResult.calendarLinks.ics, download: true, className: "bw-cal-link", children: [
2150
+ /* @__PURE__ */ jsx(CalendarPlusIcon, {}),
2151
+ " iCal"
2152
+ ] })
2153
+ ] }),
2154
+ /* @__PURE__ */ jsxs("p", { className: "bw-email-notice", children: [
2155
+ "If your host uses Google Calendar, a calendar invite will be sent to ",
2156
+ /* @__PURE__ */ jsx("strong", { children: guestInfo.email }),
2157
+ "."
2158
+ ] })
2159
+ ] }),
2160
+ loading && step === "datetime" && !selectedType && /* @__PURE__ */ jsxs("div", { className: "bw-loading", children: [
2161
+ /* @__PURE__ */ jsx(SpinnerIcon, {}),
2162
+ " Loading..."
2163
+ ] }),
2164
+ /* @__PURE__ */ jsx("style", { children: WIDGET_CSS })
2165
+ ] });
2166
+ }
2167
+ function TimeButton({
2168
+ slot,
2169
+ selected,
2170
+ confirmed,
2171
+ onClick,
2172
+ onConfirm,
2173
+ timezone,
2174
+ loading
2175
+ }) {
2176
+ if (selected && !confirmed) {
2177
+ return /* @__PURE__ */ jsxs("div", { className: "bw-time-btn-wrap selected", children: [
2178
+ /* @__PURE__ */ jsx("span", { className: "bw-time-text", children: formatTime(slot.start, timezone) }),
2179
+ /* @__PURE__ */ jsx("button", { className: "bw-time-confirm", onClick: onConfirm, disabled: loading, children: loading ? /* @__PURE__ */ jsx(SpinnerIcon, {}) : "Confirm" })
2180
+ ] });
2181
+ }
2182
+ return /* @__PURE__ */ jsx("button", { className: `bw-time-btn${selected ? " selected" : ""}`, onClick, disabled: loading, children: formatTime(slot.start, timezone) });
2183
+ }
2184
+ var WIDGET_CSS = `
2185
+ /* \u2500\u2500 Base \u2500\u2500 */
2186
+ .bw-root {
2187
+ font-family: var(--bw-font);
2188
+ color: #1a1a1a;
2189
+ line-height: 1.5;
2190
+ -webkit-font-smoothing: antialiased;
2191
+ box-sizing: border-box;
2192
+ }
2193
+ .bw-root *, .bw-root *::before, .bw-root *::after { box-sizing: border-box; }
2194
+
2195
+ /* \u2500\u2500 Animation \u2500\u2500 */
2196
+ @keyframes bw-fade-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
2197
+ @keyframes bw-spin { to { transform: rotate(360deg); } }
2198
+ .bw-fade-in { animation: bw-fade-in 0.25s ease-out; }
2199
+ .bw-spinner { animation: bw-spin 0.8s linear infinite; }
2200
+
2201
+ /* \u2500\u2500 Error \u2500\u2500 */
2202
+ .bw-error {
2203
+ display: flex; align-items: center; justify-content: space-between; gap: 12px;
2204
+ background: #fef2f2; border: 1px solid #fecaca; color: #b91c1c;
2205
+ padding: 10px 14px; border-radius: var(--bw-radius); margin-bottom: 16px; font-size: 0.875rem;
2206
+ }
2207
+ .bw-error button { background: none; border: none; color: inherit; cursor: pointer; font-size: 1.25rem; line-height: 1; padding: 0; }
2208
+
2209
+ /* \u2500\u2500 Headings \u2500\u2500 */
2210
+ .bw-heading { font-size: 1.125rem; font-weight: 600; margin: 0; letter-spacing: -0.01em; }
2211
+ .bw-meta-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
2212
+ .bw-badge {
2213
+ display: inline-flex; align-items: center; gap: 4px;
2214
+ font-size: 0.8125rem; color: #6b7280; font-weight: 400;
2215
+ }
2216
+ .bw-badge-accent {
2217
+ display: inline-flex; align-items: center; gap: 4px;
2218
+ font-size: 0.8125rem; color: var(--bw-primary); font-weight: 500;
2219
+ }
2220
+
2221
+ /* \u2500\u2500 Info Header \u2500\u2500 */
2222
+ .bw-info-header { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 20px; }
2223
+ .bw-back {
2224
+ display: inline-flex; align-items: center; justify-content: center;
2225
+ width: 32px; height: 32px; border-radius: 50%; border: 1px solid #e5e7eb;
2226
+ background: #fff; cursor: pointer; color: #4b5563; flex-shrink: 0; margin-top: 1px;
2227
+ transition: all 0.15s;
2228
+ }
2229
+ .bw-back:hover { background: #f3f4f6; border-color: #d1d5db; }
2230
+
2231
+ /* \u2500\u2500 Loading \u2500\u2500 */
2232
+ .bw-loading {
2233
+ display: flex; align-items: center; justify-content: center; gap: 8px;
2234
+ padding: 32px; color: #6b7280; font-size: 0.875rem;
2235
+ }
2236
+
2237
+ /* \u2500\u2500 Type Selection \u2500\u2500 */
2238
+ .bw-type-list { display: flex; flex-direction: column; gap: 10px; }
2239
+ .bw-type-card {
2240
+ display: flex; flex-direction: column; text-align: left; gap: 4px;
2241
+ padding: 16px; border: 1.5px solid #e5e7eb; border-radius: var(--bw-radius);
2242
+ background: #fff; cursor: pointer; transition: all 0.15s;
2243
+ }
2244
+ .bw-type-card:hover { border-color: var(--bw-primary); box-shadow: 0 0 0 3px var(--bw-primary-light); }
2245
+ .bw-type-name { font-weight: 600; font-size: 0.9375rem; }
2246
+ .bw-type-desc { color: #6b7280; font-size: 0.8125rem; }
2247
+ .bw-type-meta { display: flex; align-items: center; gap: 4px; font-size: 0.8125rem; color: #9ca3af; margin-top: 4px; }
2248
+
2249
+ /* \u2500\u2500 Date-Time Layout \u2500\u2500 */
2250
+ .bw-datetime-layout {
2251
+ display: flex; gap: 0; border-top: 1px solid #f0f0f0; padding-top: 16px;
2252
+ }
2253
+ @media (max-width: 559px) {
2254
+ .bw-datetime-layout { flex-direction: column; }
2255
+ }
2256
+ @media (min-width: 560px) {
2257
+ .bw-datetime-layout { min-height: 400px; }
2258
+ .bw-calendar { flex: 1 1 auto; padding-right: 16px; border-right: 1px solid #f0f0f0; }
2259
+ .bw-times { width: 180px; flex-shrink: 0; padding-left: 16px; }
2260
+ }
2261
+
2262
+ /* \u2500\u2500 Calendar \u2500\u2500 */
2263
+ .bw-cal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
2264
+ .bw-cal-title { font-weight: 600; font-size: 0.9375rem; }
2265
+ .bw-cal-nav {
2266
+ display: inline-flex; align-items: center; justify-content: center;
2267
+ width: 32px; height: 32px; border-radius: 50%; border: none;
2268
+ background: transparent; cursor: pointer; color: #374151; transition: background 0.15s;
2269
+ }
2270
+ .bw-cal-nav:hover:not(:disabled) { background: #f3f4f6; }
2271
+ .bw-cal-nav:disabled { opacity: 0.25; cursor: default; }
2272
+
2273
+ .bw-cal-weekdays { display: grid; grid-template-columns: repeat(7, 1fr); gap: 0; text-align: center; margin-bottom: 4px; }
2274
+ .bw-cal-wd { font-size: 0.6875rem; font-weight: 600; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.04em; padding: 4px 0; }
2275
+
2276
+ .bw-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
2277
+ .bw-cal-empty { aspect-ratio: 1; }
2278
+ .bw-cal-day {
2279
+ aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
2280
+ border: none; background: none; border-radius: 50%;
2281
+ font-size: 0.8125rem; font-weight: 500; cursor: pointer;
2282
+ color: #1a1a1a; transition: all 0.15s; position: relative;
2283
+ }
2284
+ .bw-cal-day:hover:not(.disabled):not(.selected) { background: #f3f4f6; }
2285
+ .bw-cal-day.today:not(.selected)::after {
2286
+ content: ''; position: absolute; bottom: 3px; left: 50%; transform: translateX(-50%);
2287
+ width: 4px; height: 4px; border-radius: 50%; background: var(--bw-primary);
2288
+ }
2289
+ .bw-cal-day.selected {
2290
+ background: var(--bw-primary); color: #fff; font-weight: 600;
2291
+ }
2292
+ .bw-cal-day.disabled { color: #d1d5db; cursor: default; }
2293
+
2294
+ /* \u2500\u2500 Time Slots \u2500\u2500 */
2295
+ .bw-times { transition: opacity 0.2s; }
2296
+ .bw-times:not(.visible) { opacity: 0.5; }
2297
+ .bw-times.visible { opacity: 1; }
2298
+ .bw-times-placeholder {
2299
+ display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
2300
+ height: 100%; min-height: 160px; color: #9ca3af; font-size: 0.8125rem; text-align: center; padding: 16px;
2301
+ }
2302
+ .bw-times-empty {
2303
+ display: flex; align-items: center; justify-content: center;
2304
+ height: 100%; min-height: 160px; color: #9ca3af; font-size: 0.8125rem; text-align: center; padding: 16px;
2305
+ }
2306
+ .bw-times-scroll { overflow-y: auto; max-height: 400px; overflow-x: hidden; }
2307
+ .bw-times-date { font-weight: 600; font-size: 0.8125rem; color: #374151; margin: 0 0 12px 0; }
2308
+ .bw-time-group { margin-bottom: 16px; }
2309
+ .bw-times-scroll-hint {
2310
+ font-size: 0.6875rem; color: #9ca3af; text-align: center; margin: 8px 0 0 0; padding-bottom: 8px;
2311
+ }
2312
+ .bw-time-label { display: block; font-size: 0.6875rem; font-weight: 600; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 6px; }
2313
+
2314
+ .bw-time-btn {
2315
+ display: block; width: 100%;
2316
+ padding: 10px 12px; margin-bottom: 6px;
2317
+ border: 1.5px solid #e5e7eb; border-radius: var(--bw-radius);
2318
+ background: #fff; cursor: pointer;
2319
+ font-size: 0.875rem; font-weight: 500; color: var(--bw-primary); text-align: center;
2320
+ transition: all 0.15s;
2321
+ }
2322
+ .bw-time-btn:hover:not(:disabled) {
2323
+ border-color: var(--bw-primary); background: var(--bw-primary-light);
2324
+ }
2325
+ .bw-time-btn.selected {
2326
+ border-color: var(--bw-primary); background: var(--bw-primary); color: #fff;
2327
+ }
2328
+ .bw-time-btn:disabled { opacity: 0.5; cursor: not-allowed; }
2329
+
2330
+ .bw-time-btn-wrap {
2331
+ display: flex; align-items: center; gap: 6px; margin-bottom: 6px;
2332
+ border: 1.5px solid var(--bw-primary); border-radius: var(--bw-radius); overflow: hidden;
2333
+ animation: bw-fade-in 0.15s ease-out;
2334
+ }
2335
+ .bw-time-btn-wrap .bw-time-text {
2336
+ flex: 1; padding: 10px 12px; font-size: 0.875rem; font-weight: 500; color: #374151; text-align: center;
2337
+ background: var(--bw-primary-light);
2338
+ }
2339
+ .bw-time-confirm {
2340
+ display: inline-flex; align-items: center; justify-content: center; gap: 4px;
2341
+ padding: 10px 16px; border: none;
2342
+ background: var(--bw-primary); color: #fff;
2343
+ font-size: 0.8125rem; font-weight: 600; cursor: pointer;
2344
+ transition: background 0.15s;
2345
+ }
2346
+ .bw-time-confirm:hover:not(:disabled) { background: var(--bw-primary-hover); }
2347
+ .bw-time-confirm:disabled { opacity: 0.7; cursor: not-allowed; }
2348
+
2349
+ /* \u2500\u2500 Form \u2500\u2500 */
2350
+ .bw-hold-notice {
2351
+ display: flex; align-items: center; gap: 8px;
2352
+ background: var(--bw-primary-light); color: var(--bw-primary);
2353
+ padding: 10px 14px; border-radius: var(--bw-radius);
2354
+ font-size: 0.8125rem; font-weight: 500; margin-bottom: 20px;
2355
+ }
2356
+ .bw-form { display: flex; flex-direction: column; gap: 16px; }
2357
+ .bw-field label {
2358
+ display: block; font-size: 0.8125rem; font-weight: 500; color: #374151; margin-bottom: 4px;
2359
+ }
2360
+ .bw-req { color: #ef4444; }
2361
+ .bw-field input, .bw-field textarea {
2362
+ display: block; width: 100%;
2363
+ padding: 10px 12px; border: 1.5px solid #e5e7eb; border-radius: var(--bw-radius);
2364
+ font-size: 0.9375rem; font-family: inherit; color: #1a1a1a; background: #fff;
2365
+ transition: border-color 0.15s, box-shadow 0.15s;
2366
+ }
2367
+ .bw-field input::placeholder, .bw-field textarea::placeholder { color: #c0c5cc; }
2368
+ .bw-field input:focus, .bw-field textarea:focus {
2369
+ outline: none; border-color: var(--bw-primary);
2370
+ box-shadow: 0 0 0 3px var(--bw-primary-light);
2371
+ }
2372
+ .bw-submit {
2373
+ display: inline-flex; align-items: center; justify-content: center; gap: 8px;
2374
+ width: 100%; padding: 14px; margin-top: 4px;
2375
+ background: var(--bw-primary); color: #fff; border: none;
2376
+ border-radius: var(--bw-radius); font-size: 0.9375rem; font-weight: 600;
2377
+ cursor: pointer; transition: background 0.15s;
2378
+ }
2379
+ .bw-submit:hover:not(:disabled) { background: var(--bw-primary-hover); }
2380
+ .bw-submit:disabled { opacity: 0.55; cursor: not-allowed; }
2381
+
2382
+ /* \u2500\u2500 Success \u2500\u2500 */
2383
+ .bw-success { text-align: center; }
2384
+ .bw-success-icon { margin: 0 auto 16px; width: 56px; height: 56px; animation: bw-fade-in 0.4s ease-out; }
2385
+ .bw-conf-code {
2386
+ display: inline-block; font-family: 'SF Mono', 'Fira Code', monospace;
2387
+ background: #f3f4f6; padding: 6px 14px; border-radius: 6px;
2388
+ font-size: 0.8125rem; color: #6b7280; margin: 4px 0 20px; letter-spacing: 0.04em;
2389
+ }
2390
+
2391
+ .bw-details-card {
2392
+ text-align: left; background: #f9fafb; border: 1px solid #f0f0f0;
2393
+ border-radius: var(--bw-radius); padding: 16px; margin-bottom: 20px;
2394
+ }
2395
+ .bw-detail-row { display: flex; gap: 12px; padding: 8px 0; border-bottom: 1px solid #f0f0f0; }
2396
+ .bw-detail-row:last-child { border-bottom: none; }
2397
+ .bw-detail-label { width: 70px; flex-shrink: 0; font-size: 0.8125rem; color: #9ca3af; font-weight: 500; }
2398
+ .bw-detail-value { font-size: 0.875rem; font-weight: 500; color: #374151; }
2399
+
2400
+ .bw-cal-links-label { font-size: 0.8125rem; color: #6b7280; margin: 0 0 8px 0; }
2401
+ .bw-cal-links { display: flex; gap: 8px; justify-content: center; margin-bottom: 20px; flex-wrap: wrap; }
2402
+ .bw-cal-link {
2403
+ display: inline-flex; align-items: center; gap: 6px;
2404
+ padding: 8px 14px; border: 1.5px solid #e5e7eb; border-radius: var(--bw-radius);
2405
+ text-decoration: none; color: #374151; font-size: 0.8125rem; font-weight: 500;
2406
+ transition: all 0.15s;
2407
+ }
2408
+ .bw-cal-link:hover { border-color: var(--bw-primary); color: var(--bw-primary); }
2409
+
2410
+ .bw-email-notice { font-size: 0.8125rem; color: #9ca3af; margin: 0; }
2411
+ .bw-email-notice strong { color: #374151; font-weight: 500; }
2412
+ `;
2413
+
2414
+ export { AffiliateCard, AffiliatesWidget, BookingWidget, ExperimentConversion, SetupAssistant, SignalExperiment, SiteKitProvider, createBooking, createSlotHold, detectTimezone, fetchAffiliates, fetchAvailability, fetchAvailableDates, fetchBookingTypeDetails, fetchBookingTypes, formatDate2 as formatBookingDate, formatTime as formatBookingTime, formatDuration, getTrackingUrl, releaseSlotHold, useAffiliates, useExperimentVariant, useSiteKit };
2415
+ //# sourceMappingURL=index.mjs.map
2416
+ //# sourceMappingURL=index.mjs.map