@bacano/sdk 1.1.13 → 1.1.17
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 +54 -8
- package/README.md +11 -4
- package/STOREFRONT_FORMS.md +147 -0
- package/client.d.ts +8 -1
- package/content-management-types.d.ts +31 -0
- package/content-management.cjs +39 -1
- package/content-management.js +39 -1
- package/index.cjs +172 -2
- package/index.d.ts +1 -1
- package/index.js +172 -2
- package/modules/forms.d.ts +9 -0
- package/package.json +6 -1
- package/react/form-field-control.d.ts +10 -0
- package/react/forms.cjs +450 -0
- package/react/forms.d.ts +49 -0
- package/react/forms.js +435 -0
- package/react.cjs +172 -2
- package/react.js +172 -2
- package/skills/bacano-figma-web/SKILL.md +9 -3
- package/skills/bacano-figma-web/references/content-media.md +57 -0
- package/skills/bacano-figma-web/references/content-workflow.md +7 -4
- package/skills/bacano-web/SKILL.md +3 -0
- package/skills/bacano-web/references/forms.md +50 -0
- package/skills/bacano-web/references/informational.md +3 -0
- package/types.d.ts +83 -0
package/index.d.ts
CHANGED
|
@@ -7,4 +7,4 @@ export type { MediaResponsiveSource, MediaResponsiveSourceOptions, MediaTransfor
|
|
|
7
7
|
export { buildStorageContentMediaUrls, buildStorageFileUrl, buildStorageMediaUrls, buildStorageResponsiveSources, buildStorageTransformedFileUrl, formatMediaSrcSet, } from "./media";
|
|
8
8
|
export { getWebsiteContextFromPublicConfig, getWebsitePublicConfig, } from "./modules/auth";
|
|
9
9
|
export { getPrimaryProductImage, getProductDetailVariants, getProductImages, getProductMediaByKind, getProductMediaKind, getProductVideos, resolveProductPricing, } from "./modules/catalog";
|
|
10
|
-
export type { AccessTokenProvider, AttributeFilter, BacanoClientConfig, BacanoClientContext, Brand, Cart, CartItem, CatalogAttributeList, CatalogAttributeListFilters, CatalogAttributeListItem, CatalogAttributeListValue, CatalogCategoryList, CatalogCategoryListItem, CatalogCategoryListItemSourceType, CheckoutResult, CheckoutSubmission, ContentEntry, ContentJson, ContentMedia, ContentTypeSummary, CustomerAddress, CustomerAddressType, CustomerProfile, CustomerProfileUpdate, GetAttributeListOptions, GetCategoryListOptions, GetContentOptions, GetOrdersOptions, GetProductFiltersOptions, GetProductSeoEntriesOptions, GetProductsOptions, GetSearchSuggestionsOptions, GetStaticCatalogBootstrapOptions, GetStaticCatalogSnapshotOptions, GetWebsitePublicConfigOptions, Order, OrderItem, Product, ProductAttribute, ProductAvailability, ProductCategory, ProductContentBlock, ProductFilters, ProductMedia, ProductMediaKind, ProductOptionGroup, ProductOptionGroupMember, ProductPrice, ProductPriceType, ProductRelatedProduct, ProductSeoEntriesResult, ProductSeoEntry, ProductSortBy, ProductSpecification, ProductSpecificationFacet, ProductSpecificationField, ProductSpecificationFilterSelection, ProductSpecificationMeasure, ProductSpecificationOption, ProductSpecificationSection, ProductSubcategory, ProductVariant, ProductVariantLiveState, ProductVariantPricing, PublicOrder, PublicOrderDeliverySummary, PublicOrderItem, PublicOrderLookupInput, PublicOrderPaymentSummary, ResolvedWebsitePublicConfig, SearchSuggestion, StartGuestCheckoutInput, StaticCatalogBootstrap, StaticCatalogSnapshot, VariantAttribute, WaitForPublicOrderPaymentInput, WebsiteBrandingMedia, WebsiteBrandingPublicConfig, WebsiteCampaignCaptureContact, WebsiteCampaignCaptureInput, WebsiteCampaignCaptureResult, WebsiteCampaignCaptureSource, WebsiteCheckoutAddress, WebsiteCheckoutItem, WebsiteCouponActionResult, WebsiteDeliveryOption, WebsiteDepartmentOption, WebsiteFreeShippingProgress, WebsiteFulfillmentFamily, WebsiteGuestContact, WebsiteLegalPublicConfig, WebsiteLocationOptions, WebsiteMunicipalityOption, WebsitePaymentMethod, WebsitePaymentMode, WebsitePaymentOption, WebsitePaymentOptionMethod, WebsitePaymentSessionResult, WebsitePickupInfo, WebsitePublicConfig, WebsiteShippingPolicy, WebsiteShippingQuote, } from "./types";
|
|
10
|
+
export type { AccessTokenProvider, AttributeFilter, BacanoClientConfig, BacanoClientContext, Brand, Cart, CartItem, CatalogAttributeList, CatalogAttributeListFilters, CatalogAttributeListItem, CatalogAttributeListValue, CatalogCategoryList, CatalogCategoryListItem, CatalogCategoryListItemSourceType, CheckoutResult, CheckoutSubmission, ContentEntry, ContentJson, ContentMedia, ContentTypeSummary, CustomerAddress, CustomerAddressType, CustomerProfile, CustomerProfileUpdate, GetAttributeListOptions, GetCategoryListOptions, GetContentOptions, GetOrdersOptions, GetProductFiltersOptions, GetProductSeoEntriesOptions, GetProductsOptions, GetSearchSuggestionsOptions, GetStaticCatalogBootstrapOptions, GetStaticCatalogSnapshotOptions, GetWebsitePublicConfigOptions, Order, OrderItem, Product, ProductAttribute, ProductAvailability, ProductCategory, ProductContentBlock, ProductFilters, ProductMedia, ProductMediaKind, ProductOptionGroup, ProductOptionGroupMember, ProductPrice, ProductPriceType, ProductRelatedProduct, ProductSeoEntriesResult, ProductSeoEntry, ProductSortBy, ProductSpecification, ProductSpecificationFacet, ProductSpecificationField, ProductSpecificationFilterSelection, ProductSpecificationMeasure, ProductSpecificationOption, ProductSpecificationSection, ProductSubcategory, ProductVariant, ProductVariantLiveState, ProductVariantPricing, PublicOrder, PublicOrderDeliverySummary, PublicOrderItem, PublicOrderLookupInput, PublicOrderPaymentSummary, ResolvedWebsitePublicConfig, SearchSuggestion, StartGuestCheckoutInput, StaticCatalogBootstrap, StaticCatalogSnapshot, VariantAttribute, WaitForPublicOrderPaymentInput, WebsiteBrandingMedia, WebsiteBrandingPublicConfig, WebsiteCampaignCaptureContact, WebsiteCampaignCaptureInput, WebsiteCampaignCaptureResult, WebsiteCampaignCaptureSource, WebsiteCheckoutAddress, WebsiteCheckoutItem, WebsiteCouponActionResult, WebsiteDeliveryOption, WebsiteDepartmentOption, WebsiteFormAction, WebsiteFormDefinition, WebsiteFormField, WebsiteFormFieldType, WebsiteFormOption, WebsiteFormOptionSource, WebsiteFormResponseInput, WebsiteFormResponseResult, WebsiteFormResponseSource, WebsiteFormResponseValue, WebsiteFormSection, WebsiteFormStatus, WebsiteFormStep, WebsiteFreeShippingProgress, WebsiteFulfillmentFamily, WebsiteGuestContact, WebsiteLegalPublicConfig, WebsiteLocationOptions, WebsiteMunicipalityOption, WebsitePaymentMethod, WebsitePaymentMode, WebsitePaymentOption, WebsitePaymentOptionMethod, WebsitePaymentSessionResult, WebsitePickupInfo, WebsitePublicConfig, WebsitePublishedForm, WebsiteShippingPolicy, WebsiteShippingQuote, } from "./types";
|
package/index.js
CHANGED
|
@@ -12,10 +12,10 @@ var WebsiteError = class extends Error {
|
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
14
|
var RateLimitError = class extends WebsiteError {
|
|
15
|
-
constructor(message,
|
|
15
|
+
constructor(message, retryAfterSeconds2) {
|
|
16
16
|
super(message, "RATE_LIMITED", {
|
|
17
17
|
status: 429,
|
|
18
|
-
retryAfterSeconds
|
|
18
|
+
retryAfterSeconds: retryAfterSeconds2
|
|
19
19
|
});
|
|
20
20
|
this.name = "RateLimitError";
|
|
21
21
|
}
|
|
@@ -6061,6 +6061,171 @@ function createWebsiteMarketingRuntime(options) {
|
|
|
6061
6061
|
};
|
|
6062
6062
|
}
|
|
6063
6063
|
|
|
6064
|
+
// libs/sdk/src/modules/forms.ts
|
|
6065
|
+
function retryAfterSeconds(response) {
|
|
6066
|
+
const value = Number(response.headers.get("Retry-After") ?? "1");
|
|
6067
|
+
return Number.isFinite(value) && value >= 0 ? Math.ceil(value) : 1;
|
|
6068
|
+
}
|
|
6069
|
+
function isFormKey(value) {
|
|
6070
|
+
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
|
|
6071
|
+
}
|
|
6072
|
+
function createFormsModule(apiUrl, websiteSlug, getContext) {
|
|
6073
|
+
const cache = /* @__PURE__ */ new Map();
|
|
6074
|
+
function getServerOrigin() {
|
|
6075
|
+
if (typeof window !== "undefined") return null;
|
|
6076
|
+
const domain = getContext().domain?.trim();
|
|
6077
|
+
if (!domain) return null;
|
|
6078
|
+
try {
|
|
6079
|
+
return new URL(domain.includes("://") ? domain : `https://${domain}`).origin;
|
|
6080
|
+
} catch {
|
|
6081
|
+
return null;
|
|
6082
|
+
}
|
|
6083
|
+
}
|
|
6084
|
+
return {
|
|
6085
|
+
async getDefinition(formKey, options) {
|
|
6086
|
+
const normalizedKey = formKey.trim();
|
|
6087
|
+
if (!isFormKey(normalizedKey)) {
|
|
6088
|
+
throw new WebsiteError(
|
|
6089
|
+
"formKey has an invalid format",
|
|
6090
|
+
"INVALID_FORM_KEY"
|
|
6091
|
+
);
|
|
6092
|
+
}
|
|
6093
|
+
const cached = cache.get(normalizedKey);
|
|
6094
|
+
if (!options?.force && cached && cached.expiresAt > Date.now()) {
|
|
6095
|
+
return cached.value;
|
|
6096
|
+
}
|
|
6097
|
+
const url = new URL(
|
|
6098
|
+
`${apiUrl.replace(/\/+$/, "")}/api/v1/website/forms/definition`
|
|
6099
|
+
);
|
|
6100
|
+
url.searchParams.set("websiteSlug", websiteSlug);
|
|
6101
|
+
url.searchParams.set("formKey", normalizedKey);
|
|
6102
|
+
let response;
|
|
6103
|
+
try {
|
|
6104
|
+
const serverOrigin = getServerOrigin();
|
|
6105
|
+
const headers = new Headers();
|
|
6106
|
+
if (serverOrigin) headers.set("Origin", serverOrigin);
|
|
6107
|
+
if (cached?.etag) headers.set("If-None-Match", cached.etag);
|
|
6108
|
+
response = await fetch(url, {
|
|
6109
|
+
...serverOrigin || cached?.etag ? { headers } : {},
|
|
6110
|
+
...options?.force ? { cache: "no-cache" } : {}
|
|
6111
|
+
});
|
|
6112
|
+
} catch (error) {
|
|
6113
|
+
throw new WebsiteError(
|
|
6114
|
+
`Could not load the website form: ${error instanceof Error ? error.message : String(error)}`,
|
|
6115
|
+
"NETWORK_ERROR"
|
|
6116
|
+
);
|
|
6117
|
+
}
|
|
6118
|
+
if (response.status === 304 && cached) {
|
|
6119
|
+
cache.set(normalizedKey, {
|
|
6120
|
+
...cached,
|
|
6121
|
+
expiresAt: Date.now() + 3e4
|
|
6122
|
+
});
|
|
6123
|
+
return cached.value;
|
|
6124
|
+
}
|
|
6125
|
+
if (response.status === 404) {
|
|
6126
|
+
cache.set(normalizedKey, {
|
|
6127
|
+
etag: null,
|
|
6128
|
+
expiresAt: Date.now() + 15e3,
|
|
6129
|
+
value: null
|
|
6130
|
+
});
|
|
6131
|
+
return null;
|
|
6132
|
+
}
|
|
6133
|
+
if (response.status === 429) {
|
|
6134
|
+
throw new RateLimitError(
|
|
6135
|
+
"Rate limit exceeded. Please try again later.",
|
|
6136
|
+
retryAfterSeconds(response)
|
|
6137
|
+
);
|
|
6138
|
+
}
|
|
6139
|
+
if (!response.ok) {
|
|
6140
|
+
throw new WebsiteError(
|
|
6141
|
+
`Could not load the website form (status ${response.status})`,
|
|
6142
|
+
response.status === 403 ? "ORIGIN_NOT_ALLOWED" : "FORM_DEFINITION_ERROR",
|
|
6143
|
+
{ status: response.status }
|
|
6144
|
+
);
|
|
6145
|
+
}
|
|
6146
|
+
const value = await response.json();
|
|
6147
|
+
if (value.schemaVersion !== 1 || value.key !== normalizedKey || !Array.isArray(value.definition?.fields) || !Array.isArray(value.definition?.sections) || !Array.isArray(value.definition?.steps)) {
|
|
6148
|
+
throw new WebsiteError(
|
|
6149
|
+
"Website form returned an invalid definition",
|
|
6150
|
+
"INVALID_FORM_DEFINITION"
|
|
6151
|
+
);
|
|
6152
|
+
}
|
|
6153
|
+
cache.set(normalizedKey, {
|
|
6154
|
+
etag: response.headers.get("ETag"),
|
|
6155
|
+
expiresAt: Date.now() + 3e4,
|
|
6156
|
+
value
|
|
6157
|
+
});
|
|
6158
|
+
return value;
|
|
6159
|
+
},
|
|
6160
|
+
async submitResponse(input) {
|
|
6161
|
+
const normalizedKey = input.formKey.trim();
|
|
6162
|
+
if (!isFormKey(normalizedKey)) {
|
|
6163
|
+
throw new WebsiteError(
|
|
6164
|
+
"formKey has an invalid format",
|
|
6165
|
+
"INVALID_FORM_KEY"
|
|
6166
|
+
);
|
|
6167
|
+
}
|
|
6168
|
+
const context = getContext();
|
|
6169
|
+
const attribution = getWebsiteMarketingAttribution(context.websiteId);
|
|
6170
|
+
const pageUrl = typeof window !== "undefined" ? window.location.href : void 0;
|
|
6171
|
+
const referrerUrl = typeof document !== "undefined" && document.referrer ? document.referrer : void 0;
|
|
6172
|
+
let response;
|
|
6173
|
+
try {
|
|
6174
|
+
response = await fetch(
|
|
6175
|
+
`${apiUrl.replace(/\/+$/, "")}/api/v1/website/forms/responses`,
|
|
6176
|
+
{
|
|
6177
|
+
method: "POST",
|
|
6178
|
+
headers: { "Content-Type": "application/json" },
|
|
6179
|
+
body: JSON.stringify({
|
|
6180
|
+
websiteSlug,
|
|
6181
|
+
formKey: normalizedKey,
|
|
6182
|
+
formVersion: input.formVersion,
|
|
6183
|
+
definitionHash: input.definitionHash,
|
|
6184
|
+
idempotencyKey: input.idempotencyKey,
|
|
6185
|
+
source: input.source ?? "WEBSITE",
|
|
6186
|
+
values: input.values,
|
|
6187
|
+
pageUrl,
|
|
6188
|
+
referrerUrl,
|
|
6189
|
+
attribution: attribution ? serializeWebsiteMarketingAttribution(attribution) : null,
|
|
6190
|
+
honeypot: input.honeypot ?? ""
|
|
6191
|
+
})
|
|
6192
|
+
}
|
|
6193
|
+
);
|
|
6194
|
+
} catch (error) {
|
|
6195
|
+
throw new WebsiteError(
|
|
6196
|
+
`Could not submit the website form: ${error instanceof Error ? error.message : String(error)}`,
|
|
6197
|
+
"NETWORK_ERROR"
|
|
6198
|
+
);
|
|
6199
|
+
}
|
|
6200
|
+
if (response.status === 429) {
|
|
6201
|
+
throw new RateLimitError(
|
|
6202
|
+
"Rate limit exceeded. Please try again later.",
|
|
6203
|
+
retryAfterSeconds(response)
|
|
6204
|
+
);
|
|
6205
|
+
}
|
|
6206
|
+
const result = await response.json().catch(() => null);
|
|
6207
|
+
if (!response.ok) {
|
|
6208
|
+
throw new WebsiteError(
|
|
6209
|
+
result?.message ?? `Could not submit the website form (status ${response.status})`,
|
|
6210
|
+
result?.code ?? (response.status === 403 ? "ORIGIN_NOT_ALLOWED" : "FORM_RESPONSE_ERROR"),
|
|
6211
|
+
{ status: response.status }
|
|
6212
|
+
);
|
|
6213
|
+
}
|
|
6214
|
+
if (typeof result?.accepted !== "boolean" || typeof result.message !== "string") {
|
|
6215
|
+
throw new WebsiteError(
|
|
6216
|
+
"Website form returned an invalid response",
|
|
6217
|
+
"INVALID_RESPONSE"
|
|
6218
|
+
);
|
|
6219
|
+
}
|
|
6220
|
+
return {
|
|
6221
|
+
accepted: result.accepted,
|
|
6222
|
+
responseId: typeof result.responseId === "string" ? result.responseId : null,
|
|
6223
|
+
message: result.message
|
|
6224
|
+
};
|
|
6225
|
+
}
|
|
6226
|
+
};
|
|
6227
|
+
}
|
|
6228
|
+
|
|
6064
6229
|
// libs/sdk/src/modules/marketing.ts
|
|
6065
6230
|
function readRetryAfter(response) {
|
|
6066
6231
|
const value = Number(response.headers.get("Retry-After") ?? "1");
|
|
@@ -6587,6 +6752,7 @@ function createBacanoClient(config) {
|
|
|
6587
6752
|
config.websiteSlug,
|
|
6588
6753
|
getContext
|
|
6589
6754
|
);
|
|
6755
|
+
const formsModule = createFormsModule(apiUrl, config.websiteSlug, getContext);
|
|
6590
6756
|
return {
|
|
6591
6757
|
async init() {
|
|
6592
6758
|
if (context) return;
|
|
@@ -6639,6 +6805,10 @@ function createBacanoClient(config) {
|
|
|
6639
6805
|
getByTag: contentModule.getByTag.bind(contentModule),
|
|
6640
6806
|
getForProduct: contentModule.getForProduct.bind(contentModule)
|
|
6641
6807
|
},
|
|
6808
|
+
forms: {
|
|
6809
|
+
getDefinition: formsModule.getDefinition.bind(formsModule),
|
|
6810
|
+
submitResponse: formsModule.submitResponse.bind(formsModule)
|
|
6811
|
+
},
|
|
6642
6812
|
cart: {
|
|
6643
6813
|
getOrCreate: cartModule.getOrCreate.bind(cartModule),
|
|
6644
6814
|
startCheckout: cartModule.startCheckout.bind(cartModule),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BacanoClientContext, WebsiteFormResponseInput, WebsiteFormResponseResult, WebsitePublishedForm } from "../types";
|
|
2
|
+
export interface GetWebsiteFormOptions {
|
|
3
|
+
force?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface FormsModule {
|
|
6
|
+
getDefinition(formKey: string, options?: GetWebsiteFormOptions): Promise<WebsitePublishedForm | null>;
|
|
7
|
+
submitResponse(input: WebsiteFormResponseInput): Promise<WebsiteFormResponseResult>;
|
|
8
|
+
}
|
|
9
|
+
export declare function createFormsModule(apiUrl: string, websiteSlug: string, getContext: () => BacanoClientContext): FormsModule;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bacano/sdk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.17",
|
|
4
4
|
"description": "TypeScript SDK for building websites on the Bacano ERP",
|
|
5
5
|
"main": "./index.cjs",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -37,6 +37,11 @@
|
|
|
37
37
|
"types": "./react/preview.d.ts",
|
|
38
38
|
"import": "./react/preview.js",
|
|
39
39
|
"require": "./react/preview.cjs"
|
|
40
|
+
},
|
|
41
|
+
"./react/forms": {
|
|
42
|
+
"types": "./react/forms.d.ts",
|
|
43
|
+
"import": "./react/forms.js",
|
|
44
|
+
"require": "./react/forms.cjs"
|
|
40
45
|
}
|
|
41
46
|
},
|
|
42
47
|
"sideEffects": false,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WebsiteFormField } from "../types";
|
|
2
|
+
import type { BacanoFormValues } from "./forms";
|
|
3
|
+
type BacanoFormValue = BacanoFormValues[string];
|
|
4
|
+
export declare function BacanoFormFieldControl(props: {
|
|
5
|
+
field: WebsiteFormField;
|
|
6
|
+
className?: string;
|
|
7
|
+
value: BacanoFormValue;
|
|
8
|
+
onChange(value: BacanoFormValue): void;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
10
|
+
export {};
|