@bacano/sdk 1.2.2 → 1.2.4
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/CONTENT_TYPE_MANAGEMENT.md +42 -2
- package/FLOWS.md +114 -0
- package/README.md +89 -18
- package/STOREFRONT_FORMS.md +50 -0
- package/STOREFRONT_PAYMENTS.md +231 -0
- package/client.d.ts +9 -1
- package/content-management-types.d.ts +44 -1
- package/content-management.cjs +26 -0
- package/content-management.js +26 -0
- package/flows.d.ts +109 -0
- package/form-conditions.d.ts +6 -0
- package/index.cjs +142 -3
- package/index.d.ts +3 -0
- package/index.js +142 -3
- package/modules/checkout.d.ts +1 -1
- package/modules/content.d.ts +1 -0
- 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 +106 -2
- package/react.js +106 -2
- package/skills/bacano-web/SKILL.md +2 -0
- package/skills/bacano-web/references/storefront.md +2 -0
- package/transport/documents.d.ts +2 -1
- package/types.d.ts +14 -1
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];
|
|
@@ -1307,6 +1353,35 @@ var WEBSITE_GET_PRODUCT_CONTENT = (
|
|
|
1307
1353
|
}
|
|
1308
1354
|
`
|
|
1309
1355
|
);
|
|
1356
|
+
var WEBSITE_GET_PRODUCT_CONTENT_BATCH = (
|
|
1357
|
+
/* GraphQL */
|
|
1358
|
+
`
|
|
1359
|
+
mutation WebsiteGetProductContentBatch(
|
|
1360
|
+
$websiteId: uuid!
|
|
1361
|
+
$productIds: [uuid!]!
|
|
1362
|
+
$locale: String
|
|
1363
|
+
) {
|
|
1364
|
+
result: websiteGetProductContentBatch(
|
|
1365
|
+
website_id: $websiteId
|
|
1366
|
+
product_ids: $productIds
|
|
1367
|
+
locale: $locale
|
|
1368
|
+
) {
|
|
1369
|
+
success
|
|
1370
|
+
errors
|
|
1371
|
+
blocks {
|
|
1372
|
+
id
|
|
1373
|
+
product_id
|
|
1374
|
+
locale
|
|
1375
|
+
content_json
|
|
1376
|
+
resolved_fields
|
|
1377
|
+
sort_order
|
|
1378
|
+
updated_at
|
|
1379
|
+
content_type { id key name kind }
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
`
|
|
1384
|
+
);
|
|
1310
1385
|
var CHECK_AVAILABILITY = (
|
|
1311
1386
|
/* GraphQL */
|
|
1312
1387
|
`
|
|
@@ -2183,7 +2258,7 @@ var RECONCILE_PAYMENT_SESSION = (
|
|
|
2183
2258
|
$orderId: uuid!
|
|
2184
2259
|
$publicOrderToken: String!
|
|
2185
2260
|
$paymentSessionId: uuid!
|
|
2186
|
-
$gatewayTransactionId: String
|
|
2261
|
+
$gatewayTransactionId: String
|
|
2187
2262
|
) {
|
|
2188
2263
|
websiteReconcilePaymentSession(
|
|
2189
2264
|
order_id: $orderId
|
|
@@ -5675,6 +5750,33 @@ function createContentModule(graphqlUrl, getHeaders, getContext, storageUrl) {
|
|
|
5675
5750
|
return (data.result.blocks ?? []).map(
|
|
5676
5751
|
(block) => mapProductContentBlock(block, storageUrl)
|
|
5677
5752
|
);
|
|
5753
|
+
},
|
|
5754
|
+
async getForProducts(productIds, opts) {
|
|
5755
|
+
const uniqueIds = [...new Set(productIds.map((id) => id.toLowerCase()))];
|
|
5756
|
+
const blocksByProduct = Object.fromEntries(uniqueIds.map((id) => [id, []]));
|
|
5757
|
+
if (uniqueIds.length === 0) return blocksByProduct;
|
|
5758
|
+
const ctx = getContext();
|
|
5759
|
+
for (let start = 0; start < uniqueIds.length; start += 50) {
|
|
5760
|
+
const batch = uniqueIds.slice(start, start + 50);
|
|
5761
|
+
const data = await executeGraphQL(
|
|
5762
|
+
graphqlUrl,
|
|
5763
|
+
WEBSITE_GET_PRODUCT_CONTENT_BATCH,
|
|
5764
|
+
{
|
|
5765
|
+
websiteId: ctx.websiteId,
|
|
5766
|
+
productIds: batch,
|
|
5767
|
+
locale: opts?.locale ?? DEFAULT_LOCALE
|
|
5768
|
+
},
|
|
5769
|
+
getHeaders
|
|
5770
|
+
);
|
|
5771
|
+
if (!data.result?.success) continue;
|
|
5772
|
+
for (const block of data.result.blocks ?? []) {
|
|
5773
|
+
const id = block.product_id.toLowerCase();
|
|
5774
|
+
if (Object.prototype.hasOwnProperty.call(blocksByProduct, id)) {
|
|
5775
|
+
blocksByProduct[id].push(mapProductContentBlock(block, storageUrl));
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
return blocksByProduct;
|
|
5678
5780
|
}
|
|
5679
5781
|
};
|
|
5680
5782
|
}
|
|
@@ -7007,6 +7109,7 @@ function createBacanoClient(config) {
|
|
|
7007
7109
|
await initialization;
|
|
7008
7110
|
},
|
|
7009
7111
|
getContext,
|
|
7112
|
+
flows: createFlowsClient(config),
|
|
7010
7113
|
auth: {
|
|
7011
7114
|
async getAccessToken() {
|
|
7012
7115
|
return config.getAccessToken ? await config.getAccessToken() : null;
|
|
@@ -7039,7 +7142,8 @@ function createBacanoClient(config) {
|
|
|
7039
7142
|
getByKey: contentModule.getByKey.bind(contentModule),
|
|
7040
7143
|
getByCategory: contentModule.getByCategory.bind(contentModule),
|
|
7041
7144
|
getByTag: contentModule.getByTag.bind(contentModule),
|
|
7042
|
-
getForProduct: contentModule.getForProduct.bind(contentModule)
|
|
7145
|
+
getForProduct: contentModule.getForProduct.bind(contentModule),
|
|
7146
|
+
getForProducts: contentModule.getForProducts.bind(contentModule)
|
|
7043
7147
|
},
|
|
7044
7148
|
forms: {
|
|
7045
7149
|
getDefinition: formsModule.getDefinition.bind(formsModule),
|
|
@@ -7106,6 +7210,37 @@ function getContentEntryLocatorAttributes(entry) {
|
|
|
7106
7210
|
"data-bacano-entry-name": entry.name.trim() || key
|
|
7107
7211
|
};
|
|
7108
7212
|
}
|
|
7213
|
+
|
|
7214
|
+
// libs/sdk/src/form-conditions.ts
|
|
7215
|
+
function formRootFields(definition) {
|
|
7216
|
+
return [
|
|
7217
|
+
...definition.fields,
|
|
7218
|
+
...definition.sections.flatMap((section) => section.fields),
|
|
7219
|
+
...definition.steps.flatMap((step) => [
|
|
7220
|
+
...step.fields,
|
|
7221
|
+
...step.sections.flatMap((section) => section.fields)
|
|
7222
|
+
])
|
|
7223
|
+
];
|
|
7224
|
+
}
|
|
7225
|
+
function visibleFormFields(fields, values) {
|
|
7226
|
+
return fields.flatMap((field) => [
|
|
7227
|
+
field,
|
|
7228
|
+
...visibleFormFields(
|
|
7229
|
+
field.branches?.find((branch) => branch.value === values[field.key])?.fields ?? [],
|
|
7230
|
+
values
|
|
7231
|
+
)
|
|
7232
|
+
]);
|
|
7233
|
+
}
|
|
7234
|
+
function activeFormValues(definition, values) {
|
|
7235
|
+
const keys = new Set(
|
|
7236
|
+
visibleFormFields(formRootFields(definition), values).map(
|
|
7237
|
+
(field) => field.key
|
|
7238
|
+
)
|
|
7239
|
+
);
|
|
7240
|
+
return Object.fromEntries(
|
|
7241
|
+
Object.entries(values).filter(([key]) => keys.has(key))
|
|
7242
|
+
);
|
|
7243
|
+
}
|
|
7109
7244
|
export {
|
|
7110
7245
|
AuthError,
|
|
7111
7246
|
CONTENT_ENTRY_LOCATOR_SELECTOR,
|
|
@@ -7117,6 +7252,7 @@ export {
|
|
|
7117
7252
|
RateLimitError,
|
|
7118
7253
|
WEBSITE_MARKETING_CONTACT_PLACEMENTS,
|
|
7119
7254
|
WebsiteError,
|
|
7255
|
+
activeFormValues,
|
|
7120
7256
|
buildStorageContentMediaUrls,
|
|
7121
7257
|
buildStorageFileUrl,
|
|
7122
7258
|
buildStorageMediaUrls,
|
|
@@ -7124,7 +7260,9 @@ export {
|
|
|
7124
7260
|
buildStorageTransformedFileUrl,
|
|
7125
7261
|
captureWebsiteMarketingAttribution,
|
|
7126
7262
|
createBacanoClient,
|
|
7263
|
+
createFlowsClient,
|
|
7127
7264
|
createWebsiteMarketingRuntime,
|
|
7265
|
+
formRootFields,
|
|
7128
7266
|
formatMediaSrcSet,
|
|
7129
7267
|
getAllowedMarketingParameters,
|
|
7130
7268
|
getContentEntryLocatorAttributes,
|
|
@@ -7138,5 +7276,6 @@ export {
|
|
|
7138
7276
|
getWebsiteMarketingAttribution,
|
|
7139
7277
|
getWebsitePublicConfig,
|
|
7140
7278
|
resolveProductPricing,
|
|
7141
|
-
sanitizeMarketingUrl
|
|
7279
|
+
sanitizeMarketingUrl,
|
|
7280
|
+
visibleFormFields
|
|
7142
7281
|
};
|
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/modules/content.d.ts
CHANGED
|
@@ -5,5 +5,6 @@ export interface ContentModule {
|
|
|
5
5
|
getByCategory(categoryKey: string, opts?: GetContentOptions): Promise<ContentEntry[]>;
|
|
6
6
|
getByTag(tag: string, opts?: GetContentOptions): Promise<ContentEntry[]>;
|
|
7
7
|
getForProduct(productId: string, opts?: GetContentOptions): Promise<ProductContentBlock[]>;
|
|
8
|
+
getForProducts(productIds: string[], opts?: GetContentOptions): Promise<Record<string, ProductContentBlock[]>>;
|
|
8
9
|
}
|
|
9
10
|
export declare function createContentModule(graphqlUrl: string, getHeaders: RequestHeadersProvider, getContext: () => BacanoClientContext, storageUrl?: string): ContentModule;
|
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;
|