@bacano/sdk 1.2.1 → 1.2.3
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/FLOWS.md +114 -0
- package/README.md +93 -18
- package/STOREFRONT_FORMS.md +50 -0
- package/STOREFRONT_MARKETING.md +58 -2
- package/STOREFRONT_PAYMENTS.md +231 -0
- package/client.d.ts +8 -1
- package/flows.d.ts +109 -0
- package/form-conditions.d.ts +6 -0
- package/index.cjs +153 -4
- package/index.d.ts +3 -0
- package/index.js +153 -4
- package/marketing/gtm.d.ts +6 -0
- package/modules/checkout.d.ts +1 -1
- package/package.json +1 -1
- package/react/flow-form.d.ts +9 -0
- package/react/form-scale-control.d.ts +9 -0
- package/react/forms.cjs +260 -65
- package/react/forms.d.ts +1 -0
- package/react/forms.js +254 -59
- package/react.cjs +117 -3
- package/react.js +117 -3
- package/skills/bacano-web/SKILL.md +2 -0
- package/skills/bacano-web/references/storefront.md +2 -0
- package/transport/documents.d.ts +1 -1
- package/types.d.ts +18 -1
package/flows.d.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { AccessTokenProvider, WebsiteFormDefinition, WebsiteFormResponseValue } from "./types";
|
|
2
|
+
export interface FlowCaseSummary {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
reference: string;
|
|
6
|
+
status: "open" | "completed" | "rejected";
|
|
7
|
+
flowName: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ParticipantFlowStep {
|
|
10
|
+
id: string;
|
|
11
|
+
kind: "content" | "form" | "task" | "review";
|
|
12
|
+
title: string;
|
|
13
|
+
required: boolean;
|
|
14
|
+
completed: boolean;
|
|
15
|
+
available: boolean;
|
|
16
|
+
canComplete: boolean;
|
|
17
|
+
internal: boolean;
|
|
18
|
+
canReadResponse: boolean;
|
|
19
|
+
completion?: "reference" | "continue" | "confirmation";
|
|
20
|
+
confirmationText?: string;
|
|
21
|
+
instructions?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ParticipantJourney extends FlowCaseSummary {
|
|
24
|
+
revision: number;
|
|
25
|
+
flowRevision: number;
|
|
26
|
+
stageId: string;
|
|
27
|
+
dueDate: string | null;
|
|
28
|
+
stages: Array<{
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
current: boolean;
|
|
32
|
+
/** Presentation only; availability and completion remain server-authoritative. */
|
|
33
|
+
presentation?: "step_by_step" | "single_view";
|
|
34
|
+
steps: ParticipantFlowStep[];
|
|
35
|
+
}>;
|
|
36
|
+
messages: Array<{
|
|
37
|
+
createdAt: string;
|
|
38
|
+
message: string;
|
|
39
|
+
}>;
|
|
40
|
+
}
|
|
41
|
+
export interface FlowPublishedForm {
|
|
42
|
+
name: string;
|
|
43
|
+
version: number;
|
|
44
|
+
definitionHash: string;
|
|
45
|
+
definition: WebsiteFormDefinition;
|
|
46
|
+
/** Own previous answers, scoped and filtered by the backend for a correction. */
|
|
47
|
+
initialValues?: Record<string, WebsiteFormResponseValue>;
|
|
48
|
+
correction?: {
|
|
49
|
+
attempt: number;
|
|
50
|
+
formChanged: boolean;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export interface FlowContentResource {
|
|
54
|
+
id: string;
|
|
55
|
+
name: string;
|
|
56
|
+
content: Record<string, unknown>;
|
|
57
|
+
}
|
|
58
|
+
export interface FlowResponse {
|
|
59
|
+
snapshot: Array<{
|
|
60
|
+
key: string;
|
|
61
|
+
label: string;
|
|
62
|
+
displayValue: unknown;
|
|
63
|
+
}>;
|
|
64
|
+
attempt: number;
|
|
65
|
+
submittedAt: string;
|
|
66
|
+
}
|
|
67
|
+
export interface FlowParticipantCommand {
|
|
68
|
+
stepId: string;
|
|
69
|
+
revision: number;
|
|
70
|
+
flowRevision: number;
|
|
71
|
+
idempotencyKey: string;
|
|
72
|
+
operation: "complete_content" | "complete_task" | "submit_form";
|
|
73
|
+
confirmed?: boolean;
|
|
74
|
+
submission?: {
|
|
75
|
+
formVersion: number;
|
|
76
|
+
definitionHash: string;
|
|
77
|
+
values: Record<string, WebsiteFormResponseValue>;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export interface FlowsClientConfig {
|
|
81
|
+
apiUrl: string;
|
|
82
|
+
getAccessToken?: AccessTokenProvider;
|
|
83
|
+
websiteSlug?: string;
|
|
84
|
+
}
|
|
85
|
+
export interface FlowDemoSession {
|
|
86
|
+
token: string;
|
|
87
|
+
name: string;
|
|
88
|
+
expiresAt: string;
|
|
89
|
+
}
|
|
90
|
+
/** Gateway only. Tokens are supplied per request; this client stores no credentials. */
|
|
91
|
+
export declare function createFlowsClient(config: FlowsClientConfig): {
|
|
92
|
+
/** Only the explicitly enabled local demo gateway accepts these credentials. */
|
|
93
|
+
localDemoLogin: (email: string, password: string) => Promise<FlowDemoSession>;
|
|
94
|
+
logout: () => Promise<{
|
|
95
|
+
success: boolean;
|
|
96
|
+
}>;
|
|
97
|
+
listCases: () => Promise<FlowCaseSummary[]>;
|
|
98
|
+
getJourney: (caseId: string) => Promise<ParticipantJourney>;
|
|
99
|
+
getContent: (caseId: string, stepId: string) => Promise<FlowContentResource[]>;
|
|
100
|
+
getForm: (caseId: string, stepId: string) => Promise<FlowPublishedForm>;
|
|
101
|
+
getResponse: (caseId: string, stepId: string) => Promise<FlowResponse>;
|
|
102
|
+
getFile: (caseId: string, stepId: string, fileId: string) => Promise<Blob>;
|
|
103
|
+
act: (caseId: string, command: FlowParticipantCommand) => Promise<{
|
|
104
|
+
caseId: string;
|
|
105
|
+
revision: number;
|
|
106
|
+
responseId: string | null;
|
|
107
|
+
}>;
|
|
108
|
+
};
|
|
109
|
+
export type FlowsClient = ReturnType<typeof createFlowsClient>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { WebsiteFormDefinition, WebsiteFormField } from "./types";
|
|
2
|
+
export declare function formRootFields(definition: WebsiteFormDefinition): WebsiteFormField[];
|
|
3
|
+
/** Unselected branches do not participate in validation or submissions. */
|
|
4
|
+
export declare function visibleFormFields(fields: WebsiteFormField[], values: Record<string, unknown>): WebsiteFormField[];
|
|
5
|
+
export declare function allFormFields(fields: WebsiteFormField[]): WebsiteFormField[];
|
|
6
|
+
export declare function activeFormValues<T>(definition: WebsiteFormDefinition, values: Record<string, T>): Record<string, T>;
|
package/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ __export(index_exports, {
|
|
|
30
30
|
RateLimitError: () => RateLimitError,
|
|
31
31
|
WEBSITE_MARKETING_CONTACT_PLACEMENTS: () => WEBSITE_MARKETING_CONTACT_PLACEMENTS,
|
|
32
32
|
WebsiteError: () => WebsiteError,
|
|
33
|
+
activeFormValues: () => activeFormValues,
|
|
33
34
|
buildStorageContentMediaUrls: () => buildStorageContentMediaUrls,
|
|
34
35
|
buildStorageFileUrl: () => buildStorageFileUrl,
|
|
35
36
|
buildStorageMediaUrls: () => buildStorageMediaUrls,
|
|
@@ -37,7 +38,9 @@ __export(index_exports, {
|
|
|
37
38
|
buildStorageTransformedFileUrl: () => buildStorageTransformedFileUrl,
|
|
38
39
|
captureWebsiteMarketingAttribution: () => captureWebsiteMarketingAttribution,
|
|
39
40
|
createBacanoClient: () => createBacanoClient,
|
|
41
|
+
createFlowsClient: () => createFlowsClient,
|
|
40
42
|
createWebsiteMarketingRuntime: () => createWebsiteMarketingRuntime,
|
|
43
|
+
formRootFields: () => formRootFields,
|
|
41
44
|
formatMediaSrcSet: () => formatMediaSrcSet,
|
|
42
45
|
getAllowedMarketingParameters: () => getAllowedMarketingParameters,
|
|
43
46
|
getContentEntryLocatorAttributes: () => getContentEntryLocatorAttributes,
|
|
@@ -51,7 +54,8 @@ __export(index_exports, {
|
|
|
51
54
|
getWebsiteMarketingAttribution: () => getWebsiteMarketingAttribution,
|
|
52
55
|
getWebsitePublicConfig: () => getWebsitePublicConfig,
|
|
53
56
|
resolveProductPricing: () => resolveProductPricing,
|
|
54
|
-
sanitizeMarketingUrl: () => sanitizeMarketingUrl
|
|
57
|
+
sanitizeMarketingUrl: () => sanitizeMarketingUrl,
|
|
58
|
+
visibleFormFields: () => visibleFormFields
|
|
55
59
|
});
|
|
56
60
|
module.exports = __toCommonJS(index_exports);
|
|
57
61
|
|
|
@@ -110,6 +114,52 @@ var GraphQLRequestError = class extends WebsiteError {
|
|
|
110
114
|
}
|
|
111
115
|
};
|
|
112
116
|
|
|
117
|
+
// libs/sdk/src/flows.ts
|
|
118
|
+
function createFlowsClient(config) {
|
|
119
|
+
const base = config.apiUrl.replace(/\/+$/, "") + "/api/v1/website/flows";
|
|
120
|
+
async function request(path, body, binary = false) {
|
|
121
|
+
const token = await config.getAccessToken?.();
|
|
122
|
+
const response = await fetch(base + path, {
|
|
123
|
+
method: body === void 0 ? "GET" : "POST",
|
|
124
|
+
cache: "no-store",
|
|
125
|
+
headers: {
|
|
126
|
+
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
127
|
+
...body !== void 0 ? { "Content-Type": "application/json" } : {},
|
|
128
|
+
...config.websiteSlug ? { "x-bacano-website-slug": config.websiteSlug } : {}
|
|
129
|
+
},
|
|
130
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
131
|
+
});
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
const result = await response.json().catch(() => ({ error: "FLOW_REQUEST_FAILED" }));
|
|
134
|
+
throw new WebsiteError(
|
|
135
|
+
result.error ?? "FLOW_REQUEST_FAILED",
|
|
136
|
+
result.error ?? "FLOW_REQUEST_FAILED"
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return binary ? await response.blob() : await response.json();
|
|
140
|
+
}
|
|
141
|
+
const stepPath = (caseId, stepId) => `/cases/${encodeURIComponent(caseId)}/steps/${encodeURIComponent(stepId)}`;
|
|
142
|
+
return {
|
|
143
|
+
/** Only the explicitly enabled local demo gateway accepts these credentials. */
|
|
144
|
+
localDemoLogin: (email, password) => request("/local-session", { email, password }),
|
|
145
|
+
logout: () => request("/logout", {}),
|
|
146
|
+
listCases: () => request("/cases"),
|
|
147
|
+
getJourney: (caseId) => request(`/cases/${encodeURIComponent(caseId)}`),
|
|
148
|
+
getContent: (caseId, stepId) => request(stepPath(caseId, stepId) + "/content"),
|
|
149
|
+
getForm: (caseId, stepId) => request(stepPath(caseId, stepId) + "/form"),
|
|
150
|
+
getResponse: (caseId, stepId) => request(stepPath(caseId, stepId) + "/response"),
|
|
151
|
+
getFile: (caseId, stepId, fileId) => request(
|
|
152
|
+
stepPath(caseId, stepId) + `/files/${encodeURIComponent(fileId)}`,
|
|
153
|
+
void 0,
|
|
154
|
+
true
|
|
155
|
+
),
|
|
156
|
+
act: (caseId, command) => request(
|
|
157
|
+
`/cases/${encodeURIComponent(caseId)}/actions`,
|
|
158
|
+
command
|
|
159
|
+
)
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
113
163
|
// libs/sdk/src/media.ts
|
|
114
164
|
var DEFAULT_RESPONSIVE_WIDTHS = [240, 384, 640, 960, 1400];
|
|
115
165
|
var CONTENT_HERO_WIDTHS = [640, 960, 1400, 1920, 2560];
|
|
@@ -394,7 +444,8 @@ async function getWebsitePublicConfig(options) {
|
|
|
394
444
|
pixelId: marketing.destinations.meta.pixel_id,
|
|
395
445
|
diagnosticMode: marketing.destinations.meta.diagnostic_mode ?? false
|
|
396
446
|
} : null,
|
|
397
|
-
clarity: marketing.destinations.clarity ? { projectId: marketing.destinations.clarity.project_id } : null
|
|
447
|
+
clarity: marketing.destinations.clarity ? { projectId: marketing.destinations.clarity.project_id } : null,
|
|
448
|
+
...marketing.destinations.gtm ? { gtm: { containerId: marketing.destinations.gtm.container_id } } : {}
|
|
398
449
|
}
|
|
399
450
|
},
|
|
400
451
|
legal: {
|
|
@@ -2239,7 +2290,7 @@ var RECONCILE_PAYMENT_SESSION = (
|
|
|
2239
2290
|
$orderId: uuid!
|
|
2240
2291
|
$publicOrderToken: String!
|
|
2241
2292
|
$paymentSessionId: uuid!
|
|
2242
|
-
$gatewayTransactionId: String
|
|
2293
|
+
$gatewayTransactionId: String
|
|
2243
2294
|
) {
|
|
2244
2295
|
websiteReconcilePaymentSession(
|
|
2245
2296
|
order_id: $orderId
|
|
@@ -6066,6 +6117,61 @@ function matchesCanonicalMarketingHost(canonicalDomain, currentHostname) {
|
|
|
6066
6117
|
}
|
|
6067
6118
|
}
|
|
6068
6119
|
|
|
6120
|
+
// libs/sdk/src/marketing/gtm.ts
|
|
6121
|
+
var CONTAINER_ID = /^GTM-[A-Z0-9]{4,32}$/;
|
|
6122
|
+
var preparedQueues = /* @__PURE__ */ new WeakMap();
|
|
6123
|
+
function prepareGtm(containerId) {
|
|
6124
|
+
const target = marketingWindow();
|
|
6125
|
+
if (!target || !CONTAINER_ID.test(containerId)) return;
|
|
6126
|
+
target.dataLayer ??= [];
|
|
6127
|
+
const containers = preparedQueues.get(target.dataLayer) ?? /* @__PURE__ */ new Set();
|
|
6128
|
+
if (containers.has(containerId)) return;
|
|
6129
|
+
target.dataLayer.push({ "gtm.start": Date.now(), event: "gtm.js" });
|
|
6130
|
+
containers.add(containerId);
|
|
6131
|
+
preparedQueues.set(target.dataLayer, containers);
|
|
6132
|
+
}
|
|
6133
|
+
function loadGtmScript(containerId, onError) {
|
|
6134
|
+
const target = marketingWindow();
|
|
6135
|
+
if (!target || typeof document === "undefined" || !CONTAINER_ID.test(containerId))
|
|
6136
|
+
return;
|
|
6137
|
+
const id = `bacano-gtm-${containerId}`;
|
|
6138
|
+
if (document.getElementById(id)) return;
|
|
6139
|
+
prepareGtm(containerId);
|
|
6140
|
+
const script = document.createElement("script");
|
|
6141
|
+
script.id = id;
|
|
6142
|
+
script.async = true;
|
|
6143
|
+
script.src = `https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(containerId)}`;
|
|
6144
|
+
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
6145
|
+
script.onerror = () => onError();
|
|
6146
|
+
document.head.appendChild(script);
|
|
6147
|
+
}
|
|
6148
|
+
function sendGtm(event) {
|
|
6149
|
+
const target = marketingWindow();
|
|
6150
|
+
if (!target?.dataLayer) return;
|
|
6151
|
+
target.dataLayer.push({ bacano: null });
|
|
6152
|
+
target.dataLayer.push({
|
|
6153
|
+
event: `bacano_${event.eventName}`,
|
|
6154
|
+
bacano: {
|
|
6155
|
+
schema_version: event.schemaVersion,
|
|
6156
|
+
event_name: event.eventName,
|
|
6157
|
+
event_id: event.eventId,
|
|
6158
|
+
...interactionParameters(event),
|
|
6159
|
+
page_location: event.pageLocation,
|
|
6160
|
+
currency: event.currency,
|
|
6161
|
+
value: event.value,
|
|
6162
|
+
items: mapGa4Items(event.items),
|
|
6163
|
+
item_list_id: event.itemListId,
|
|
6164
|
+
item_list_name: event.itemListName,
|
|
6165
|
+
shipping_tier: event.shippingTier,
|
|
6166
|
+
payment_type: event.paymentType,
|
|
6167
|
+
method: event.contactMethod,
|
|
6168
|
+
contact_placement: event.eventName === "contact" && WEBSITE_MARKETING_CONTACT_PLACEMENTS.some(
|
|
6169
|
+
(value) => value === event.contactPlacement
|
|
6170
|
+
) ? event.contactPlacement : void 0
|
|
6171
|
+
}
|
|
6172
|
+
});
|
|
6173
|
+
}
|
|
6174
|
+
|
|
6069
6175
|
// libs/sdk/src/marketing/runtime.ts
|
|
6070
6176
|
function createWebsiteMarketingRuntime(options) {
|
|
6071
6177
|
let destroyed = false;
|
|
@@ -6086,7 +6192,7 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
6086
6192
|
target.location.hostname
|
|
6087
6193
|
)
|
|
6088
6194
|
) && Boolean(
|
|
6089
|
-
options.config.destinations.ga4 || options.config.destinations.meta || options.config.destinations.clarity
|
|
6195
|
+
options.config.destinations.ga4 || options.config.destinations.meta || options.config.destinations.clarity || options.config.destinations.gtm
|
|
6090
6196
|
);
|
|
6091
6197
|
if (!enabled)
|
|
6092
6198
|
diagnostic({
|
|
@@ -6127,6 +6233,10 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
6127
6233
|
const { projectId } = options.config.destinations.clarity;
|
|
6128
6234
|
guard("Clarity", () => prepareClarity(projectId));
|
|
6129
6235
|
}
|
|
6236
|
+
if (options.config.destinations.gtm) {
|
|
6237
|
+
const { containerId } = options.config.destinations.gtm;
|
|
6238
|
+
guard("GTM", () => prepareGtm(containerId));
|
|
6239
|
+
}
|
|
6130
6240
|
cancelScheduledLoad = scheduleScriptLoad(() => {
|
|
6131
6241
|
if (destroyed) return;
|
|
6132
6242
|
if (options.config.destinations.ga4) {
|
|
@@ -6142,6 +6252,10 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
6142
6252
|
() => loadClarityScript(projectId, scriptError("Clarity"))
|
|
6143
6253
|
);
|
|
6144
6254
|
}
|
|
6255
|
+
if (options.config.destinations.gtm) {
|
|
6256
|
+
const { containerId } = options.config.destinations.gtm;
|
|
6257
|
+
guard("GTM", () => loadGtmScript(containerId, scriptError("GTM")));
|
|
6258
|
+
}
|
|
6145
6259
|
}, options.scriptLoadStrategy);
|
|
6146
6260
|
captureWebsiteMarketingAttribution(
|
|
6147
6261
|
options.websiteId,
|
|
@@ -6261,6 +6375,9 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
6261
6375
|
if (options.config.destinations.clarity) {
|
|
6262
6376
|
dispatch("Clarity", () => sendClarityEvent(event.eventName));
|
|
6263
6377
|
}
|
|
6378
|
+
if (options.config.destinations.gtm && /^GTM-[A-Z0-9]{4,32}$/.test(options.config.destinations.gtm.containerId)) {
|
|
6379
|
+
dispatch("GTM", () => sendGtm(event));
|
|
6380
|
+
}
|
|
6264
6381
|
},
|
|
6265
6382
|
captureAttribution() {
|
|
6266
6383
|
if (!enabled || destroyed) return null;
|
|
@@ -6997,6 +7114,7 @@ function createBacanoClient(config) {
|
|
|
6997
7114
|
await initialization;
|
|
6998
7115
|
},
|
|
6999
7116
|
getContext,
|
|
7117
|
+
flows: createFlowsClient(config),
|
|
7000
7118
|
auth: {
|
|
7001
7119
|
async getAccessToken() {
|
|
7002
7120
|
return config.getAccessToken ? await config.getAccessToken() : null;
|
|
@@ -7096,3 +7214,34 @@ function getContentEntryLocatorAttributes(entry) {
|
|
|
7096
7214
|
"data-bacano-entry-name": entry.name.trim() || key
|
|
7097
7215
|
};
|
|
7098
7216
|
}
|
|
7217
|
+
|
|
7218
|
+
// libs/sdk/src/form-conditions.ts
|
|
7219
|
+
function formRootFields(definition) {
|
|
7220
|
+
return [
|
|
7221
|
+
...definition.fields,
|
|
7222
|
+
...definition.sections.flatMap((section) => section.fields),
|
|
7223
|
+
...definition.steps.flatMap((step) => [
|
|
7224
|
+
...step.fields,
|
|
7225
|
+
...step.sections.flatMap((section) => section.fields)
|
|
7226
|
+
])
|
|
7227
|
+
];
|
|
7228
|
+
}
|
|
7229
|
+
function visibleFormFields(fields, values) {
|
|
7230
|
+
return fields.flatMap((field) => [
|
|
7231
|
+
field,
|
|
7232
|
+
...visibleFormFields(
|
|
7233
|
+
field.branches?.find((branch) => branch.value === values[field.key])?.fields ?? [],
|
|
7234
|
+
values
|
|
7235
|
+
)
|
|
7236
|
+
]);
|
|
7237
|
+
}
|
|
7238
|
+
function activeFormValues(definition, values) {
|
|
7239
|
+
const keys = new Set(
|
|
7240
|
+
visibleFormFields(formRootFields(definition), values).map(
|
|
7241
|
+
(field) => field.key
|
|
7242
|
+
)
|
|
7243
|
+
);
|
|
7244
|
+
return Object.fromEntries(
|
|
7245
|
+
Object.entries(values).filter(([key]) => keys.has(key))
|
|
7246
|
+
);
|
|
7247
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ export { createBacanoClient } from "./client";
|
|
|
3
3
|
export type { ContentEntryLocatorAttributes, ContentEntryLocatorSource, } from "./content-entry-locator";
|
|
4
4
|
export { CONTENT_ENTRY_LOCATOR_SELECTOR, CONTENT_ENTRY_LOCATOR_VERSION, getContentEntryLocatorAttributes, } from "./content-entry-locator";
|
|
5
5
|
export { AuthError, CheckoutError, GraphQLRequestError, NotInitializedError, RateLimitError, WebsiteError, } from "./errors";
|
|
6
|
+
export type { FlowCaseSummary, FlowContentResource, FlowDemoSession, FlowParticipantCommand, FlowPublishedForm, FlowResponse, FlowsClient, FlowsClientConfig, ParticipantFlowStep, ParticipantJourney, } from "./flows";
|
|
7
|
+
export { createFlowsClient } from "./flows";
|
|
8
|
+
export { activeFormValues, formRootFields, visibleFormFields, } from "./form-conditions";
|
|
6
9
|
export type { WebsiteAdvertisingConsent, WebsiteMarketingAttribution, WebsiteMarketingBrowserEvent, WebsiteMarketingBrowserEventName, WebsiteMarketingContactPlacement, WebsiteMarketingDiagnostic, WebsiteMarketingItem, WebsiteMarketingRuntime, WebsiteMarketingRuntimeOptions, WebsiteMarketingScriptLoadStrategy, WebsiteMarketingTouchpoint, } from "./marketing";
|
|
7
10
|
export { captureWebsiteMarketingAttribution, createWebsiteMarketingRuntime, getAllowedMarketingParameters, getWebsiteMarketingAttribution, MARKETING_SCHEMA_VERSION, sanitizeMarketingUrl, WEBSITE_MARKETING_CONTACT_PLACEMENTS, } from "./marketing";
|
|
8
11
|
export type { MediaResponsiveSource, MediaResponsiveSourceOptions, MediaTransformFormat, MediaTransformOptions, MediaUrlVariant, } from "./media";
|
package/index.js
CHANGED
|
@@ -53,6 +53,52 @@ var GraphQLRequestError = class extends WebsiteError {
|
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
+
// libs/sdk/src/flows.ts
|
|
57
|
+
function createFlowsClient(config) {
|
|
58
|
+
const base = config.apiUrl.replace(/\/+$/, "") + "/api/v1/website/flows";
|
|
59
|
+
async function request(path, body, binary = false) {
|
|
60
|
+
const token = await config.getAccessToken?.();
|
|
61
|
+
const response = await fetch(base + path, {
|
|
62
|
+
method: body === void 0 ? "GET" : "POST",
|
|
63
|
+
cache: "no-store",
|
|
64
|
+
headers: {
|
|
65
|
+
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
66
|
+
...body !== void 0 ? { "Content-Type": "application/json" } : {},
|
|
67
|
+
...config.websiteSlug ? { "x-bacano-website-slug": config.websiteSlug } : {}
|
|
68
|
+
},
|
|
69
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const result = await response.json().catch(() => ({ error: "FLOW_REQUEST_FAILED" }));
|
|
73
|
+
throw new WebsiteError(
|
|
74
|
+
result.error ?? "FLOW_REQUEST_FAILED",
|
|
75
|
+
result.error ?? "FLOW_REQUEST_FAILED"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return binary ? await response.blob() : await response.json();
|
|
79
|
+
}
|
|
80
|
+
const stepPath = (caseId, stepId) => `/cases/${encodeURIComponent(caseId)}/steps/${encodeURIComponent(stepId)}`;
|
|
81
|
+
return {
|
|
82
|
+
/** Only the explicitly enabled local demo gateway accepts these credentials. */
|
|
83
|
+
localDemoLogin: (email, password) => request("/local-session", { email, password }),
|
|
84
|
+
logout: () => request("/logout", {}),
|
|
85
|
+
listCases: () => request("/cases"),
|
|
86
|
+
getJourney: (caseId) => request(`/cases/${encodeURIComponent(caseId)}`),
|
|
87
|
+
getContent: (caseId, stepId) => request(stepPath(caseId, stepId) + "/content"),
|
|
88
|
+
getForm: (caseId, stepId) => request(stepPath(caseId, stepId) + "/form"),
|
|
89
|
+
getResponse: (caseId, stepId) => request(stepPath(caseId, stepId) + "/response"),
|
|
90
|
+
getFile: (caseId, stepId, fileId) => request(
|
|
91
|
+
stepPath(caseId, stepId) + `/files/${encodeURIComponent(fileId)}`,
|
|
92
|
+
void 0,
|
|
93
|
+
true
|
|
94
|
+
),
|
|
95
|
+
act: (caseId, command) => request(
|
|
96
|
+
`/cases/${encodeURIComponent(caseId)}/actions`,
|
|
97
|
+
command
|
|
98
|
+
)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
56
102
|
// libs/sdk/src/media.ts
|
|
57
103
|
var DEFAULT_RESPONSIVE_WIDTHS = [240, 384, 640, 960, 1400];
|
|
58
104
|
var CONTENT_HERO_WIDTHS = [640, 960, 1400, 1920, 2560];
|
|
@@ -337,7 +383,8 @@ async function getWebsitePublicConfig(options) {
|
|
|
337
383
|
pixelId: marketing.destinations.meta.pixel_id,
|
|
338
384
|
diagnosticMode: marketing.destinations.meta.diagnostic_mode ?? false
|
|
339
385
|
} : null,
|
|
340
|
-
clarity: marketing.destinations.clarity ? { projectId: marketing.destinations.clarity.project_id } : null
|
|
386
|
+
clarity: marketing.destinations.clarity ? { projectId: marketing.destinations.clarity.project_id } : null,
|
|
387
|
+
...marketing.destinations.gtm ? { gtm: { containerId: marketing.destinations.gtm.container_id } } : {}
|
|
341
388
|
}
|
|
342
389
|
},
|
|
343
390
|
legal: {
|
|
@@ -2182,7 +2229,7 @@ var RECONCILE_PAYMENT_SESSION = (
|
|
|
2182
2229
|
$orderId: uuid!
|
|
2183
2230
|
$publicOrderToken: String!
|
|
2184
2231
|
$paymentSessionId: uuid!
|
|
2185
|
-
$gatewayTransactionId: String
|
|
2232
|
+
$gatewayTransactionId: String
|
|
2186
2233
|
) {
|
|
2187
2234
|
websiteReconcilePaymentSession(
|
|
2188
2235
|
order_id: $orderId
|
|
@@ -6009,6 +6056,61 @@ function matchesCanonicalMarketingHost(canonicalDomain, currentHostname) {
|
|
|
6009
6056
|
}
|
|
6010
6057
|
}
|
|
6011
6058
|
|
|
6059
|
+
// libs/sdk/src/marketing/gtm.ts
|
|
6060
|
+
var CONTAINER_ID = /^GTM-[A-Z0-9]{4,32}$/;
|
|
6061
|
+
var preparedQueues = /* @__PURE__ */ new WeakMap();
|
|
6062
|
+
function prepareGtm(containerId) {
|
|
6063
|
+
const target = marketingWindow();
|
|
6064
|
+
if (!target || !CONTAINER_ID.test(containerId)) return;
|
|
6065
|
+
target.dataLayer ??= [];
|
|
6066
|
+
const containers = preparedQueues.get(target.dataLayer) ?? /* @__PURE__ */ new Set();
|
|
6067
|
+
if (containers.has(containerId)) return;
|
|
6068
|
+
target.dataLayer.push({ "gtm.start": Date.now(), event: "gtm.js" });
|
|
6069
|
+
containers.add(containerId);
|
|
6070
|
+
preparedQueues.set(target.dataLayer, containers);
|
|
6071
|
+
}
|
|
6072
|
+
function loadGtmScript(containerId, onError) {
|
|
6073
|
+
const target = marketingWindow();
|
|
6074
|
+
if (!target || typeof document === "undefined" || !CONTAINER_ID.test(containerId))
|
|
6075
|
+
return;
|
|
6076
|
+
const id = `bacano-gtm-${containerId}`;
|
|
6077
|
+
if (document.getElementById(id)) return;
|
|
6078
|
+
prepareGtm(containerId);
|
|
6079
|
+
const script = document.createElement("script");
|
|
6080
|
+
script.id = id;
|
|
6081
|
+
script.async = true;
|
|
6082
|
+
script.src = `https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(containerId)}`;
|
|
6083
|
+
script.referrerPolicy = "strict-origin-when-cross-origin";
|
|
6084
|
+
script.onerror = () => onError();
|
|
6085
|
+
document.head.appendChild(script);
|
|
6086
|
+
}
|
|
6087
|
+
function sendGtm(event) {
|
|
6088
|
+
const target = marketingWindow();
|
|
6089
|
+
if (!target?.dataLayer) return;
|
|
6090
|
+
target.dataLayer.push({ bacano: null });
|
|
6091
|
+
target.dataLayer.push({
|
|
6092
|
+
event: `bacano_${event.eventName}`,
|
|
6093
|
+
bacano: {
|
|
6094
|
+
schema_version: event.schemaVersion,
|
|
6095
|
+
event_name: event.eventName,
|
|
6096
|
+
event_id: event.eventId,
|
|
6097
|
+
...interactionParameters(event),
|
|
6098
|
+
page_location: event.pageLocation,
|
|
6099
|
+
currency: event.currency,
|
|
6100
|
+
value: event.value,
|
|
6101
|
+
items: mapGa4Items(event.items),
|
|
6102
|
+
item_list_id: event.itemListId,
|
|
6103
|
+
item_list_name: event.itemListName,
|
|
6104
|
+
shipping_tier: event.shippingTier,
|
|
6105
|
+
payment_type: event.paymentType,
|
|
6106
|
+
method: event.contactMethod,
|
|
6107
|
+
contact_placement: event.eventName === "contact" && WEBSITE_MARKETING_CONTACT_PLACEMENTS.some(
|
|
6108
|
+
(value) => value === event.contactPlacement
|
|
6109
|
+
) ? event.contactPlacement : void 0
|
|
6110
|
+
}
|
|
6111
|
+
});
|
|
6112
|
+
}
|
|
6113
|
+
|
|
6012
6114
|
// libs/sdk/src/marketing/runtime.ts
|
|
6013
6115
|
function createWebsiteMarketingRuntime(options) {
|
|
6014
6116
|
let destroyed = false;
|
|
@@ -6029,7 +6131,7 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
6029
6131
|
target.location.hostname
|
|
6030
6132
|
)
|
|
6031
6133
|
) && Boolean(
|
|
6032
|
-
options.config.destinations.ga4 || options.config.destinations.meta || options.config.destinations.clarity
|
|
6134
|
+
options.config.destinations.ga4 || options.config.destinations.meta || options.config.destinations.clarity || options.config.destinations.gtm
|
|
6033
6135
|
);
|
|
6034
6136
|
if (!enabled)
|
|
6035
6137
|
diagnostic({
|
|
@@ -6070,6 +6172,10 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
6070
6172
|
const { projectId } = options.config.destinations.clarity;
|
|
6071
6173
|
guard("Clarity", () => prepareClarity(projectId));
|
|
6072
6174
|
}
|
|
6175
|
+
if (options.config.destinations.gtm) {
|
|
6176
|
+
const { containerId } = options.config.destinations.gtm;
|
|
6177
|
+
guard("GTM", () => prepareGtm(containerId));
|
|
6178
|
+
}
|
|
6073
6179
|
cancelScheduledLoad = scheduleScriptLoad(() => {
|
|
6074
6180
|
if (destroyed) return;
|
|
6075
6181
|
if (options.config.destinations.ga4) {
|
|
@@ -6085,6 +6191,10 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
6085
6191
|
() => loadClarityScript(projectId, scriptError("Clarity"))
|
|
6086
6192
|
);
|
|
6087
6193
|
}
|
|
6194
|
+
if (options.config.destinations.gtm) {
|
|
6195
|
+
const { containerId } = options.config.destinations.gtm;
|
|
6196
|
+
guard("GTM", () => loadGtmScript(containerId, scriptError("GTM")));
|
|
6197
|
+
}
|
|
6088
6198
|
}, options.scriptLoadStrategy);
|
|
6089
6199
|
captureWebsiteMarketingAttribution(
|
|
6090
6200
|
options.websiteId,
|
|
@@ -6204,6 +6314,9 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
6204
6314
|
if (options.config.destinations.clarity) {
|
|
6205
6315
|
dispatch("Clarity", () => sendClarityEvent(event.eventName));
|
|
6206
6316
|
}
|
|
6317
|
+
if (options.config.destinations.gtm && /^GTM-[A-Z0-9]{4,32}$/.test(options.config.destinations.gtm.containerId)) {
|
|
6318
|
+
dispatch("GTM", () => sendGtm(event));
|
|
6319
|
+
}
|
|
6207
6320
|
},
|
|
6208
6321
|
captureAttribution() {
|
|
6209
6322
|
if (!enabled || destroyed) return null;
|
|
@@ -6940,6 +7053,7 @@ function createBacanoClient(config) {
|
|
|
6940
7053
|
await initialization;
|
|
6941
7054
|
},
|
|
6942
7055
|
getContext,
|
|
7056
|
+
flows: createFlowsClient(config),
|
|
6943
7057
|
auth: {
|
|
6944
7058
|
async getAccessToken() {
|
|
6945
7059
|
return config.getAccessToken ? await config.getAccessToken() : null;
|
|
@@ -7039,6 +7153,37 @@ function getContentEntryLocatorAttributes(entry) {
|
|
|
7039
7153
|
"data-bacano-entry-name": entry.name.trim() || key
|
|
7040
7154
|
};
|
|
7041
7155
|
}
|
|
7156
|
+
|
|
7157
|
+
// libs/sdk/src/form-conditions.ts
|
|
7158
|
+
function formRootFields(definition) {
|
|
7159
|
+
return [
|
|
7160
|
+
...definition.fields,
|
|
7161
|
+
...definition.sections.flatMap((section) => section.fields),
|
|
7162
|
+
...definition.steps.flatMap((step) => [
|
|
7163
|
+
...step.fields,
|
|
7164
|
+
...step.sections.flatMap((section) => section.fields)
|
|
7165
|
+
])
|
|
7166
|
+
];
|
|
7167
|
+
}
|
|
7168
|
+
function visibleFormFields(fields, values) {
|
|
7169
|
+
return fields.flatMap((field) => [
|
|
7170
|
+
field,
|
|
7171
|
+
...visibleFormFields(
|
|
7172
|
+
field.branches?.find((branch) => branch.value === values[field.key])?.fields ?? [],
|
|
7173
|
+
values
|
|
7174
|
+
)
|
|
7175
|
+
]);
|
|
7176
|
+
}
|
|
7177
|
+
function activeFormValues(definition, values) {
|
|
7178
|
+
const keys = new Set(
|
|
7179
|
+
visibleFormFields(formRootFields(definition), values).map(
|
|
7180
|
+
(field) => field.key
|
|
7181
|
+
)
|
|
7182
|
+
);
|
|
7183
|
+
return Object.fromEntries(
|
|
7184
|
+
Object.entries(values).filter(([key]) => keys.has(key))
|
|
7185
|
+
);
|
|
7186
|
+
}
|
|
7042
7187
|
export {
|
|
7043
7188
|
AuthError,
|
|
7044
7189
|
CONTENT_ENTRY_LOCATOR_SELECTOR,
|
|
@@ -7050,6 +7195,7 @@ export {
|
|
|
7050
7195
|
RateLimitError,
|
|
7051
7196
|
WEBSITE_MARKETING_CONTACT_PLACEMENTS,
|
|
7052
7197
|
WebsiteError,
|
|
7198
|
+
activeFormValues,
|
|
7053
7199
|
buildStorageContentMediaUrls,
|
|
7054
7200
|
buildStorageFileUrl,
|
|
7055
7201
|
buildStorageMediaUrls,
|
|
@@ -7057,7 +7203,9 @@ export {
|
|
|
7057
7203
|
buildStorageTransformedFileUrl,
|
|
7058
7204
|
captureWebsiteMarketingAttribution,
|
|
7059
7205
|
createBacanoClient,
|
|
7206
|
+
createFlowsClient,
|
|
7060
7207
|
createWebsiteMarketingRuntime,
|
|
7208
|
+
formRootFields,
|
|
7061
7209
|
formatMediaSrcSet,
|
|
7062
7210
|
getAllowedMarketingParameters,
|
|
7063
7211
|
getContentEntryLocatorAttributes,
|
|
@@ -7071,5 +7219,6 @@ export {
|
|
|
7071
7219
|
getWebsiteMarketingAttribution,
|
|
7072
7220
|
getWebsitePublicConfig,
|
|
7073
7221
|
resolveProductPricing,
|
|
7074
|
-
sanitizeMarketingUrl
|
|
7222
|
+
sanitizeMarketingUrl,
|
|
7223
|
+
visibleFormFields
|
|
7075
7224
|
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { WebsiteMarketingBrowserEvent } from "./types";
|
|
2
|
+
/** Preserve the shared Google queue, including an existing native gtag.js. */
|
|
3
|
+
export declare function prepareGtm(containerId: string): void;
|
|
4
|
+
export declare function loadGtmScript(containerId: string, onError: () => void): void;
|
|
5
|
+
/** Explicit allowlist: never spread caller input, customer data or attribution. */
|
|
6
|
+
export declare function sendGtm(event: WebsiteMarketingBrowserEvent): void;
|
package/modules/checkout.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export interface CheckoutModule {
|
|
|
36
36
|
orderId: string;
|
|
37
37
|
publicOrderToken: string;
|
|
38
38
|
paymentSessionId: string;
|
|
39
|
-
gatewayTransactionId
|
|
39
|
+
gatewayTransactionId?: string;
|
|
40
40
|
}): Promise<WebsitePaymentSessionResult>;
|
|
41
41
|
}
|
|
42
42
|
export declare function createCheckoutModule(graphqlUrl: string, getHeaders: RequestHeadersProvider, getContext: () => BacanoClientContext, cartSessionStorage: CartSessionStorage): CheckoutModule;
|
package/package.json
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FlowPublishedForm } from "../flows";
|
|
2
|
+
import type { BacanoFormClassNames, BacanoFormValues } from "./forms";
|
|
3
|
+
/** Reuses the Forms controls but never calls a website form-entry endpoint. */
|
|
4
|
+
export declare function BacanoFlowForm({ form, onSubmit, onRefresh, classNames, }: {
|
|
5
|
+
form: FlowPublishedForm;
|
|
6
|
+
onSubmit(values: BacanoFormValues, form: FlowPublishedForm): Promise<void>;
|
|
7
|
+
onRefresh(): Promise<void>;
|
|
8
|
+
classNames?: BacanoFormClassNames;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { WebsiteFormField } from "../types";
|
|
2
|
+
/** Discrete radios preserve keyboard support, required validation and an empty initial answer. */
|
|
3
|
+
export declare function FormScaleControl({ field, id, value, onChange, className, }: {
|
|
4
|
+
field: WebsiteFormField;
|
|
5
|
+
id: string;
|
|
6
|
+
value: string | string[] | boolean;
|
|
7
|
+
onChange(value: string): void;
|
|
8
|
+
className?: string;
|
|
9
|
+
}): import("react").JSX.Element | null;
|