@businessdash/sdk 0.9.60

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 (268) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +899 -0
  3. package/dist/adapters-revalidate.d.ts +44 -0
  4. package/dist/adapters-revalidate.d.ts.map +1 -0
  5. package/dist/adapters-revalidate.js +68 -0
  6. package/dist/adapters-revalidate.js.map +1 -0
  7. package/dist/alpha.d.ts +21 -0
  8. package/dist/alpha.d.ts.map +1 -0
  9. package/dist/alpha.js +21 -0
  10. package/dist/alpha.js.map +1 -0
  11. package/dist/analytics-core.d.ts +34 -0
  12. package/dist/analytics-core.d.ts.map +1 -0
  13. package/dist/analytics-core.js +138 -0
  14. package/dist/analytics-core.js.map +1 -0
  15. package/dist/analytics-server.d.ts +54 -0
  16. package/dist/analytics-server.d.ts.map +1 -0
  17. package/dist/analytics-server.js +78 -0
  18. package/dist/analytics-server.js.map +1 -0
  19. package/dist/attribution-logo.d.ts +2 -0
  20. package/dist/attribution-logo.d.ts.map +1 -0
  21. package/dist/attribution-logo.js +5 -0
  22. package/dist/attribution-logo.js.map +1 -0
  23. package/dist/auth-handler.d.ts +92 -0
  24. package/dist/auth-handler.d.ts.map +1 -0
  25. package/dist/auth-handler.js +277 -0
  26. package/dist/auth-handler.js.map +1 -0
  27. package/dist/biab-forms.css +484 -0
  28. package/dist/cli.d.ts +46 -0
  29. package/dist/cli.d.ts.map +1 -0
  30. package/dist/cli.js +1824 -0
  31. package/dist/cli.js.map +1 -0
  32. package/dist/client.d.ts +1681 -0
  33. package/dist/client.d.ts.map +1 -0
  34. package/dist/client.js +2845 -0
  35. package/dist/client.js.map +1 -0
  36. package/dist/collections.d.ts +13 -0
  37. package/dist/collections.d.ts.map +1 -0
  38. package/dist/collections.js +13 -0
  39. package/dist/collections.js.map +1 -0
  40. package/dist/contracts.d.ts +14609 -0
  41. package/dist/contracts.d.ts.map +1 -0
  42. package/dist/contracts.js +2428 -0
  43. package/dist/contracts.js.map +1 -0
  44. package/dist/data-model-schema.d.ts +962 -0
  45. package/dist/data-model-schema.d.ts.map +1 -0
  46. package/dist/data-model-schema.js +2295 -0
  47. package/dist/data-model-schema.js.map +1 -0
  48. package/dist/distribution.d.ts +38 -0
  49. package/dist/distribution.d.ts.map +1 -0
  50. package/dist/distribution.js +61 -0
  51. package/dist/distribution.js.map +1 -0
  52. package/dist/element.d.ts +121 -0
  53. package/dist/element.d.ts.map +1 -0
  54. package/dist/element.js +276 -0
  55. package/dist/element.js.map +1 -0
  56. package/dist/enums.d.ts +25 -0
  57. package/dist/enums.d.ts.map +1 -0
  58. package/dist/enums.js +38 -0
  59. package/dist/enums.js.map +1 -0
  60. package/dist/errors.d.ts +195 -0
  61. package/dist/errors.d.ts.map +1 -0
  62. package/dist/errors.js +188 -0
  63. package/dist/errors.js.map +1 -0
  64. package/dist/forms-core/autocomplete.d.ts +37 -0
  65. package/dist/forms-core/autocomplete.d.ts.map +1 -0
  66. package/dist/forms-core/autocomplete.js +82 -0
  67. package/dist/forms-core/autocomplete.js.map +1 -0
  68. package/dist/forms-core/business-function.d.ts +61 -0
  69. package/dist/forms-core/business-function.d.ts.map +1 -0
  70. package/dist/forms-core/business-function.js +149 -0
  71. package/dist/forms-core/business-function.js.map +1 -0
  72. package/dist/forms-core/calculation.d.ts +72 -0
  73. package/dist/forms-core/calculation.d.ts.map +1 -0
  74. package/dist/forms-core/calculation.js +103 -0
  75. package/dist/forms-core/calculation.js.map +1 -0
  76. package/dist/forms-core/controller.d.ts +249 -0
  77. package/dist/forms-core/controller.d.ts.map +1 -0
  78. package/dist/forms-core/controller.js +590 -0
  79. package/dist/forms-core/controller.js.map +1 -0
  80. package/dist/forms-core/elements.d.ts +44 -0
  81. package/dist/forms-core/elements.d.ts.map +1 -0
  82. package/dist/forms-core/elements.js +68 -0
  83. package/dist/forms-core/elements.js.map +1 -0
  84. package/dist/forms-core/email-domains.d.ts +10 -0
  85. package/dist/forms-core/email-domains.d.ts.map +1 -0
  86. package/dist/forms-core/email-domains.js +69 -0
  87. package/dist/forms-core/email-domains.js.map +1 -0
  88. package/dist/forms-core/file-rules.d.ts +51 -0
  89. package/dist/forms-core/file-rules.d.ts.map +1 -0
  90. package/dist/forms-core/file-rules.js +105 -0
  91. package/dist/forms-core/file-rules.js.map +1 -0
  92. package/dist/forms-core/formula.d.ts +114 -0
  93. package/dist/forms-core/formula.d.ts.map +1 -0
  94. package/dist/forms-core/formula.js +1170 -0
  95. package/dist/forms-core/formula.js.map +1 -0
  96. package/dist/forms-core/index.d.ts +24 -0
  97. package/dist/forms-core/index.d.ts.map +1 -0
  98. package/dist/forms-core/index.js +28 -0
  99. package/dist/forms-core/index.js.map +1 -0
  100. package/dist/forms-core/units.d.ts +49 -0
  101. package/dist/forms-core/units.d.ts.map +1 -0
  102. package/dist/forms-core/units.js +255 -0
  103. package/dist/forms-core/units.js.map +1 -0
  104. package/dist/forms-core/us-states.d.ts +14 -0
  105. package/dist/forms-core/us-states.d.ts.map +1 -0
  106. package/dist/forms-core/us-states.js +66 -0
  107. package/dist/forms-core/us-states.js.map +1 -0
  108. package/dist/forms-core/validation.d.ts +266 -0
  109. package/dist/forms-core/validation.d.ts.map +1 -0
  110. package/dist/forms-core/validation.js +850 -0
  111. package/dist/forms-core/validation.js.map +1 -0
  112. package/dist/forms.d.ts +19 -0
  113. package/dist/forms.d.ts.map +1 -0
  114. package/dist/forms.js +29 -0
  115. package/dist/forms.js.map +1 -0
  116. package/dist/gallery-types.d.ts +32 -0
  117. package/dist/gallery-types.d.ts.map +1 -0
  118. package/dist/gallery-types.js +2 -0
  119. package/dist/gallery-types.js.map +1 -0
  120. package/dist/graphql.d.ts +97 -0
  121. package/dist/graphql.d.ts.map +1 -0
  122. package/dist/graphql.js +212 -0
  123. package/dist/graphql.js.map +1 -0
  124. package/dist/index.cjs +19448 -0
  125. package/dist/index.d.cts +19 -0
  126. package/dist/index.d.mts +19 -0
  127. package/dist/index.d.ts +19 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +20 -0
  130. package/dist/index.js.map +1 -0
  131. package/dist/marketing-schema.d.ts +216 -0
  132. package/dist/marketing-schema.d.ts.map +1 -0
  133. package/dist/marketing-schema.js +200 -0
  134. package/dist/marketing-schema.js.map +1 -0
  135. package/dist/mcp.d.ts +42 -0
  136. package/dist/mcp.d.ts.map +1 -0
  137. package/dist/mcp.js +87 -0
  138. package/dist/mcp.js.map +1 -0
  139. package/dist/native.d.ts +45 -0
  140. package/dist/native.d.ts.map +1 -0
  141. package/dist/native.js +43 -0
  142. package/dist/native.js.map +1 -0
  143. package/dist/next-revalidate.d.ts +43 -0
  144. package/dist/next-revalidate.d.ts.map +1 -0
  145. package/dist/next-revalidate.js +84 -0
  146. package/dist/next-revalidate.js.map +1 -0
  147. package/dist/parallel-pages.d.ts +102 -0
  148. package/dist/parallel-pages.d.ts.map +1 -0
  149. package/dist/parallel-pages.js +108 -0
  150. package/dist/parallel-pages.js.map +1 -0
  151. package/dist/payment-icons.d.ts +66 -0
  152. package/dist/payment-icons.d.ts.map +1 -0
  153. package/dist/payment-icons.js +160 -0
  154. package/dist/payment-icons.js.map +1 -0
  155. package/dist/proxy.d.ts +67 -0
  156. package/dist/proxy.d.ts.map +1 -0
  157. package/dist/proxy.js +97 -0
  158. package/dist/proxy.js.map +1 -0
  159. package/dist/react-analytics.d.ts +39 -0
  160. package/dist/react-analytics.d.ts.map +1 -0
  161. package/dist/react-analytics.js +59 -0
  162. package/dist/react-analytics.js.map +1 -0
  163. package/dist/react-attribution.d.ts +99 -0
  164. package/dist/react-attribution.d.ts.map +1 -0
  165. package/dist/react-attribution.js +52 -0
  166. package/dist/react-attribution.js.map +1 -0
  167. package/dist/react-marketing.d.ts +181 -0
  168. package/dist/react-marketing.d.ts.map +1 -0
  169. package/dist/react-marketing.js +215 -0
  170. package/dist/react-marketing.js.map +1 -0
  171. package/dist/react.d.ts +727 -0
  172. package/dist/react.d.ts.map +1 -0
  173. package/dist/react.js +2418 -0
  174. package/dist/react.js.map +1 -0
  175. package/dist/reserved-paths.d.ts +24 -0
  176. package/dist/reserved-paths.d.ts.map +1 -0
  177. package/dist/reserved-paths.js +60 -0
  178. package/dist/reserved-paths.js.map +1 -0
  179. package/dist/revalidate-verify.d.ts +30 -0
  180. package/dist/revalidate-verify.d.ts.map +1 -0
  181. package/dist/revalidate-verify.js +74 -0
  182. package/dist/revalidate-verify.js.map +1 -0
  183. package/dist/sdk.d.ts +678 -0
  184. package/dist/sdk.d.ts.map +1 -0
  185. package/dist/sdk.js +801 -0
  186. package/dist/sdk.js.map +1 -0
  187. package/dist/seo.d.ts +218 -0
  188. package/dist/seo.d.ts.map +1 -0
  189. package/dist/seo.js +309 -0
  190. package/dist/seo.js.map +1 -0
  191. package/dist/socials.d.ts +47 -0
  192. package/dist/socials.d.ts.map +1 -0
  193. package/dist/socials.js +172 -0
  194. package/dist/socials.js.map +1 -0
  195. package/dist/static.d.ts +14 -0
  196. package/dist/static.d.ts.map +1 -0
  197. package/dist/static.js +14 -0
  198. package/dist/static.js.map +1 -0
  199. package/dist/url-pattern.d.ts +47 -0
  200. package/dist/url-pattern.d.ts.map +1 -0
  201. package/dist/url-pattern.js +124 -0
  202. package/dist/url-pattern.js.map +1 -0
  203. package/dist/vanilla.d.ts +77 -0
  204. package/dist/vanilla.d.ts.map +1 -0
  205. package/dist/vanilla.js +903 -0
  206. package/dist/vanilla.js.map +1 -0
  207. package/openapi.json +13779 -0
  208. package/package.json +204 -0
  209. package/src/adapters-revalidate.ts +88 -0
  210. package/src/alpha.ts +89 -0
  211. package/src/analytics-core.ts +175 -0
  212. package/src/analytics-server.ts +99 -0
  213. package/src/angular/biab-form.component.ts +1443 -0
  214. package/src/angular/index.ts +26 -0
  215. package/src/attribution-logo.ts +5 -0
  216. package/src/auth-handler.ts +352 -0
  217. package/src/cli.ts +2517 -0
  218. package/src/client.ts +3578 -0
  219. package/src/collections.ts +101 -0
  220. package/src/contracts.ts +3390 -0
  221. package/src/data-model-schema.ts +4042 -0
  222. package/src/distribution.ts +73 -0
  223. package/src/element.ts +321 -0
  224. package/src/enums.ts +65 -0
  225. package/src/errors.ts +338 -0
  226. package/src/forms-core/autocomplete.ts +97 -0
  227. package/src/forms-core/business-function.ts +209 -0
  228. package/src/forms-core/calculation.ts +167 -0
  229. package/src/forms-core/controller.ts +922 -0
  230. package/src/forms-core/elements.ts +86 -0
  231. package/src/forms-core/email-domains.ts +68 -0
  232. package/src/forms-core/file-rules.ts +123 -0
  233. package/src/forms-core/formula.ts +1339 -0
  234. package/src/forms-core/index.ts +122 -0
  235. package/src/forms-core/units.ts +308 -0
  236. package/src/forms-core/us-states.ts +68 -0
  237. package/src/forms-core/validation.ts +1037 -0
  238. package/src/forms.ts +63 -0
  239. package/src/gallery-types.ts +31 -0
  240. package/src/graphql.ts +295 -0
  241. package/src/index.ts +505 -0
  242. package/src/marketing-schema.ts +395 -0
  243. package/src/mcp.ts +108 -0
  244. package/src/native.ts +73 -0
  245. package/src/next-revalidate.ts +124 -0
  246. package/src/parallel-pages.ts +192 -0
  247. package/src/payment-icons.ts +205 -0
  248. package/src/proxy.ts +173 -0
  249. package/src/qwik.tsx +1234 -0
  250. package/src/react-analytics.tsx +76 -0
  251. package/src/react-attribution.tsx +192 -0
  252. package/src/react-marketing.tsx +338 -0
  253. package/src/react.tsx +5495 -0
  254. package/src/reserved-paths.ts +65 -0
  255. package/src/revalidate-verify.ts +98 -0
  256. package/src/sdk.ts +1671 -0
  257. package/src/seo.ts +488 -0
  258. package/src/socials.ts +230 -0
  259. package/src/solid.tsx +1466 -0
  260. package/src/static.ts +21 -0
  261. package/src/styles/biab-forms.css +484 -0
  262. package/src/svelte/BiabForm.svelte +1069 -0
  263. package/src/svelte/context.ts +57 -0
  264. package/src/svelte/index.ts +79 -0
  265. package/src/url-pattern.ts +153 -0
  266. package/src/vanilla.ts +1290 -0
  267. package/src/vue/BiabForm.vue +1538 -0
  268. package/src/vue/index.ts +119 -0
@@ -0,0 +1,76 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+
5
+ import {
6
+ type InitBiabAnalyticsOptions,
7
+ initBiabAnalytics,
8
+ } from "./analytics-core.js";
9
+
10
+ /**
11
+ * <BIABAnalytics /> — privacy-conscious site analytics for React
12
+ * SDK consumers. Thin wrapper over `initBiabAnalytics()`; non-React
13
+ * consumers can import that function directly from
14
+ * `@businessdash/sdk/analytics-core`.
15
+ *
16
+ * Drop into the root layout once:
17
+ *
18
+ * import { BIABAnalytics } from "@businessdash/sdk/react-analytics";
19
+ *
20
+ * <BIABAnalytics
21
+ * siteId={process.env.BIAB_SITE_ID!}
22
+ * baseUrl={process.env.BIAB_PACKAGE_API_BASE_URL!}
23
+ * apiKey={process.env.NEXT_PUBLIC_BIAB_PUBLIC_KEY!}
24
+ * />
25
+ *
26
+ * What it does + privacy posture: see analytics-core.ts.
27
+ */
28
+ export function BIABAnalytics(props: InitBiabAnalyticsOptions) {
29
+ // Destructure so the effect closes over the individual config values (its real
30
+ // dependencies) rather than the `props` object identity, which changes every render.
31
+ const { siteId, baseUrl, apiKey, respectDnt } = props;
32
+ useEffect(() => {
33
+ const tracker = initBiabAnalytics({
34
+ siteId,
35
+ baseUrl,
36
+ apiKey,
37
+ ...(respectDnt !== undefined ? { respectDnt } : {}),
38
+ });
39
+ return () => tracker.stop();
40
+ }, [siteId, baseUrl, apiKey, respectDnt]);
41
+ return null;
42
+ }
43
+
44
+ /** BusinessDash-era name for `<BIABAnalytics />` — same component; the
45
+ * original export stays for every existing consumer. */
46
+ export const BusinessDashAnalytics = BIABAnalytics;
47
+
48
+ /**
49
+ * Fire a custom event (e.g. "newsletter_signup_clicked") from
50
+ * anywhere in the consumer site. The first call boots a tracker
51
+ * if one isn't already running on this page; subsequent calls
52
+ * piggyback on the existing one.
53
+ */
54
+ export function trackBiabEvent(input: {
55
+ siteId: string;
56
+ baseUrl: string;
57
+ apiKey: string;
58
+ customEventName: string;
59
+ path?: string;
60
+ }): void {
61
+ if (typeof window === "undefined") return;
62
+ const tracker = initBiabAnalytics({
63
+ siteId: input.siteId,
64
+ baseUrl: input.baseUrl,
65
+ apiKey: input.apiKey,
66
+ });
67
+ tracker.track(
68
+ input.customEventName,
69
+ input.path ? { path: input.path } : undefined,
70
+ );
71
+ tracker.stop();
72
+ }
73
+
74
+ export type { InitBiabAnalyticsOptions } from "./analytics-core.js";
75
+ // Re-export the core for non-React consumers convenience.
76
+ export { initBiabAnalytics } from "./analytics-core.js";
@@ -0,0 +1,192 @@
1
+ import { BUSINESS_DASH_LOGO } from "./attribution-logo.js";
2
+
3
+ /**
4
+ * The "Powered by BusinessDash" attribution badge.
5
+ *
6
+ * Two exports:
7
+ * - {@link BusinessDashFooterBanner} — the presentational badge. Pure, server-
8
+ * renderable, no data fetching. Drop it in and pass `siteId`.
9
+ * - {@link BusinessDashFooter} — an async Server Component that reads the site's
10
+ * attribution policy first, so it renders the badge when required and lets a
11
+ * paid plan hide it. Prefer this in a Next.js Server Component.
12
+ *
13
+ * ── Why server-render it ──
14
+ *
15
+ * BusinessDash verifies the badge by fetching your live domain and looking for
16
+ * the backlink below. If the badge is server-rendered it's in the raw HTML the
17
+ * check sees; if it's client-only, the check would need a headless browser to
18
+ * find it. So rendering it server-side isn't about stopping tampering (you own
19
+ * your code) — it's about the badge being *visible to the verification*.
20
+ *
21
+ * The `?ref=<siteId>` on the link is that marker. Keep it.
22
+ */
23
+
24
+ type Variation = "block" | "inline" | "none";
25
+
26
+ export interface BusinessDashFooterBannerProps {
27
+ /**
28
+ * Your BusinessDash site id. Used to build the verifiable backlink
29
+ * (`biab.app/?ref=<siteId>`). Omitting it renders a plain link with no marker
30
+ * — fine visually, but a verification crawl won't credit it.
31
+ */
32
+ siteId?: string;
33
+ /** Company/brand name shown after the © symbol. */
34
+ companyName?: string;
35
+ /** Year shown after the © symbol. Defaults to the current year. */
36
+ year?: number;
37
+ /** Layout variation. Defaults to "block". */
38
+ variation?: Variation;
39
+ /**
40
+ * Override the link target. Defaults to `https://biab.app/?ref=<siteId>` (or
41
+ * `https://biab.app` without a siteId). Only override if you know you want to
42
+ * — a non-biab.app href won't verify.
43
+ */
44
+ href?: string;
45
+ /**
46
+ * Render nothing. Handy for a plan that's allowed to remove the badge — but
47
+ * note removal is only *permitted*, not enforced, by this prop. See
48
+ * {@link BusinessDashFooter}, which sets it from the live policy.
49
+ */
50
+ hidden?: boolean;
51
+ /** Extra classes applied to the outer wrapper. */
52
+ className?: string;
53
+ }
54
+
55
+ /**
56
+ * The default link: the verifiable backlink when we have a siteId. Exported for
57
+ * tests — the `?ref` marker is what a verification crawl matches on, so it's the
58
+ * one bit worth pinning.
59
+ */
60
+ export function resolveHref(siteId?: string, href?: string): string {
61
+ if (href) return href;
62
+ return siteId
63
+ ? `https://biab.app/?ref=${encodeURIComponent(siteId)}`
64
+ : "https://biab.app";
65
+ }
66
+
67
+ export function BusinessDashFooterBanner({
68
+ siteId,
69
+ companyName = "Your Company",
70
+ year = new Date().getFullYear(),
71
+ variation = "block",
72
+ href,
73
+ hidden = false,
74
+ className = "",
75
+ }: BusinessDashFooterBannerProps) {
76
+ if (hidden || variation === "none") return null;
77
+
78
+ const linkHref = resolveHref(siteId, href);
79
+
80
+ const content = (
81
+ <>
82
+ <span className="whitespace-nowrap text-zinc-500">
83
+ <span className="font-bold">&copy;</span> {year} {companyName}
84
+ </span>
85
+ <span className="text-zinc-600">&middot;</span>
86
+ <span className="flex items-center gap-1 whitespace-nowrap text-zinc-600">
87
+ Powered by
88
+ <a
89
+ aria-label="BusinessDash"
90
+ className="inline-flex cursor-pointer items-center opacity-90 transition-opacity hover:opacity-100"
91
+ href={linkHref}
92
+ rel="noopener"
93
+ target="_blank"
94
+ >
95
+ {/* A plain <img>, not next/image: this ships to any React app,
96
+ and the logo is an inline data URI with nothing to optimize. */}
97
+ <img
98
+ alt="BusinessDash"
99
+ className="h-[24px] w-auto"
100
+ src={BUSINESS_DASH_LOGO}
101
+ />
102
+ </a>
103
+ </span>
104
+ </>
105
+ );
106
+
107
+ if (variation === "inline") {
108
+ return (
109
+ <>
110
+ {/* Desktop: centered along the bottom of a relative footer */}
111
+ <div
112
+ className={`absolute bottom-0 left-0 hidden w-screen flex-wrap items-center justify-center gap-x-3 gap-y-1 pb-1 text-[13px] md:flex ${className}`}
113
+ >
114
+ {content}
115
+ </div>
116
+ {/* Small screens: static, centered */}
117
+ <div
118
+ className={`mx-auto flex w-fit flex-wrap items-center justify-center gap-x-3 gap-y-1 pb-1 text-[13px] md:hidden ${className}`}
119
+ >
120
+ {content}
121
+ </div>
122
+ </>
123
+ );
124
+ }
125
+
126
+ // block (default)
127
+ return (
128
+ <div
129
+ className={`mx-auto flex w-fit flex-wrap items-center justify-center gap-x-3 gap-y-1 text-[13px] ${className}`}
130
+ >
131
+ {content}
132
+ </div>
133
+ );
134
+ }
135
+
136
+ export default BusinessDashFooterBanner;
137
+
138
+ /** The slice of the site client this component needs — kept structural so it
139
+ * doesn't drag the whole client type in. */
140
+ export interface AttributionPolicyReader {
141
+ attribution: {
142
+ policy: () => Promise<{ siteId: string; required: boolean }>;
143
+ };
144
+ }
145
+
146
+ export interface BusinessDashFooterProps
147
+ extends Omit<BusinessDashFooterBannerProps, "siteId" | "hidden"> {
148
+ /** A configured site client, e.g. `biab.site(siteId)`. */
149
+ client: AttributionPolicyReader;
150
+ /**
151
+ * Ask to hide the badge. Honored ONLY when the plan permits removal
152
+ * (`policy.required === false`); on a plan that requires the badge this is
153
+ * ignored and the badge shows. Defaults to `false` (always show).
154
+ */
155
+ optOut?: boolean;
156
+ }
157
+
158
+ /**
159
+ * Policy-aware badge for a Next.js Server Component.
160
+ *
161
+ * Reads the live attribution policy, then renders {@link BusinessDashFooterBanner}
162
+ * with the right `siteId` (for the marker) and an auto-resolved `hidden`: a paid
163
+ * plan that passed `optOut` gets a blank; a plan that requires the badge always
164
+ * shows it, `optOut` or not.
165
+ *
166
+ * ```tsx
167
+ * // app/layout.tsx (Server Component)
168
+ * export default async function Layout({ children }) {
169
+ * return (
170
+ * <>
171
+ * {children}
172
+ * <BusinessDashFooter client={biab.site(SITE_ID)} companyName="Acme Co" />
173
+ * </>
174
+ * )
175
+ * }
176
+ * ```
177
+ */
178
+ export async function BusinessDashFooter({
179
+ client,
180
+ optOut = false,
181
+ ...bannerProps
182
+ }: BusinessDashFooterProps) {
183
+ const policy = await client.attribution.policy();
184
+ const hidden = optOut && !policy.required;
185
+ return (
186
+ <BusinessDashFooterBanner
187
+ {...bannerProps}
188
+ hidden={hidden}
189
+ siteId={policy.siteId}
190
+ />
191
+ );
192
+ }
@@ -0,0 +1,338 @@
1
+ /**
2
+ * React hooks for the schema-driven marketing-page flow.
3
+ *
4
+ * The base SDK file (`react.tsx`) is already past the workspace's
5
+ * "stop and ask before growing" file-size threshold, so these new hooks
6
+ * live alongside it in their own module. Same import target for
7
+ * consumers either way — both files are re-exported from `react.tsx`
8
+ * via the SDK's `./react` entrypoint.
9
+ *
10
+ * Hooks here intentionally bypass `useEffect`-managed sessions (the
11
+ * approach the older iframe surfaces use) — marketing data is plain
12
+ * JSON-over-fetch with process-wide caching on the client. That keeps
13
+ * the SSR story simple: the same `client.site(siteId).marketing.*`
14
+ * methods consumed in `getServerSideProps` / RSC code paths are what
15
+ * these hooks call client-side.
16
+ */
17
+
18
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
19
+
20
+ import { BiabDevClient } from "./client.js";
21
+ import type {
22
+ GetMarketingLocalesResponse,
23
+ GetMarketingPageBundleResponse,
24
+ GetPublishedMarketingSchemaResponse,
25
+ SiteMarketingSectionEntry,
26
+ } from "./contracts.js";
27
+
28
+ // ── Bundle hook ─────────────────────────────────────────────────────
29
+
30
+ type BundleState =
31
+ | { status: "idle"; bundle: null; error: null }
32
+ | { status: "loading"; bundle: null; error: null }
33
+ | { status: "ready"; bundle: GetMarketingPageBundleResponse; error: null }
34
+ | { status: "error"; bundle: null; error: Error };
35
+
36
+ /**
37
+ * Live-site bundle reader. One call returns:
38
+ * - `sections`: `Record<sectionKey, {ok:true,data,source} | {ok:false,error}>`
39
+ * — a malformed section degrades to empty in the consumer's renderer
40
+ * instead of crashing the page.
41
+ * - `seo`: a fully-shaped SEO object with brand tokens already expanded.
42
+ * - `availableLocales` for the language switcher.
43
+ *
44
+ * The hook reads from the SDK's process-wide cache when present
45
+ * (warmed by `prefetchPageBundle()` or an earlier render on the same
46
+ * `(siteId, pageKey, locale)` key) and only hits the network on cache
47
+ * miss. `refresh()` does a `skipCache` round-trip — useful after a
48
+ * dashboard save that the SDK didn't initiate.
49
+ */
50
+ export function useMarketingPageBundle(options: {
51
+ apiKey: string;
52
+ baseUrl: string;
53
+ siteId: string;
54
+ pageKey?: string;
55
+ locale?: string;
56
+ enabled?: boolean;
57
+ }): BundleState & { refresh: () => Promise<void> } {
58
+ const {
59
+ apiKey,
60
+ baseUrl,
61
+ siteId,
62
+ pageKey = "home",
63
+ locale = "en",
64
+ enabled = true,
65
+ } = options;
66
+ const client = useMemo(
67
+ () => new BiabDevClient({ apiKey, baseUrl }),
68
+ [apiKey, baseUrl],
69
+ );
70
+
71
+ const initial = client
72
+ .site(siteId)
73
+ .marketing.getPageBundleCached({ pageKey, locale });
74
+ const [state, setState] = useState<BundleState>(
75
+ initial
76
+ ? { status: "ready", bundle: initial, error: null }
77
+ : { status: "idle", bundle: null, error: null },
78
+ );
79
+
80
+ const fetchOnce = useCallback(
81
+ async (skipCache = false) => {
82
+ if (!enabled) return;
83
+ setState({ status: "loading", bundle: null, error: null });
84
+ try {
85
+ const response = await client
86
+ .site(siteId)
87
+ .marketing.getPageBundle({ pageKey, locale }, { skipCache });
88
+ setState({ status: "ready", bundle: response, error: null });
89
+ } catch (err) {
90
+ setState({
91
+ status: "error",
92
+ bundle: null,
93
+ error: err instanceof Error ? err : new Error("Bundle load failed"),
94
+ });
95
+ }
96
+ },
97
+ [client, siteId, pageKey, locale, enabled],
98
+ );
99
+
100
+ useEffect(() => {
101
+ if (!enabled) return;
102
+ const hit = client
103
+ .site(siteId)
104
+ .marketing.getPageBundleCached({ pageKey, locale });
105
+ if (hit) {
106
+ setState({ status: "ready", bundle: hit, error: null });
107
+ return;
108
+ }
109
+ void fetchOnce(false);
110
+ }, [client, siteId, pageKey, locale, enabled, fetchOnce]);
111
+
112
+ return { ...state, refresh: () => fetchOnce(true) };
113
+ }
114
+
115
+ // ── Single-section convenience hook ─────────────────────────────────
116
+
117
+ type SectionState<T> =
118
+ | { status: "idle" | "loading"; data: null; entry: null; error: null }
119
+ | {
120
+ status: "ready";
121
+ data: T | null;
122
+ entry: SiteMarketingSectionEntry | null;
123
+ error: null;
124
+ }
125
+ | { status: "error"; data: null; entry: null; error: Error };
126
+
127
+ /**
128
+ * Pull one section out of the bundle for `(pageKey, locale)`. Pass a
129
+ * `parse` function to validate the section data against the consumer's
130
+ * zod schema (this is what `defineSiteMarketingSchema(...).parseSection(...)`
131
+ * gives you).
132
+ *
133
+ * When the underlying section entry is `{ ok: false }`, `data` resolves
134
+ * to `null` so the consumer renderer can short-circuit to "render
135
+ * empty" without checking the entry shape.
136
+ */
137
+ export function useMarketingSection<T>(options: {
138
+ apiKey: string;
139
+ baseUrl: string;
140
+ siteId: string;
141
+ pageKey?: string;
142
+ locale?: string;
143
+ sectionKey: string;
144
+ parse?: (entry: SiteMarketingSectionEntry | null | undefined) => T | null;
145
+ enabled?: boolean;
146
+ }): SectionState<T> {
147
+ const { apiKey, baseUrl, siteId, sectionKey, parse } = options;
148
+
149
+ // Build the inner bundle-hook options conditionally so the optional
150
+ // keys aren't present when the consumer didn't pass them. tsc's
151
+ // `exactOptionalPropertyTypes` flag distinguishes `prop: undefined`
152
+ // from "prop absent" and the latter is what the bundle hook expects.
153
+ const bundleOptions: Parameters<typeof useMarketingPageBundle>[0] = {
154
+ apiKey,
155
+ baseUrl,
156
+ siteId,
157
+ };
158
+ if (options.pageKey !== undefined) bundleOptions.pageKey = options.pageKey;
159
+ if (options.locale !== undefined) bundleOptions.locale = options.locale;
160
+ if (options.enabled !== undefined) bundleOptions.enabled = options.enabled;
161
+
162
+ const bundle = useMarketingPageBundle(bundleOptions);
163
+
164
+ return useMemo<SectionState<T>>(() => {
165
+ if (bundle.status === "loading" || bundle.status === "idle") {
166
+ return { status: bundle.status, data: null, entry: null, error: null };
167
+ }
168
+ if (bundle.status === "error") {
169
+ return { status: "error", data: null, entry: null, error: bundle.error };
170
+ }
171
+ const entry = bundle.bundle.sections[sectionKey] ?? null;
172
+ const data: T | null = parse
173
+ ? parse(entry)
174
+ : entry?.ok
175
+ ? (entry.data as T)
176
+ : null;
177
+ return { status: "ready", data, entry, error: null };
178
+ }, [bundle, sectionKey, parse]);
179
+ }
180
+
181
+ // ── SEO sub-hook ────────────────────────────────────────────────────
182
+
183
+ export function useMarketingPageSeo(options: {
184
+ apiKey: string;
185
+ baseUrl: string;
186
+ siteId: string;
187
+ pageKey?: string;
188
+ locale?: string;
189
+ }) {
190
+ const bundle = useMarketingPageBundle(options);
191
+ return useMemo(() => {
192
+ if (bundle.status !== "ready") {
193
+ return { status: bundle.status, seo: null } as const;
194
+ }
195
+ return { status: "ready" as const, seo: bundle.bundle.seo };
196
+ }, [bundle]);
197
+ }
198
+
199
+ // ── Published schema (rarely changes) ────────────────────────────────
200
+
201
+ type SchemaState =
202
+ | { status: "idle" | "loading"; schema: null; error: null }
203
+ | {
204
+ status: "ready";
205
+ schema: GetPublishedMarketingSchemaResponse["schema"];
206
+ error: null;
207
+ }
208
+ | { status: "error"; schema: null; error: Error };
209
+
210
+ /**
211
+ * Fetch the consumer's *published* schema once per process (cache
212
+ * scoped to `siteId`). Most consumers don't need this in the browser —
213
+ * the schema lives in their own bundle via `defineSiteMarketingSchema`
214
+ * — but it's the right primitive for "preview-as-other-tenant"
215
+ * tooling or for editors that introspect their own published artifact.
216
+ */
217
+ export function usePublishedMarketingSchema(options: {
218
+ apiKey: string;
219
+ baseUrl: string;
220
+ siteId: string;
221
+ enabled?: boolean;
222
+ }): SchemaState & { refresh: () => Promise<void> } {
223
+ const { apiKey, baseUrl, siteId, enabled = true } = options;
224
+ const client = useMemo(
225
+ () => new BiabDevClient({ apiKey, baseUrl }),
226
+ [apiKey, baseUrl],
227
+ );
228
+ const [state, setState] = useState<SchemaState>({
229
+ status: "idle",
230
+ schema: null,
231
+ error: null,
232
+ });
233
+
234
+ const fetchOnce = useCallback(
235
+ async (skipCache = false) => {
236
+ if (!enabled) return;
237
+ setState({ status: "loading", schema: null, error: null });
238
+ try {
239
+ const response = await client
240
+ .site(siteId)
241
+ .marketing.getPublishedSchema({ skipCache });
242
+ setState({ status: "ready", schema: response.schema, error: null });
243
+ } catch (err) {
244
+ setState({
245
+ status: "error",
246
+ schema: null,
247
+ error: err instanceof Error ? err : new Error("Schema load failed"),
248
+ });
249
+ }
250
+ },
251
+ [client, siteId, enabled],
252
+ );
253
+
254
+ useEffect(() => {
255
+ void fetchOnce(false);
256
+ }, [fetchOnce]);
257
+
258
+ return { ...state, refresh: () => fetchOnce(true) };
259
+ }
260
+
261
+ // ── Locales + brand tokens (sites with i18n) ─────────────────────────
262
+
263
+ type LocalesState =
264
+ | { status: "idle" | "loading"; data: null; error: null }
265
+ | { status: "ready"; data: GetMarketingLocalesResponse; error: null }
266
+ | { status: "error"; data: null; error: Error };
267
+
268
+ export function useMarketingLocales(options: {
269
+ apiKey: string;
270
+ baseUrl: string;
271
+ siteId: string;
272
+ }): LocalesState {
273
+ const { apiKey, baseUrl, siteId } = options;
274
+ const client = useMemo(
275
+ () => new BiabDevClient({ apiKey, baseUrl }),
276
+ [apiKey, baseUrl],
277
+ );
278
+ const [state, setState] = useState<LocalesState>({
279
+ status: "idle",
280
+ data: null,
281
+ error: null,
282
+ });
283
+
284
+ // Avoid double-fires in strict mode by remembering the last
285
+ // `(siteId, apiKey, baseUrl)` we resolved against.
286
+ const inflight = useRef("");
287
+
288
+ useEffect(() => {
289
+ const fp = `${apiKey}|${baseUrl}|${siteId}`;
290
+ if (inflight.current === fp) return;
291
+ inflight.current = fp;
292
+ setState({ status: "loading", data: null, error: null });
293
+ client
294
+ .site(siteId)
295
+ .marketing.getLocales()
296
+ .then((data) => setState({ status: "ready", data, error: null }))
297
+ .catch((err) =>
298
+ setState({
299
+ status: "error",
300
+ data: null,
301
+ error: err instanceof Error ? err : new Error("Locales load failed"),
302
+ }),
303
+ );
304
+ }, [client, siteId, apiKey, baseUrl]);
305
+
306
+ return state;
307
+ }
308
+
309
+ // ── Bundle preloader ────────────────────────────────────────────────
310
+
311
+ /**
312
+ * Stable `prefetch({ pageKey, locale })` you can wire to
313
+ * `<Link onPointerEnter>` / `onFocus` so the next page is already
314
+ * loaded by the time the visitor clicks. Idempotent — repeat calls hit
315
+ * the cache.
316
+ */
317
+ export function useMarketingBundlePreloader(options: {
318
+ apiKey: string;
319
+ baseUrl: string;
320
+ siteId: string;
321
+ }): (input?: { pageKey?: string; locale?: string }) => Promise<void> {
322
+ const { apiKey, baseUrl, siteId } = options;
323
+ const client = useMemo(
324
+ () => new BiabDevClient({ apiKey, baseUrl }),
325
+ [apiKey, baseUrl],
326
+ );
327
+ return useCallback(
328
+ async (input?: { pageKey?: string; locale?: string }) => {
329
+ try {
330
+ await client.site(siteId).marketing.prefetchPageBundle(input);
331
+ } catch {
332
+ // preload failures are non-fatal — the actual page render will
333
+ // surface the error via the bundle hook.
334
+ }
335
+ },
336
+ [client, siteId],
337
+ );
338
+ }