@businessdash/sdk 0.9.80 → 0.9.90
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.
- package/README.md +133 -949
- package/assets/{biab-sw.js → bd-sw.js} +2 -2
- package/dist/account-core/index.d.ts +1 -1
- package/dist/account-core/index.d.ts.map +1 -1
- package/dist/adapters-revalidate.d.ts +2 -2
- package/dist/adapters-revalidate.js +2 -2
- package/dist/adapters-revalidate.js.map +1 -1
- package/dist/analytics-core.d.ts +6 -6
- package/dist/analytics-core.d.ts.map +1 -1
- package/dist/analytics-core.js +4 -4
- package/dist/analytics-core.js.map +1 -1
- package/dist/analytics-server.d.ts +6 -6
- package/dist/analytics-server.d.ts.map +1 -1
- package/dist/analytics-server.js +5 -5
- package/dist/auth-handler.d.ts +14 -14
- package/dist/auth-handler.d.ts.map +1 -1
- package/dist/auth-handler.js +16 -16
- package/dist/auth-handler.js.map +1 -1
- package/dist/auth-links.d.ts +2 -2
- package/dist/auth-links.d.ts.map +1 -1
- package/dist/auth-links.js +2 -2
- package/dist/auth-links.js.map +1 -1
- package/dist/{biab-forms.css → bd-forms.css} +158 -158
- package/dist/cli.d.ts +4 -4
- package/dist/cli.js +78 -78
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +158 -147
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +156 -137
- package/dist/client.js.map +1 -1
- package/dist/consent-core/index.cjs +2 -2
- package/dist/consent-core/index.d.ts +2 -2
- package/dist/consent-core/index.d.ts.map +1 -1
- package/dist/consent-core/index.js +10 -8
- package/dist/consent-core/index.js.map +1 -1
- package/dist/contracts.d.ts +411 -52
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +89 -15
- package/dist/contracts.js.map +1 -1
- package/dist/data-model-schema.d.ts +15 -15
- package/dist/data-model-schema.d.ts.map +1 -1
- package/dist/data-model-schema.js +19 -17
- package/dist/data-model-schema.js.map +1 -1
- package/dist/distribution.d.ts +5 -5
- package/dist/distribution.d.ts.map +1 -1
- package/dist/distribution.js +4 -4
- package/dist/element.d.ts +36 -36
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +43 -43
- package/dist/element.js.map +1 -1
- package/dist/embed-protocol.d.ts +11 -11
- package/dist/embed-protocol.d.ts.map +1 -1
- package/dist/embed-protocol.js +1 -1
- package/dist/embed-protocol.js.map +1 -1
- package/dist/errors.d.ts +15 -15
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +18 -18
- package/dist/errors.js.map +1 -1
- package/dist/forms-core/autocomplete.d.ts +3 -3
- package/dist/forms-core/autocomplete.d.ts.map +1 -1
- package/dist/forms-core/autocomplete.js +2 -2
- package/dist/forms-core/controller.d.ts +10 -10
- package/dist/forms-core/controller.d.ts.map +1 -1
- package/dist/forms-core/controller.js +4 -4
- package/dist/forms-core/controller.js.map +1 -1
- package/dist/forms-core/elements.d.ts +4 -4
- package/dist/forms-core/elements.d.ts.map +1 -1
- package/dist/forms-core/elements.js +7 -7
- package/dist/forms-core/elements.js.map +1 -1
- package/dist/forms-core/formula.d.ts +1 -1
- package/dist/forms-core/formula.js +1 -1
- package/dist/forms-core/index.d.ts +6 -6
- package/dist/forms-core/index.d.ts.map +1 -1
- package/dist/forms-core/index.js +5 -5
- package/dist/forms-core/index.js.map +1 -1
- package/dist/forms-core/units.d.ts +1 -1
- package/dist/forms-core/units.js +1 -1
- package/dist/forms-core/us-states.d.ts +1 -1
- package/dist/forms-core/us-states.js +1 -1
- package/dist/forms-core/validation.d.ts +12 -3
- package/dist/forms-core/validation.d.ts.map +1 -1
- package/dist/forms-core/validation.js +152 -17
- package/dist/forms-core/validation.js.map +1 -1
- package/dist/forms.d.ts +2 -2
- package/dist/forms.d.ts.map +1 -1
- package/dist/forms.js +2 -2
- package/dist/forms.js.map +1 -1
- package/dist/framework-support.js +1 -1
- package/dist/framework-support.js.map +1 -1
- package/dist/gallery-types.d.ts +1 -1
- package/dist/graphql.d.ts +3 -3
- package/dist/graphql.d.ts.map +1 -1
- package/dist/graphql.js +3 -3
- package/dist/graphql.js.map +1 -1
- package/dist/index.cjs +351 -272
- package/dist/index.d.cts +5 -5
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/legal-core/index.cjs +8 -8
- package/dist/legal-core/index.d.ts +3 -3
- package/dist/legal-core/index.d.ts.map +1 -1
- package/dist/legal-core/index.js +11 -11
- package/dist/legal-core/index.js.map +1 -1
- package/dist/marketing-core/index.d.ts +51 -1
- package/dist/marketing-core/index.d.ts.map +1 -1
- package/dist/marketing-core/index.js +35 -0
- package/dist/marketing-core/index.js.map +1 -1
- package/dist/marketing-schema.d.ts +9 -9
- package/dist/marketing-schema.d.ts.map +1 -1
- package/dist/marketing-schema.js +1 -1
- package/dist/mcp.d.ts +4 -4
- package/dist/mcp.js +4 -4
- package/dist/native.d.ts +6 -6
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +4 -4
- package/dist/native.js.map +1 -1
- package/dist/next-revalidate.d.ts +8 -8
- package/dist/next-revalidate.d.ts.map +1 -1
- package/dist/next-revalidate.js +7 -7
- package/dist/next-revalidate.js.map +1 -1
- package/dist/notifications-core/index.d.ts +1 -1
- package/dist/notifications-core/index.js +1 -1
- package/dist/parallel-pages.d.ts +4 -4
- package/dist/parallel-pages.d.ts.map +1 -1
- package/dist/parallel-pages.js +5 -5
- package/dist/parallel-pages.js.map +1 -1
- package/dist/portal-core/index.d.ts +1 -1
- package/dist/portal-core/index.js +1 -1
- package/dist/proxy.d.ts +18 -18
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +10 -10
- package/dist/proxy.js.map +1 -1
- package/dist/push-core/index.cjs +6 -2
- package/dist/push-core/index.d.ts +1 -1
- package/dist/push-core/index.d.ts.map +1 -1
- package/dist/push-core/index.js +6 -2
- package/dist/push-core/index.js.map +1 -1
- package/dist/react-analytics.d.ts +14 -14
- package/dist/react-analytics.d.ts.map +1 -1
- package/dist/react-analytics.js +15 -15
- package/dist/react-analytics.js.map +1 -1
- package/dist/react-attribution.d.ts +2 -2
- package/dist/react-attribution.d.ts.map +1 -1
- package/dist/react-attribution.js +1 -1
- package/dist/react-marketing.js +5 -5
- package/dist/react-marketing.js.map +1 -1
- package/dist/react.d.ts +38 -61
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +247 -261
- package/dist/react.js.map +1 -1
- package/dist/reserved-paths.d.ts +2 -2
- package/dist/reserved-paths.js +3 -3
- package/dist/reserved-paths.js.map +1 -1
- package/dist/revalidate-verify.d.ts +3 -3
- package/dist/revalidate-verify.d.ts.map +1 -1
- package/dist/revalidate-verify.js +1 -1
- package/dist/revalidate-verify.js.map +1 -1
- package/dist/sdk.d.ts +51 -45
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +48 -50
- package/dist/sdk.js.map +1 -1
- package/dist/seed-core/index.js +8 -8
- package/dist/seed-core/index.js.map +1 -1
- package/dist/seo-core/blog.d.ts +1 -1
- package/dist/seo-core/blog.js +1 -1
- package/dist/seo-core/blog.js.map +1 -1
- package/dist/seo-core/index.cjs +2 -2
- package/dist/seo-core/index.js +1 -1
- package/dist/seo-core/index.js.map +1 -1
- package/dist/seo.d.ts +1 -1
- package/dist/seo.d.ts.map +1 -1
- package/dist/seo.js +1 -1
- package/dist/seo.js.map +1 -1
- package/dist/sitemap-core/index.cjs +5 -5
- package/dist/sitemap-core/index.d.ts +13 -5
- package/dist/sitemap-core/index.d.ts.map +1 -1
- package/dist/sitemap-core/index.js +4 -4
- package/dist/sitemap-core/index.js.map +1 -1
- package/dist/socials.d.ts +6 -6
- package/dist/socials.d.ts.map +1 -1
- package/dist/socials.js +5 -5
- package/dist/socials.js.map +1 -1
- package/dist/solid-compat.d.ts +1 -1
- package/dist/solid-compat.js +4 -4
- package/dist/solid-compat.js.map +1 -1
- package/dist/store-core/index.d.ts +1 -1
- package/dist/vanilla.d.ts +12 -12
- package/dist/vanilla.d.ts.map +1 -1
- package/dist/vanilla.js +192 -145
- package/dist/vanilla.js.map +1 -1
- package/openapi.json +264 -1
- package/package.json +8 -8
- package/src/account-core/index.ts +1 -1
- package/src/adapters-revalidate.ts +3 -3
- package/src/analytics-core.ts +8 -8
- package/src/analytics-server.ts +6 -6
- package/src/angular/{biab-form.component.ts → bd-form.component.ts} +269 -161
- package/src/angular/headless.ts +12 -12
- package/src/angular/index.ts +9 -9
- package/src/auth-handler.ts +22 -22
- package/src/auth-links.ts +2 -2
- package/src/cli.ts +81 -81
- package/src/client.ts +218 -193
- package/src/consent-core/index.ts +10 -8
- package/src/contracts.ts +99 -15
- package/src/data-model-schema.ts +29 -26
- package/src/distribution.ts +5 -5
- package/src/element.ts +54 -54
- package/src/embed-protocol.ts +14 -14
- package/src/errors.ts +27 -27
- package/src/forms-core/autocomplete.ts +3 -3
- package/src/forms-core/controller.ts +12 -12
- package/src/forms-core/elements.ts +7 -7
- package/src/forms-core/formula.ts +1 -1
- package/src/forms-core/index.ts +8 -8
- package/src/forms-core/units.ts +1 -1
- package/src/forms-core/us-states.ts +1 -1
- package/src/forms-core/validation.ts +158 -14
- package/src/forms.ts +2 -2
- package/src/framework-support.ts +1 -1
- package/src/gallery-types.ts +1 -1
- package/src/graphql.ts +4 -4
- package/src/index.ts +38 -38
- package/src/legal-core/index.ts +12 -12
- package/src/marketing-core/index.ts +62 -0
- package/src/marketing-schema.ts +9 -9
- package/src/mcp.ts +4 -4
- package/src/native.ts +14 -14
- package/src/next-revalidate.ts +10 -10
- package/src/notifications-core/index.ts +1 -1
- package/src/parallel-pages.ts +6 -6
- package/src/portal-core/index.ts +1 -1
- package/src/proxy.ts +28 -28
- package/src/push-core/index.ts +11 -5
- package/src/qwik.tsx +262 -166
- package/src/react-analytics.tsx +17 -17
- package/src/react-attribution.tsx +2 -2
- package/src/react-marketing.tsx +5 -5
- package/src/react.tsx +541 -500
- package/src/reserved-paths.ts +3 -3
- package/src/revalidate-verify.ts +5 -5
- package/src/sdk.ts +131 -101
- package/src/seed-core/index.ts +8 -8
- package/src/seo-core/blog.ts +2 -2
- package/src/seo-core/index.ts +1 -1
- package/src/seo.ts +1 -1
- package/src/sitemap-core/index.ts +17 -9
- package/src/socials.ts +6 -6
- package/src/solid-compat.tsx +5 -5
- package/src/solid-surfaces.tsx +28 -28
- package/src/solid.tsx +264 -175
- package/src/store-core/index.ts +1 -1
- package/src/styles/{biab-forms.css → bd-forms.css} +158 -158
- package/src/svelte/{BiabForm.svelte → BdForm.svelte} +240 -173
- package/src/svelte/context.ts +16 -16
- package/src/svelte/headless.ts +10 -10
- package/src/svelte/index.ts +16 -16
- package/src/vanilla.ts +250 -169
- package/src/vue/{BiabForm.vue → BdForm.vue} +270 -157
- package/src/vue/headless.ts +11 -11
- package/src/vue/index.ts +26 -26
package/src/react.tsx
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* WHY THE DIRECTIVE IS HERE
|
|
11
11
|
*
|
|
12
12
|
* Without it, a React Server Components framework treats this module as server
|
|
13
|
-
* code by default, and importing `<
|
|
13
|
+
* code by default, and importing `<BdForm>` into a page throws "you're
|
|
14
14
|
* importing a component that needs useState". The only way out is for every
|
|
15
15
|
* consumer to hand-write a `"use client"` wrapper per component — which is
|
|
16
16
|
* exactly what the Next App Router starter had to do, sixteen times, for
|
|
@@ -40,45 +40,27 @@ import {
|
|
|
40
40
|
createFollowersController,
|
|
41
41
|
createUserController,
|
|
42
42
|
} from "./account-core/index.js";
|
|
43
|
-
import {
|
|
44
|
-
buildAuthHref,
|
|
45
|
-
clearLocalFollow,
|
|
46
|
-
DEFAULT_AUTH_BASE_PATH,
|
|
47
|
-
followerStoreKey,
|
|
48
|
-
type LocalFollow,
|
|
49
|
-
readLocalFollow,
|
|
50
|
-
writeLocalFollow,
|
|
51
|
-
} from "./auth-links.js";
|
|
43
|
+
import { buildAuthHref, DEFAULT_AUTH_BASE_PATH } from "./auth-links.js";
|
|
52
44
|
import { createBlogController } from "./blog-core/index.js";
|
|
53
45
|
import { createChatController } from "./chat-core/index.js";
|
|
54
46
|
import { createCheckoutController } from "./checkout-core/index.js";
|
|
55
|
-
import {
|
|
47
|
+
import { BdApiClient } from "./client.js";
|
|
56
48
|
import type {
|
|
57
49
|
BlogSessionInput,
|
|
58
50
|
BlogSessionResponse,
|
|
59
51
|
CartSessionResponse,
|
|
60
52
|
ChatbotAvailability,
|
|
61
|
-
ChatbotChatInput,
|
|
62
|
-
ChatbotChatResponse,
|
|
63
|
-
ChatbotConfig,
|
|
64
53
|
ChatbotFormDefinition,
|
|
65
|
-
ChatbotFormSubmitInput,
|
|
66
|
-
ChatbotFormSubmitResponse,
|
|
67
|
-
ChatbotMessage,
|
|
68
54
|
ChatbotSessionResponse,
|
|
69
|
-
ChatbotUiAction,
|
|
70
55
|
CheckoutSessionInput,
|
|
71
56
|
CheckoutSessionResponse,
|
|
72
57
|
DashboardSessionResponse,
|
|
73
58
|
GetSiteMarketingPageResponse,
|
|
74
|
-
ListSiteMarketingPagesResponse,
|
|
75
|
-
SiteMarketingPage,
|
|
76
|
-
SiteMarketingPageSummary,
|
|
77
59
|
StorefrontSessionInput,
|
|
78
60
|
StorefrontSessionResponse,
|
|
79
61
|
} from "./contracts.js";
|
|
80
62
|
import {
|
|
81
|
-
type
|
|
63
|
+
type BdEmbedEvent,
|
|
82
64
|
DEFAULT_EMBED_SANDBOX,
|
|
83
65
|
DEFAULT_REFRESH_LEAD_MS,
|
|
84
66
|
type EmbedSessionState,
|
|
@@ -87,7 +69,7 @@ import {
|
|
|
87
69
|
} from "./embed-protocol.js";
|
|
88
70
|
import {
|
|
89
71
|
type AutocompleteConfig,
|
|
90
|
-
type
|
|
72
|
+
type BdFormsClient,
|
|
91
73
|
buildAcceptAttr,
|
|
92
74
|
type CalculationConfig,
|
|
93
75
|
type CreateFormControllerOptions,
|
|
@@ -117,10 +99,16 @@ import {
|
|
|
117
99
|
unitsForCategory,
|
|
118
100
|
} from "./forms-core/units.js";
|
|
119
101
|
import { US_STATES } from "./forms-core/us-states.js";
|
|
120
|
-
import {
|
|
102
|
+
import {
|
|
103
|
+
createMarketingController,
|
|
104
|
+
type MarketingListView,
|
|
105
|
+
type MarketingPageView,
|
|
106
|
+
marketingListView,
|
|
107
|
+
marketingPageView,
|
|
108
|
+
} from "./marketing-core/index.js";
|
|
121
109
|
import { createPortalController } from "./portal-core/index.js";
|
|
122
110
|
import type { FormFileValue, FormSchema, FormSubmitResult } from "./sdk.js";
|
|
123
|
-
import {
|
|
111
|
+
import { createBdClient } from "./sdk.js";
|
|
124
112
|
import {
|
|
125
113
|
resolveSocialLinks,
|
|
126
114
|
SOCIAL_FALLBACK_GLYPH,
|
|
@@ -138,7 +126,7 @@ import {
|
|
|
138
126
|
type SessionState<T> = EmbedSessionState<T>;
|
|
139
127
|
|
|
140
128
|
function useEmbedSession<T extends { url: string; expiresAt: string }>(
|
|
141
|
-
loader: (client:
|
|
129
|
+
loader: (client: BdApiClient) => Promise<T>,
|
|
142
130
|
options: {
|
|
143
131
|
apiKey: string;
|
|
144
132
|
baseUrl: string;
|
|
@@ -147,7 +135,7 @@ function useEmbedSession<T extends { url: string; expiresAt: string }>(
|
|
|
147
135
|
) {
|
|
148
136
|
const { apiKey, baseUrl, refreshLeadMs = DEFAULT_REFRESH_LEAD_MS } = options;
|
|
149
137
|
const client = useMemo(
|
|
150
|
-
() => new
|
|
138
|
+
() => new BdApiClient({ apiKey, baseUrl }),
|
|
151
139
|
[apiKey, baseUrl],
|
|
152
140
|
);
|
|
153
141
|
const [state, setState] = useState<SessionState<T>>({ status: "idle" });
|
|
@@ -200,11 +188,11 @@ function useEmbedSession<T extends { url: string; expiresAt: string }>(
|
|
|
200
188
|
* that guards it live together. Kept exported here so existing
|
|
201
189
|
* `@businessdash/sdk/react` imports keep working.
|
|
202
190
|
*/
|
|
203
|
-
export type {
|
|
191
|
+
export type { BdEmbedEvent } from "./embed-protocol.js";
|
|
204
192
|
|
|
205
193
|
function useEmbedMessages(
|
|
206
194
|
iframeRef: React.RefObject<HTMLIFrameElement | null>,
|
|
207
|
-
handler: (event:
|
|
195
|
+
handler: (event: BdEmbedEvent) => void,
|
|
208
196
|
) {
|
|
209
197
|
// The listener, and the origin check inside it, live in
|
|
210
198
|
// `embed-protocol.ts` so the Solid binding enforces exactly the same rule.
|
|
@@ -341,7 +329,7 @@ type EmbedIframeProps<T extends { url: string }> = {
|
|
|
341
329
|
errorFallback?:
|
|
342
330
|
| ((error: Error, refresh: () => void) => ReactNode)
|
|
343
331
|
| undefined;
|
|
344
|
-
onEvent?: ((event:
|
|
332
|
+
onEvent?: ((event: BdEmbedEvent) => void) | undefined;
|
|
345
333
|
/** Extra `sandbox` tokens beyond the safe defaults. */
|
|
346
334
|
extraSandbox?: string | undefined;
|
|
347
335
|
};
|
|
@@ -416,7 +404,7 @@ export type StorefrontProps = {
|
|
|
416
404
|
loadingFallback?: ReactNode;
|
|
417
405
|
errorFallback?: (error: Error, refresh: () => void) => ReactNode;
|
|
418
406
|
/** Fires for every event the embed posts. */
|
|
419
|
-
onEvent?: (event:
|
|
407
|
+
onEvent?: (event: BdEmbedEvent) => void;
|
|
420
408
|
/** Convenience: fires only on cart updates. */
|
|
421
409
|
onCartChange?: (count: number, subtotalCents: number) => void;
|
|
422
410
|
refreshLeadMs?: number;
|
|
@@ -444,9 +432,9 @@ export function Storefront({
|
|
|
444
432
|
{ apiKey, baseUrl, refreshLeadMs },
|
|
445
433
|
);
|
|
446
434
|
const handleEvent = useCallback(
|
|
447
|
-
(event:
|
|
435
|
+
(event: BdEmbedEvent) => {
|
|
448
436
|
onEvent?.(event);
|
|
449
|
-
if (event.type === "
|
|
437
|
+
if (event.type === "bd:cart:update") {
|
|
450
438
|
onCartChange?.(event.count, event.subtotalCents);
|
|
451
439
|
}
|
|
452
440
|
},
|
|
@@ -476,7 +464,7 @@ export type CartProps = {
|
|
|
476
464
|
title?: string;
|
|
477
465
|
loadingFallback?: ReactNode;
|
|
478
466
|
errorFallback?: (error: Error, refresh: () => void) => ReactNode;
|
|
479
|
-
onEvent?: (event:
|
|
467
|
+
onEvent?: (event: BdEmbedEvent) => void;
|
|
480
468
|
onCartChange?: (count: number, subtotalCents: number) => void;
|
|
481
469
|
refreshLeadMs?: number;
|
|
482
470
|
};
|
|
@@ -498,9 +486,9 @@ export function Cart({
|
|
|
498
486
|
{ apiKey, baseUrl, refreshLeadMs },
|
|
499
487
|
);
|
|
500
488
|
const handleEvent = useCallback(
|
|
501
|
-
(event:
|
|
489
|
+
(event: BdEmbedEvent) => {
|
|
502
490
|
onEvent?.(event);
|
|
503
|
-
if (event.type === "
|
|
491
|
+
if (event.type === "bd:cart:update") {
|
|
504
492
|
onCartChange?.(event.count, event.subtotalCents);
|
|
505
493
|
}
|
|
506
494
|
},
|
|
@@ -536,7 +524,7 @@ export type CheckoutProps = {
|
|
|
536
524
|
children?: ReactNode;
|
|
537
525
|
loadingFallback?: ReactNode;
|
|
538
526
|
errorFallback?: (error: Error, refresh: () => void) => ReactNode;
|
|
539
|
-
onEvent?: (event:
|
|
527
|
+
onEvent?: (event: BdEmbedEvent) => void;
|
|
540
528
|
onSuccess?: (sessionId: string) => void;
|
|
541
529
|
onCancel?: (sessionId: string) => void;
|
|
542
530
|
onError?: (error: Error) => void;
|
|
@@ -561,7 +549,7 @@ export function Checkout(props: CheckoutProps) {
|
|
|
561
549
|
} = props;
|
|
562
550
|
|
|
563
551
|
const client = useMemo(
|
|
564
|
-
() => new
|
|
552
|
+
() => new BdApiClient({ apiKey, baseUrl }),
|
|
565
553
|
[apiKey, baseUrl],
|
|
566
554
|
);
|
|
567
555
|
|
|
@@ -585,12 +573,11 @@ export function Checkout(props: CheckoutProps) {
|
|
|
585
573
|
}, [client, cart, mode, onError]);
|
|
586
574
|
|
|
587
575
|
const handleEvent = useCallback(
|
|
588
|
-
(event:
|
|
576
|
+
(event: BdEmbedEvent) => {
|
|
589
577
|
onEvent?.(event);
|
|
590
|
-
if (event.type === "
|
|
591
|
-
else if (event.type === "
|
|
592
|
-
|
|
593
|
-
else if (event.type === "biab:checkout:error")
|
|
578
|
+
if (event.type === "bd:checkout:success") onSuccess?.(event.sessionId);
|
|
579
|
+
else if (event.type === "bd:checkout:cancel") onCancel?.(event.sessionId);
|
|
580
|
+
else if (event.type === "bd:checkout:error")
|
|
594
581
|
onError?.(new Error(event.message));
|
|
595
582
|
},
|
|
596
583
|
[onEvent, onSuccess, onCancel, onError],
|
|
@@ -674,7 +661,7 @@ export function CheckoutResult(props: CheckoutResultProps) {
|
|
|
674
661
|
setState({ status: "error", error: new Error("Missing session id.") });
|
|
675
662
|
return;
|
|
676
663
|
}
|
|
677
|
-
const client = new
|
|
664
|
+
const client = new BdApiClient({ apiKey, baseUrl });
|
|
678
665
|
// We hit the *public* tRPC endpoint via fetch — no API key required.
|
|
679
666
|
const trpcUrl = baseUrl.replace(/\/api\/package\/v1\/?$/, "");
|
|
680
667
|
const requestUrl = `${trpcUrl}/api/trpc/store.checkoutSessionStatus?input=${encodeURIComponent(
|
|
@@ -743,7 +730,7 @@ export type BlogProps = {
|
|
|
743
730
|
title?: string;
|
|
744
731
|
loadingFallback?: ReactNode;
|
|
745
732
|
errorFallback?: (error: Error, refresh: () => void) => ReactNode;
|
|
746
|
-
onEvent?: (event:
|
|
733
|
+
onEvent?: (event: BdEmbedEvent) => void;
|
|
747
734
|
refreshLeadMs?: number;
|
|
748
735
|
};
|
|
749
736
|
|
|
@@ -757,7 +744,7 @@ export type ChatbotProps = {
|
|
|
757
744
|
title?: string;
|
|
758
745
|
loadingFallback?: ReactNode;
|
|
759
746
|
errorFallback?: (error: Error, refresh: () => void) => ReactNode;
|
|
760
|
-
onEvent?: (event:
|
|
747
|
+
onEvent?: (event: BdEmbedEvent) => void;
|
|
761
748
|
/** Convenience: fires when the assistant replies. */
|
|
762
749
|
onAssistantMessage?: (content: string, model?: string) => void;
|
|
763
750
|
refreshLeadMs?: number;
|
|
@@ -780,9 +767,9 @@ export function Chatbot({
|
|
|
780
767
|
{ apiKey, baseUrl, refreshLeadMs },
|
|
781
768
|
);
|
|
782
769
|
const handleEvent = useCallback(
|
|
783
|
-
(event:
|
|
770
|
+
(event: BdEmbedEvent) => {
|
|
784
771
|
onEvent?.(event);
|
|
785
|
-
if (event.type === "
|
|
772
|
+
if (event.type === "bd:chatbot:assistant-message") {
|
|
786
773
|
onAssistantMessage?.(event.content, event.model);
|
|
787
774
|
}
|
|
788
775
|
},
|
|
@@ -817,7 +804,7 @@ export function useChatbot(options: { apiKey: string; baseUrl: string }) {
|
|
|
817
804
|
const controller = useMemo(
|
|
818
805
|
() =>
|
|
819
806
|
createChatController({
|
|
820
|
-
client: new
|
|
807
|
+
client: new BdApiClient({ apiKey, baseUrl }),
|
|
821
808
|
}),
|
|
822
809
|
[apiKey, baseUrl],
|
|
823
810
|
);
|
|
@@ -832,7 +819,7 @@ export function useChatbot(options: { apiKey: string; baseUrl: string }) {
|
|
|
832
819
|
|
|
833
820
|
const uploadFile = useCallback(
|
|
834
821
|
(args: { sessionId: string; file: File; uploaderEmail?: string | null }) =>
|
|
835
|
-
new
|
|
822
|
+
new BdApiClient({ apiKey, baseUrl }).chatbot.uploadFile(args),
|
|
836
823
|
[apiKey, baseUrl],
|
|
837
824
|
);
|
|
838
825
|
|
|
@@ -876,7 +863,7 @@ export function useStorefront(options: {
|
|
|
876
863
|
const controller = useMemo(
|
|
877
864
|
() =>
|
|
878
865
|
createStoreController({
|
|
879
|
-
client: new
|
|
866
|
+
client: new BdApiClient({ apiKey, baseUrl }),
|
|
880
867
|
...(pageSize ? { pageSize } : {}),
|
|
881
868
|
...(categoryId ? { categoryId } : {}),
|
|
882
869
|
}),
|
|
@@ -1009,52 +996,52 @@ const inlineLayoutStyles = {
|
|
|
1009
996
|
* Opt-in default visual theme for {@link ChatbotInline}. Every rule is wrapped
|
|
1010
997
|
* in `:where(…)` so it has **zero specificity** — any class a consumer passes
|
|
1011
998
|
* via `classNames` (or their global CSS) overrides it without `!important`.
|
|
1012
|
-
* Scoped to the `[data-
|
|
999
|
+
* Scoped to the `[data-bd-chatbot]` root (set only when `unstyled` is false),
|
|
1013
1000
|
* and targets the per-slot `data-slot` attributes the component stamps on each
|
|
1014
1001
|
* element. Injected once into `<head>` (id-guarded). This is what makes the
|
|
1015
1002
|
* inline component a polished drop-in instead of bare divs, while preserving
|
|
1016
1003
|
* the headless contract for anyone who wants to style it themselves.
|
|
1017
1004
|
*/
|
|
1018
|
-
const CHATBOT_INLINE_STYLE_ID = "
|
|
1005
|
+
const CHATBOT_INLINE_STYLE_ID = "bd-chatbot-inline-styles";
|
|
1019
1006
|
const CHATBOT_INLINE_CSS = `
|
|
1020
|
-
:where([data-
|
|
1021
|
-
:where([data-
|
|
1022
|
-
:where([data-
|
|
1023
|
-
:where([data-
|
|
1024
|
-
:where([data-
|
|
1025
|
-
:where([data-
|
|
1026
|
-
:where([data-
|
|
1027
|
-
:where([data-
|
|
1028
|
-
:where([data-
|
|
1029
|
-
:where([data-
|
|
1030
|
-
:where([data-
|
|
1031
|
-
:where([data-
|
|
1032
|
-
:where([data-
|
|
1033
|
-
:where([data-
|
|
1034
|
-
:where([data-
|
|
1035
|
-
:where([data-
|
|
1036
|
-
:where([data-
|
|
1037
|
-
:where([data-
|
|
1038
|
-
:where([data-
|
|
1039
|
-
:where([data-
|
|
1040
|
-
:where([data-
|
|
1041
|
-
:where([data-
|
|
1042
|
-
:where([data-
|
|
1043
|
-
:where([data-
|
|
1044
|
-
:where([data-
|
|
1045
|
-
:where([data-
|
|
1046
|
-
:where([data-
|
|
1047
|
-
:where([data-
|
|
1048
|
-
:where([data-
|
|
1049
|
-
:where([data-
|
|
1050
|
-
:where([data-
|
|
1051
|
-
:where([data-
|
|
1052
|
-
:where([data-
|
|
1053
|
-
@keyframes
|
|
1054
|
-
@media (prefers-reduced-motion:reduce){:where([data-
|
|
1055
|
-
@property --
|
|
1056
|
-
@keyframes
|
|
1057
|
-
@supports (animation-timeline:scroll()){:where([data-
|
|
1007
|
+
:where([data-bd-chatbot]){font-size:14px;color:inherit;background:var(--bd-chatbot-bg,Canvas);border:1px solid color-mix(in srgb,currentColor 12%,transparent);border-radius:16px;}
|
|
1008
|
+
:where([data-bd-chatbot] [data-slot="header"]){padding:12px 16px;border-bottom:1px solid color-mix(in srgb,currentColor 10%,transparent);}
|
|
1009
|
+
:where([data-bd-chatbot] [data-slot="header-title"]){font-weight:600;}
|
|
1010
|
+
:where([data-bd-chatbot] [data-slot="header-subtitle"]){font-size:12px;opacity:.6;margin-top:2px;}
|
|
1011
|
+
:where([data-bd-chatbot] [data-slot="presence-strip"]){display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 16px;font-size:12px;border-bottom:1px solid color-mix(in srgb,currentColor 8%,transparent);opacity:.85;}
|
|
1012
|
+
:where([data-bd-chatbot] [data-slot="message-list"]){padding:16px;gap:12px;}
|
|
1013
|
+
:where([data-bd-chatbot] [data-slot="message-row"]){max-width:85%;width:fit-content;padding:8px 12px;border-radius:16px;line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere;box-shadow:0 1px 2px color-mix(in srgb,currentColor 8%,transparent);}
|
|
1014
|
+
:where([data-bd-chatbot] [data-slot="message-row"][data-role="user"]){align-self:flex-end;background:var(--bd-chatbot-accent,#0d9488);color:#fff;border-bottom-right-radius:4px;}
|
|
1015
|
+
:where([data-bd-chatbot] [data-slot="message-row"][data-role="assistant"]){align-self:flex-start;background:color-mix(in srgb,currentColor 8%,transparent);border-bottom-left-radius:4px;}
|
|
1016
|
+
:where([data-bd-chatbot] [data-slot="message-pending"]){align-self:flex-start;width:fit-content;padding:8px 12px;background-image:linear-gradient(90deg,currentColor 35%,color-mix(in srgb,currentColor 25%,transparent) 50%,currentColor 65%);background-size:200% 100%;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;animation:bd-chatbot-shimmer 2s linear infinite;}
|
|
1017
|
+
:where([data-bd-chatbot] [data-slot="error-text"]){align-self:center;font-size:12px;color:#dc2626;}
|
|
1018
|
+
:where([data-bd-chatbot] [data-slot="input-bar"]){gap:8px;padding:12px;border-top:1px solid color-mix(in srgb,currentColor 10%,transparent);}
|
|
1019
|
+
:where([data-bd-chatbot] [data-slot="input-field"]){padding:8px 12px;border-radius:10px;border:1px solid color-mix(in srgb,currentColor 15%,transparent);background:transparent;color:inherit;font:inherit;outline:none;}
|
|
1020
|
+
:where([data-bd-chatbot] [data-slot="input-field"]:focus-visible){border-color:var(--bd-chatbot-accent,#0d9488);}
|
|
1021
|
+
:where([data-bd-chatbot] [data-slot="send-button"]){padding:8px 14px;border-radius:10px;border:none;background:var(--bd-chatbot-accent,#0d9488);color:#fff;font-weight:500;cursor:pointer;}
|
|
1022
|
+
:where([data-bd-chatbot] [data-slot="send-button"]:disabled){opacity:.5;cursor:default;}
|
|
1023
|
+
:where([data-bd-chatbot] [data-slot="upload-button"]){display:inline-flex;align-items:center;justify-content:center;padding:0 6px;border:none;background:transparent;color:inherit;font-size:18px;line-height:1;cursor:pointer;opacity:.7;}
|
|
1024
|
+
:where([data-bd-chatbot] [data-slot="upload-button"]:hover){opacity:1;}
|
|
1025
|
+
:where([data-bd-chatbot] [data-slot="upload-button"]:disabled){opacity:.4;cursor:default;}
|
|
1026
|
+
:where([data-bd-chatbot] [data-slot="message-file"]){align-self:flex-end;max-width:85%;padding:8px 12px;border-radius:16px;border-bottom-right-radius:4px;background:var(--bd-chatbot-accent,#0d9488);color:#fff;text-decoration:none;font-size:13px;word-break:break-all;}
|
|
1027
|
+
:where([data-bd-chatbot] [data-slot="message-image"]){align-self:flex-end;max-width:85%;border-radius:12px;overflow:hidden;}
|
|
1028
|
+
:where([data-bd-chatbot] [data-slot="jump-latest"]){position:absolute;left:50%;bottom:72px;transform:translateX(-50%);display:inline-flex;align-items:center;gap:4px;padding:6px 12px;border-radius:999px;border:1px solid color-mix(in srgb,currentColor 15%,transparent);background:var(--bd-chatbot-bg,Canvas);font-size:12px;font-weight:500;cursor:pointer;box-shadow:0 2px 10px color-mix(in srgb,currentColor 20%,transparent);}
|
|
1029
|
+
:where([data-bd-chatbot] [data-slot="form-card"]){display:flex;flex-direction:column;gap:10px;align-self:stretch;padding:14px;border-radius:14px;border:1px solid color-mix(in srgb,currentColor 14%,transparent);background:color-mix(in srgb,currentColor 4%,transparent);}
|
|
1030
|
+
:where([data-bd-chatbot] [data-slot="form-title"]){font-weight:600;}
|
|
1031
|
+
:where([data-bd-chatbot] [data-slot="form-description"]){font-size:12px;opacity:.7;}
|
|
1032
|
+
:where([data-bd-chatbot] [data-slot="form-field"]){gap:4px;}
|
|
1033
|
+
:where([data-bd-chatbot] [data-slot="form-label"]){font-size:12px;font-weight:500;opacity:.85;}
|
|
1034
|
+
:where([data-bd-chatbot] [data-slot="form-input"],[data-bd-chatbot] [data-slot="form-textarea"],[data-bd-chatbot] [data-slot="form-select"]){padding:8px 10px;border-radius:8px;border:1px solid color-mix(in srgb,currentColor 15%,transparent);background:transparent;color:inherit;font:inherit;outline:none;}
|
|
1035
|
+
:where([data-bd-chatbot] [data-slot="form-input"]:focus-visible,[data-bd-chatbot] [data-slot="form-textarea"]:focus-visible,[data-bd-chatbot] [data-slot="form-select"]:focus-visible){border-color:var(--bd-chatbot-accent,#0d9488);}
|
|
1036
|
+
:where([data-bd-chatbot] [data-slot="form-submit"]){padding:8px 14px;border-radius:8px;border:none;background:var(--bd-chatbot-accent,#0d9488);color:#fff;font-weight:500;cursor:pointer;}
|
|
1037
|
+
:where([data-bd-chatbot] [data-slot="form-submit"]:disabled){opacity:.5;cursor:default;}
|
|
1038
|
+
:where([data-bd-chatbot] [data-slot="form-error"]){font-size:12px;color:#dc2626;}
|
|
1039
|
+
:where([data-bd-chatbot] [data-slot="form-receipt"]){font-size:13px;opacity:.8;}
|
|
1040
|
+
@keyframes bd-chatbot-shimmer{from{background-position:100% 0;}to{background-position:0 0;}}
|
|
1041
|
+
@media (prefers-reduced-motion:reduce){:where([data-bd-chatbot] [data-slot="message-pending"]){animation:none;-webkit-text-fill-color:currentColor;}}
|
|
1042
|
+
@property --bd-chatbot-fade{syntax:"<length>";inherits:false;initial-value:0px;}
|
|
1043
|
+
@keyframes bd-chatbot-fade-b{from{--bd-chatbot-fade:24px;}to{--bd-chatbot-fade:0px;}}
|
|
1044
|
+
@supports (animation-timeline:scroll()){:where([data-bd-chatbot] [data-slot="message-list"]){-webkit-mask-image:linear-gradient(to bottom,#000 0,#000 calc(100% - var(--bd-chatbot-fade,0px)),transparent 100%);mask-image:linear-gradient(to bottom,#000 0,#000 calc(100% - var(--bd-chatbot-fade,0px)),transparent 100%);animation:bd-chatbot-fade-b 1ms linear both;animation-timeline:scroll(self y);animation-range:calc(100% - 96px) 100%;}}
|
|
1058
1045
|
`;
|
|
1059
1046
|
|
|
1060
1047
|
export type ChatbotInlineProps = {
|
|
@@ -1460,7 +1447,7 @@ export function ChatbotInline({
|
|
|
1460
1447
|
className={
|
|
1461
1448
|
[classNames.root, className].filter(Boolean).join(" ") || undefined
|
|
1462
1449
|
}
|
|
1463
|
-
data-
|
|
1450
|
+
data-bd-chatbot={unstyled ? undefined : ""}
|
|
1464
1451
|
style={{ ...inlineLayoutStyles.root, ...style }}
|
|
1465
1452
|
>
|
|
1466
1453
|
{title !== null ? (
|
|
@@ -2118,7 +2105,7 @@ export type ProgrammaticAuthOptions = {
|
|
|
2118
2105
|
returnTo?: string;
|
|
2119
2106
|
/** Pre-fill the email on the platform-hosted page (sign-in / sign-up only). */
|
|
2120
2107
|
loginHint?: string;
|
|
2121
|
-
/** Override the auth-handler basePath. Defaults to "/api/
|
|
2108
|
+
/** Override the auth-handler basePath. Defaults to "/api/bd-auth". */
|
|
2122
2109
|
basePath?: string;
|
|
2123
2110
|
};
|
|
2124
2111
|
|
|
@@ -2141,7 +2128,7 @@ export function signUp(options: ProgrammaticAuthOptions = {}): void {
|
|
|
2141
2128
|
}
|
|
2142
2129
|
|
|
2143
2130
|
export type ProgrammaticSignOutOptions = {
|
|
2144
|
-
/** Override the auth-handler basePath. Defaults to "/api/
|
|
2131
|
+
/** Override the auth-handler basePath. Defaults to "/api/bd-auth". */
|
|
2145
2132
|
basePath?: string;
|
|
2146
2133
|
/**
|
|
2147
2134
|
* Default `"redirect"` — navigates through `/sign-out` (matches the
|
|
@@ -2181,7 +2168,7 @@ export async function signOut(
|
|
|
2181
2168
|
|
|
2182
2169
|
export type ProgrammaticPasswordResetInput = {
|
|
2183
2170
|
email: string;
|
|
2184
|
-
/** Override the auth-handler basePath. Defaults to "/api/
|
|
2171
|
+
/** Override the auth-handler basePath. Defaults to "/api/bd-auth". */
|
|
2185
2172
|
basePath?: string;
|
|
2186
2173
|
/**
|
|
2187
2174
|
* Optional return path the platform-hosted reset page sends the user to
|
|
@@ -2193,7 +2180,7 @@ export type ProgrammaticPasswordResetInput = {
|
|
|
2193
2180
|
|
|
2194
2181
|
/**
|
|
2195
2182
|
* Programmatic "forgot password" — POSTs to the auth handler's
|
|
2196
|
-
* `/password-reset` route, which the SDK forwards to
|
|
2183
|
+
* `/password-reset` route, which the SDK forwards to BD. The server
|
|
2197
2184
|
* returns `{ ok: true }` regardless of whether an account matched
|
|
2198
2185
|
* (intentional anti-leak), and emails the platform-hosted reset link
|
|
2199
2186
|
* out-of-band when a match exists.
|
|
@@ -2297,7 +2284,7 @@ export function useFollowers(options: UseFollowersOptions) {
|
|
|
2297
2284
|
const controller = useMemo(
|
|
2298
2285
|
() =>
|
|
2299
2286
|
createFollowersController({
|
|
2300
|
-
client:
|
|
2287
|
+
client: createBdClient({
|
|
2301
2288
|
apiKey: token,
|
|
2302
2289
|
siteId,
|
|
2303
2290
|
baseUrl: baseUrl ?? DEFAULT_PACKAGE_BASE_URL,
|
|
@@ -2338,7 +2325,7 @@ export function useCheckout(options: { apiKey: string; baseUrl: string }) {
|
|
|
2338
2325
|
const controller = useMemo(
|
|
2339
2326
|
() =>
|
|
2340
2327
|
createCheckoutController({
|
|
2341
|
-
client: new
|
|
2328
|
+
client: new BdApiClient({ apiKey, baseUrl }),
|
|
2342
2329
|
}),
|
|
2343
2330
|
[apiKey, baseUrl],
|
|
2344
2331
|
);
|
|
@@ -2362,12 +2349,46 @@ export function useCheckout(options: { apiKey: string; baseUrl: string }) {
|
|
|
2362
2349
|
}
|
|
2363
2350
|
|
|
2364
2351
|
// ─── Marketing pages — list, lazy-load per page, preload ─────────────────────
|
|
2352
|
+
//
|
|
2353
|
+
// All three hooks are thin React shells over `createMarketingController`.
|
|
2354
|
+
// They used to reimplement the fetch, the status flag and the in-flight
|
|
2355
|
+
// guard here, which meant a fix in the core never reached React — and the
|
|
2356
|
+
// parity gate was satisfied by an unused import rather than by the code.
|
|
2357
|
+
//
|
|
2358
|
+
// What stays React's own business is the SYNCHRONOUS cache read. The core
|
|
2359
|
+
// deliberately does not duplicate the client's cache, and React needs the
|
|
2360
|
+
// first render to be able to paint a cached page without a loading flash.
|
|
2361
|
+
|
|
2362
|
+
/** Unchanged public shape — now an alias of the core's, so the two cannot
|
|
2363
|
+
* drift apart the way they did before. */
|
|
2364
|
+
type MarketingPageState = MarketingPageView;
|
|
2365
|
+
|
|
2366
|
+
/** One controller per hook instance. The cache they share lives a layer down
|
|
2367
|
+
* in the client, so this costs a subscriber set and nothing else. */
|
|
2368
|
+
function useMarketingController(
|
|
2369
|
+
apiKey: string,
|
|
2370
|
+
baseUrl: string,
|
|
2371
|
+
siteId: string,
|
|
2372
|
+
) {
|
|
2373
|
+
const client = useMemo(
|
|
2374
|
+
() => new BdApiClient({ apiKey, baseUrl }),
|
|
2375
|
+
[apiKey, baseUrl],
|
|
2376
|
+
);
|
|
2377
|
+
const controller = useMemo(
|
|
2378
|
+
() => createMarketingController({ client: client.site(siteId) }),
|
|
2379
|
+
[client, siteId],
|
|
2380
|
+
);
|
|
2381
|
+
useEffect(() => () => controller.destroy(), [controller]);
|
|
2365
2382
|
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2383
|
+
const subscribe = useCallback(
|
|
2384
|
+
(onStoreChange: () => void) => controller.subscribe(() => onStoreChange()),
|
|
2385
|
+
[controller],
|
|
2386
|
+
);
|
|
2387
|
+
const getSnapshot = useCallback(() => controller.snapshot(), [controller]);
|
|
2388
|
+
const snap = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
2389
|
+
|
|
2390
|
+
return { client, controller, snap };
|
|
2391
|
+
}
|
|
2371
2392
|
|
|
2372
2393
|
/**
|
|
2373
2394
|
* Lazy-fetch a single marketing page by `pageKey`. The hook reads from the
|
|
@@ -2389,61 +2410,30 @@ export function useMarketingPage(options: {
|
|
|
2389
2410
|
refresh: () => Promise<void>;
|
|
2390
2411
|
} {
|
|
2391
2412
|
const { apiKey, baseUrl, siteId, pageKey, enabled = true } = options;
|
|
2392
|
-
const client =
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
const initial = client.site(siteId).marketingPages.getCached(pageKey);
|
|
2397
|
-
const [state, setState] = useState<MarketingPageState>(
|
|
2398
|
-
initial
|
|
2399
|
-
? { status: "ready", page: initial.page }
|
|
2400
|
-
: { status: "idle", page: null },
|
|
2401
|
-
);
|
|
2402
|
-
|
|
2403
|
-
const fetchOnce = useCallback(
|
|
2404
|
-
async (skipCache = false) => {
|
|
2405
|
-
if (!enabled) return;
|
|
2406
|
-
setState({ status: "loading", page: null });
|
|
2407
|
-
try {
|
|
2408
|
-
const response = await client
|
|
2409
|
-
.site(siteId)
|
|
2410
|
-
.marketingPages.get(pageKey, { skipCache });
|
|
2411
|
-
setState({ status: "ready", page: response.page });
|
|
2412
|
-
} catch (err) {
|
|
2413
|
-
setState({
|
|
2414
|
-
status: "error",
|
|
2415
|
-
page: null,
|
|
2416
|
-
error:
|
|
2417
|
-
err instanceof Error
|
|
2418
|
-
? err
|
|
2419
|
-
: new Error("Marketing page load failed"),
|
|
2420
|
-
});
|
|
2421
|
-
}
|
|
2422
|
-
},
|
|
2423
|
-
[client, siteId, pageKey, enabled],
|
|
2413
|
+
const { client, controller, snap } = useMarketingController(
|
|
2414
|
+
apiKey,
|
|
2415
|
+
baseUrl,
|
|
2416
|
+
siteId,
|
|
2424
2417
|
);
|
|
2425
2418
|
|
|
2426
2419
|
useEffect(() => {
|
|
2427
2420
|
if (!enabled) return;
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
if (hit) {
|
|
2431
|
-
setState({ status: "ready", page: hit.page });
|
|
2432
|
-
return;
|
|
2433
|
-
}
|
|
2434
|
-
void fetchOnce(false);
|
|
2435
|
-
}, [client, siteId, pageKey, enabled, fetchOnce]);
|
|
2421
|
+
void controller.open(pageKey);
|
|
2422
|
+
}, [controller, pageKey, enabled]);
|
|
2436
2423
|
|
|
2437
|
-
const refresh = useCallback(
|
|
2424
|
+
const refresh = useCallback(
|
|
2425
|
+
() => controller.open(pageKey, { skipCache: true }),
|
|
2426
|
+
[controller, pageKey],
|
|
2427
|
+
);
|
|
2438
2428
|
|
|
2439
|
-
|
|
2429
|
+
// The synchronous cache read is React's to do — the controller cannot,
|
|
2430
|
+
// because the cache lives in the client. Everything after it is the core's.
|
|
2431
|
+
const cached =
|
|
2432
|
+
client.site(siteId).marketingPages.getCached(pageKey)?.page ?? null;
|
|
2433
|
+
return { ...marketingPageView(snap, pageKey, cached), refresh };
|
|
2440
2434
|
}
|
|
2441
2435
|
|
|
2442
|
-
type MarketingPageListState =
|
|
2443
|
-
| { status: "idle"; pages: null }
|
|
2444
|
-
| { status: "loading"; pages: null }
|
|
2445
|
-
| { status: "ready"; pages: SiteMarketingPageSummary[] }
|
|
2446
|
-
| { status: "error"; pages: null; error: Error };
|
|
2436
|
+
type MarketingPageListState = MarketingListView;
|
|
2447
2437
|
|
|
2448
2438
|
/**
|
|
2449
2439
|
* Loads the payload-free directory of marketing pages for a site. Pair with
|
|
@@ -2458,38 +2448,18 @@ export function useMarketingPagesList(options: {
|
|
|
2458
2448
|
refresh: () => Promise<void>;
|
|
2459
2449
|
} {
|
|
2460
2450
|
const { apiKey, baseUrl, siteId, enabled = true } = options;
|
|
2461
|
-
const
|
|
2462
|
-
() => new BiabDevClient({ apiKey, baseUrl }),
|
|
2463
|
-
[apiKey, baseUrl],
|
|
2464
|
-
);
|
|
2465
|
-
const [state, setState] = useState<MarketingPageListState>({
|
|
2466
|
-
status: "idle",
|
|
2467
|
-
pages: null,
|
|
2468
|
-
});
|
|
2451
|
+
const { controller, snap } = useMarketingController(apiKey, baseUrl, siteId);
|
|
2469
2452
|
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
try {
|
|
2474
|
-
const response: ListSiteMarketingPagesResponse = await client
|
|
2475
|
-
.site(siteId)
|
|
2476
|
-
.marketingPages.list();
|
|
2477
|
-
setState({ status: "ready", pages: response.pages });
|
|
2478
|
-
} catch (err) {
|
|
2479
|
-
setState({
|
|
2480
|
-
status: "error",
|
|
2481
|
-
pages: null,
|
|
2482
|
-
error:
|
|
2483
|
-
err instanceof Error ? err : new Error("Marketing pages list failed"),
|
|
2484
|
-
});
|
|
2485
|
-
}
|
|
2486
|
-
}, [client, siteId, enabled]);
|
|
2453
|
+
// This hook only ever calls `load()`, so the controller's single `status`
|
|
2454
|
+
// is describing the index and nothing else.
|
|
2455
|
+
const refresh = useCallback(() => controller.load(), [controller]);
|
|
2487
2456
|
|
|
2488
2457
|
useEffect(() => {
|
|
2489
|
-
|
|
2490
|
-
|
|
2458
|
+
if (!enabled) return;
|
|
2459
|
+
void controller.load();
|
|
2460
|
+
}, [controller, enabled]);
|
|
2491
2461
|
|
|
2492
|
-
return { ...
|
|
2462
|
+
return { ...marketingListView(snap), refresh };
|
|
2493
2463
|
}
|
|
2494
2464
|
|
|
2495
2465
|
/**
|
|
@@ -2504,19 +2474,21 @@ export function useMarketingPagePreloader(options: {
|
|
|
2504
2474
|
siteId: string;
|
|
2505
2475
|
}): (pageKey: string) => Promise<GetSiteMarketingPageResponse | null> {
|
|
2506
2476
|
const { apiKey, baseUrl, siteId } = options;
|
|
2507
|
-
const client =
|
|
2508
|
-
|
|
2509
|
-
|
|
2477
|
+
const { client, controller } = useMarketingController(
|
|
2478
|
+
apiKey,
|
|
2479
|
+
baseUrl,
|
|
2480
|
+
siteId,
|
|
2510
2481
|
);
|
|
2511
2482
|
return useCallback(
|
|
2512
2483
|
async (pageKey: string) => {
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2484
|
+
// The core swallows prefetch failures on purpose — a guess about
|
|
2485
|
+
// where the visitor is going must never surface as an error on the
|
|
2486
|
+
// page they are actually reading. Reading the cache afterwards is
|
|
2487
|
+
// how this keeps returning the response its signature promises.
|
|
2488
|
+
await controller.prefetch(pageKey);
|
|
2489
|
+
return client.site(siteId).marketingPages.getCached(pageKey) ?? null;
|
|
2518
2490
|
},
|
|
2519
|
-
[client, siteId],
|
|
2491
|
+
[client, controller, siteId],
|
|
2520
2492
|
);
|
|
2521
2493
|
}
|
|
2522
2494
|
|
|
@@ -2549,7 +2521,7 @@ export function VerifyEmailFlow(props: {
|
|
|
2549
2521
|
}) {
|
|
2550
2522
|
const { apiKey, baseUrl, sessionToken, currentEmail } = props;
|
|
2551
2523
|
const client = useMemo(
|
|
2552
|
-
() => new
|
|
2524
|
+
() => new BdApiClient({ apiKey, baseUrl }),
|
|
2553
2525
|
[apiKey, baseUrl],
|
|
2554
2526
|
);
|
|
2555
2527
|
const [email, setEmail] = useState(currentEmail ?? "");
|
|
@@ -2627,7 +2599,7 @@ export function VerifyEmailConfirm(props: {
|
|
|
2627
2599
|
}) {
|
|
2628
2600
|
const { apiKey, baseUrl, sessionToken, token, onVerified } = props;
|
|
2629
2601
|
const client = useMemo(
|
|
2630
|
-
() => new
|
|
2602
|
+
() => new BdApiClient({ apiKey, baseUrl }),
|
|
2631
2603
|
[apiKey, baseUrl],
|
|
2632
2604
|
);
|
|
2633
2605
|
const [state, setState] = useState<
|
|
@@ -2698,7 +2670,7 @@ export function VerifyPhoneFlow(props: {
|
|
|
2698
2670
|
}) {
|
|
2699
2671
|
const { apiKey, baseUrl, sessionToken, currentPhone, onVerified } = props;
|
|
2700
2672
|
const client = useMemo(
|
|
2701
|
-
() => new
|
|
2673
|
+
() => new BdApiClient({ apiKey, baseUrl }),
|
|
2702
2674
|
[apiKey, baseUrl],
|
|
2703
2675
|
);
|
|
2704
2676
|
const [phone, setPhone] = useState(currentPhone ?? "");
|
|
@@ -2818,7 +2790,7 @@ export function VerifyPhoneFlow(props: {
|
|
|
2818
2790
|
* bundled: it's ~1,600 entries that change only when Lucide does. An author who
|
|
2819
2791
|
* curated `allowedIcons` skips the fetch entirely.
|
|
2820
2792
|
*/
|
|
2821
|
-
function
|
|
2793
|
+
function BdIconSelect({
|
|
2822
2794
|
field,
|
|
2823
2795
|
value,
|
|
2824
2796
|
set,
|
|
@@ -2851,19 +2823,19 @@ function BiabIconSelect({
|
|
|
2851
2823
|
const results = useMemo(() => filterIconNames(all, query), [all, query]);
|
|
2852
2824
|
|
|
2853
2825
|
return (
|
|
2854
|
-
<div className="
|
|
2826
|
+
<div className="bd-iconselect">
|
|
2855
2827
|
<input
|
|
2856
|
-
className="
|
|
2828
|
+
className="bd-input bd-iconselect__search"
|
|
2857
2829
|
onChange={(e) => setQuery(e.target.value)}
|
|
2858
2830
|
placeholder={field.placeholder || "Search icons…"}
|
|
2859
2831
|
type="text"
|
|
2860
2832
|
value={query}
|
|
2861
2833
|
/>
|
|
2862
2834
|
{selected && (
|
|
2863
|
-
<span className="
|
|
2835
|
+
<span className="bd-iconselect__current">
|
|
2864
2836
|
<img alt={selected} height={16} src={iconUrl(selected)} width={16} />
|
|
2865
2837
|
<button
|
|
2866
|
-
className="
|
|
2838
|
+
className="bd-iconselect__clear"
|
|
2867
2839
|
onClick={() => set("")}
|
|
2868
2840
|
type="button"
|
|
2869
2841
|
>
|
|
@@ -2871,12 +2843,12 @@ function BiabIconSelect({
|
|
|
2871
2843
|
</button>
|
|
2872
2844
|
</span>
|
|
2873
2845
|
)}
|
|
2874
|
-
<div className="
|
|
2846
|
+
<div className="bd-iconselect__grid">
|
|
2875
2847
|
{results.map((name) => (
|
|
2876
2848
|
<button
|
|
2877
2849
|
aria-label={name}
|
|
2878
2850
|
aria-pressed={selected === name}
|
|
2879
|
-
className={`
|
|
2851
|
+
className={`bd-iconselect__item${
|
|
2880
2852
|
selected === name ? "is-selected" : ""
|
|
2881
2853
|
}`}
|
|
2882
2854
|
key={name}
|
|
@@ -2899,7 +2871,7 @@ function BiabIconSelect({
|
|
|
2899
2871
|
* pending file and flushed by the controller at submit, exactly like
|
|
2900
2872
|
* `file_upload`, then replaced by its stored URL.
|
|
2901
2873
|
*/
|
|
2902
|
-
function
|
|
2874
|
+
function BdImageInput({
|
|
2903
2875
|
field,
|
|
2904
2876
|
value,
|
|
2905
2877
|
set,
|
|
@@ -2928,15 +2900,13 @@ function BiabImageInput({
|
|
|
2928
2900
|
const url = typeof value === "string" ? value : "";
|
|
2929
2901
|
|
|
2930
2902
|
return (
|
|
2931
|
-
<div className="
|
|
2903
|
+
<div className="bd-imageinput">
|
|
2932
2904
|
{sources === "both" && (
|
|
2933
|
-
<div className="
|
|
2905
|
+
<div className="bd-imageinput__modes">
|
|
2934
2906
|
{(["upload", "url"] as const).map((m) => (
|
|
2935
2907
|
<button
|
|
2936
2908
|
aria-pressed={mode === m}
|
|
2937
|
-
className={`
|
|
2938
|
-
mode === m ? "is-active" : ""
|
|
2939
|
-
}`}
|
|
2909
|
+
className={`bd-imageinput__mode${mode === m ? "is-active" : ""}`}
|
|
2940
2910
|
key={m}
|
|
2941
2911
|
onClick={() => setMode(m)}
|
|
2942
2912
|
type="button"
|
|
@@ -2949,7 +2919,7 @@ function BiabImageInput({
|
|
|
2949
2919
|
{mode === "upload" && sources !== "url" ? (
|
|
2950
2920
|
<input
|
|
2951
2921
|
accept={(cfg.allowedTypes ?? ["image/*"]).join(",")}
|
|
2952
|
-
className="
|
|
2922
|
+
className="bd-input bd-imageinput__file"
|
|
2953
2923
|
onChange={(e) => {
|
|
2954
2924
|
const file = e.target.files?.[0];
|
|
2955
2925
|
if (!file) return;
|
|
@@ -2966,7 +2936,7 @@ function BiabImageInput({
|
|
|
2966
2936
|
/>
|
|
2967
2937
|
) : (
|
|
2968
2938
|
<input
|
|
2969
|
-
className="
|
|
2939
|
+
className="bd-input bd-imageinput__url"
|
|
2970
2940
|
onChange={(e) => set(e.target.value)}
|
|
2971
2941
|
placeholder={field.placeholder || "https://example.com/image.png"}
|
|
2972
2942
|
type="url"
|
|
@@ -2976,12 +2946,12 @@ function BiabImageInput({
|
|
|
2976
2946
|
{cfg.showPreview !== false && (pending || url) ? (
|
|
2977
2947
|
<img
|
|
2978
2948
|
alt=""
|
|
2979
|
-
className="
|
|
2949
|
+
className="bd-imageinput__preview"
|
|
2980
2950
|
src={pending ? "" : url}
|
|
2981
2951
|
/>
|
|
2982
2952
|
) : null}
|
|
2983
2953
|
{pending && (
|
|
2984
|
-
<span className="
|
|
2954
|
+
<span className="bd-imageinput__pending">
|
|
2985
2955
|
{pending.name} — uploads on submit.
|
|
2986
2956
|
</span>
|
|
2987
2957
|
)}
|
|
@@ -2989,13 +2959,13 @@ function BiabImageInput({
|
|
|
2989
2959
|
);
|
|
2990
2960
|
}
|
|
2991
2961
|
|
|
2992
|
-
// ───
|
|
2962
|
+
// ─── BdForm (schema-driven drop-in) ──────────────────────────────────────
|
|
2993
2963
|
//
|
|
2994
|
-
// `<
|
|
2995
|
-
// multi-step-aware form from a
|
|
2964
|
+
// `<BdForm slug="contact" client={bd} />` renders a complete, validated,
|
|
2965
|
+
// multi-step-aware form from a BD form schema. All runtime behaviour lives in
|
|
2996
2966
|
// the framework-agnostic `createFormController` (from `@businessdash/sdk/forms`);
|
|
2997
2967
|
// this binding is the React view layer: it subscribes via `useSyncExternalStore`
|
|
2998
|
-
// and renders the controller's `snapshot()` with the `
|
|
2968
|
+
// and renders the controller's `snapshot()` with the `bd-*` light-DOM class
|
|
2999
2969
|
// convention (no shadow root) so consumer CSS cascades and overrides freely.
|
|
3000
2970
|
//
|
|
3001
2971
|
// It reproduces the dashboard FormPreview: per-type field rendering,
|
|
@@ -3003,14 +2973,14 @@ function BiabImageInput({
|
|
|
3003
2973
|
// gating, flex_row layout, multi-step ("consecutive") navigation with a bar or
|
|
3004
2974
|
// step progress indicator, entrance animations, and the org icon.
|
|
3005
2975
|
|
|
3006
|
-
// `
|
|
2976
|
+
// `BdFormsClient` is defined in `forms-core/controller.ts` — the layer that
|
|
3007
2977
|
// actually consumes it — and re-exported here so it stays importable from
|
|
3008
2978
|
// `@businessdash/sdk/react`, where consumers have always found it.
|
|
3009
|
-
export type {
|
|
2979
|
+
export type { BdFormsClient };
|
|
3010
2980
|
|
|
3011
|
-
// A `
|
|
3012
|
-
// drop `<
|
|
3013
|
-
const
|
|
2981
|
+
// A `BdFormProvider` lets a consumer supply the client once at the root and
|
|
2982
|
+
// drop `<BdForm slug="…" />` anywhere below without threading the client prop.
|
|
2983
|
+
const BdFormClientContext = createContext<BdFormsClient | null>(null);
|
|
3014
2984
|
|
|
3015
2985
|
/** Production package-API base URL — the default target for a publishable token
|
|
3016
2986
|
* when no `baseUrl` is supplied. Override via the `baseUrl` prop for staging or
|
|
@@ -3019,11 +2989,11 @@ const BiabFormClientContext = createContext<BiabFormsClient | null>(null);
|
|
|
3019
2989
|
* request), so a browser publishable-token client must target the apex. */
|
|
3020
2990
|
export const DEFAULT_PACKAGE_BASE_URL = "https://biab.app/api/package/v1";
|
|
3021
2991
|
|
|
3022
|
-
export type
|
|
2992
|
+
export type BdFormProviderProps = {
|
|
3023
2993
|
/** A forms client (or same-origin proxy). Provide this OR a publishable `token`. */
|
|
3024
|
-
client?:
|
|
2994
|
+
client?: BdFormsClient;
|
|
3025
2995
|
/**
|
|
3026
|
-
* A publishable token (`pk_…`) — builds a browser client that calls
|
|
2996
|
+
* A publishable token (`pk_…`) — builds a browser client that calls BD
|
|
3027
2997
|
* directly. Safe in browser JS: it's origin-locked (the browser sends Origin
|
|
3028
2998
|
* automatically), rate-limited, and scoped to public read/submit actions
|
|
3029
2999
|
* only. Requires `siteId`. Use instead of `client` for the zero-backend path.
|
|
@@ -3036,21 +3006,21 @@ export type BiabFormProviderProps = {
|
|
|
3036
3006
|
children: ReactNode;
|
|
3037
3007
|
};
|
|
3038
3008
|
|
|
3039
|
-
/** Provide a
|
|
3009
|
+
/** Provide a BD forms client to every nested `<BdForm>`. Pass a `client`, or
|
|
3040
3010
|
* a publishable `token` + `siteId` (+ optional `baseUrl`) to build a browser
|
|
3041
3011
|
* client. */
|
|
3042
|
-
export function
|
|
3012
|
+
export function BdFormProvider({
|
|
3043
3013
|
client,
|
|
3044
3014
|
token,
|
|
3045
3015
|
siteId,
|
|
3046
3016
|
baseUrl,
|
|
3047
3017
|
children,
|
|
3048
|
-
}:
|
|
3049
|
-
const resolved = useMemo<
|
|
3018
|
+
}: BdFormProviderProps) {
|
|
3019
|
+
const resolved = useMemo<BdFormsClient | null>(
|
|
3050
3020
|
() =>
|
|
3051
3021
|
client ??
|
|
3052
3022
|
(token && siteId
|
|
3053
|
-
?
|
|
3023
|
+
? createBdClient({
|
|
3054
3024
|
apiKey: token,
|
|
3055
3025
|
siteId,
|
|
3056
3026
|
baseUrl: baseUrl ?? DEFAULT_PACKAGE_BASE_URL,
|
|
@@ -3059,18 +3029,18 @@ export function BiabFormProvider({
|
|
|
3059
3029
|
[client, token, siteId, baseUrl],
|
|
3060
3030
|
);
|
|
3061
3031
|
return (
|
|
3062
|
-
<
|
|
3032
|
+
<BdFormClientContext.Provider value={resolved}>
|
|
3063
3033
|
{children}
|
|
3064
|
-
</
|
|
3034
|
+
</BdFormClientContext.Provider>
|
|
3065
3035
|
);
|
|
3066
3036
|
}
|
|
3067
3037
|
|
|
3068
|
-
/** Read the client from a surrounding `<
|
|
3069
|
-
export function
|
|
3070
|
-
return useContext(
|
|
3038
|
+
/** Read the client from a surrounding `<BdFormProvider>`, if any. */
|
|
3039
|
+
export function useBdFormsClient(): BdFormsClient | null {
|
|
3040
|
+
return useContext(BdFormClientContext);
|
|
3071
3041
|
}
|
|
3072
3042
|
|
|
3073
|
-
/** Fetch open booking slots for an auto-`schedule` field.
|
|
3043
|
+
/** Fetch open booking slots for an auto-`schedule` field. BdForm derives this
|
|
3074
3044
|
* from its client + slug and provides it via context so the deep field renderer
|
|
3075
3045
|
* needn't thread it. Undefined when the client can't fetch slots. */
|
|
3076
3046
|
type FetchScheduleSlots = (input: {
|
|
@@ -3082,28 +3052,28 @@ const FetchScheduleSlotsContext = createContext<FetchScheduleSlots | undefined>(
|
|
|
3082
3052
|
undefined,
|
|
3083
3053
|
);
|
|
3084
3054
|
|
|
3085
|
-
/** Autofill (`autocomplete`) config for the field tree, provided by `<
|
|
3055
|
+
/** Autofill (`autocomplete`) config for the field tree, provided by `<BdForm>`.
|
|
3086
3056
|
* Default `{}` → inference on, no overrides. */
|
|
3087
3057
|
const AutocompleteContext = createContext<AutocompleteConfig>({});
|
|
3088
3058
|
|
|
3089
|
-
type
|
|
3059
|
+
type BdScheduleAutoValue = { slotStartAt?: string; timezone?: string };
|
|
3090
3060
|
|
|
3091
|
-
/** Auto-schedule (instant booking) picker for `<
|
|
3061
|
+
/** Auto-schedule (instant booking) picker for `<BdForm>`: fetches the assigned
|
|
3092
3062
|
* staff's open slots and lets the visitor pick one. Value `{ slotStartAt,
|
|
3093
3063
|
* timezone }`; the server books the meeting on submit. Dependency-light (plain
|
|
3094
|
-
* elements + `
|
|
3095
|
-
function
|
|
3064
|
+
* elements + `bd-field__schedule-*` classes). */
|
|
3065
|
+
function BdScheduleAuto({
|
|
3096
3066
|
field,
|
|
3097
3067
|
value,
|
|
3098
3068
|
onChange,
|
|
3099
3069
|
}: {
|
|
3100
3070
|
field: { id: string };
|
|
3101
3071
|
value: unknown;
|
|
3102
|
-
onChange: (v:
|
|
3072
|
+
onChange: (v: BdScheduleAutoValue) => void;
|
|
3103
3073
|
}) {
|
|
3104
3074
|
const fetchSlots = useContext(FetchScheduleSlotsContext);
|
|
3105
|
-
const v:
|
|
3106
|
-
value && typeof value === "object" ? (value as
|
|
3075
|
+
const v: BdScheduleAutoValue =
|
|
3076
|
+
value && typeof value === "object" ? (value as BdScheduleAutoValue) : {};
|
|
3107
3077
|
const tz =
|
|
3108
3078
|
typeof Intl !== "undefined"
|
|
3109
3079
|
? (Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC")
|
|
@@ -3135,24 +3105,20 @@ function BiabScheduleAuto({
|
|
|
3135
3105
|
|
|
3136
3106
|
if (!fetchSlots) {
|
|
3137
3107
|
return (
|
|
3138
|
-
<p className="
|
|
3108
|
+
<p className="bd-field__schedule-hint">Booking isn't available here.</p>
|
|
3139
3109
|
);
|
|
3140
3110
|
}
|
|
3141
3111
|
if (phase === "loading" || phase === "idle") {
|
|
3142
|
-
return
|
|
3143
|
-
<p className="biab-field__schedule-hint">Loading available times…</p>
|
|
3144
|
-
);
|
|
3112
|
+
return <p className="bd-field__schedule-hint">Loading available times…</p>;
|
|
3145
3113
|
}
|
|
3146
3114
|
if (phase === "error") {
|
|
3147
3115
|
return (
|
|
3148
|
-
<p className="
|
|
3149
|
-
Couldn't load times right now.
|
|
3150
|
-
</p>
|
|
3116
|
+
<p className="bd-field__schedule-hint">Couldn't load times right now.</p>
|
|
3151
3117
|
);
|
|
3152
3118
|
}
|
|
3153
3119
|
if (slots.length === 0) {
|
|
3154
3120
|
return (
|
|
3155
|
-
<p className="
|
|
3121
|
+
<p className="bd-field__schedule-hint">
|
|
3156
3122
|
No open times in the next few weeks.
|
|
3157
3123
|
</p>
|
|
3158
3124
|
);
|
|
@@ -3176,14 +3142,14 @@ function BiabScheduleAuto({
|
|
|
3176
3142
|
}
|
|
3177
3143
|
|
|
3178
3144
|
return (
|
|
3179
|
-
<div className="
|
|
3145
|
+
<div className="bd-field__schedule-auto">
|
|
3180
3146
|
{[...byDay.entries()].map(([day, times]) => (
|
|
3181
|
-
<div className="
|
|
3182
|
-
<p className="
|
|
3183
|
-
<div className="
|
|
3147
|
+
<div className="bd-field__schedule-day" key={day}>
|
|
3148
|
+
<p className="bd-field__schedule-day-label">{day}</p>
|
|
3149
|
+
<div className="bd-field__schedule-times">
|
|
3184
3150
|
{times.map((t) => (
|
|
3185
3151
|
<button
|
|
3186
|
-
className="
|
|
3152
|
+
className="bd-field__schedule-slot"
|
|
3187
3153
|
data-selected={v.slotStartAt === t.startAt ? "true" : undefined}
|
|
3188
3154
|
key={t.startAt}
|
|
3189
3155
|
onClick={() =>
|
|
@@ -3216,18 +3182,18 @@ function useFormSnapshot(controller: FormController) {
|
|
|
3216
3182
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
3217
3183
|
}
|
|
3218
3184
|
|
|
3219
|
-
export type
|
|
3185
|
+
export type BdFormProps = {
|
|
3220
3186
|
/** Form slug to load via the client. Omit when passing a pre-fetched `schema`. */
|
|
3221
3187
|
slug?: string;
|
|
3222
3188
|
/**
|
|
3223
3189
|
* SDK client (or same-origin proxy). Falls back to `token`, then to a
|
|
3224
|
-
* surrounding `<
|
|
3190
|
+
* surrounding `<BdFormProvider>`. Required unless a pre-fetched `schema` is
|
|
3225
3191
|
* supplied AND no submit is needed (submit needs a client).
|
|
3226
3192
|
*/
|
|
3227
|
-
client?:
|
|
3193
|
+
client?: BdFormsClient;
|
|
3228
3194
|
/**
|
|
3229
|
-
* A publishable token (`pk_…`) — builds a browser client that calls
|
|
3230
|
-
* directly, so `<
|
|
3195
|
+
* A publishable token (`pk_…`) — builds a browser client that calls BD
|
|
3196
|
+
* directly, so `<BdForm slug token siteId />` works with zero backend
|
|
3231
3197
|
* wiring. Safe in browser JS (origin-locked, rate-limited, public
|
|
3232
3198
|
* read/submit only). Requires `siteId`.
|
|
3233
3199
|
*/
|
|
@@ -3270,14 +3236,14 @@ export type BiabFormProps = {
|
|
|
3270
3236
|
* (off). Wins over `autoComplete` and any schema-level token.
|
|
3271
3237
|
*/
|
|
3272
3238
|
fieldAutoComplete?: Record<string, string | boolean>;
|
|
3273
|
-
/** Extra class on the root `.
|
|
3239
|
+
/** Extra class on the root `.bd-form` element. `class` is accepted as an
|
|
3274
3240
|
* alias for non-JSX callers / parity with other framework bindings. */
|
|
3275
3241
|
className?: string;
|
|
3276
3242
|
class?: string;
|
|
3277
3243
|
style?: CSSProperties;
|
|
3278
3244
|
};
|
|
3279
3245
|
|
|
3280
|
-
// ──
|
|
3246
|
+
// ── bd-* class helpers (mirrors FormPreview's `fieldCssKey`) ───────────────
|
|
3281
3247
|
|
|
3282
3248
|
function cx(...parts: Array<string | false | null | undefined>): string {
|
|
3283
3249
|
return parts.filter(Boolean).join(" ");
|
|
@@ -3360,7 +3326,7 @@ function safeHttpHref(raw: string | undefined): string | undefined {
|
|
|
3360
3326
|
// ── Self-contained date-range picker (no external calendar dep) ─────────────
|
|
3361
3327
|
// A popover trigger + a month grid with click-to-select range. Stored value is
|
|
3362
3328
|
// `{ start, end }` as `yyyy-MM-dd` strings (identical to the app's contract).
|
|
3363
|
-
// Styled via
|
|
3329
|
+
// Styled via bd-* classes so consumers can theme it.
|
|
3364
3330
|
|
|
3365
3331
|
function parseIsoDate(s: string | undefined): Date | null {
|
|
3366
3332
|
if (!s) return null;
|
|
@@ -3375,7 +3341,7 @@ function toIsoDate(d: Date): string {
|
|
|
3375
3341
|
return `${y}-${m}-${day}`;
|
|
3376
3342
|
}
|
|
3377
3343
|
|
|
3378
|
-
const
|
|
3344
|
+
const BD_MONTHS = [
|
|
3379
3345
|
"January",
|
|
3380
3346
|
"February",
|
|
3381
3347
|
"March",
|
|
@@ -3389,9 +3355,9 @@ const BIAB_MONTHS = [
|
|
|
3389
3355
|
"November",
|
|
3390
3356
|
"December",
|
|
3391
3357
|
];
|
|
3392
|
-
const
|
|
3358
|
+
const BD_WEEKDAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
3393
3359
|
|
|
3394
|
-
function
|
|
3360
|
+
function BdDateRange({
|
|
3395
3361
|
value,
|
|
3396
3362
|
setValue,
|
|
3397
3363
|
}: {
|
|
@@ -3458,12 +3424,12 @@ function BiabDateRange({
|
|
|
3458
3424
|
: "Pick a date range";
|
|
3459
3425
|
|
|
3460
3426
|
// Positioning is the only inline style (so the popover never overlaps content
|
|
3461
|
-
// even before the CSS loads); everything visual lives in `
|
|
3427
|
+
// even before the CSS loads); everything visual lives in `bd-forms.css`,
|
|
3462
3428
|
// which mirrors the COSS/shadcn calendar (import it from the SDK).
|
|
3463
3429
|
return (
|
|
3464
|
-
<div className="
|
|
3430
|
+
<div className="bd-daterange" ref={ref} style={{ position: "relative" }}>
|
|
3465
3431
|
<button
|
|
3466
|
-
className="
|
|
3432
|
+
className="bd-input bd-field__input bd-daterange__trigger"
|
|
3467
3433
|
onClick={() => setOpen((o) => !o)}
|
|
3468
3434
|
type="button"
|
|
3469
3435
|
>
|
|
@@ -3471,13 +3437,13 @@ function BiabDateRange({
|
|
|
3471
3437
|
</button>
|
|
3472
3438
|
{open && (
|
|
3473
3439
|
<div
|
|
3474
|
-
className="
|
|
3440
|
+
className="bd-daterange__popover"
|
|
3475
3441
|
style={{ position: "absolute", zIndex: 50 }}
|
|
3476
3442
|
>
|
|
3477
|
-
<div className="
|
|
3443
|
+
<div className="bd-daterange__nav">
|
|
3478
3444
|
<button
|
|
3479
3445
|
aria-label="Previous month"
|
|
3480
|
-
className="
|
|
3446
|
+
className="bd-daterange__navbtn"
|
|
3481
3447
|
onClick={() =>
|
|
3482
3448
|
setView((s) =>
|
|
3483
3449
|
s.m === 0 ? { y: s.y - 1, m: 11 } : { y: s.y, m: s.m - 1 },
|
|
@@ -3487,12 +3453,12 @@ function BiabDateRange({
|
|
|
3487
3453
|
>
|
|
3488
3454
|
‹
|
|
3489
3455
|
</button>
|
|
3490
|
-
<span className="
|
|
3491
|
-
{
|
|
3456
|
+
<span className="bd-daterange__title">
|
|
3457
|
+
{BD_MONTHS[view.m]} {view.y}
|
|
3492
3458
|
</span>
|
|
3493
3459
|
<button
|
|
3494
3460
|
aria-label="Next month"
|
|
3495
|
-
className="
|
|
3461
|
+
className="bd-daterange__navbtn"
|
|
3496
3462
|
onClick={() =>
|
|
3497
3463
|
setView((s) =>
|
|
3498
3464
|
s.m === 11 ? { y: s.y + 1, m: 0 } : { y: s.y, m: s.m + 1 },
|
|
@@ -3503,22 +3469,20 @@ function BiabDateRange({
|
|
|
3503
3469
|
›
|
|
3504
3470
|
</button>
|
|
3505
3471
|
</div>
|
|
3506
|
-
<div className="
|
|
3507
|
-
{
|
|
3508
|
-
<span className="
|
|
3472
|
+
<div className="bd-daterange__grid">
|
|
3473
|
+
{BD_WEEKDAYS.map((w) => (
|
|
3474
|
+
<span className="bd-daterange__dow" key={w}>
|
|
3509
3475
|
{w}
|
|
3510
3476
|
</span>
|
|
3511
3477
|
))}
|
|
3512
3478
|
{cells.map((d, i) => {
|
|
3513
3479
|
if (!d) {
|
|
3514
|
-
return
|
|
3515
|
-
<span className="biab-daterange__blank" key={`b-${i}`} />
|
|
3516
|
-
);
|
|
3480
|
+
return <span className="bd-daterange__blank" key={`b-${i}`} />;
|
|
3517
3481
|
}
|
|
3518
3482
|
const st = dayState(d);
|
|
3519
3483
|
return (
|
|
3520
3484
|
<button
|
|
3521
|
-
className="
|
|
3485
|
+
className="bd-daterange__day"
|
|
3522
3486
|
data-range-end={st.end || undefined}
|
|
3523
3487
|
data-range-middle={st.middle || undefined}
|
|
3524
3488
|
data-range-start={st.start || undefined}
|
|
@@ -3539,9 +3503,9 @@ function BiabDateRange({
|
|
|
3539
3503
|
);
|
|
3540
3504
|
}
|
|
3541
3505
|
|
|
3542
|
-
// ── Per-type input renderer (light DOM,
|
|
3506
|
+
// ── Per-type input renderer (light DOM, bd-* classes, no shadcn) ──────────
|
|
3543
3507
|
|
|
3544
|
-
function
|
|
3508
|
+
function BdFieldInput({
|
|
3545
3509
|
field,
|
|
3546
3510
|
value,
|
|
3547
3511
|
setValue,
|
|
@@ -3556,23 +3520,23 @@ function BiabFieldInput({
|
|
|
3556
3520
|
const set = (v: unknown) => setValue(field.id, v);
|
|
3557
3521
|
const ph = field.placeholder || "";
|
|
3558
3522
|
// Resolved HTML autofill token for this field (or undefined to omit the
|
|
3559
|
-
// attribute). Honors the global `<
|
|
3523
|
+
// attribute). Honors the global `<BdForm autoComplete>` switch and per-field
|
|
3560
3524
|
// overrides; see forms-core/autocomplete.ts.
|
|
3561
3525
|
const acConfig = useContext(AutocompleteContext);
|
|
3562
3526
|
const autoComplete = resolveFieldAutocomplete(field, acConfig);
|
|
3563
3527
|
|
|
3564
3528
|
switch (field.type) {
|
|
3565
3529
|
case "title":
|
|
3566
|
-
return <h2 className="
|
|
3530
|
+
return <h2 className="bd-field__title">{field.label || "Untitled"}</h2>;
|
|
3567
3531
|
case "subtitle":
|
|
3568
|
-
return <h3 className="
|
|
3532
|
+
return <h3 className="bd-field__subtitle">{field.label || ""}</h3>;
|
|
3569
3533
|
case "separator":
|
|
3570
|
-
return <hr className="
|
|
3534
|
+
return <hr className="bd-field__separator" />;
|
|
3571
3535
|
case "richtext":
|
|
3572
3536
|
// `content` is server-sanitized HTML.
|
|
3573
3537
|
return field.content ? (
|
|
3574
3538
|
<div
|
|
3575
|
-
className="
|
|
3539
|
+
className="bd-field__richtext"
|
|
3576
3540
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: server-sanitized
|
|
3577
3541
|
dangerouslySetInnerHTML={{ __html: field.content }}
|
|
3578
3542
|
/>
|
|
@@ -3580,7 +3544,7 @@ function BiabFieldInput({
|
|
|
3580
3544
|
case "body_text":
|
|
3581
3545
|
// Plain body paragraph (no HTML). Display only.
|
|
3582
3546
|
return field.content ? (
|
|
3583
|
-
<p className="
|
|
3547
|
+
<p className="bd-field__body">{field.content}</p>
|
|
3584
3548
|
) : null;
|
|
3585
3549
|
case "bullet_list": {
|
|
3586
3550
|
const lc = field.listConfig ?? {};
|
|
@@ -3588,20 +3552,20 @@ function BiabFieldInput({
|
|
|
3588
3552
|
const items = lc.items ?? [];
|
|
3589
3553
|
if (variant === "details") {
|
|
3590
3554
|
return (
|
|
3591
|
-
<div className="
|
|
3555
|
+
<div className="bd-field__points">
|
|
3592
3556
|
{items.map((it, i) => (
|
|
3593
|
-
<div className="
|
|
3557
|
+
<div className="bd-field__point" key={`${field.id}-li-${i}`}>
|
|
3594
3558
|
{it.icon && (
|
|
3595
3559
|
<span
|
|
3596
3560
|
aria-hidden="true"
|
|
3597
|
-
className="
|
|
3561
|
+
className="bd-field__point-icon"
|
|
3598
3562
|
data-icon={it.icon}
|
|
3599
3563
|
/>
|
|
3600
3564
|
)}
|
|
3601
|
-
<span className="
|
|
3602
|
-
<span className="
|
|
3565
|
+
<span className="bd-field__point-body">
|
|
3566
|
+
<span className="bd-field__point-title">{it.text}</span>
|
|
3603
3567
|
{it.description && (
|
|
3604
|
-
<span className="
|
|
3568
|
+
<span className="bd-field__point-desc">
|
|
3605
3569
|
{it.description}
|
|
3606
3570
|
</span>
|
|
3607
3571
|
)}
|
|
@@ -3612,9 +3576,9 @@ function BiabFieldInput({
|
|
|
3612
3576
|
);
|
|
3613
3577
|
}
|
|
3614
3578
|
return (
|
|
3615
|
-
<div className="
|
|
3579
|
+
<div className="bd-field__bullets">
|
|
3616
3580
|
{lc.title && (
|
|
3617
|
-
<div className="
|
|
3581
|
+
<div className="bd-field__bullets-title">{lc.title}</div>
|
|
3618
3582
|
)}
|
|
3619
3583
|
<ul>
|
|
3620
3584
|
{items.map((it, i) => (
|
|
@@ -3630,7 +3594,7 @@ function BiabFieldInput({
|
|
|
3630
3594
|
const rows = cfg.rows ?? [];
|
|
3631
3595
|
if (columns.length === 0) return null;
|
|
3632
3596
|
return (
|
|
3633
|
-
<div className="
|
|
3597
|
+
<div className="bd-field__table">
|
|
3634
3598
|
<table>
|
|
3635
3599
|
<thead>
|
|
3636
3600
|
<tr>
|
|
@@ -3680,13 +3644,13 @@ function BiabFieldInput({
|
|
|
3680
3644
|
const hasVal = value !== null && value !== undefined && value !== "";
|
|
3681
3645
|
if (cfg?.sensitive && !hasVal) {
|
|
3682
3646
|
return (
|
|
3683
|
-
<output className="
|
|
3647
|
+
<output className="bd-field__calc bd-field__calc--pending">
|
|
3684
3648
|
Calculated on submit.
|
|
3685
3649
|
</output>
|
|
3686
3650
|
);
|
|
3687
3651
|
}
|
|
3688
3652
|
return (
|
|
3689
|
-
<output className="
|
|
3653
|
+
<output className="bd-field__calc">
|
|
3690
3654
|
{hasVal ? formatCalcValue(value as FormulaValue, cfg?.display) : "—"}
|
|
3691
3655
|
</output>
|
|
3692
3656
|
);
|
|
@@ -3701,7 +3665,7 @@ function BiabFieldInput({
|
|
|
3701
3665
|
return cfg.src ? (
|
|
3702
3666
|
<img
|
|
3703
3667
|
alt={cfg.alt || ""}
|
|
3704
|
-
className="
|
|
3668
|
+
className="bd-field__image"
|
|
3705
3669
|
src={cfg.src}
|
|
3706
3670
|
style={{
|
|
3707
3671
|
width: cfg.width ? `${cfg.width}px` : "auto",
|
|
@@ -3719,7 +3683,7 @@ function BiabFieldInput({
|
|
|
3719
3683
|
return (
|
|
3720
3684
|
<textarea
|
|
3721
3685
|
autoComplete={autoComplete}
|
|
3722
|
-
className="
|
|
3686
|
+
className="bd-textarea bd-field__input"
|
|
3723
3687
|
onChange={(e) => set(e.target.value)}
|
|
3724
3688
|
placeholder={ph}
|
|
3725
3689
|
rows={cfg.rows ?? 4}
|
|
@@ -3744,11 +3708,11 @@ function BiabFieldInput({
|
|
|
3744
3708
|
(field as { display?: string }).display,
|
|
3745
3709
|
)
|
|
3746
3710
|
) {
|
|
3747
|
-
const listId = `
|
|
3711
|
+
const listId = `bd-list-${field.id}`;
|
|
3748
3712
|
return (
|
|
3749
3713
|
<>
|
|
3750
3714
|
<input
|
|
3751
|
-
className="
|
|
3715
|
+
className="bd-input bd-combobox bd-field__input"
|
|
3752
3716
|
list={listId}
|
|
3753
3717
|
onChange={(e) => set(e.target.value)}
|
|
3754
3718
|
placeholder={ph || "Search…"}
|
|
@@ -3765,7 +3729,7 @@ function BiabFieldInput({
|
|
|
3765
3729
|
}
|
|
3766
3730
|
return (
|
|
3767
3731
|
<select
|
|
3768
|
-
className="
|
|
3732
|
+
className="bd-select bd-field__input"
|
|
3769
3733
|
onChange={(e) => set(e.target.value)}
|
|
3770
3734
|
value={typeof value === "string" ? value : ""}
|
|
3771
3735
|
>
|
|
@@ -3779,14 +3743,14 @@ function BiabFieldInput({
|
|
|
3779
3743
|
);
|
|
3780
3744
|
}
|
|
3781
3745
|
case "icon_select":
|
|
3782
|
-
return <
|
|
3746
|
+
return <BdIconSelect field={field} set={set} value={value} />;
|
|
3783
3747
|
case "image_input":
|
|
3784
|
-
return <
|
|
3748
|
+
return <BdImageInput field={field} set={set} value={value} />;
|
|
3785
3749
|
case "radio":
|
|
3786
3750
|
return (
|
|
3787
|
-
<div className="
|
|
3751
|
+
<div className="bd-field__radios">
|
|
3788
3752
|
{(field.options ?? []).map((opt) => (
|
|
3789
|
-
<label className="
|
|
3753
|
+
<label className="bd-field__radio" key={opt}>
|
|
3790
3754
|
<input
|
|
3791
3755
|
checked={value === opt}
|
|
3792
3756
|
name={field.id}
|
|
@@ -3806,7 +3770,7 @@ function BiabFieldInput({
|
|
|
3806
3770
|
if ((field as { display?: string }).display === "dropdown") {
|
|
3807
3771
|
return (
|
|
3808
3772
|
<select
|
|
3809
|
-
className="
|
|
3773
|
+
className="bd-select bd-field__input"
|
|
3810
3774
|
multiple
|
|
3811
3775
|
onChange={(e) =>
|
|
3812
3776
|
set(Array.from(e.target.selectedOptions).map((o) => o.value))
|
|
@@ -3822,11 +3786,11 @@ function BiabFieldInput({
|
|
|
3822
3786
|
);
|
|
3823
3787
|
}
|
|
3824
3788
|
return (
|
|
3825
|
-
<div className="
|
|
3789
|
+
<div className="bd-field__checkboxes">
|
|
3826
3790
|
{(field.options ?? []).map((opt) => {
|
|
3827
3791
|
const checked = arr.includes(opt);
|
|
3828
3792
|
return (
|
|
3829
|
-
<label className="
|
|
3793
|
+
<label className="bd-field__checkbox" key={opt}>
|
|
3830
3794
|
<input
|
|
3831
3795
|
checked={checked}
|
|
3832
3796
|
onChange={(e) =>
|
|
@@ -3865,7 +3829,7 @@ function BiabFieldInput({
|
|
|
3865
3829
|
};
|
|
3866
3830
|
return (
|
|
3867
3831
|
<div
|
|
3868
|
-
className="
|
|
3832
|
+
className="bd-field__choice-cards"
|
|
3869
3833
|
role={ccMulti ? "group" : "radiogroup"}
|
|
3870
3834
|
>
|
|
3871
3835
|
{ccOptions.map((opt) => {
|
|
@@ -3875,7 +3839,7 @@ function BiabFieldInput({
|
|
|
3875
3839
|
return (
|
|
3876
3840
|
<button
|
|
3877
3841
|
aria-pressed={active}
|
|
3878
|
-
className="
|
|
3842
|
+
className="bd-field__choice-card"
|
|
3879
3843
|
data-active={active ? "" : undefined}
|
|
3880
3844
|
data-product-id={opt.productId || undefined}
|
|
3881
3845
|
key={opt.label}
|
|
@@ -3886,35 +3850,35 @@ function BiabFieldInput({
|
|
|
3886
3850
|
{opt.imageUrl ? (
|
|
3887
3851
|
<img
|
|
3888
3852
|
alt={opt.label}
|
|
3889
|
-
className="
|
|
3853
|
+
className="bd-field__choice-card-image"
|
|
3890
3854
|
src={opt.imageUrl}
|
|
3891
3855
|
/>
|
|
3892
3856
|
) : opt.icon ? (
|
|
3893
3857
|
<span
|
|
3894
3858
|
aria-hidden="true"
|
|
3895
|
-
className="
|
|
3859
|
+
className="bd-field__choice-card-icon"
|
|
3896
3860
|
data-icon={opt.icon}
|
|
3897
3861
|
/>
|
|
3898
3862
|
) : null}
|
|
3899
|
-
<span className="
|
|
3900
|
-
<span className="
|
|
3901
|
-
<span className="
|
|
3863
|
+
<span className="bd-field__choice-card-body">
|
|
3864
|
+
<span className="bd-field__choice-card-header">
|
|
3865
|
+
<span className="bd-field__choice-card-label">
|
|
3902
3866
|
{opt.label}
|
|
3903
3867
|
</span>
|
|
3904
3868
|
{opt.price ? (
|
|
3905
|
-
<span className="
|
|
3869
|
+
<span className="bd-field__choice-card-price">
|
|
3906
3870
|
{opt.price}
|
|
3907
3871
|
</span>
|
|
3908
3872
|
) : null}
|
|
3909
3873
|
</span>
|
|
3910
3874
|
{opt.description ? (
|
|
3911
|
-
<span className="
|
|
3875
|
+
<span className="bd-field__choice-card-description">
|
|
3912
3876
|
{opt.description}
|
|
3913
3877
|
</span>
|
|
3914
3878
|
) : null}
|
|
3915
3879
|
{safeHttpHref(opt.href) ? (
|
|
3916
3880
|
<a
|
|
3917
|
-
className="
|
|
3881
|
+
className="bd-field__choice-card-link"
|
|
3918
3882
|
href={safeHttpHref(opt.href)}
|
|
3919
3883
|
onClick={(e) => e.stopPropagation()}
|
|
3920
3884
|
rel="noopener noreferrer nofollow"
|
|
@@ -3932,7 +3896,7 @@ function BiabFieldInput({
|
|
|
3932
3896
|
}
|
|
3933
3897
|
case "boolean":
|
|
3934
3898
|
return (
|
|
3935
|
-
<label className="
|
|
3899
|
+
<label className="bd-field__switch">
|
|
3936
3900
|
<input
|
|
3937
3901
|
checked={value === true}
|
|
3938
3902
|
onChange={(e) => set(e.target.checked)}
|
|
@@ -3944,7 +3908,7 @@ function BiabFieldInput({
|
|
|
3944
3908
|
case "date":
|
|
3945
3909
|
return (
|
|
3946
3910
|
<input
|
|
3947
|
-
className="
|
|
3911
|
+
className="bd-input bd-field__input"
|
|
3948
3912
|
onChange={(e) => set(e.target.value)}
|
|
3949
3913
|
type="date"
|
|
3950
3914
|
value={typeof value === "string" ? value : ""}
|
|
@@ -3973,7 +3937,7 @@ function BiabFieldInput({
|
|
|
3973
3937
|
};
|
|
3974
3938
|
return (
|
|
3975
3939
|
<select
|
|
3976
|
-
className="
|
|
3940
|
+
className="bd-select bd-field__input"
|
|
3977
3941
|
onChange={(e) => set(e.target.value)}
|
|
3978
3942
|
value={typeof value === "string" ? value : ""}
|
|
3979
3943
|
>
|
|
@@ -3996,15 +3960,15 @@ function BiabFieldInput({
|
|
|
3996
3960
|
: {};
|
|
3997
3961
|
const inputType = (k: string) => (k === "date" ? "date" : "time");
|
|
3998
3962
|
return (
|
|
3999
|
-
<div className="
|
|
3963
|
+
<div className="bd-field__grid2">
|
|
4000
3964
|
{pair.map((k) => (
|
|
4001
|
-
<label className="
|
|
4002
|
-
<span className="
|
|
3965
|
+
<label className="bd-field__sub" key={k}>
|
|
3966
|
+
<span className="bd-field__sublabel">
|
|
4003
3967
|
{k[0]?.toUpperCase()}
|
|
4004
3968
|
{k.slice(1)}
|
|
4005
3969
|
</span>
|
|
4006
3970
|
<input
|
|
4007
|
-
className="
|
|
3971
|
+
className="bd-input"
|
|
4008
3972
|
onChange={(e) => set({ ...v, [k]: e.target.value })}
|
|
4009
3973
|
type={inputType(k)}
|
|
4010
3974
|
value={v[k] ?? ""}
|
|
@@ -4015,7 +3979,7 @@ function BiabFieldInput({
|
|
|
4015
3979
|
);
|
|
4016
3980
|
}
|
|
4017
3981
|
case "date_range":
|
|
4018
|
-
return <
|
|
3982
|
+
return <BdDateRange setValue={(next) => set(next)} value={value} />;
|
|
4019
3983
|
case "slider": {
|
|
4020
3984
|
const min = field.sliderConfig?.min ?? 0;
|
|
4021
3985
|
const max = field.sliderConfig?.max ?? 100;
|
|
@@ -4025,10 +3989,10 @@ function BiabFieldInput({
|
|
|
4025
3989
|
unit === "currency" ? `$${n}` : unit === "percent" ? `${n}%` : `${n}`;
|
|
4026
3990
|
const v = typeof value === "number" ? value : min;
|
|
4027
3991
|
return (
|
|
4028
|
-
<div className="
|
|
4029
|
-
<div className="
|
|
3992
|
+
<div className="bd-field__slider">
|
|
3993
|
+
<div className="bd-field__sliderscale">
|
|
4030
3994
|
<span>{fmt(min)}</span>
|
|
4031
|
-
<span className="
|
|
3995
|
+
<span className="bd-field__slidervalue">{fmt(v)}</span>
|
|
4032
3996
|
<span>{fmt(max)}</span>
|
|
4033
3997
|
</div>
|
|
4034
3998
|
<input
|
|
@@ -4046,14 +4010,11 @@ function BiabFieldInput({
|
|
|
4046
4010
|
const max = field.starRatingConfig?.maxStars ?? 5;
|
|
4047
4011
|
const v = typeof value === "number" ? value : 0;
|
|
4048
4012
|
return (
|
|
4049
|
-
<div className="
|
|
4013
|
+
<div className="bd-field__stars">
|
|
4050
4014
|
{Array.from({ length: max }).map((_, i) => (
|
|
4051
4015
|
<button
|
|
4052
4016
|
aria-label={`${i + 1} star${i ? "s" : ""}`}
|
|
4053
|
-
className={cx(
|
|
4054
|
-
"biab-field__star",
|
|
4055
|
-
i < v && "biab-field__star--on",
|
|
4056
|
-
)}
|
|
4017
|
+
className={cx("bd-field__star", i < v && "bd-field__star--on")}
|
|
4057
4018
|
key={`${field.id}-star-${i}`}
|
|
4058
4019
|
onClick={() => set(i + 1)}
|
|
4059
4020
|
type="button"
|
|
@@ -4067,12 +4028,12 @@ function BiabFieldInput({
|
|
|
4067
4028
|
case "happiness_rating": {
|
|
4068
4029
|
const v = typeof value === "number" ? value : -1;
|
|
4069
4030
|
return (
|
|
4070
|
-
<div className="
|
|
4031
|
+
<div className="bd-field__happiness">
|
|
4071
4032
|
{["😞", "😕", "😐", "🙂", "😄"].map((emoji, i) => (
|
|
4072
4033
|
<button
|
|
4073
4034
|
className={cx(
|
|
4074
|
-
"
|
|
4075
|
-
i === v && "
|
|
4035
|
+
"bd-field__emoji",
|
|
4036
|
+
i === v && "bd-field__emoji--on",
|
|
4076
4037
|
)}
|
|
4077
4038
|
key={emoji}
|
|
4078
4039
|
onClick={() => set(i)}
|
|
@@ -4087,16 +4048,16 @@ function BiabFieldInput({
|
|
|
4087
4048
|
case "stepper": {
|
|
4088
4049
|
const v = typeof value === "number" ? value : 0;
|
|
4089
4050
|
return (
|
|
4090
|
-
<div className="
|
|
4051
|
+
<div className="bd-field__stepper">
|
|
4091
4052
|
<button
|
|
4092
|
-
className="
|
|
4053
|
+
className="bd-btn bd-btn--ghost"
|
|
4093
4054
|
onClick={() => set(v - 1)}
|
|
4094
4055
|
type="button"
|
|
4095
4056
|
>
|
|
4096
4057
|
−
|
|
4097
4058
|
</button>
|
|
4098
4059
|
<input
|
|
4099
|
-
className="
|
|
4060
|
+
className="bd-field__steppervalue"
|
|
4100
4061
|
inputMode="numeric"
|
|
4101
4062
|
onChange={(e) => {
|
|
4102
4063
|
const n = Number(e.target.value.replace(/[^\d-]/g, ""));
|
|
@@ -4105,7 +4066,7 @@ function BiabFieldInput({
|
|
|
4105
4066
|
value={String(v)}
|
|
4106
4067
|
/>
|
|
4107
4068
|
<button
|
|
4108
|
-
className="
|
|
4069
|
+
className="bd-btn bd-btn--ghost"
|
|
4109
4070
|
onClick={() => set(v + 1)}
|
|
4110
4071
|
type="button"
|
|
4111
4072
|
>
|
|
@@ -4114,6 +4075,87 @@ function BiabFieldInput({
|
|
|
4114
4075
|
</div>
|
|
4115
4076
|
);
|
|
4116
4077
|
}
|
|
4078
|
+
case "name": {
|
|
4079
|
+
const v = (value && typeof value === "object" ? value : {}) as {
|
|
4080
|
+
prefix?: string;
|
|
4081
|
+
first?: string;
|
|
4082
|
+
middle?: string;
|
|
4083
|
+
last?: string;
|
|
4084
|
+
suffix?: string;
|
|
4085
|
+
nicknames?: string[] | string;
|
|
4086
|
+
};
|
|
4087
|
+
const cfg = field.nameConfig ?? {};
|
|
4088
|
+
// Per-subfield autofill tokens (given/family/honorifics), suppressed when
|
|
4089
|
+
// autofill is off for this field (global switch or per-field override).
|
|
4090
|
+
const acOff = autoComplete === "off";
|
|
4091
|
+
const part = (
|
|
4092
|
+
k: "prefix" | "first" | "middle" | "last" | "suffix",
|
|
4093
|
+
label: string,
|
|
4094
|
+
ac: string,
|
|
4095
|
+
placeholder: string,
|
|
4096
|
+
) => (
|
|
4097
|
+
<label className="bd-field__sub" key={k}>
|
|
4098
|
+
<span className="bd-field__sublabel">{label}</span>
|
|
4099
|
+
<input
|
|
4100
|
+
autoComplete={acOff ? "off" : ac}
|
|
4101
|
+
className="bd-input"
|
|
4102
|
+
onChange={(e) => set({ ...v, [k]: e.target.value })}
|
|
4103
|
+
placeholder={placeholder}
|
|
4104
|
+
value={v[k] ?? ""}
|
|
4105
|
+
/>
|
|
4106
|
+
</label>
|
|
4107
|
+
);
|
|
4108
|
+
// The nicknames input keeps the raw comma-separated string while the
|
|
4109
|
+
// visitor types; submit normalizes it into a string[] (and the server
|
|
4110
|
+
// re-normalizes authoritatively).
|
|
4111
|
+
const nicknamesText = Array.isArray(v.nicknames)
|
|
4112
|
+
? v.nicknames.join(", ")
|
|
4113
|
+
: typeof v.nicknames === "string"
|
|
4114
|
+
? v.nicknames
|
|
4115
|
+
: "";
|
|
4116
|
+
return (
|
|
4117
|
+
<div className="bd-field__name">
|
|
4118
|
+
{cfg.prefix && part("prefix", "Title", "honorific-prefix", "Dr.")}
|
|
4119
|
+
{cfg.middle ? (
|
|
4120
|
+
<>
|
|
4121
|
+
<div className="bd-field__grid2">
|
|
4122
|
+
{part("first", "First name", "given-name", "Jane")}
|
|
4123
|
+
{part("middle", "Middle name", "additional-name", "Quinn")}
|
|
4124
|
+
</div>
|
|
4125
|
+
{cfg.suffix ? (
|
|
4126
|
+
<div className="bd-field__grid2">
|
|
4127
|
+
{part("last", "Last name", "family-name", "Doe")}
|
|
4128
|
+
{part("suffix", "Suffix", "honorific-suffix", "Jr.")}
|
|
4129
|
+
</div>
|
|
4130
|
+
) : (
|
|
4131
|
+
part("last", "Last name", "family-name", "Doe")
|
|
4132
|
+
)}
|
|
4133
|
+
</>
|
|
4134
|
+
) : (
|
|
4135
|
+
<>
|
|
4136
|
+
<div className="bd-field__grid2">
|
|
4137
|
+
{part("first", "First name", "given-name", "Jane")}
|
|
4138
|
+
{part("last", "Last name", "family-name", "Doe")}
|
|
4139
|
+
</div>
|
|
4140
|
+
{cfg.suffix &&
|
|
4141
|
+
part("suffix", "Suffix", "honorific-suffix", "Jr.")}
|
|
4142
|
+
</>
|
|
4143
|
+
)}
|
|
4144
|
+
{cfg.nicknames && (
|
|
4145
|
+
<label className="bd-field__sub" key="nicknames">
|
|
4146
|
+
<span className="bd-field__sublabel">Nicknames</span>
|
|
4147
|
+
<input
|
|
4148
|
+
autoComplete={acOff ? "off" : undefined}
|
|
4149
|
+
className="bd-input"
|
|
4150
|
+
onChange={(e) => set({ ...v, nicknames: e.target.value })}
|
|
4151
|
+
placeholder="Johnny, JM — separate with commas"
|
|
4152
|
+
value={nicknamesText}
|
|
4153
|
+
/>
|
|
4154
|
+
</label>
|
|
4155
|
+
)}
|
|
4156
|
+
</div>
|
|
4157
|
+
);
|
|
4158
|
+
}
|
|
4117
4159
|
case "address": {
|
|
4118
4160
|
const v =
|
|
4119
4161
|
value && typeof value === "object"
|
|
@@ -4128,23 +4170,23 @@ function BiabFieldInput({
|
|
|
4128
4170
|
{ k: "city", label: "City", ac: "address-level2" },
|
|
4129
4171
|
];
|
|
4130
4172
|
return (
|
|
4131
|
-
<div className="
|
|
4173
|
+
<div className="bd-field__address">
|
|
4132
4174
|
{rows.map(({ k, label, ac }) => (
|
|
4133
|
-
<label className="
|
|
4134
|
-
<span className="
|
|
4175
|
+
<label className="bd-field__sub" key={k}>
|
|
4176
|
+
<span className="bd-field__sublabel">{label}</span>
|
|
4135
4177
|
<input
|
|
4136
4178
|
autoComplete={acOff ? "off" : ac}
|
|
4137
|
-
className="
|
|
4179
|
+
className="bd-input"
|
|
4138
4180
|
onChange={(e) => set({ ...v, [k]: e.target.value })}
|
|
4139
4181
|
value={v[k] ?? ""}
|
|
4140
4182
|
/>
|
|
4141
4183
|
</label>
|
|
4142
4184
|
))}
|
|
4143
|
-
<label className="
|
|
4144
|
-
<span className="
|
|
4185
|
+
<label className="bd-field__sub" key="state">
|
|
4186
|
+
<span className="bd-field__sublabel">State</span>
|
|
4145
4187
|
<select
|
|
4146
4188
|
autoComplete={acOff ? "off" : "address-level1"}
|
|
4147
|
-
className="
|
|
4189
|
+
className="bd-select"
|
|
4148
4190
|
onChange={(e) => set({ ...v, state: e.target.value })}
|
|
4149
4191
|
value={v["state"] ?? ""}
|
|
4150
4192
|
>
|
|
@@ -4156,11 +4198,11 @@ function BiabFieldInput({
|
|
|
4156
4198
|
))}
|
|
4157
4199
|
</select>
|
|
4158
4200
|
</label>
|
|
4159
|
-
<label className="
|
|
4160
|
-
<span className="
|
|
4201
|
+
<label className="bd-field__sub" key="zip">
|
|
4202
|
+
<span className="bd-field__sublabel">Zip</span>
|
|
4161
4203
|
<input
|
|
4162
4204
|
autoComplete={acOff ? "off" : "postal-code"}
|
|
4163
|
-
className="
|
|
4205
|
+
className="bd-input"
|
|
4164
4206
|
inputMode="numeric"
|
|
4165
4207
|
onChange={(e) =>
|
|
4166
4208
|
// Numbers only (allow a single hyphen for ZIP+4).
|
|
@@ -4178,12 +4220,12 @@ function BiabFieldInput({
|
|
|
4178
4220
|
const presets = field.options ?? [];
|
|
4179
4221
|
const isCustom = v !== "" && !presets.includes(v);
|
|
4180
4222
|
return (
|
|
4181
|
-
<div className="
|
|
4223
|
+
<div className="bd-field__colors">
|
|
4182
4224
|
{presets.map((color) => (
|
|
4183
4225
|
<button
|
|
4184
4226
|
className={cx(
|
|
4185
|
-
"
|
|
4186
|
-
v === color && "
|
|
4227
|
+
"bd-field__color",
|
|
4228
|
+
v === color && "bd-field__color--on",
|
|
4187
4229
|
)}
|
|
4188
4230
|
key={color}
|
|
4189
4231
|
onClick={() => set(color)}
|
|
@@ -4195,8 +4237,8 @@ function BiabFieldInput({
|
|
|
4195
4237
|
{/* "Other" — native color input returning a single hex. */}
|
|
4196
4238
|
<label
|
|
4197
4239
|
className={cx(
|
|
4198
|
-
"
|
|
4199
|
-
isCustom && "
|
|
4240
|
+
"bd-field__color bd-field__color--custom",
|
|
4241
|
+
isCustom && "bd-field__color--on",
|
|
4200
4242
|
)}
|
|
4201
4243
|
style={
|
|
4202
4244
|
isCustom
|
|
@@ -4230,14 +4272,14 @@ function BiabFieldInput({
|
|
|
4230
4272
|
const maxFiles = cfg.maxFiles ?? 1;
|
|
4231
4273
|
const remaining = Math.max(maxFiles - files.length, 0);
|
|
4232
4274
|
return (
|
|
4233
|
-
<div className="
|
|
4234
|
-
{/* NOTE: no `
|
|
4275
|
+
<div className="bd-field__files">
|
|
4276
|
+
{/* NOTE: no `bd-input` class — a consumer's base input styling
|
|
4235
4277
|
(e.g. `appearance: none`) breaks the native file control's
|
|
4236
4278
|
rendering in Safari (glitched text + layout overlap). The file
|
|
4237
|
-
field is styled solely via `
|
|
4279
|
+
field is styled solely via `bd-field__fileinput`. */}
|
|
4238
4280
|
<input
|
|
4239
4281
|
accept={buildAcceptAttr(cfg.allowedTypes)}
|
|
4240
|
-
className="
|
|
4282
|
+
className="bd-field__fileinput"
|
|
4241
4283
|
disabled={remaining === 0}
|
|
4242
4284
|
multiple={maxFiles > 1}
|
|
4243
4285
|
onChange={(e) => {
|
|
@@ -4266,19 +4308,19 @@ function BiabFieldInput({
|
|
|
4266
4308
|
type="file"
|
|
4267
4309
|
/>
|
|
4268
4310
|
{files.length > 0 && (
|
|
4269
|
-
<ul className="
|
|
4311
|
+
<ul className="bd-field__filelist">
|
|
4270
4312
|
{files.map((f, i) => (
|
|
4271
|
-
<li className="
|
|
4272
|
-
<span className="
|
|
4313
|
+
<li className="bd-field__fileitem" key={`${f.name}-${i}`}>
|
|
4314
|
+
<span className="bd-field__filename">{f.name}</span>
|
|
4273
4315
|
{isPendingFileValue(f) ? (
|
|
4274
|
-
<span className="
|
|
4316
|
+
<span className="bd-field__filepending">
|
|
4275
4317
|
{" "}
|
|
4276
4318
|
· uploads on submit
|
|
4277
4319
|
</span>
|
|
4278
4320
|
) : null}
|
|
4279
4321
|
<button
|
|
4280
4322
|
aria-label="Remove file"
|
|
4281
|
-
className="
|
|
4323
|
+
className="bd-field__fileremove"
|
|
4282
4324
|
onClick={() => set(files.filter((_, idx) => idx !== i))}
|
|
4283
4325
|
type="button"
|
|
4284
4326
|
>
|
|
@@ -4308,24 +4350,24 @@ function BiabFieldInput({
|
|
|
4308
4350
|
? `${(pc.amountCents / 100).toFixed(2)} ${(pc.currency ?? "usd").toUpperCase()}`
|
|
4309
4351
|
: null;
|
|
4310
4352
|
return (
|
|
4311
|
-
<div className="
|
|
4312
|
-
<div className="
|
|
4313
|
-
<span className="
|
|
4314
|
-
<span className="
|
|
4315
|
-
<span className="
|
|
4353
|
+
<div className="bd-field__card">
|
|
4354
|
+
<div className="bd-field__card-row">
|
|
4355
|
+
<span className="bd-field__card-brand">CARD</span>
|
|
4356
|
+
<span className="bd-field__card-dots">•••• •••• •••• ••••</span>
|
|
4357
|
+
<span className="bd-field__card-mode">
|
|
4316
4358
|
{deposit
|
|
4317
4359
|
? `Deposit${amount ? ` · ${amount}` : ""}`
|
|
4318
4360
|
: "Card on file"}
|
|
4319
4361
|
</span>
|
|
4320
4362
|
</div>
|
|
4321
4363
|
{pc?.description ? (
|
|
4322
|
-
<p className="
|
|
4364
|
+
<p className="bd-field__card-desc">{pc.description}</p>
|
|
4323
4365
|
) : null}
|
|
4324
|
-
<p className="
|
|
4366
|
+
<p className="bd-field__card-note">
|
|
4325
4367
|
Secure card entry via Stripe. Initialize Elements with{" "}
|
|
4326
|
-
<code>
|
|
4327
|
-
|
|
4328
|
-
|
|
4368
|
+
<code>bd.forms.paymentIntent(slug, "{field.id}")</code> (returns the
|
|
4369
|
+
clientSecret + the org's connected account), confirm, then store the
|
|
4370
|
+
intent id on this field before submit.
|
|
4329
4371
|
</p>
|
|
4330
4372
|
</div>
|
|
4331
4373
|
);
|
|
@@ -4335,7 +4377,7 @@ function BiabFieldInput({
|
|
|
4335
4377
|
(field as { scheduleConfig?: { mode?: string } }).scheduleConfig
|
|
4336
4378
|
?.mode === "auto"
|
|
4337
4379
|
) {
|
|
4338
|
-
return <
|
|
4380
|
+
return <BdScheduleAuto field={field} onChange={set} value={value} />;
|
|
4339
4381
|
}
|
|
4340
4382
|
const sc = (
|
|
4341
4383
|
field as {
|
|
@@ -4399,9 +4441,9 @@ function BiabFieldInput({
|
|
|
4399
4441
|
});
|
|
4400
4442
|
};
|
|
4401
4443
|
return (
|
|
4402
|
-
<div className="
|
|
4444
|
+
<div className="bd-field__schedule">
|
|
4403
4445
|
{sc?.allowAsap !== false && (
|
|
4404
|
-
<label className="
|
|
4446
|
+
<label className="bd-field__schedule-asap">
|
|
4405
4447
|
<input
|
|
4406
4448
|
checked={sv.asap === true}
|
|
4407
4449
|
onChange={(e) => set({ ...sv, asap: e.target.checked })}
|
|
@@ -4411,14 +4453,14 @@ function BiabFieldInput({
|
|
|
4411
4453
|
</label>
|
|
4412
4454
|
)}
|
|
4413
4455
|
{/* Availability is additive: day/time choices show alongside ASAP. */}
|
|
4414
|
-
<div className="
|
|
4415
|
-
<span className="
|
|
4416
|
-
<div className="
|
|
4456
|
+
<div className="bd-field__schedule-group">
|
|
4457
|
+
<span className="bd-field__schedule-label">Preferred days</span>
|
|
4458
|
+
<div className="bd-field__schedule-chips">
|
|
4417
4459
|
{days.map((d) => (
|
|
4418
4460
|
<button
|
|
4419
4461
|
className={cx(
|
|
4420
|
-
"
|
|
4421
|
-
sv.days?.includes(d) && "
|
|
4462
|
+
"bd-field__chip",
|
|
4463
|
+
sv.days?.includes(d) && "bd-field__chip--on",
|
|
4422
4464
|
)}
|
|
4423
4465
|
key={d}
|
|
4424
4466
|
onClick={() => toggle("days", d)}
|
|
@@ -4429,14 +4471,14 @@ function BiabFieldInput({
|
|
|
4429
4471
|
))}
|
|
4430
4472
|
</div>
|
|
4431
4473
|
</div>
|
|
4432
|
-
<div className="
|
|
4433
|
-
<span className="
|
|
4434
|
-
<div className="
|
|
4474
|
+
<div className="bd-field__schedule-group">
|
|
4475
|
+
<span className="bd-field__schedule-label">Preferred times</span>
|
|
4476
|
+
<div className="bd-field__schedule-chips">
|
|
4435
4477
|
{windows.map((w) => (
|
|
4436
4478
|
<button
|
|
4437
4479
|
className={cx(
|
|
4438
|
-
"
|
|
4439
|
-
sv.times?.includes(w) && "
|
|
4480
|
+
"bd-field__chip",
|
|
4481
|
+
sv.times?.includes(w) && "bd-field__chip--on",
|
|
4440
4482
|
)}
|
|
4441
4483
|
key={w}
|
|
4442
4484
|
onClick={() => toggle("times", w)}
|
|
@@ -4449,7 +4491,7 @@ function BiabFieldInput({
|
|
|
4449
4491
|
</div>
|
|
4450
4492
|
{sc?.allowNotes !== false && (
|
|
4451
4493
|
<textarea
|
|
4452
|
-
className="
|
|
4494
|
+
className="bd-input bd-field__schedule-notes"
|
|
4453
4495
|
onChange={(e) => set({ ...sv, notes: e.target.value })}
|
|
4454
4496
|
placeholder="Anything we should know about timing?"
|
|
4455
4497
|
value={sv.notes ?? ""}
|
|
@@ -4465,15 +4507,15 @@ function BiabFieldInput({
|
|
|
4465
4507
|
phoneIntl,
|
|
4466
4508
|
);
|
|
4467
4509
|
return (
|
|
4468
|
-
<div className="
|
|
4510
|
+
<div className="bd-field__phone">
|
|
4469
4511
|
{field.phoneConfig?.showFlags && (
|
|
4470
|
-
<span aria-hidden className="
|
|
4512
|
+
<span aria-hidden className="bd-field__flag">
|
|
4471
4513
|
🇺🇸
|
|
4472
4514
|
</span>
|
|
4473
4515
|
)}
|
|
4474
4516
|
<input
|
|
4475
4517
|
autoComplete={autoComplete}
|
|
4476
|
-
className="
|
|
4518
|
+
className="bd-input bd-field__input"
|
|
4477
4519
|
inputMode="tel"
|
|
4478
4520
|
maxLength={phoneIntl ? 20 : 14}
|
|
4479
4521
|
onChange={(e) => set(formatPhoneLocal(e.target.value, phoneIntl))}
|
|
@@ -4490,7 +4532,7 @@ function BiabFieldInput({
|
|
|
4490
4532
|
return (
|
|
4491
4533
|
<input
|
|
4492
4534
|
autoComplete={autoComplete}
|
|
4493
|
-
className="
|
|
4535
|
+
className="bd-input bd-field__input"
|
|
4494
4536
|
onChange={(e) => set(e.target.value)}
|
|
4495
4537
|
placeholder={ph || "you@example.com"}
|
|
4496
4538
|
type="email"
|
|
@@ -4506,7 +4548,7 @@ function BiabFieldInput({
|
|
|
4506
4548
|
return (
|
|
4507
4549
|
<input
|
|
4508
4550
|
autoComplete={autoComplete}
|
|
4509
|
-
className="
|
|
4551
|
+
className="bd-input bd-field__input"
|
|
4510
4552
|
inputMode="url"
|
|
4511
4553
|
onChange={(e) => set({ ...uv, url: e.target.value })}
|
|
4512
4554
|
placeholder={ph || "https://example.com"}
|
|
@@ -4521,7 +4563,7 @@ function BiabFieldInput({
|
|
|
4521
4563
|
(field as { allowNegative?: boolean }).allowNegative === false;
|
|
4522
4564
|
return (
|
|
4523
4565
|
<input
|
|
4524
|
-
className="
|
|
4566
|
+
className="bd-input bd-field__input"
|
|
4525
4567
|
inputMode={allowDecimal ? "decimal" : "numeric"}
|
|
4526
4568
|
min={noNegative ? 0 : undefined}
|
|
4527
4569
|
onChange={(e) => {
|
|
@@ -4548,12 +4590,12 @@ function BiabFieldInput({
|
|
|
4548
4590
|
const allowNeg = field.currencyConfig?.allowNegative === true;
|
|
4549
4591
|
const allowDec = field.currencyConfig?.allowDecimal !== false;
|
|
4550
4592
|
return (
|
|
4551
|
-
<div className="
|
|
4552
|
-
<span aria-hidden="true" className="
|
|
4593
|
+
<div className="bd-field__currency">
|
|
4594
|
+
<span aria-hidden="true" className="bd-field__currency-symbol">
|
|
4553
4595
|
{symbol}
|
|
4554
4596
|
</span>
|
|
4555
4597
|
<input
|
|
4556
|
-
className="
|
|
4598
|
+
className="bd-input bd-field__input"
|
|
4557
4599
|
inputMode="decimal"
|
|
4558
4600
|
min={allowNeg ? undefined : 0}
|
|
4559
4601
|
onChange={(e) => {
|
|
@@ -4605,9 +4647,9 @@ function BiabFieldInput({
|
|
|
4605
4647
|
const hint =
|
|
4606
4648
|
typeof num === "number" ? formatMeasurement(num, unit, category) : "";
|
|
4607
4649
|
return (
|
|
4608
|
-
<div className="
|
|
4650
|
+
<div className="bd-field__measurement">
|
|
4609
4651
|
<input
|
|
4610
|
-
className="
|
|
4652
|
+
className="bd-input bd-field__input"
|
|
4611
4653
|
inputMode="decimal"
|
|
4612
4654
|
onChange={(e) => {
|
|
4613
4655
|
const raw = e.target.value;
|
|
@@ -4621,12 +4663,12 @@ function BiabFieldInput({
|
|
|
4621
4663
|
value={num}
|
|
4622
4664
|
/>
|
|
4623
4665
|
{mode === "fixed" ? (
|
|
4624
|
-
<span aria-hidden="true" className="
|
|
4666
|
+
<span aria-hidden="true" className="bd-field__unit">
|
|
4625
4667
|
{unit}
|
|
4626
4668
|
</span>
|
|
4627
4669
|
) : (
|
|
4628
4670
|
<select
|
|
4629
|
-
className="
|
|
4671
|
+
className="bd-select bd-field__unit-select"
|
|
4630
4672
|
onChange={(e) => emit(num, e.target.value)}
|
|
4631
4673
|
value={unit}
|
|
4632
4674
|
>
|
|
@@ -4638,7 +4680,7 @@ function BiabFieldInput({
|
|
|
4638
4680
|
</select>
|
|
4639
4681
|
)}
|
|
4640
4682
|
{hint ? (
|
|
4641
|
-
<span className="
|
|
4683
|
+
<span className="bd-field__measurement-hint">≈ {hint}</span>
|
|
4642
4684
|
) : null}
|
|
4643
4685
|
</div>
|
|
4644
4686
|
);
|
|
@@ -4647,7 +4689,7 @@ function BiabFieldInput({
|
|
|
4647
4689
|
return (
|
|
4648
4690
|
<input
|
|
4649
4691
|
autoComplete={autoComplete}
|
|
4650
|
-
className="
|
|
4692
|
+
className="bd-input bd-field__input"
|
|
4651
4693
|
onChange={(e) => set(e.target.value)}
|
|
4652
4694
|
placeholder={ph}
|
|
4653
4695
|
value={typeof value === "string" ? value : ""}
|
|
@@ -4666,7 +4708,7 @@ const NO_LABEL_TYPES = new Set([
|
|
|
4666
4708
|
"table",
|
|
4667
4709
|
]);
|
|
4668
4710
|
|
|
4669
|
-
function
|
|
4711
|
+
function BdFieldRow({
|
|
4670
4712
|
field,
|
|
4671
4713
|
value,
|
|
4672
4714
|
error,
|
|
@@ -4683,39 +4725,39 @@ function BiabFieldRow({
|
|
|
4683
4725
|
return (
|
|
4684
4726
|
<div
|
|
4685
4727
|
className={cx(
|
|
4686
|
-
"
|
|
4687
|
-
`
|
|
4688
|
-
field.required && "
|
|
4689
|
-
error && "
|
|
4728
|
+
"bd-field",
|
|
4729
|
+
`bd-field--${field.type}`,
|
|
4730
|
+
field.required && "bd-field--required",
|
|
4731
|
+
error && "bd-field--error",
|
|
4690
4732
|
)}
|
|
4691
|
-
data-
|
|
4692
|
-
data-
|
|
4733
|
+
data-bd-field={fieldCssKey(field)}
|
|
4734
|
+
data-bd-type={field.type}
|
|
4693
4735
|
data-field-id={field.id}
|
|
4694
4736
|
>
|
|
4695
4737
|
{showLabel && (
|
|
4696
4738
|
// A visual field heading, not a control-associated <label> — the field's
|
|
4697
|
-
// inputs render in
|
|
4739
|
+
// inputs render in BdFieldInput below and carry no shared id to point a
|
|
4698
4740
|
// `htmlFor` at, so a <label> here would be an orphan.
|
|
4699
|
-
<span className="
|
|
4741
|
+
<span className="bd-field__label bd-label">
|
|
4700
4742
|
{field.label || ""}
|
|
4701
|
-
{field.required && <span className="
|
|
4743
|
+
{field.required && <span className="bd-field__required">*</span>}
|
|
4702
4744
|
</span>
|
|
4703
4745
|
)}
|
|
4704
|
-
<
|
|
4746
|
+
<BdFieldInput
|
|
4705
4747
|
field={field}
|
|
4706
4748
|
setValue={setValue}
|
|
4707
4749
|
upload={upload}
|
|
4708
4750
|
value={value}
|
|
4709
4751
|
/>
|
|
4710
|
-
{error && <p className="
|
|
4711
|
-
{field.helpText && <p className="
|
|
4752
|
+
{error && <p className="bd-field__error">{error.message}</p>}
|
|
4753
|
+
{field.helpText && <p className="bd-field__help">{field.helpText}</p>}
|
|
4712
4754
|
</div>
|
|
4713
4755
|
);
|
|
4714
4756
|
}
|
|
4715
4757
|
|
|
4716
4758
|
/** Recursively render a field subtree, mirroring FormPreview's `RenderTree`:
|
|
4717
4759
|
* input_group (+ concurrent reveal), condition_block, or_condition, flex_row. */
|
|
4718
|
-
function
|
|
4760
|
+
function BdRenderTree({
|
|
4719
4761
|
fields,
|
|
4720
4762
|
values,
|
|
4721
4763
|
errors,
|
|
@@ -4731,7 +4773,7 @@ function BiabRenderTree({
|
|
|
4731
4773
|
flat?: boolean | undefined;
|
|
4732
4774
|
}) {
|
|
4733
4775
|
return (
|
|
4734
|
-
<div className="
|
|
4776
|
+
<div className="bd-tree">
|
|
4735
4777
|
{fields.map((f) => {
|
|
4736
4778
|
if (f.type === "multi_populator") {
|
|
4737
4779
|
const cfg = f.populatorConfig ?? {};
|
|
@@ -4746,8 +4788,8 @@ function BiabRenderTree({
|
|
|
4746
4788
|
setValue(f.id, next);
|
|
4747
4789
|
return (
|
|
4748
4790
|
<div
|
|
4749
|
-
className="
|
|
4750
|
-
data-
|
|
4791
|
+
className="bd-populator"
|
|
4792
|
+
data-bd-field={fieldCssKey(f)}
|
|
4751
4793
|
data-field-id={f.id}
|
|
4752
4794
|
key={f.id}
|
|
4753
4795
|
>
|
|
@@ -4759,17 +4801,17 @@ function BiabRenderTree({
|
|
|
4759
4801
|
};
|
|
4760
4802
|
return (
|
|
4761
4803
|
<div
|
|
4762
|
-
className="
|
|
4804
|
+
className="bd-populator__item"
|
|
4763
4805
|
key={`${f.id}-inst-${idx}`}
|
|
4764
4806
|
>
|
|
4765
|
-
<div className="
|
|
4766
|
-
<span className="
|
|
4807
|
+
<div className="bd-populator__item-head">
|
|
4808
|
+
<span className="bd-populator__item-label">
|
|
4767
4809
|
{cfg.itemLabel || f.label || "Entry"} {idx + 1}
|
|
4768
4810
|
</span>
|
|
4769
4811
|
{instances.length > 1 && (
|
|
4770
4812
|
<button
|
|
4771
4813
|
aria-label="Remove entry"
|
|
4772
|
-
className="
|
|
4814
|
+
className="bd-populator__remove"
|
|
4773
4815
|
onClick={() =>
|
|
4774
4816
|
writeInstances(
|
|
4775
4817
|
instances.filter((_, i) => i !== idx),
|
|
@@ -4781,7 +4823,7 @@ function BiabRenderTree({
|
|
|
4781
4823
|
</button>
|
|
4782
4824
|
)}
|
|
4783
4825
|
</div>
|
|
4784
|
-
<
|
|
4826
|
+
<BdRenderTree
|
|
4785
4827
|
errors={errors}
|
|
4786
4828
|
fields={children}
|
|
4787
4829
|
setValue={scopedSetValue}
|
|
@@ -4793,7 +4835,7 @@ function BiabRenderTree({
|
|
|
4793
4835
|
})}
|
|
4794
4836
|
{instances.length < max && (
|
|
4795
4837
|
<button
|
|
4796
|
-
className="
|
|
4838
|
+
className="bd-populator__add"
|
|
4797
4839
|
onClick={() => writeInstances([...instances, {}])}
|
|
4798
4840
|
type="button"
|
|
4799
4841
|
>
|
|
@@ -4813,14 +4855,14 @@ function BiabRenderTree({
|
|
|
4813
4855
|
const shown = concurrent ? children.slice(0, revealCount) : children;
|
|
4814
4856
|
return (
|
|
4815
4857
|
<div
|
|
4816
|
-
className={cx("
|
|
4817
|
-
data-
|
|
4858
|
+
className={cx("bd-group", flat && "bd-group--flat")}
|
|
4859
|
+
data-bd-group={fieldCssKey(f)}
|
|
4818
4860
|
key={f.id}
|
|
4819
4861
|
>
|
|
4820
4862
|
{f.label && showGroupLabel && (
|
|
4821
|
-
<div className="
|
|
4863
|
+
<div className="bd-group__label">{f.label}</div>
|
|
4822
4864
|
)}
|
|
4823
|
-
<
|
|
4865
|
+
<BdRenderTree
|
|
4824
4866
|
errors={errors}
|
|
4825
4867
|
fields={shown}
|
|
4826
4868
|
setValue={setValue}
|
|
@@ -4833,7 +4875,7 @@ function BiabRenderTree({
|
|
|
4833
4875
|
if (f.type === "condition_block") {
|
|
4834
4876
|
if (!evalCondition(f.conditionRule, values)) return null;
|
|
4835
4877
|
return (
|
|
4836
|
-
<
|
|
4878
|
+
<BdRenderTree
|
|
4837
4879
|
errors={errors}
|
|
4838
4880
|
fields={(f.children ?? []) as FormCoreField[]}
|
|
4839
4881
|
key={f.id}
|
|
@@ -4853,22 +4895,19 @@ function BiabRenderTree({
|
|
|
4853
4895
|
["Option A", "Option B", "Option C"][i] ||
|
|
4854
4896
|
`Option ${i + 1}`;
|
|
4855
4897
|
return (
|
|
4856
|
-
<div
|
|
4857
|
-
className={cx("biab-or", errored && "biab-or--error")}
|
|
4858
|
-
key={f.id}
|
|
4859
|
-
>
|
|
4898
|
+
<div className={cx("bd-or", errored && "bd-or--error")} key={f.id}>
|
|
4860
4899
|
{f.label && (
|
|
4861
|
-
<div className="
|
|
4900
|
+
<div className="bd-or__label">
|
|
4862
4901
|
{f.label}
|
|
4863
|
-
<span className="
|
|
4902
|
+
<span className="bd-or__hint">Fill at least one side</span>
|
|
4864
4903
|
</div>
|
|
4865
4904
|
)}
|
|
4866
|
-
<div className="
|
|
4905
|
+
<div className="bd-or__sides">
|
|
4867
4906
|
{branches.flatMap((child, i) => {
|
|
4868
4907
|
const side = (
|
|
4869
|
-
<div className="
|
|
4870
|
-
<div className="
|
|
4871
|
-
<
|
|
4908
|
+
<div className="bd-or__side" key={child.id}>
|
|
4909
|
+
<div className="bd-or__sidelabel">{branchLabel(i)}</div>
|
|
4910
|
+
<BdRenderTree
|
|
4872
4911
|
errors={errors}
|
|
4873
4912
|
fields={[child]}
|
|
4874
4913
|
setValue={setValue}
|
|
@@ -4879,7 +4918,7 @@ function BiabRenderTree({
|
|
|
4879
4918
|
);
|
|
4880
4919
|
if (i === 0) return [side];
|
|
4881
4920
|
return [
|
|
4882
|
-
<div className="
|
|
4921
|
+
<div className="bd-or__divider" key={`or-${child.id}`}>
|
|
4883
4922
|
OR
|
|
4884
4923
|
</div>,
|
|
4885
4924
|
side,
|
|
@@ -4887,7 +4926,7 @@ function BiabRenderTree({
|
|
|
4887
4926
|
})}
|
|
4888
4927
|
</div>
|
|
4889
4928
|
{errored && (
|
|
4890
|
-
<p className="
|
|
4929
|
+
<p className="bd-field__error">
|
|
4891
4930
|
Fill in at least one side to continue.
|
|
4892
4931
|
</p>
|
|
4893
4932
|
)}
|
|
@@ -4902,7 +4941,7 @@ function BiabRenderTree({
|
|
|
4902
4941
|
};
|
|
4903
4942
|
return (
|
|
4904
4943
|
<div
|
|
4905
|
-
className="
|
|
4944
|
+
className="bd-flexrow"
|
|
4906
4945
|
key={f.id}
|
|
4907
4946
|
style={{
|
|
4908
4947
|
display: "flex",
|
|
@@ -4927,8 +4966,8 @@ function BiabRenderTree({
|
|
|
4927
4966
|
}}
|
|
4928
4967
|
>
|
|
4929
4968
|
{((f.children ?? []) as FormCoreField[]).map((child) => (
|
|
4930
|
-
<div className="
|
|
4931
|
-
<
|
|
4969
|
+
<div className="bd-flexrow__cell" key={child.id}>
|
|
4970
|
+
<BdRenderTree
|
|
4932
4971
|
errors={errors}
|
|
4933
4972
|
fields={[child]}
|
|
4934
4973
|
setValue={setValue}
|
|
@@ -4941,7 +4980,7 @@ function BiabRenderTree({
|
|
|
4941
4980
|
);
|
|
4942
4981
|
}
|
|
4943
4982
|
return (
|
|
4944
|
-
<
|
|
4983
|
+
<BdFieldRow
|
|
4945
4984
|
error={errors[f.id]}
|
|
4946
4985
|
field={f}
|
|
4947
4986
|
key={f.id}
|
|
@@ -4955,15 +4994,15 @@ function BiabRenderTree({
|
|
|
4955
4994
|
);
|
|
4956
4995
|
}
|
|
4957
4996
|
|
|
4958
|
-
export function
|
|
4959
|
-
const providerClient =
|
|
4960
|
-
// A publishable token (`pk_…`) builds a browser client that calls
|
|
4997
|
+
export function BdForm(props: BdFormProps) {
|
|
4998
|
+
const providerClient = useBdFormsClient();
|
|
4999
|
+
// A publishable token (`pk_…`) builds a browser client that calls BD
|
|
4961
5000
|
// directly — the zero-backend path. Explicit `client` still wins, then the
|
|
4962
5001
|
// token client, then a surrounding provider.
|
|
4963
|
-
const tokenClient = useMemo<
|
|
5002
|
+
const tokenClient = useMemo<BdFormsClient | null>(
|
|
4964
5003
|
() =>
|
|
4965
5004
|
props.token && props.siteId
|
|
4966
|
-
?
|
|
5005
|
+
? createBdClient({
|
|
4967
5006
|
apiKey: props.token,
|
|
4968
5007
|
siteId: props.siteId,
|
|
4969
5008
|
baseUrl: props.baseUrl ?? DEFAULT_PACKAGE_BASE_URL,
|
|
@@ -5035,8 +5074,10 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5035
5074
|
// Autofill config provided to the field tree. Keyed by the serialized
|
|
5036
5075
|
// overrides so a fresh-but-equal `fieldAutoComplete` literal doesn't churn.
|
|
5037
5076
|
const fieldAutoCompleteKey = JSON.stringify(props.fieldAutoComplete ?? null);
|
|
5077
|
+
// The directive has to sit immediately above the flagged `useMemo`; inside
|
|
5078
|
+
// the call it attaches to the callback and does nothing.
|
|
5079
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: keyed by fieldAutoCompleteKey, so a fresh-but-equal literal does not churn
|
|
5038
5080
|
const autocompleteConfig = useMemo<AutocompleteConfig>(
|
|
5039
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: keyed by fieldAutoCompleteKey
|
|
5040
5081
|
() => ({
|
|
5041
5082
|
...(props.autoComplete !== undefined
|
|
5042
5083
|
? { enabled: props.autoComplete }
|
|
@@ -5072,13 +5113,13 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5072
5113
|
return () => window.removeEventListener("beforeunload", onBeforeUnload);
|
|
5073
5114
|
}, [exitConfirm?.enabled, exitConfirm?.message, snap.submitted, exitDirty]);
|
|
5074
5115
|
|
|
5075
|
-
const rootClass = cx("
|
|
5116
|
+
const rootClass = cx("bd-form", props.className, props.class);
|
|
5076
5117
|
|
|
5077
5118
|
if (snap.status === "loading" || snap.status === "idle") {
|
|
5078
5119
|
return (
|
|
5079
5120
|
<div className={rootClass} style={props.style}>
|
|
5080
5121
|
{props.loadingFallback ?? (
|
|
5081
|
-
<div className="
|
|
5122
|
+
<div className="bd-form__loading">Loading…</div>
|
|
5082
5123
|
)}
|
|
5083
5124
|
</div>
|
|
5084
5125
|
);
|
|
@@ -5090,7 +5131,7 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5090
5131
|
{props.errorFallback ? (
|
|
5091
5132
|
props.errorFallback(message)
|
|
5092
5133
|
) : (
|
|
5093
|
-
<div className="
|
|
5134
|
+
<div className="bd-form__error">{message}</div>
|
|
5094
5135
|
)}
|
|
5095
5136
|
</div>
|
|
5096
5137
|
);
|
|
@@ -5102,7 +5143,7 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5102
5143
|
{props.successFallback ? (
|
|
5103
5144
|
props.successFallback(ok)
|
|
5104
5145
|
) : (
|
|
5105
|
-
<div className="
|
|
5146
|
+
<div className="bd-form__success">
|
|
5106
5147
|
Thanks — your response was received.
|
|
5107
5148
|
</div>
|
|
5108
5149
|
)}
|
|
@@ -5114,15 +5155,15 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5114
5155
|
const consecutive = settings.isConsecutive && snap.stepCount > 1;
|
|
5115
5156
|
const animClass =
|
|
5116
5157
|
settings.animation === "fade"
|
|
5117
|
-
? "
|
|
5158
|
+
? "bd-anim--fade"
|
|
5118
5159
|
: settings.animation === "scale"
|
|
5119
|
-
? "
|
|
5160
|
+
? "bd-anim--scale"
|
|
5120
5161
|
: settings.animation === "slide"
|
|
5121
5162
|
? snap.forward
|
|
5122
|
-
? "
|
|
5123
|
-
: "
|
|
5163
|
+
? "bd-anim--slide-fwd"
|
|
5164
|
+
: "bd-anim--slide-back"
|
|
5124
5165
|
: settings.animation === "scroll"
|
|
5125
|
-
? "
|
|
5166
|
+
? "bd-anim--scroll"
|
|
5126
5167
|
: "";
|
|
5127
5168
|
|
|
5128
5169
|
const handleSubmit = async () => {
|
|
@@ -5160,14 +5201,14 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5160
5201
|
<AutocompleteContext.Provider value={autocompleteConfig}>
|
|
5161
5202
|
<div className={rootClass} style={props.style}>
|
|
5162
5203
|
{snap.orgIcon && (
|
|
5163
|
-
<img alt="" className="
|
|
5204
|
+
<img alt="" className="bd-form__orgicon" src={snap.orgIcon} />
|
|
5164
5205
|
)}
|
|
5165
5206
|
|
|
5166
5207
|
{(snap.title || snap.description) && (
|
|
5167
|
-
<div className="
|
|
5168
|
-
{snap.title && <h2 className="
|
|
5208
|
+
<div className="bd-form__head">
|
|
5209
|
+
{snap.title && <h2 className="bd-form__title">{snap.title}</h2>}
|
|
5169
5210
|
{snap.description && (
|
|
5170
|
-
<p className="
|
|
5211
|
+
<p className="bd-form__desc">{snap.description}</p>
|
|
5171
5212
|
)}
|
|
5172
5213
|
</div>
|
|
5173
5214
|
)}
|
|
@@ -5176,7 +5217,7 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5176
5217
|
{consecutive &&
|
|
5177
5218
|
settings.showProgress &&
|
|
5178
5219
|
settings.progressStyle !== "bar" && (
|
|
5179
|
-
<div className="
|
|
5220
|
+
<div className="bd-progress bd-progress--steps">
|
|
5180
5221
|
{snap.steps.map((s, i) => {
|
|
5181
5222
|
const f = s[0];
|
|
5182
5223
|
const title = f?.stepTitle || f?.label || `Step ${i + 1}`;
|
|
@@ -5187,24 +5228,24 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5187
5228
|
{i > 0 && (
|
|
5188
5229
|
<span
|
|
5189
5230
|
className={cx(
|
|
5190
|
-
"
|
|
5191
|
-
i <= snap.step && "
|
|
5231
|
+
"bd-progress__line",
|
|
5232
|
+
i <= snap.step && "bd-progress__line--done",
|
|
5192
5233
|
)}
|
|
5193
5234
|
/>
|
|
5194
5235
|
)}
|
|
5195
5236
|
<button
|
|
5196
5237
|
className={cx(
|
|
5197
|
-
"
|
|
5198
|
-
current && "
|
|
5199
|
-
done && "
|
|
5238
|
+
"bd-progress__step",
|
|
5239
|
+
current && "bd-progress__step--current",
|
|
5240
|
+
done && "bd-progress__step--done",
|
|
5200
5241
|
)}
|
|
5201
5242
|
onClick={() => controller.goToStep(i)}
|
|
5202
5243
|
type="button"
|
|
5203
5244
|
>
|
|
5204
|
-
<span className="
|
|
5245
|
+
<span className="bd-progress__num">
|
|
5205
5246
|
{done ? "✓" : i + 1}
|
|
5206
5247
|
</span>
|
|
5207
|
-
<span className="
|
|
5248
|
+
<span className="bd-progress__title">{title}</span>
|
|
5208
5249
|
</button>
|
|
5209
5250
|
</Fragment>
|
|
5210
5251
|
);
|
|
@@ -5216,13 +5257,13 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5216
5257
|
{consecutive &&
|
|
5217
5258
|
settings.showProgress &&
|
|
5218
5259
|
settings.progressStyle === "bar" && (
|
|
5219
|
-
<div className="
|
|
5220
|
-
<span className="
|
|
5260
|
+
<div className="bd-progress bd-progress--bar">
|
|
5261
|
+
<span className="bd-progress__count">
|
|
5221
5262
|
{snap.step + 1} / {snap.stepCount}
|
|
5222
5263
|
</span>
|
|
5223
|
-
<div className="
|
|
5264
|
+
<div className="bd-progress__track">
|
|
5224
5265
|
<div
|
|
5225
|
-
className="
|
|
5266
|
+
className="bd-progress__fill"
|
|
5226
5267
|
style={{ width: `${snap.progressPercent}%` }}
|
|
5227
5268
|
/>
|
|
5228
5269
|
</div>
|
|
@@ -5231,12 +5272,12 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5231
5272
|
|
|
5232
5273
|
{/* Aggregate required-field notice */}
|
|
5233
5274
|
{snap.errorList.length > 0 && (
|
|
5234
|
-
<div className="
|
|
5235
|
-
<div className="
|
|
5275
|
+
<div className="bd-form__notice" role="alert">
|
|
5276
|
+
<div className="bd-form__notice-title">
|
|
5236
5277
|
Please complete the highlighted field
|
|
5237
5278
|
{snap.errorList.length === 1 ? "" : "s"}.
|
|
5238
5279
|
</div>
|
|
5239
|
-
<div className="
|
|
5280
|
+
<div className="bd-form__notice-list">
|
|
5240
5281
|
{snap.errorList.map((e) => e.label).join(", ")}
|
|
5241
5282
|
</div>
|
|
5242
5283
|
</div>
|
|
@@ -5244,10 +5285,10 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5244
5285
|
|
|
5245
5286
|
{/* Field tree for the active step (single-page = all fields) */}
|
|
5246
5287
|
<div
|
|
5247
|
-
className={cx("
|
|
5248
|
-
key={`
|
|
5288
|
+
className={cx("bd-form__body", animClass)}
|
|
5289
|
+
key={`bd-step-${snap.step}`}
|
|
5249
5290
|
>
|
|
5250
|
-
<
|
|
5291
|
+
<BdRenderTree
|
|
5251
5292
|
errors={snap.errors}
|
|
5252
5293
|
fields={snap.visibleFields}
|
|
5253
5294
|
flat={consecutive}
|
|
@@ -5271,10 +5312,10 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5271
5312
|
? a.coupon.notice.replace(/\{code\}/g, code)
|
|
5272
5313
|
: `Coupon ${code} will be applied to this submission.`;
|
|
5273
5314
|
return (
|
|
5274
|
-
<div className="
|
|
5275
|
-
<p className="
|
|
5315
|
+
<div className="bd-coupon-notice" key={`coupon-${i}`}>
|
|
5316
|
+
<p className="bd-coupon-notice__text">{text}</p>
|
|
5276
5317
|
{a.coupon?.finePrint && (
|
|
5277
|
-
<p className="
|
|
5318
|
+
<p className="bd-coupon-notice__fine">
|
|
5278
5319
|
{a.coupon.finePrint}
|
|
5279
5320
|
</p>
|
|
5280
5321
|
)}
|
|
@@ -5284,9 +5325,9 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5284
5325
|
|
|
5285
5326
|
{/* Navigation */}
|
|
5286
5327
|
{consecutive ? (
|
|
5287
|
-
<div className="
|
|
5328
|
+
<div className="bd-nav">
|
|
5288
5329
|
<button
|
|
5289
|
-
className="
|
|
5330
|
+
className="bd-btn bd-btn--back bd-btn--ghost"
|
|
5290
5331
|
disabled={snap.isFirstStep}
|
|
5291
5332
|
onClick={() => controller.back()}
|
|
5292
5333
|
type="button"
|
|
@@ -5295,7 +5336,7 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5295
5336
|
</button>
|
|
5296
5337
|
{!snap.isLastStep ? (
|
|
5297
5338
|
<button
|
|
5298
|
-
className="
|
|
5339
|
+
className="bd-btn bd-btn--next"
|
|
5299
5340
|
disabled={!snap.canAdvance}
|
|
5300
5341
|
onClick={() => controller.next()}
|
|
5301
5342
|
type="button"
|
|
@@ -5304,7 +5345,7 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5304
5345
|
</button>
|
|
5305
5346
|
) : (
|
|
5306
5347
|
<button
|
|
5307
|
-
className="
|
|
5348
|
+
className="bd-btn bd-btn--submit"
|
|
5308
5349
|
disabled={snap.submitting || snap.uploading}
|
|
5309
5350
|
onClick={() => void handleSubmit()}
|
|
5310
5351
|
type="button"
|
|
@@ -5314,9 +5355,9 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5314
5355
|
)}
|
|
5315
5356
|
</div>
|
|
5316
5357
|
) : (
|
|
5317
|
-
<div className="
|
|
5358
|
+
<div className="bd-nav">
|
|
5318
5359
|
<button
|
|
5319
|
-
className="
|
|
5360
|
+
className="bd-btn bd-btn--submit"
|
|
5320
5361
|
disabled={snap.submitting || snap.uploading}
|
|
5321
5362
|
onClick={() => void handleSubmit()}
|
|
5322
5363
|
type="button"
|
|
@@ -5328,7 +5369,7 @@ export function BiabForm(props: BiabFormProps) {
|
|
|
5328
5369
|
|
|
5329
5370
|
{/* Inline failure message (non-validation, e.g. plan gate / network) */}
|
|
5330
5371
|
{snap.result && !snap.result.ok && snap.errorList.length === 0 && (
|
|
5331
|
-
<div className="
|
|
5372
|
+
<div className="bd-form__error" role="alert">
|
|
5332
5373
|
{snap.result.message}
|
|
5333
5374
|
</div>
|
|
5334
5375
|
)}
|
|
@@ -5397,7 +5438,7 @@ export type SocialLinksProps = {
|
|
|
5397
5438
|
|
|
5398
5439
|
/**
|
|
5399
5440
|
* Render every social profile on the company as an icon + link, in canonical
|
|
5400
|
-
* order, resolving
|
|
5441
|
+
* order, resolving BD's exact keys (`meta`, `twitterX`, …) for you. Branded
|
|
5401
5442
|
* glyphs come from the simple-icons CDN; platforms without one fall back to a
|
|
5402
5443
|
* globe. Returns `null` when no socials are set.
|
|
5403
5444
|
*/
|
|
@@ -5458,7 +5499,7 @@ export function useBlog(options: {
|
|
|
5458
5499
|
const controller = useMemo(
|
|
5459
5500
|
() =>
|
|
5460
5501
|
createBlogController({
|
|
5461
|
-
client: new
|
|
5502
|
+
client: new BdApiClient({ apiKey, baseUrl }),
|
|
5462
5503
|
...(pageSize ? { pageSize } : {}),
|
|
5463
5504
|
...(categoryId ? { categoryId } : {}),
|
|
5464
5505
|
}),
|
|
@@ -5506,7 +5547,7 @@ export function useVerify(
|
|
|
5506
5547
|
const controller = useMemo(
|
|
5507
5548
|
() =>
|
|
5508
5549
|
createVerifyController({
|
|
5509
|
-
client: new
|
|
5550
|
+
client: new BdApiClient({ apiKey, baseUrl }),
|
|
5510
5551
|
kind,
|
|
5511
5552
|
...(resendCooldownSeconds ? { resendCooldownSeconds } : {}),
|
|
5512
5553
|
}),
|
|
@@ -5556,7 +5597,7 @@ export function usePortal(options: {
|
|
|
5556
5597
|
const controller = useMemo(
|
|
5557
5598
|
() =>
|
|
5558
5599
|
createPortalController({
|
|
5559
|
-
client: new
|
|
5600
|
+
client: new BdApiClient({ apiKey, baseUrl }),
|
|
5560
5601
|
sessionToken,
|
|
5561
5602
|
}),
|
|
5562
5603
|
[apiKey, baseUrl, sessionToken],
|