@businessdash/sdk 0.9.60 → 0.9.70

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 (233) hide show
  1. package/README.md +1012 -186
  2. package/assets/biab-sw.js +92 -0
  3. package/dist/account-core/index.d.ts +126 -0
  4. package/dist/account-core/index.d.ts.map +1 -0
  5. package/dist/account-core/index.js +191 -0
  6. package/dist/account-core/index.js.map +1 -0
  7. package/dist/account.d.ts +11 -0
  8. package/dist/account.d.ts.map +1 -0
  9. package/dist/account.js +11 -0
  10. package/dist/account.js.map +1 -0
  11. package/dist/auth-handler.d.ts +1 -1
  12. package/dist/auth-links.d.ts +33 -0
  13. package/dist/auth-links.d.ts.map +1 -0
  14. package/dist/auth-links.js +64 -0
  15. package/dist/auth-links.js.map +1 -0
  16. package/dist/blog-core/index.d.ts +112 -0
  17. package/dist/blog-core/index.d.ts.map +1 -0
  18. package/dist/blog-core/index.js +330 -0
  19. package/dist/blog-core/index.js.map +1 -0
  20. package/dist/blog.d.ts +11 -0
  21. package/dist/blog.d.ts.map +1 -0
  22. package/dist/blog.js +11 -0
  23. package/dist/blog.js.map +1 -0
  24. package/dist/chat-core/index.d.ts +97 -0
  25. package/dist/chat-core/index.d.ts.map +1 -0
  26. package/dist/chat-core/index.js +237 -0
  27. package/dist/chat-core/index.js.map +1 -0
  28. package/dist/chat.d.ts +11 -0
  29. package/dist/chat.d.ts.map +1 -0
  30. package/dist/chat.js +11 -0
  31. package/dist/chat.js.map +1 -0
  32. package/dist/checkout-core/index.d.ts +65 -0
  33. package/dist/checkout-core/index.d.ts.map +1 -0
  34. package/dist/checkout-core/index.js +159 -0
  35. package/dist/checkout-core/index.js.map +1 -0
  36. package/dist/checkout.d.ts +10 -0
  37. package/dist/checkout.d.ts.map +1 -0
  38. package/dist/checkout.js +10 -0
  39. package/dist/checkout.js.map +1 -0
  40. package/dist/cli.js +133 -25
  41. package/dist/cli.js.map +1 -1
  42. package/dist/client.d.ts +567 -1
  43. package/dist/client.d.ts.map +1 -1
  44. package/dist/client.js +807 -1
  45. package/dist/client.js.map +1 -1
  46. package/dist/contracts.d.ts +3309 -761
  47. package/dist/contracts.d.ts.map +1 -1
  48. package/dist/contracts.js +541 -4
  49. package/dist/contracts.js.map +1 -1
  50. package/dist/data-model-schema.d.ts +1 -1
  51. package/dist/data-model-schema.d.ts.map +1 -1
  52. package/dist/element.d.ts +30 -0
  53. package/dist/element.d.ts.map +1 -1
  54. package/dist/element.js +56 -0
  55. package/dist/element.js.map +1 -1
  56. package/dist/embed-protocol.d.ts +86 -0
  57. package/dist/embed-protocol.d.ts.map +1 -0
  58. package/dist/embed-protocol.js +60 -0
  59. package/dist/embed-protocol.js.map +1 -0
  60. package/dist/framework-support.d.ts +122 -0
  61. package/dist/framework-support.d.ts.map +1 -0
  62. package/dist/framework-support.js +323 -0
  63. package/dist/framework-support.js.map +1 -0
  64. package/dist/index.cjs +1244 -5
  65. package/dist/legal-core/index.cjs +115 -0
  66. package/dist/legal-core/index.d.ts +160 -0
  67. package/dist/legal-core/index.d.ts.map +1 -0
  68. package/dist/legal-core/index.js +220 -0
  69. package/dist/legal-core/index.js.map +1 -0
  70. package/dist/marketing-core/index.d.ts +66 -0
  71. package/dist/marketing-core/index.d.ts.map +1 -0
  72. package/dist/marketing-core/index.js +154 -0
  73. package/dist/marketing-core/index.js.map +1 -0
  74. package/dist/marketing.d.ts +7 -0
  75. package/dist/marketing.d.ts.map +1 -0
  76. package/dist/marketing.js +7 -0
  77. package/dist/marketing.js.map +1 -0
  78. package/dist/mcp-adapters/index.d.ts +135 -0
  79. package/dist/mcp-adapters/index.d.ts.map +1 -0
  80. package/dist/mcp-adapters/index.js +187 -0
  81. package/dist/mcp-adapters/index.js.map +1 -0
  82. package/dist/mcp-adapters.d.ts +7 -0
  83. package/dist/mcp-adapters.d.ts.map +1 -0
  84. package/dist/mcp-adapters.js +7 -0
  85. package/dist/mcp-adapters.js.map +1 -0
  86. package/dist/model3d-core/index.cjs +95 -0
  87. package/dist/model3d-core/index.d.ts +167 -0
  88. package/dist/model3d-core/index.d.ts.map +1 -0
  89. package/dist/model3d-core/index.js +158 -0
  90. package/dist/model3d-core/index.js.map +1 -0
  91. package/dist/notifications-core/index.cjs +315 -0
  92. package/dist/notifications-core/index.d.ts +229 -0
  93. package/dist/notifications-core/index.d.ts.map +1 -0
  94. package/dist/notifications-core/index.js +445 -0
  95. package/dist/notifications-core/index.js.map +1 -0
  96. package/dist/portal-core/index.d.ts +451 -0
  97. package/dist/portal-core/index.d.ts.map +1 -0
  98. package/dist/portal-core/index.js +819 -0
  99. package/dist/portal-core/index.js.map +1 -0
  100. package/dist/portal.d.ts +7 -0
  101. package/dist/portal.d.ts.map +1 -0
  102. package/dist/portal.js +7 -0
  103. package/dist/portal.js.map +1 -0
  104. package/dist/push-core/index.cjs +171 -0
  105. package/dist/push-core/index.d.ts +149 -0
  106. package/dist/push-core/index.d.ts.map +1 -0
  107. package/dist/push-core/index.js +231 -0
  108. package/dist/push-core/index.js.map +1 -0
  109. package/dist/react-marketing.d.ts +1 -17
  110. package/dist/react-marketing.d.ts.map +1 -1
  111. package/dist/react-marketing.js +4 -0
  112. package/dist/react-marketing.js.map +1 -1
  113. package/dist/react.d.ts +294 -101
  114. package/dist/react.d.ts.map +1 -1
  115. package/dist/react.js +302 -246
  116. package/dist/react.js.map +1 -1
  117. package/dist/seed-core/index.d.ts +354 -0
  118. package/dist/seed-core/index.d.ts.map +1 -0
  119. package/dist/seed-core/index.js +420 -0
  120. package/dist/seed-core/index.js.map +1 -0
  121. package/dist/seed.d.ts +7 -0
  122. package/dist/seed.d.ts.map +1 -0
  123. package/dist/seed.js +7 -0
  124. package/dist/seed.js.map +1 -0
  125. package/dist/seo-core/blog.d.ts +94 -0
  126. package/dist/seo-core/blog.d.ts.map +1 -0
  127. package/dist/seo-core/blog.js +149 -0
  128. package/dist/seo-core/blog.js.map +1 -0
  129. package/dist/seo-core/commerce.d.ts +64 -0
  130. package/dist/seo-core/commerce.d.ts.map +1 -0
  131. package/dist/seo-core/commerce.js +87 -0
  132. package/dist/seo-core/commerce.js.map +1 -0
  133. package/dist/seo-core/index.cjs +751 -0
  134. package/dist/seo-core/index.d.ts +226 -0
  135. package/dist/seo-core/index.d.ts.map +1 -0
  136. package/dist/seo-core/index.js +455 -0
  137. package/dist/seo-core/index.js.map +1 -0
  138. package/dist/seo-core/local.d.ts +181 -0
  139. package/dist/seo-core/local.d.ts.map +1 -0
  140. package/dist/seo-core/local.js +193 -0
  141. package/dist/seo-core/local.js.map +1 -0
  142. package/dist/sitemap-core/index.cjs +167 -0
  143. package/dist/sitemap-core/index.d.ts +155 -0
  144. package/dist/sitemap-core/index.d.ts.map +1 -0
  145. package/dist/sitemap-core/index.js +195 -0
  146. package/dist/sitemap-core/index.js.map +1 -0
  147. package/dist/social-core/index.d.ts +74 -0
  148. package/dist/social-core/index.d.ts.map +1 -0
  149. package/dist/social-core/index.js +72 -0
  150. package/dist/social-core/index.js.map +1 -0
  151. package/dist/social.d.ts +7 -0
  152. package/dist/social.d.ts.map +1 -0
  153. package/dist/social.js +7 -0
  154. package/dist/social.js.map +1 -0
  155. package/dist/solid-compat.d.ts +204 -0
  156. package/dist/solid-compat.d.ts.map +1 -0
  157. package/dist/solid-compat.js +221 -0
  158. package/dist/solid-compat.js.map +1 -0
  159. package/dist/store-core/index.d.ts +141 -0
  160. package/dist/store-core/index.d.ts.map +1 -0
  161. package/dist/store-core/index.js +277 -0
  162. package/dist/store-core/index.js.map +1 -0
  163. package/dist/store.d.ts +12 -0
  164. package/dist/store.d.ts.map +1 -0
  165. package/dist/store.js +12 -0
  166. package/dist/store.js.map +1 -0
  167. package/dist/vanilla.d.ts +111 -0
  168. package/dist/vanilla.d.ts.map +1 -1
  169. package/dist/vanilla.js +206 -0
  170. package/dist/vanilla.js.map +1 -1
  171. package/dist/verify-core/index.d.ts +98 -0
  172. package/dist/verify-core/index.d.ts.map +1 -0
  173. package/dist/verify-core/index.js +186 -0
  174. package/dist/verify-core/index.js.map +1 -0
  175. package/dist/verify.d.ts +8 -0
  176. package/dist/verify.d.ts.map +1 -0
  177. package/dist/verify.js +8 -0
  178. package/dist/verify.js.map +1 -0
  179. package/openapi.json +4316 -1313
  180. package/package.json +121 -9
  181. package/src/account-core/index.ts +309 -0
  182. package/src/account.ts +11 -0
  183. package/src/angular/headless.ts +519 -0
  184. package/src/angular/index.ts +3 -0
  185. package/src/auth-links.ts +76 -0
  186. package/src/blog-core/index.ts +423 -0
  187. package/src/blog.ts +11 -0
  188. package/src/chat-core/index.ts +335 -0
  189. package/src/chat.ts +11 -0
  190. package/src/checkout-core/index.ts +225 -0
  191. package/src/checkout.ts +10 -0
  192. package/src/cli.ts +184 -28
  193. package/src/client.ts +998 -0
  194. package/src/contracts.ts +730 -11
  195. package/src/data-model-schema.ts +1 -1
  196. package/src/element.ts +102 -0
  197. package/src/embed-protocol.ts +100 -0
  198. package/src/framework-support.ts +440 -0
  199. package/src/legal-core/index.ts +276 -0
  200. package/src/marketing-core/index.ts +209 -0
  201. package/src/marketing.ts +7 -0
  202. package/src/mcp-adapters/index.ts +264 -0
  203. package/src/mcp-adapters.ts +7 -0
  204. package/src/model3d-core/index.ts +248 -0
  205. package/src/notifications-core/index.ts +650 -0
  206. package/src/portal-core/index.ts +1564 -0
  207. package/src/portal.ts +7 -0
  208. package/src/push-core/index.ts +335 -0
  209. package/src/qwik.tsx +602 -2
  210. package/src/react-marketing.tsx +5 -0
  211. package/src/react.tsx +434 -319
  212. package/src/seed-core/index.ts +735 -0
  213. package/src/seed.ts +7 -0
  214. package/src/seo-core/blog.ts +216 -0
  215. package/src/seo-core/commerce.ts +138 -0
  216. package/src/seo-core/index.ts +605 -0
  217. package/src/seo-core/local.ts +333 -0
  218. package/src/sitemap-core/index.ts +296 -0
  219. package/src/social-core/index.ts +103 -0
  220. package/src/social.ts +7 -0
  221. package/src/solid-compat.tsx +347 -0
  222. package/src/solid-surfaces.tsx +1311 -0
  223. package/src/solid.tsx +82 -60
  224. package/src/store-core/index.ts +426 -0
  225. package/src/store.ts +12 -0
  226. package/src/svelte/SocialLinks.svelte +84 -0
  227. package/src/svelte/headless.ts +435 -0
  228. package/src/svelte/index.ts +9 -0
  229. package/src/vanilla.ts +367 -0
  230. package/src/verify-core/index.ts +288 -0
  231. package/src/verify.ts +8 -0
  232. package/src/vue/headless.ts +527 -0
  233. package/src/vue/index.ts +3 -0
package/dist/react.js CHANGED
@@ -1,13 +1,46 @@
1
+ "use client";
1
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Every export in this file is browser-only. The components hold state and
5
+ * subscribe to stores; the four programmatic helpers (`signIn`, `signUp`,
6
+ * `signOut`, `requestPasswordReset`) each bail out on
7
+ * `typeof window === "undefined"` because they navigate. Nothing here is meant
8
+ * to execute during a server render.
9
+ *
10
+ * WHY THE DIRECTIVE IS HERE
11
+ *
12
+ * Without it, a React Server Components framework treats this module as server
13
+ * code by default, and importing `<BiabForm>` into a page throws "you're
14
+ * importing a component that needs useState". The only way out is for every
15
+ * consumer to hand-write a `"use client"` wrapper per component — which is
16
+ * exactly what the Next App Router starter had to do, sixteen times, for
17
+ * components the SDK could have marked itself.
18
+ *
19
+ * `react-analytics.tsx` already carried this. `react.tsx` — the entry point
20
+ * that actually holds the components — did not.
21
+ *
22
+ * Bundlers that don't implement the directive treat it as a no-op string
23
+ * expression, so this is inert everywhere except RSC.
24
+ */
2
25
  import { createContext, Fragment, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react";
26
+ import { createFollowersController, createUserController, } from "./account-core/index.js";
27
+ import { buildAuthHref, clearLocalFollow, DEFAULT_AUTH_BASE_PATH, followerStoreKey, readLocalFollow, writeLocalFollow, } from "./auth-links.js";
28
+ import { createBlogController } from "./blog-core/index.js";
29
+ import { createChatController } from "./chat-core/index.js";
30
+ import { createCheckoutController } from "./checkout-core/index.js";
3
31
  import { BiabDevClient } from "./client.js";
32
+ import { DEFAULT_EMBED_SANDBOX, DEFAULT_REFRESH_LEAD_MS, refreshDelayMs, subscribeToEmbedMessages, } from "./embed-protocol.js";
4
33
  import { buildAcceptAttr, concurrentRevealCount, createFormController, evalCondition, fetchIconNames, filterIconNames, flattenValueFields, formatCalcValue, iconUrl, isPendingFileValue, resolveFieldAutocomplete, shouldSearchOptions, validateFile, } from "./forms-core/index.js";
5
34
  import { baseUnitFor, convertToBase, formatMeasurement, isMeasurementCategory, unitsForCategory, } from "./forms-core/units.js";
6
35
  import { US_STATES } from "./forms-core/us-states.js";
36
+ import { createMarketingController } from "./marketing-core/index.js";
37
+ import { createPortalController } from "./portal-core/index.js";
7
38
  import { createBiabClient } from "./sdk.js";
8
- import { resolveSocialProfiles } from "./socials.js";
39
+ import { resolveSocialLinks, SOCIAL_FALLBACK_GLYPH, SOCIAL_LIST_STYLE, } from "./social-core/index.js";
40
+ import { createStoreController } from "./store-core/index.js";
41
+ import { createVerifyController, } from "./verify-core/index.js";
9
42
  function useEmbedSession(loader, options) {
10
- const { apiKey, baseUrl, refreshLeadMs = 30_000 } = options;
43
+ const { apiKey, baseUrl, refreshLeadMs = DEFAULT_REFRESH_LEAD_MS } = options;
11
44
  const client = useMemo(() => new BiabDevClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
12
45
  const [state, setState] = useState({ status: "idle" });
13
46
  // `loader` is recreated per surface; depending on it would refetch on every
@@ -32,10 +65,12 @@ function useEmbedSession(loader, options) {
32
65
  useEffect(() => {
33
66
  if (state.status !== "ready")
34
67
  return;
35
- const expiresAt = Date.parse(state.session.expiresAt);
36
- if (Number.isNaN(expiresAt))
68
+ // The schedule itself lives in `embed-protocol.ts` so both bindings
69
+ // re-mint at the same moment — an embed that expires in Solid but not in
70
+ // React would be a bug nobody thinks to look for.
71
+ const delay = refreshDelayMs(state.session.expiresAt, refreshLeadMs, Date.now());
72
+ if (delay === null)
37
73
  return;
38
- const delay = Math.max(1_000, expiresAt - Date.now() - refreshLeadMs);
39
74
  const timer = setTimeout(() => {
40
75
  void refresh();
41
76
  }, delay);
@@ -44,23 +79,9 @@ function useEmbedSession(loader, options) {
44
79
  return { client, state, refresh };
45
80
  }
46
81
  function useEmbedMessages(iframeRef, handler) {
47
- useEffect(() => {
48
- const onMessage = (e) => {
49
- if (!e.data || typeof e.data !== "object")
50
- return;
51
- if (e.data.kind !== "biab")
52
- return;
53
- if (iframeRef.current &&
54
- e.source !== null &&
55
- e.source !== iframeRef.current.contentWindow)
56
- return;
57
- const { kind, ...rest } = e.data;
58
- void kind;
59
- handler(rest);
60
- };
61
- window.addEventListener("message", onMessage);
62
- return () => window.removeEventListener("message", onMessage);
63
- }, [handler, iframeRef]);
82
+ // The listener, and the origin check inside it, live in
83
+ // `embed-protocol.ts` so the Solid binding enforces exactly the same rule.
84
+ useEffect(() => subscribeToEmbedMessages(() => iframeRef.current, handler), [handler, iframeRef]);
64
85
  }
65
86
  // Asserted-compat with the underlying DashboardSessionResponse so the public
66
87
  // `useDashboardSession` shape doesn't churn for existing callers.
@@ -102,7 +123,7 @@ export function Dashboard({ className, style, title = "Business In A Box Dashboa
102
123
  if (state.status !== "ready") {
103
124
  return (_jsx(_Fragment, { children: loadingFallback ?? (_jsx("div", { className: className, style: { padding: 16, ...style }, children: "Loading dashboard\u2026" })) }));
104
125
  }
105
- return (_jsx("iframe", { className: className, sandbox: "allow-scripts allow-forms allow-same-origin allow-popups", src: state.session.url, style: { ...defaultIframeStyle, ...style }, title: title }));
126
+ return (_jsx("iframe", { className: className, sandbox: DEFAULT_EMBED_SANDBOX, src: state.session.url, style: { ...defaultIframeStyle, ...style }, title: title }));
106
127
  }
107
128
  function EmbedIframe({ state, refresh, title, className, style, loadingFallback, errorFallback, onEvent, extraSandbox, }) {
108
129
  const iframeRef = useRef(null);
@@ -121,7 +142,9 @@ function EmbedIframe({ state, refresh, title, className, style, loadingFallback,
121
142
  if (state.status !== "ready") {
122
143
  return (_jsx(_Fragment, { children: loadingFallback ?? (_jsx("div", { className: className, style: { padding: 16, ...style }, children: "Loading\u2026" })) }));
123
144
  }
124
- const sandbox = `allow-scripts allow-forms allow-same-origin allow-popups${extraSandbox ? ` ${extraSandbox}` : ""}`;
145
+ const sandbox = extraSandbox
146
+ ? `${DEFAULT_EMBED_SANDBOX} ${extraSandbox}`
147
+ : DEFAULT_EMBED_SANDBOX;
125
148
  return (_jsx("iframe", { className: className, ref: iframeRef, sandbox: sandbox, src: state.session.url, style: { ...defaultIframeStyle, ...style }, title: title }));
126
149
  }
127
150
  export function Storefront({ apiKey, baseUrl, productId, className, style, title = "Storefront", loadingFallback, errorFallback, onEvent, onCartChange, refreshLeadMs, }) {
@@ -272,103 +295,72 @@ export function Chatbot({ apiKey, baseUrl, className, style, title = "Assistant"
272
295
  */
273
296
  export function useChatbot(options) {
274
297
  const { apiKey, baseUrl } = options;
275
- const client = useMemo(() => new BiabDevClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
276
- const [messages, setMessages] = useState([]);
277
- const [isPending, setIsPending] = useState(false);
278
- const [error, setError] = useState(null);
279
- const [latestUiActions, setLatestUiActions] = useState([]);
280
- const [availability, setAvailability] = useState(null);
281
- const send = useCallback(async (content) => {
282
- const trimmed = content.trim();
283
- if (!trimmed)
284
- return null;
285
- setError(null);
286
- setIsPending(true);
287
- const next = [
288
- ...messages,
289
- { role: "user", content: trimmed },
290
- ];
291
- setMessages(next);
292
- try {
293
- const input = { messages: next };
294
- const reply = await client.chatbot.chat(input);
295
- setMessages((prev) => [
296
- ...prev,
297
- { role: "assistant", content: reply.text },
298
- ]);
299
- setLatestUiActions(reply.uiActions ?? []);
300
- if (reply.availability)
301
- setAvailability(reply.availability);
302
- return reply;
303
- }
304
- catch (err) {
305
- const e = err instanceof Error ? err : new Error("Chat failed");
306
- setError(e);
307
- return null;
308
- }
309
- finally {
310
- setIsPending(false);
311
- }
312
- }, [client, messages]);
313
- const reset = useCallback(() => {
314
- setMessages([]);
315
- setError(null);
316
- setLatestUiActions([]);
317
- }, []);
318
- const getConfig = useCallback(async () => client.chatbot.getConfig(), [client]);
319
- const refreshAvailability = useCallback(async () => {
320
- try {
321
- const next = await client.chatbot.getAvailability();
322
- setAvailability(next);
323
- return next;
324
- }
325
- catch {
326
- return null;
327
- }
328
- }, [client]);
329
- const getForm = useCallback(async (slug) => {
330
- try {
331
- // Chat `show_form` flow → the chatbot path (Live + chatbotAccess).
332
- return await client.chatbot.getChatForm(slug);
333
- }
334
- catch {
335
- return null;
336
- }
337
- }, [client]);
338
- const submitForm = useCallback(async (slug, input) => {
339
- try {
340
- const priorChat = messages.map((m, i) => ({
341
- role: m.role,
342
- content: m.content,
343
- createdAt: i,
344
- }));
345
- // Chatbot submit carries the transcript → chatbot path.
346
- return await client.chatbot.submitChatForm(slug, {
347
- ...input,
348
- priorChat: input.priorChat ?? priorChat,
349
- });
350
- }
351
- catch (err) {
352
- setError(err instanceof Error ? err : new Error("Submit failed"));
353
- return null;
354
- }
355
- }, [client, messages]);
356
- const uploadFile = useCallback((args) => client.chatbot.uploadFile(args), [client]);
298
+ const controller = useMemo(() => createChatController({
299
+ client: new BiabDevClient({ apiKey, baseUrl }),
300
+ }), [apiKey, baseUrl]);
301
+ useEffect(() => () => controller.destroy(), [controller]);
302
+ const subscribe = useCallback((onStoreChange) => controller.subscribe(() => onStoreChange()), [controller]);
303
+ const getSnapshot = useCallback(() => controller.snapshot(), [controller]);
304
+ const snap = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
305
+ const uploadFile = useCallback((args) => new BiabDevClient({ apiKey, baseUrl }).chatbot.uploadFile(args), [apiKey, baseUrl]);
306
+ // The returned shape is unchanged from before this hook was moved onto
307
+ // `chat-core`, so existing consumers keep working — only the implementation
308
+ // moved, and it now backs every other framework's chat binding too.
357
309
  return {
358
- messages,
359
- send,
360
- reset,
361
- isPending,
362
- error,
363
- getConfig,
364
- latestUiActions,
365
- availability,
366
- refreshAvailability,
367
- getForm,
368
- submitForm,
310
+ messages: snap.messages,
311
+ send: controller.send,
312
+ reset: controller.reset,
313
+ isPending: snap.isPending,
314
+ error: snap.error,
315
+ getConfig: controller.loadConfig,
316
+ latestUiActions: snap.uiActions,
317
+ availability: snap.availability,
318
+ refreshAvailability: controller.refreshAvailability,
319
+ getForm: controller.getForm,
320
+ submitForm: controller.submitForm,
321
+ requestHuman: controller.requestHuman,
369
322
  uploadFile,
370
323
  };
371
324
  }
325
+ /**
326
+ * Headless storefront + cart for React — the native alternative to
327
+ * `<Storefront>`, which is a hosted page in an iframe. Everything lives in
328
+ * `createStoreController`; this only bridges it into React.
329
+ *
330
+ * ```tsx
331
+ * const store = useStorefront({ apiKey, baseUrl });
332
+ * return store.products.map((p) => <MyCard key={p.id} product={p} />);
333
+ * ```
334
+ */
335
+ export function useStorefront(options) {
336
+ const { apiKey, baseUrl, pageSize, categoryId } = options;
337
+ const controller = useMemo(() => createStoreController({
338
+ client: new BiabDevClient({ apiKey, baseUrl }),
339
+ ...(pageSize ? { pageSize } : {}),
340
+ ...(categoryId ? { categoryId } : {}),
341
+ }), [apiKey, baseUrl, pageSize, categoryId]);
342
+ useEffect(() => {
343
+ void controller.load();
344
+ return () => controller.destroy();
345
+ }, [controller]);
346
+ const subscribe = useCallback((onStoreChange) => controller.subscribe(() => onStoreChange()), [controller]);
347
+ const getSnapshot = useCallback(() => controller.snapshot(), [controller]);
348
+ const snap = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
349
+ return {
350
+ ...snap,
351
+ loadMore: controller.loadMore,
352
+ filterByCategory: controller.filterByCategory,
353
+ openProduct: controller.openProduct,
354
+ closeProduct: controller.closeProduct,
355
+ addToCart: controller.addToCart,
356
+ updateQuantity: controller.updateQuantity,
357
+ removeItem: controller.removeItem,
358
+ applyCoupon: controller.applyCoupon,
359
+ removeCoupon: controller.removeCoupon,
360
+ clearCart: controller.clearCart,
361
+ refreshCart: controller.refreshCart,
362
+ };
363
+ }
372
364
  const STATIC_FIELD_TYPES = new Set([
373
365
  "title",
374
366
  "subtitle",
@@ -646,7 +638,10 @@ export function ChatbotInline({ apiKey, baseUrl, welcomeMessage, title = "Assist
646
638
  let cancelled = false;
647
639
  void getConfig()
648
640
  .then((cfg) => {
649
- if (cancelled)
641
+ // `null` means the config couldn't be read — the core reports that
642
+ // rather than throwing, because a chat that can't fetch its upload
643
+ // settings should still let the visitor talk. Keep the defaults.
644
+ if (cancelled || !cfg)
650
645
  return;
651
646
  setFileUploadsEnabled(cfg.fileUploadsEnabled);
652
647
  setEmailCaptureMode(cfg.emailCaptureMode);
@@ -925,17 +920,8 @@ export function Blog({ apiKey, baseUrl, postSlug, className, style, title = "Blo
925
920
  const { state, refresh } = useEmbedSession((client) => client.blog.createSession(input), { apiKey, baseUrl, refreshLeadMs });
926
921
  return (_jsx(EmbedIframe, { className: className, errorFallback: errorFallback, loadingFallback: loadingFallback, onEvent: onEvent, refresh: refresh, state: state, style: style, title: title }));
927
922
  }
928
- const DEFAULT_AUTH_BASE_PATH = "/api/biab-auth";
929
- function buildAuthHref(subroute, props) {
930
- const base = (props.basePath ?? DEFAULT_AUTH_BASE_PATH).replace(/\/$/, "");
931
- const params = new URLSearchParams();
932
- if (props.returnTo)
933
- params.set("returnTo", props.returnTo);
934
- if (props.loginHint)
935
- params.set("loginHint", props.loginHint);
936
- const qs = params.toString();
937
- return `${base}/${subroute}${qs ? `?${qs}` : ""}`;
938
- }
923
+ // `buildAuthHref` and `DEFAULT_AUTH_BASE_PATH` come from `./auth-links.js` so
924
+ // every binding builds the same handler URLs from the same query contract.
939
925
  export function SignIn({ children, ...rest }) {
940
926
  return (_jsx("a", { className: rest.className, href: buildAuthHref("sign-in", rest), style: rest.style, children: children ?? "Sign in" }));
941
927
  }
@@ -1021,73 +1007,21 @@ export async function requestPasswordReset(input) {
1021
1007
  * the cookie + handler path are the source of truth.
1022
1008
  */
1023
1009
  export function useUser(options) {
1024
- const basePath = (options?.basePath ?? DEFAULT_AUTH_BASE_PATH).replace(/\/$/, "");
1025
- const [state, setState] = useState({
1026
- status: "loading",
1027
- user: null,
1028
- });
1010
+ const basePath = options?.basePath;
1011
+ const controller = useMemo(() => createUserController(basePath ? { basePath } : {}), [basePath]);
1029
1012
  useEffect(() => {
1030
- let cancelled = false;
1031
- fetch(`${basePath}/me`, { credentials: "include" })
1032
- .then((r) => (r.ok ? r.json() : null))
1033
- .then((body) => {
1034
- if (cancelled)
1035
- return;
1036
- if (body) {
1037
- setState({ status: "signed-in", user: body });
1038
- }
1039
- else {
1040
- setState({ status: "signed-out", user: null });
1041
- }
1042
- })
1043
- .catch(() => {
1044
- if (cancelled)
1045
- return;
1046
- setState({ status: "signed-out", user: null });
1047
- });
1048
- return () => {
1049
- cancelled = true;
1050
- };
1051
- }, [basePath]);
1052
- return state;
1053
- }
1054
- const FOLLOWER_STORE_PREFIX = "biab.followers.";
1055
- function readLocalFollow(key) {
1056
- if (typeof window === "undefined")
1057
- return null;
1058
- try {
1059
- const raw = window.localStorage.getItem(key);
1060
- if (!raw)
1061
- return null;
1062
- const parsed = JSON.parse(raw);
1063
- return typeof parsed?.email === "string"
1064
- ? { email: parsed.email, at: Number(parsed.at) || 0 }
1065
- : null;
1066
- }
1067
- catch {
1068
- return null;
1069
- }
1070
- }
1071
- function writeLocalFollow(key, value) {
1072
- if (typeof window === "undefined")
1073
- return;
1074
- try {
1075
- window.localStorage.setItem(key, JSON.stringify(value));
1076
- }
1077
- catch {
1078
- // storage disabled / quota — the local hint is best-effort.
1079
- }
1080
- }
1081
- function clearLocalFollow(key) {
1082
- if (typeof window === "undefined")
1083
- return;
1084
- try {
1085
- window.localStorage.removeItem(key);
1086
- }
1087
- catch {
1088
- // ignore
1089
- }
1013
+ controller.start();
1014
+ return () => controller.destroy();
1015
+ }, [controller]);
1016
+ const subscribe = useCallback((onStoreChange) => controller.subscribe(() => onStoreChange()), [controller]);
1017
+ const getSnapshot = useCallback(() => controller.snapshot(), [controller]);
1018
+ // The shape is unchanged for callers; the fetch moved to `account-core` so
1019
+ // every framework reads the session the same way.
1020
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1090
1021
  }
1022
+ // `LocalFollow`, `followerStoreKey`, `readLocalFollow`, `writeLocalFollow` and
1023
+ // `clearLocalFollow` come from `./auth-links.js` — one storage format, so a
1024
+ // visitor who subscribed through one binding is recognised by another.
1091
1025
  /**
1092
1026
  * Visitor-facing followers/subscribe client, bound to a publishable token so it
1093
1027
  * works browser-side with no backend.
@@ -1112,43 +1046,50 @@ function clearLocalFollow(key) {
1112
1046
  */
1113
1047
  export function useFollowers(options) {
1114
1048
  const { token, siteId, baseUrl } = options;
1115
- const client = useMemo(() => createBiabClient({
1116
- apiKey: token,
1049
+ const controller = useMemo(() => createFollowersController({
1050
+ client: createBiabClient({
1051
+ apiKey: token,
1052
+ siteId,
1053
+ baseUrl: baseUrl ?? DEFAULT_PACKAGE_BASE_URL,
1054
+ }),
1117
1055
  siteId,
1118
- baseUrl: baseUrl ?? DEFAULT_PACKAGE_BASE_URL,
1119
1056
  }), [token, siteId, baseUrl]);
1120
- const storeKey = `${FOLLOWER_STORE_PREFIX}${siteId}`;
1121
- const [local, setLocal] = useState(() => readLocalFollow(storeKey));
1122
- useEffect(() => {
1123
- setLocal(readLocalFollow(storeKey));
1124
- }, [storeKey]);
1125
- const join = useCallback(async (input) => {
1126
- const res = await client.followers.join(input);
1127
- const entry = { email: input.email, at: Date.now() };
1128
- writeLocalFollow(storeKey, entry);
1129
- setLocal(entry);
1130
- return res;
1131
- }, [client, storeKey]);
1132
- const leave = useCallback(async (email) => {
1133
- const res = await client.followers.leave({ email });
1134
- clearLocalFollow(storeKey);
1135
- setLocal(null);
1136
- return res;
1137
- }, [client, storeKey]);
1138
- const me = useCallback((email) => client.followers.me({ email }), [client]);
1139
- const edit = useCallback((email, patch) => client.followers.edit({ email, patch }), [client]);
1140
- return useMemo(() => ({
1141
- join,
1142
- leave,
1143
- me,
1144
- edit,
1145
- /** This browser already subscribed (instant, no network). Use to hide a
1146
- * newsletter form for anonymous visitors. */
1147
- subscribedLocally: local !== null,
1148
- /** Email this browser subscribed with — pass to `me()` for an
1149
- * authoritative re-check, or `null` if this browser hasn't subscribed. */
1150
- localEmail: local?.email ?? null,
1151
- }), [join, leave, me, edit, local]);
1057
+ useEffect(() => () => controller.destroy(), [controller]);
1058
+ const subscribe = useCallback((onStoreChange) => controller.subscribe(() => onStoreChange()), [controller]);
1059
+ const getSnapshot = useCallback(() => controller.snapshot(), [controller]);
1060
+ const snap = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1061
+ // Same shape as before the move onto `account-core`, including the
1062
+ // local-storage hint, so existing consumers are untouched.
1063
+ return {
1064
+ join: controller.join,
1065
+ leave: controller.leave,
1066
+ me: controller.me,
1067
+ edit: controller.edit,
1068
+ subscribedLocally: snap.subscribedLocally,
1069
+ localEmail: snap.localEmail,
1070
+ };
1071
+ }
1072
+ /**
1073
+ * Headless checkout for React. `start()` mints a Stripe session and `redirect()`
1074
+ * navigates — split apart so a native app or an SSR render can use the first
1075
+ * without the second.
1076
+ */
1077
+ export function useCheckout(options) {
1078
+ const { apiKey, baseUrl } = options;
1079
+ const controller = useMemo(() => createCheckoutController({
1080
+ client: new BiabDevClient({ apiKey, baseUrl }),
1081
+ }), [apiKey, baseUrl]);
1082
+ useEffect(() => () => controller.destroy(), [controller]);
1083
+ const subscribe = useCallback((onStoreChange) => controller.subscribe(() => onStoreChange()), [controller]);
1084
+ const getSnapshot = useCallback(() => controller.snapshot(), [controller]);
1085
+ const snap = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1086
+ return {
1087
+ ...snap,
1088
+ start: controller.start,
1089
+ redirect: controller.redirect,
1090
+ resolve: controller.resolve,
1091
+ reset: controller.reset,
1092
+ };
1152
1093
  }
1153
1094
  /**
1154
1095
  * Lazy-fetch a single marketing page by `pageKey`. The hook reads from the
@@ -2384,16 +2325,13 @@ export function BiabForm(props) {
2384
2325
  return (_jsxs("div", { className: "biab-coupon-notice", children: [_jsx("p", { className: "biab-coupon-notice__text", children: text }), a.coupon?.finePrint && (_jsx("p", { className: "biab-coupon-notice__fine", children: a.coupon.finePrint }))] }, `coupon-${i}`));
2385
2326
  }), consecutive ? (_jsxs("div", { className: "biab-nav", children: [_jsx("button", { className: "biab-btn biab-btn--back biab-btn--ghost", disabled: snap.isFirstStep, onClick: () => controller.back(), type: "button", children: "Back" }), !snap.isLastStep ? (_jsx("button", { className: "biab-btn biab-btn--next", disabled: !snap.canAdvance, onClick: () => controller.next(), type: "button", children: "Next" })) : (_jsx("button", { className: "biab-btn biab-btn--submit", disabled: snap.submitting || snap.uploading, onClick: () => void handleSubmit(), type: "button", children: snap.submitting ? "Sending…" : submitLabel }))] })) : (_jsx("div", { className: "biab-nav", children: _jsx("button", { className: "biab-btn biab-btn--submit", disabled: snap.submitting || snap.uploading, onClick: () => void handleSubmit(), type: "button", children: snap.submitting ? "Sending…" : submitLabel }) })), snap.result && !snap.result.ok && snap.errorList.length === 0 && (_jsx("div", { className: "biab-form__error", role: "alert", children: snap.result.message }))] }) }) }));
2386
2327
  }
2387
- function SocialGlyph({ iconSlug, color, size, className, label, }) {
2388
- if (iconSlug) {
2389
- const src = color
2390
- ? `https://cdn.simpleicons.org/${iconSlug}/${encodeURIComponent(color)}`
2391
- : `https://cdn.simpleicons.org/${iconSlug}`;
2328
+ function SocialGlyph({ iconUrl, size, className, label, }) {
2329
+ if (iconUrl) {
2392
2330
  return (
2393
2331
  // biome-ignore lint: remote brand icon from the simple-icons CDN
2394
- _jsx("img", { alt: label, className: className, height: size, loading: "lazy", src: src, width: size }));
2332
+ _jsx("img", { alt: label, className: className, height: size, loading: "lazy", src: iconUrl, width: size }));
2395
2333
  }
2396
- return (_jsxs("svg", { "aria-hidden": true, className: className, fill: "none", height: size, stroke: "currentColor", strokeWidth: 1.8, viewBox: "0 0 24 24", width: size, children: [_jsx("title", { children: label }), _jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18" })] }));
2334
+ return (_jsxs("svg", { "aria-hidden": true, className: className, fill: "none", height: size, stroke: "currentColor", strokeWidth: SOCIAL_FALLBACK_GLYPH.strokeWidth, viewBox: SOCIAL_FALLBACK_GLYPH.viewBox, width: size, children: [_jsx("title", { children: label }), _jsx("circle", { ...SOCIAL_FALLBACK_GLYPH.circle }), _jsx("path", { d: SOCIAL_FALLBACK_GLYPH.path })] }));
2397
2335
  }
2398
2336
  /**
2399
2337
  * Render every social profile on the company as an icon + link, in canonical
@@ -2402,17 +2340,135 @@ function SocialGlyph({ iconSlug, color, size, className, label, }) {
2402
2340
  * globe. Returns `null` when no socials are set.
2403
2341
  */
2404
2342
  export function SocialLinks({ socials, color, size = 20, className, linkClassName, iconClassName, }) {
2405
- const rows = resolveSocialProfiles(socials);
2343
+ // Rows, URLs and icon URLs come from `social-core`, shared with every other
2344
+ // binding — only the markup below is React's.
2345
+ const rows = resolveSocialLinks(socials, { color });
2406
2346
  if (rows.length === 0)
2407
2347
  return null;
2408
- return (_jsx("ul", { className: className, style: {
2409
- display: "flex",
2410
- flexWrap: "wrap",
2411
- alignItems: "center",
2412
- gap: "0.75rem",
2413
- listStyle: "none",
2414
- margin: 0,
2415
- padding: 0,
2416
- }, children: rows.map((row) => (_jsx("li", { children: _jsx("a", { "aria-label": row.label, className: linkClassName, href: row.url, rel: "noopener noreferrer", target: "_blank", title: row.label, children: _jsx(SocialGlyph, { className: iconClassName, color: color, iconSlug: row.iconSlug, label: row.label, size: size }) }) }, row.key))) }));
2348
+ return (_jsx("ul", { className: className, style: SOCIAL_LIST_STYLE, children: rows.map((row) => (_jsx("li", { children: _jsx("a", { "aria-label": row.label, className: linkClassName, href: row.url, rel: "noopener noreferrer", target: "_blank", title: row.label, children: _jsx(SocialGlyph, { className: iconClassName, iconUrl: row.iconUrl, label: row.label, size: size }) }) }, row.key))) }));
2349
+ }
2350
+ /**
2351
+ * Headless blog for React — the native alternative to `<Blog>`, which is a
2352
+ * hosted page in an iframe. Posts, taxonomy, a single post, comments and likes
2353
+ * as data; the layout and the URLs are yours.
2354
+ *
2355
+ * ```tsx
2356
+ * const blog = useBlog({ apiKey, baseUrl });
2357
+ * return blog.posts.map((p) => <MyCard key={p.slug} post={p} />);
2358
+ * ```
2359
+ */
2360
+ export function useBlog(options) {
2361
+ const { apiKey, baseUrl, pageSize, categoryId } = options;
2362
+ const controller = useMemo(() => createBlogController({
2363
+ client: new BiabDevClient({ apiKey, baseUrl }),
2364
+ ...(pageSize ? { pageSize } : {}),
2365
+ ...(categoryId ? { categoryId } : {}),
2366
+ }), [apiKey, baseUrl, pageSize, categoryId]);
2367
+ useEffect(() => {
2368
+ void controller.load();
2369
+ return () => controller.destroy();
2370
+ }, [controller]);
2371
+ const subscribe = useCallback((onStoreChange) => controller.subscribe(() => onStoreChange()), [controller]);
2372
+ const getSnapshot = useCallback(() => controller.snapshot(), [controller]);
2373
+ const snap = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
2374
+ return {
2375
+ ...snap,
2376
+ loadMore: controller.loadMore,
2377
+ filterByCategory: controller.filterByCategory,
2378
+ openPost: controller.openPost,
2379
+ closePost: controller.closePost,
2380
+ loadComments: controller.loadComments,
2381
+ loadMoreComments: controller.loadMoreComments,
2382
+ postComment: controller.postComment,
2383
+ toggleLike: controller.toggleLike,
2384
+ };
2385
+ }
2386
+ /**
2387
+ * Headless email / phone verification for React.
2388
+ *
2389
+ * Read `flow` before rendering: `otp` wants a code field, `link` wants a
2390
+ * "check your email" message and no field at all.
2391
+ */
2392
+ export function useVerify(options) {
2393
+ const { apiKey, baseUrl, kind, resendCooldownSeconds } = options;
2394
+ const controller = useMemo(() => createVerifyController({
2395
+ client: new BiabDevClient({ apiKey, baseUrl }),
2396
+ kind,
2397
+ ...(resendCooldownSeconds ? { resendCooldownSeconds } : {}),
2398
+ }), [apiKey, baseUrl, kind, resendCooldownSeconds]);
2399
+ useEffect(() => () => controller.destroy(), [controller]);
2400
+ const subscribe = useCallback((onStoreChange) => controller.subscribe(() => onStoreChange()), [controller]);
2401
+ const getSnapshot = useCallback(() => controller.snapshot(), [controller]);
2402
+ const snap = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
2403
+ return {
2404
+ ...snap,
2405
+ start: controller.start,
2406
+ resend: controller.resend,
2407
+ confirm: controller.confirm,
2408
+ canResend: controller.canResend,
2409
+ reset: controller.reset,
2410
+ };
2411
+ }
2412
+ /**
2413
+ * The signed-in customer's portal for React — work, quotes, contracts,
2414
+ * invoices, orders, downloads, shipping, reviews, referrals, profile and
2415
+ * notification settings. The native alternative to `<Dashboard>`, which is a
2416
+ * hosted page in an iframe.
2417
+ *
2418
+ * `sessionToken` is required: every portal route is scoped to the signed-in
2419
+ * customer, and without one the portal renders empty rather than signed-out.
2420
+ *
2421
+ * Two things the API imposes and this hook does not hide. There are no list
2422
+ * endpoints for invoices, quotes, contracts or shipments — `work` carries the
2423
+ * counts and you open each by id. And a submitted review is queued for staff
2424
+ * approval, so it will NOT appear in `reviews`; watch `reviewAwaitingModeration`
2425
+ * to tell the customer it is pending rather than live.
2426
+ */
2427
+ export function usePortal(options) {
2428
+ const { apiKey, baseUrl, sessionToken } = options;
2429
+ const controller = useMemo(() => createPortalController({
2430
+ client: new BiabDevClient({ apiKey, baseUrl }),
2431
+ sessionToken,
2432
+ }), [apiKey, baseUrl, sessionToken]);
2433
+ useEffect(() => {
2434
+ void controller.load();
2435
+ return () => controller.destroy();
2436
+ }, [controller]);
2437
+ const subscribe = useCallback((onStoreChange) => controller.subscribe(() => onStoreChange()), [controller]);
2438
+ const getSnapshot = useCallback(() => controller.snapshot(), [controller]);
2439
+ const snap = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
2440
+ return {
2441
+ ...snap,
2442
+ loadOrders: controller.loadOrders,
2443
+ loadInvoices: controller.loadInvoices,
2444
+ loadQuotes: controller.loadQuotes,
2445
+ loadContracts: controller.loadContracts,
2446
+ loadShipments: controller.loadShipments,
2447
+ loadMessages: controller.loadMessages,
2448
+ postMessage: controller.postMessage,
2449
+ markMessagesRead: controller.markMessagesRead,
2450
+ loadPayments: controller.loadPayments,
2451
+ loadReviews: controller.loadReviews,
2452
+ loadReferrals: controller.loadReferrals,
2453
+ loadNotificationPreferences: controller.loadNotificationPreferences,
2454
+ loadOtherOrgs: controller.loadOtherOrgs,
2455
+ openJob: controller.openJob,
2456
+ openOrder: controller.openOrder,
2457
+ openInvoice: controller.openInvoice,
2458
+ openQuote: controller.openQuote,
2459
+ openContract: controller.openContract,
2460
+ trackShipment: controller.trackShipment,
2461
+ closeDetail: controller.closeDetail,
2462
+ postJobComment: controller.postJobComment,
2463
+ acceptQuote: controller.acceptQuote,
2464
+ rejectQuote: controller.rejectQuote,
2465
+ signContract: controller.signContract,
2466
+ payInvoice: controller.payInvoice,
2467
+ updateProfile: controller.updateProfile,
2468
+ updateNotificationPreferences: controller.updateNotificationPreferences,
2469
+ submitReview: controller.submitReview,
2470
+ submitReturnRequest: controller.submitReturnRequest,
2471
+ contactCompany: controller.contactCompany,
2472
+ };
2417
2473
  }
2418
2474
  //# sourceMappingURL=react.js.map