@behio/storefront-sdk 1.8.0 → 1.17.0

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/dist/react.mjs CHANGED
@@ -5,6 +5,38 @@ import { useRef, useEffect, useMemo, useState, useCallback } from "react";
5
5
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
6
6
 
7
7
  // src/types.ts
8
+ var BE_CODE_TO_ERROR = {
9
+ "be.storefront.cartIsEmpty": "CART_EMPTY",
10
+ "be.storefront.productNotFound": "PRODUCT_NOT_FOUND",
11
+ "be.storefront.productNotAvailable": "PRODUCT_NOT_FOUND",
12
+ "be.storefront.invalidEmailOrPassword": "INVALID_CREDENTIALS",
13
+ "be.storefront.currentPasswordIncorrect": "INVALID_CREDENTIALS",
14
+ "be.storefront.discountExpired": "DISCOUNT_EXPIRED",
15
+ "be.storefront.discountExpiredOrLimit": "DISCOUNT_EXPIRED",
16
+ "be.storefront.invalidDiscountCode": "INVALID_DISCOUNT",
17
+ "be.storefront.discountInvalid": "INVALID_DISCOUNT",
18
+ "be.storefront.discountInactive": "INVALID_DISCOUNT",
19
+ "be.storefront.discountWrongCurrency": "INVALID_DISCOUNT",
20
+ "be.storefront.discountNotApplicable": "INVALID_DISCOUNT",
21
+ "be.storefront.discountNotYetValid": "INVALID_DISCOUNT",
22
+ "be.storefront.discountUsageLimitReached": "INVALID_DISCOUNT",
23
+ "be.storefront.discountLimitedToGroups": "INVALID_DISCOUNT",
24
+ "be.storefront.tokenInvalid": "TOKEN_INVALID",
25
+ "be.storefront.orderAccessTokenInvalid": "TOKEN_INVALID",
26
+ "be.storefront.resetTokenInvalid": "TOKEN_INVALID",
27
+ "be.storefront.invalidVerificationToken": "TOKEN_INVALID",
28
+ "be.storefront.invalidRefreshToken": "TOKEN_INVALID",
29
+ "be.storefront.refreshTokenExpired": "TOKEN_EXPIRED",
30
+ "be.storefront.apiKeyExpired": "TOKEN_EXPIRED",
31
+ "be.storefront.customerEmailTaken": "EMAIL_ALREADY_EXISTS",
32
+ "be.storefront.orderNotCancellable": "ORDER_NOT_CANCELLABLE",
33
+ "be.storefront.orderCancelledNotReturnable": "ORDER_NOT_CANCELLABLE",
34
+ "be.validation.failed": "VALIDATION_ERROR",
35
+ "be.forms.formNotFound": "NOT_FOUND",
36
+ "be.forms.validationFailed": "VALIDATION_ERROR",
37
+ "be.forms.consentRequired": "VALIDATION_ERROR",
38
+ "be.forms.tooLarge": "VALIDATION_ERROR"
39
+ };
8
40
  var BehioApiError = class _BehioApiError extends Error {
9
41
  constructor(status, body, message) {
10
42
  super(message || `API Error ${status}`);
@@ -21,6 +53,11 @@ var BehioApiError = class _BehioApiError extends Error {
21
53
  if (status === 409) return "EMAIL_ALREADY_EXISTS";
22
54
  if (status === 429) return "RATE_LIMITED";
23
55
  if (status >= 500) return "INTERNAL_ERROR";
56
+ const rawCode = body?.code;
57
+ if (typeof rawCode === "string") {
58
+ const mapped = BE_CODE_TO_ERROR[rawCode];
59
+ if (mapped) return mapped;
60
+ }
24
61
  const msg = (body?.message || "").toLowerCase();
25
62
  if (msg.includes("invalid") && msg.includes("password"))
26
63
  return "INVALID_CREDENTIALS";
@@ -126,6 +163,8 @@ var BehioStorefront = class {
126
163
  this.orders = new OrdersModule(this);
127
164
  this.customer = new CustomerModule(this);
128
165
  this.pages = new PagesModule(this);
166
+ this.blog = new BlogModule(this);
167
+ this.forms = new FormsModule(this);
129
168
  this.wishlist = new WishlistModule(this);
130
169
  this.reviews = new ReviewsModule(this);
131
170
  this.returns = new ReturnsModule(this);
@@ -194,6 +233,21 @@ var BehioStorefront = class {
194
233
  body: input
195
234
  });
196
235
  }
236
+ /**
237
+ * Visitor messages from merchant automations (storefront.event action).
238
+ * Consent-gated visitor id; each message carries a merchant-defined `name`
239
+ * and free-form `payload` the template reacts to (modal, banner, ...).
240
+ * Messages stay listed until acknowledged via `ackVisitorMessage`.
241
+ */
242
+ async getVisitorMessages(visitorId) {
243
+ return this.request("GET", "/messages", { query: { visitorId } });
244
+ }
245
+ /** Acknowledge a visitor message so it is not delivered again. */
246
+ async ackVisitorMessage(messageId, visitorId) {
247
+ return this.request("POST", `/messages/${messageId}/ack`, {
248
+ query: { visitorId }
249
+ });
250
+ }
197
251
  /** Get basic shop info */
198
252
  async getShopInfo() {
199
253
  return this.request("GET", "/shop");
@@ -813,16 +867,27 @@ var CatalogModule = class {
813
867
  { body: input }
814
868
  );
815
869
  }
816
- /** List configured payment methods (filtered by currency). */
870
+ /**
871
+ * List configured payment methods (filtered by currency). `locale` picks the
872
+ * language of `instruments[].label` / `swifts[].label` (SDK 1.14.0); pass the
873
+ * locale of the page so the instrument tiles read in the shopper's language.
874
+ */
817
875
  async listPaymentMethods(opts) {
818
876
  const query = {};
819
877
  if (opts?.currency) query.currency = opts.currency;
878
+ if (opts?.locale) query.locale = opts.locale;
879
+ if (opts?.country) query.country = opts.country;
880
+ if (opts?.shippingMethodId) query.shippingMethodId = opts.shippingMethodId;
820
881
  return this.client.request(
821
882
  "GET",
822
883
  "/catalog/payment-methods",
823
884
  { query }
824
885
  );
825
886
  }
887
+ /** Alias of `listPaymentMethods` (SDK 1.14.0). */
888
+ paymentMethods(opts) {
889
+ return this.listPaymentMethods(opts);
890
+ }
826
891
  };
827
892
  var AuthModule = class {
828
893
  constructor(client) {
@@ -976,6 +1041,26 @@ var CartModule = class {
976
1041
  this.client.emit("cart:updated", res.data);
977
1042
  return res;
978
1043
  }
1044
+ /**
1045
+ * Tell the cart where the order will ship (and, for B2B, the buyer's VAT
1046
+ * ID) so the VAT breakdown matches the checkout before the address form:
1047
+ * destination-country rate (OSS), 0 % export outside the EU, or reverse
1048
+ * charge for an EU business with a VIES-valid VAT ID. `cart.vatMode` says
1049
+ * which rule applied. SDK 1.17.0.
1050
+ *
1051
+ * ```ts
1052
+ * await client.cart.setDestination({ country: "SK" });
1053
+ * await client.cart.setDestination({ vatId: "SK2020000001" }); // "" clears
1054
+ * ```
1055
+ */
1056
+ async setDestination(input) {
1057
+ const res = await this.client.request("PUT", "/cart/destination", {
1058
+ body: input
1059
+ });
1060
+ if (res.error) return res;
1061
+ this.client.emit("cart:updated", res.data);
1062
+ return res;
1063
+ }
979
1064
  /** Update item quantity */
980
1065
  async updateQuantity(itemId, quantity) {
981
1066
  const res = await this.client.request(
@@ -1127,6 +1212,30 @@ var OrdersModule = class {
1127
1212
  constructor(client) {
1128
1213
  this.client = client;
1129
1214
  }
1215
+ /**
1216
+ * Ask the backend to re-check this order's payment with the gateway.
1217
+ *
1218
+ * Call it on the thank-you page the customer lands on after paying, BEFORE
1219
+ * you read the order. Some gateways (Tatrapay+) have no server-to-server
1220
+ * notification at all, so the customer's return is the only fast way the
1221
+ * payment gets confirmed; for the others it is a safety net for a lost
1222
+ * notification.
1223
+ *
1224
+ * The response is deliberately opaque (`{ok: true}` every time, even for an
1225
+ * order number that does not exist): order numbers are sequential, so
1226
+ * anything else would turn this into a probe for other people's orders.
1227
+ * Read the actual state afterwards through a path that proves entitlement
1228
+ * ({@link get}, {@link track} or the guest access-code flow).
1229
+ *
1230
+ * Never throws for a missing order and never blocks the page: treat a
1231
+ * failure as "not confirmed yet", the backend poller catches up on its own.
1232
+ */
1233
+ async syncPaymentOnReturn(orderNumber) {
1234
+ return this.client.request(
1235
+ "POST",
1236
+ `/payments/return/${orderNumber}`
1237
+ );
1238
+ }
1130
1239
  /** List customer orders (requires auth) */
1131
1240
  async list(options) {
1132
1241
  return this.client.request(
@@ -1479,6 +1588,59 @@ var PagesModule = class {
1479
1588
  });
1480
1589
  }
1481
1590
  };
1591
+ var BlogModule = class {
1592
+ constructor(client) {
1593
+ this.client = client;
1594
+ }
1595
+ /** List the site's blogs (active only). */
1596
+ async list(locale) {
1597
+ return this.client.request("GET", "/blogs", {
1598
+ query: { locale }
1599
+ });
1600
+ }
1601
+ /** Published posts of one blog, newest first (featured first), paginated. */
1602
+ async posts(handle, query = {}) {
1603
+ return this.client.request("GET", `/blogs/${handle}/posts`, {
1604
+ query: {
1605
+ locale: query.locale,
1606
+ page: query.page,
1607
+ limit: query.limit,
1608
+ tag: query.tag
1609
+ }
1610
+ });
1611
+ }
1612
+ /** One published post with sanitised HTML content and related posts. */
1613
+ async post(handle, slug, locale) {
1614
+ return this.client.request("GET", `/blogs/${handle}/posts/${slug}`, {
1615
+ query: { locale }
1616
+ });
1617
+ }
1618
+ };
1619
+ var FormsModule = class {
1620
+ constructor(client) {
1621
+ this.client = client;
1622
+ }
1623
+ /** Public definition of one form (fields + settings). 404 for an unknown or inactive slug. */
1624
+ async get(slug) {
1625
+ return this.client.request(
1626
+ "GET",
1627
+ `/forms/${encodeURIComponent(slug)}`
1628
+ );
1629
+ }
1630
+ /**
1631
+ * Submit a response. On `be.forms.validationFailed` the returned error
1632
+ * carries per-field codes: read them with `formFieldErrors(error)`. Other
1633
+ * rejections: `be.forms.consentRequired`, `be.forms.tooLarge`,
1634
+ * `be.forms.formNotFound`; rate limit 10 submits per minute per visitor.
1635
+ */
1636
+ async submit(slug, input) {
1637
+ return this.client.request(
1638
+ "POST",
1639
+ `/forms/${encodeURIComponent(slug)}/submit`,
1640
+ { body: input, auth: false }
1641
+ );
1642
+ }
1643
+ };
1482
1644
  var WishlistModule = class {
1483
1645
  constructor(client) {
1484
1646
  this.client = client;
@@ -3066,9 +3228,10 @@ import { useQuery as useQuery25 } from "@tanstack/react-query";
3066
3228
  function usePaymentMethods(options) {
3067
3229
  const { client, currency: activeCurrency } = useBehio();
3068
3230
  const currency = options?.currency ?? activeCurrency;
3231
+ const locale = options?.locale;
3069
3232
  const { data, isLoading, error, refetch } = useQuery25({
3070
- queryKey: ["behio", "payment-methods", currency ?? ""],
3071
- queryFn: () => unwrap(client.catalog.listPaymentMethods({ currency })),
3233
+ queryKey: ["behio", "payment-methods", currency ?? "", locale ?? ""],
3234
+ queryFn: () => unwrap(client.catalog.listPaymentMethods({ currency, ...locale ? { locale } : {} })),
3072
3235
  enabled: options?.enabled !== false
3073
3236
  });
3074
3237
  return { methods: data?.items ?? [], isLoading, error, refetch };
@@ -3133,11 +3296,78 @@ function usePersonalOffers(options) {
3133
3296
  };
3134
3297
  }
3135
3298
 
3299
+ // src/react/hooks/use-visitor-messages.ts
3300
+ import { useCallback as useCallback9, useEffect as useEffect5, useRef as useRef3, useState as useState6 } from "react";
3301
+ import { useQuery as useQuery27, useQueryClient as useQueryClient13 } from "@tanstack/react-query";
3302
+ function useVisitorMessages(options) {
3303
+ const { client } = useBehio();
3304
+ const queryClient = useQueryClient13();
3305
+ const [polledVid, setPolledVid] = useState6(null);
3306
+ const seenRef = useRef3(/* @__PURE__ */ new Set());
3307
+ const explicitVid = options?.visitorId;
3308
+ useEffect5(() => {
3309
+ if (explicitVid) return;
3310
+ const read = () => {
3311
+ const vid = client.getAnalyticsVisitorId();
3312
+ if (vid) setPolledVid(vid);
3313
+ return vid;
3314
+ };
3315
+ if (read()) return;
3316
+ const timer = setInterval(() => {
3317
+ if (read()) clearInterval(timer);
3318
+ }, 3e3);
3319
+ return () => clearInterval(timer);
3320
+ }, [client, explicitVid]);
3321
+ const visitorId = explicitVid ?? polledVid;
3322
+ const pollMs = options?.pollMs ?? 3e4;
3323
+ const queryKey = ["behio", "visitor-messages", visitorId ?? ""];
3324
+ const { data, isLoading, error, refetch } = useQuery27({
3325
+ queryKey,
3326
+ queryFn: () => unwrap(client.getVisitorMessages(visitorId)),
3327
+ enabled: options?.enabled !== false && !!visitorId,
3328
+ refetchInterval: pollMs > 0 ? pollMs : false
3329
+ });
3330
+ const ack = useCallback9(
3331
+ async (messageId) => {
3332
+ if (!visitorId) return false;
3333
+ const result = await unwrap(client.ackVisitorMessage(messageId, visitorId));
3334
+ queryClient.setQueryData(
3335
+ queryKey,
3336
+ (prev) => prev ? { items: prev.items.filter((m) => m.id !== messageId) } : prev
3337
+ );
3338
+ return result.ok;
3339
+ },
3340
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3341
+ [client, visitorId, queryClient]
3342
+ );
3343
+ const onMessage = options?.onMessage;
3344
+ const dispatchDom = options?.dispatchDomEvents !== false;
3345
+ useEffect5(() => {
3346
+ for (const message of data?.items ?? []) {
3347
+ if (seenRef.current.has(message.id)) continue;
3348
+ seenRef.current.add(message.id);
3349
+ if (dispatchDom && typeof window !== "undefined") {
3350
+ window.dispatchEvent(new CustomEvent("behio:visitor-message", { detail: message }));
3351
+ }
3352
+ onMessage?.(message);
3353
+ }
3354
+ }, [data, onMessage, dispatchDom]);
3355
+ return {
3356
+ messages: data?.items ?? [],
3357
+ isLoading,
3358
+ error,
3359
+ refetch,
3360
+ /** Mark a message as shown; it will not be delivered again. */
3361
+ ack,
3362
+ visitorId
3363
+ };
3364
+ }
3365
+
3136
3366
  // src/react/hooks/use-analytics-events.ts
3137
- import { useCallback as useCallback9 } from "react";
3367
+ import { useCallback as useCallback10 } from "react";
3138
3368
  function useAnalyticsEvents() {
3139
3369
  const { client } = useBehio();
3140
- const track = useCallback9(
3370
+ const track = useCallback10(
3141
3371
  (events, opts) => {
3142
3372
  const list = Array.isArray(events) ? events : [events];
3143
3373
  if (list.length === 0) return Promise.resolve();
@@ -3149,7 +3379,7 @@ function useAnalyticsEvents() {
3149
3379
  },
3150
3380
  [client]
3151
3381
  );
3152
- const trackEvent = useCallback9(
3382
+ const trackEvent = useCallback10(
3153
3383
  (name, props) => track({ type: "custom", name, props }),
3154
3384
  [track]
3155
3385
  );
@@ -3179,12 +3409,12 @@ function useNewsletterUnsubscribe() {
3179
3409
  }
3180
3410
 
3181
3411
  // src/react/hooks/use-orders.ts
3182
- import { useCallback as useCallback10, useMemo as useMemo3, useState as useState6 } from "react";
3412
+ import { useCallback as useCallback11, useMemo as useMemo3, useState as useState7 } from "react";
3183
3413
  import { useInfiniteQuery as useInfiniteQuery2 } from "@tanstack/react-query";
3184
3414
  function useOrders(options) {
3185
3415
  const { client } = useBehio();
3186
3416
  const { page: initialPage, limit, enabled } = options ?? {};
3187
- const [page, setPage] = useState6(initialPage ?? 1);
3417
+ const [page, setPage] = useState7(initialPage ?? 1);
3188
3418
  const infinite = useInfiniteQuery2({
3189
3419
  queryKey: ["behio", "orders", limit, page],
3190
3420
  queryFn: ({ pageParam }) => unwrap(client.orders.list({ limit, page: pageParam })),
@@ -3198,13 +3428,13 @@ function useOrders(options) {
3198
3428
  [infinite.data]
3199
3429
  );
3200
3430
  const lastPage = infinite.data?.pages[infinite.data.pages.length - 1];
3201
- const loadMore = useCallback10(() => {
3431
+ const loadMore = useCallback11(() => {
3202
3432
  if (infinite.hasNextPage && !infinite.isFetchingNextPage) {
3203
3433
  return infinite.fetchNextPage();
3204
3434
  }
3205
3435
  return Promise.resolve();
3206
3436
  }, [infinite]);
3207
- const goToPage = useCallback10((newPage) => {
3437
+ const goToPage = useCallback11((newPage) => {
3208
3438
  setPage(newPage);
3209
3439
  }, []);
3210
3440
  return {
@@ -3230,16 +3460,16 @@ function useOrders(options) {
3230
3460
  }
3231
3461
 
3232
3462
  // src/react/hooks/use-order.ts
3233
- import { useCallback as useCallback11 } from "react";
3234
- import { useQuery as useQuery27, useMutation as useMutation12, useQueryClient as useQueryClient13 } from "@tanstack/react-query";
3463
+ import { useCallback as useCallback12 } from "react";
3464
+ import { useQuery as useQuery28, useMutation as useMutation12, useQueryClient as useQueryClient14 } from "@tanstack/react-query";
3235
3465
  function useOrder(orderNumber, options) {
3236
3466
  const { client } = useBehio();
3237
- const queryClient = useQueryClient13();
3467
+ const queryClient = useQueryClient14();
3238
3468
  const {
3239
3469
  data,
3240
3470
  isLoading,
3241
3471
  error
3242
- } = useQuery27({
3472
+ } = useQuery28({
3243
3473
  queryKey: ["behio", "order", orderNumber],
3244
3474
  queryFn: () => unwrap(client.orders.get(orderNumber)),
3245
3475
  enabled: options?.enabled !== false && !!orderNumber && !!client.getAccessToken()
@@ -3251,7 +3481,7 @@ function useOrder(orderNumber, options) {
3251
3481
  queryClient.invalidateQueries({ queryKey: ["behio", "orders"] });
3252
3482
  }
3253
3483
  });
3254
- const cancel = useCallback11(
3484
+ const cancel = useCallback12(
3255
3485
  () => cancelMutation.mutateAsync(),
3256
3486
  [cancelMutation]
3257
3487
  );
@@ -3265,14 +3495,14 @@ function useOrder(orderNumber, options) {
3265
3495
  }
3266
3496
 
3267
3497
  // src/react/hooks/use-order-access.ts
3268
- import { useCallback as useCallback12, useState as useState7 } from "react";
3498
+ import { useCallback as useCallback13, useState as useState8 } from "react";
3269
3499
  import { useMutation as useMutation13 } from "@tanstack/react-query";
3270
3500
  function useOrderAccess() {
3271
3501
  const { client } = useBehio();
3272
- const [orderNumber, setOrderNumber] = useState7(null);
3273
- const [email, setEmail] = useState7(null);
3274
- const [order, setOrder] = useState7(null);
3275
- const [accessToken, setAccessToken] = useState7(null);
3502
+ const [orderNumber, setOrderNumber] = useState8(null);
3503
+ const [email, setEmail] = useState8(null);
3504
+ const [order, setOrder] = useState8(null);
3505
+ const [accessToken, setAccessToken] = useState8(null);
3276
3506
  const requestMutation = useMutation13({
3277
3507
  mutationFn: (input) => unwrap(client.orders.requestAccessCode(input.orderNumber, input.email)),
3278
3508
  onSuccess: (_data, input) => {
@@ -3292,12 +3522,12 @@ function useOrderAccess() {
3292
3522
  setAccessToken(result.accessToken);
3293
3523
  }
3294
3524
  });
3295
- const requestCode = useCallback12(
3525
+ const requestCode = useCallback13(
3296
3526
  (on, em) => requestMutation.mutateAsync({ orderNumber: on, email: em }),
3297
3527
  [requestMutation]
3298
3528
  );
3299
- const verifyCode = useCallback12((code) => verifyMutation.mutateAsync(code), [verifyMutation]);
3300
- const reset = useCallback12(() => {
3529
+ const verifyCode = useCallback13((code) => verifyMutation.mutateAsync(code), [verifyMutation]);
3530
+ const reset = useCallback13(() => {
3301
3531
  setOrderNumber(null);
3302
3532
  setEmail(null);
3303
3533
  setOrder(null);
@@ -3325,12 +3555,12 @@ function useOrderAccess() {
3325
3555
  }
3326
3556
 
3327
3557
  // src/react/hooks/use-checkout.ts
3328
- import { useState as useState8, useCallback as useCallback13 } from "react";
3329
- import { useMutation as useMutation14, useQueryClient as useQueryClient14 } from "@tanstack/react-query";
3558
+ import { useState as useState9, useCallback as useCallback14 } from "react";
3559
+ import { useMutation as useMutation14, useQueryClient as useQueryClient15 } from "@tanstack/react-query";
3330
3560
  function useCheckout() {
3331
3561
  const { client, storage } = useBehio();
3332
- const queryClient = useQueryClient14();
3333
- const [order, setOrder] = useState8(null);
3562
+ const queryClient = useQueryClient15();
3563
+ const [order, setOrder] = useState9(null);
3334
3564
  const mutation = useMutation14({
3335
3565
  mutationFn: (input) => unwrap(client.checkout.createOrder(input)),
3336
3566
  onSuccess: (result) => {
@@ -3340,11 +3570,11 @@ function useCheckout() {
3340
3570
  queryClient.invalidateQueries({ queryKey: ["behio", "orders"] });
3341
3571
  }
3342
3572
  });
3343
- const createOrder = useCallback13(
3573
+ const createOrder = useCallback14(
3344
3574
  (input) => mutation.mutateAsync(input),
3345
3575
  [mutation]
3346
3576
  );
3347
- const reset = useCallback13(() => {
3577
+ const reset = useCallback14(() => {
3348
3578
  setOrder(null);
3349
3579
  mutation.reset();
3350
3580
  }, [mutation]);
@@ -3358,10 +3588,10 @@ function useCheckout() {
3358
3588
  }
3359
3589
 
3360
3590
  // src/react/hooks/use-pages.ts
3361
- import { useQuery as useQuery28 } from "@tanstack/react-query";
3591
+ import { useQuery as useQuery29 } from "@tanstack/react-query";
3362
3592
  function usePages(locale, options) {
3363
3593
  const { client } = useBehio();
3364
- return useQuery28({
3594
+ return useQuery29({
3365
3595
  queryKey: ["behio", "pages", locale],
3366
3596
  queryFn: async () => {
3367
3597
  const result = await unwrap(client.pages.list(locale));
@@ -3372,18 +3602,141 @@ function usePages(locale, options) {
3372
3602
  }
3373
3603
  function usePage(slug, locale, options) {
3374
3604
  const { client } = useBehio();
3375
- return useQuery28({
3605
+ return useQuery29({
3376
3606
  queryKey: ["behio", "page", slug, locale],
3377
3607
  queryFn: () => unwrap(client.pages.get(slug, locale)),
3378
3608
  enabled: options?.enabled !== false && !!slug
3379
3609
  });
3380
3610
  }
3381
3611
 
3612
+ // src/react/hooks/use-blog.ts
3613
+ import { useQuery as useQuery30 } from "@tanstack/react-query";
3614
+ function useBlogs(locale, options) {
3615
+ const { client } = useBehio();
3616
+ return useQuery30({
3617
+ queryKey: ["behio", "blogs", locale],
3618
+ queryFn: async () => {
3619
+ const result = await unwrap(client.blog.list(locale));
3620
+ return result.blogs;
3621
+ },
3622
+ enabled: options?.enabled !== false
3623
+ });
3624
+ }
3625
+ function useBlogPosts(handle, query, options) {
3626
+ const { client } = useBehio();
3627
+ return useQuery30({
3628
+ queryKey: ["behio", "blog", handle, "posts", query?.locale, query?.page, query?.limit, query?.tag],
3629
+ queryFn: () => unwrap(client.blog.posts(handle, query)),
3630
+ enabled: options?.enabled !== false && !!handle
3631
+ });
3632
+ }
3633
+ function useBlogPost(handle, slug, locale, options) {
3634
+ const { client } = useBehio();
3635
+ return useQuery30({
3636
+ queryKey: ["behio", "blog", handle, "post", slug, locale],
3637
+ queryFn: () => unwrap(client.blog.post(handle, slug, locale)),
3638
+ enabled: options?.enabled !== false && !!handle && !!slug
3639
+ });
3640
+ }
3641
+
3642
+ // src/react/hooks/use-site-form.ts
3643
+ import { useCallback as useCallback15, useMemo as useMemo4 } from "react";
3644
+ import { useMutation as useMutation15, useQuery as useQuery31 } from "@tanstack/react-query";
3645
+
3646
+ // src/errors.ts
3647
+ function bodyOf(err) {
3648
+ const body = err instanceof BehioApiError ? err.body : err && typeof err === "object" && "body" in err ? err.body : err;
3649
+ return body && typeof body === "object" ? body : null;
3650
+ }
3651
+ function errorCode(err) {
3652
+ const body = bodyOf(err);
3653
+ if (!body) return null;
3654
+ const code = body.code ?? body.key;
3655
+ return typeof code === "string" && code.startsWith("be.") ? code : null;
3656
+ }
3657
+ function errorParams(err) {
3658
+ const body = bodyOf(err);
3659
+ const params = body?.params;
3660
+ return params && typeof params === "object" && !Array.isArray(params) ? params : {};
3661
+ }
3662
+ var FORM_FIELD_ERROR_CODES = /* @__PURE__ */ new Set([
3663
+ "required",
3664
+ "invalid",
3665
+ "tooShort",
3666
+ "tooLong",
3667
+ "min",
3668
+ "max",
3669
+ "notOption",
3670
+ "pattern"
3671
+ ]);
3672
+ function formFieldErrors(err) {
3673
+ if (errorCode(err) !== "be.forms.validationFailed") return [];
3674
+ const raw = errorParams(err).errors;
3675
+ if (!Array.isArray(raw)) return [];
3676
+ const out = [];
3677
+ for (const entry of raw) {
3678
+ if (typeof entry !== "string") continue;
3679
+ const idx = entry.lastIndexOf(":");
3680
+ if (idx <= 0) continue;
3681
+ const key = entry.slice(0, idx);
3682
+ const code = entry.slice(idx + 1);
3683
+ if (!FORM_FIELD_ERROR_CODES.has(code)) continue;
3684
+ out.push({ key, code });
3685
+ }
3686
+ return out;
3687
+ }
3688
+
3689
+ // src/react/hooks/use-site-form.ts
3690
+ function useSiteForm(slug, options) {
3691
+ const { client } = useBehio();
3692
+ return useQuery31({
3693
+ queryKey: ["behio", "form", slug],
3694
+ queryFn: () => unwrap(client.forms.get(slug)),
3695
+ enabled: options?.enabled !== false && !!slug
3696
+ });
3697
+ }
3698
+ function useSiteFormSubmit(slug) {
3699
+ const { client } = useBehio();
3700
+ const mutation = useMutation15({
3701
+ mutationFn: (input) => unwrap(client.forms.submit(slug, input))
3702
+ });
3703
+ const sdkError = mutation.error instanceof UnwrappedError ? mutation.error.sdkError : null;
3704
+ const fieldErrors = useMemo4(() => {
3705
+ const out = {};
3706
+ for (const e of formFieldErrors(sdkError)) out[e.key] = e.code;
3707
+ return out;
3708
+ }, [sdkError]);
3709
+ const errorCode2 = useMemo4(() => {
3710
+ const body = sdkError?.body;
3711
+ const code = body?.code ?? body?.key;
3712
+ return typeof code === "string" ? code : sdkError ? sdkError.code : null;
3713
+ }, [sdkError]);
3714
+ const submit = useCallback15(
3715
+ (input) => mutation.mutateAsync(input).catch(() => null),
3716
+ [mutation]
3717
+ );
3718
+ return {
3719
+ /** Resolves to the result, or null when the submit was rejected (see `fieldErrors` / `errorCode`). */
3720
+ submit,
3721
+ isSubmitting: mutation.isPending,
3722
+ isSuccess: mutation.isSuccess,
3723
+ /** `{ok, id, message, redirectUrl}` after a successful submit. */
3724
+ result: mutation.data ?? null,
3725
+ /** Field key -> validation code after a rejected submit. */
3726
+ fieldErrors,
3727
+ /** Backend error key (`be.forms.*`) or SDK code when the rejection is not per field. */
3728
+ errorCode: errorCode2,
3729
+ /** Raw SDK error (pass to `errorMessage(error, locale)` for a sentence). */
3730
+ error: sdkError,
3731
+ reset: mutation.reset
3732
+ };
3733
+ }
3734
+
3382
3735
  // src/react/hooks/use-shop-info.ts
3383
- import { useQuery as useQuery29 } from "@tanstack/react-query";
3736
+ import { useQuery as useQuery32 } from "@tanstack/react-query";
3384
3737
  function useShopInfo(options) {
3385
3738
  const { client } = useBehio();
3386
- return useQuery29({
3739
+ return useQuery32({
3387
3740
  queryKey: ["behio", "shop-info"],
3388
3741
  queryFn: () => unwrap(client.getShopInfo()),
3389
3742
  enabled: options?.enabled !== false
@@ -3391,10 +3744,10 @@ function useShopInfo(options) {
3391
3744
  }
3392
3745
 
3393
3746
  // src/react/hooks/use-shop-scripts.ts
3394
- import { useQuery as useQuery30 } from "@tanstack/react-query";
3747
+ import { useQuery as useQuery33 } from "@tanstack/react-query";
3395
3748
  function useShopScripts(options) {
3396
3749
  const { client } = useBehio();
3397
- return useQuery30({
3750
+ return useQuery33({
3398
3751
  queryKey: ["behio", "shop-scripts"],
3399
3752
  queryFn: () => unwrap(client.getShopScripts()),
3400
3753
  enabled: options?.enabled !== false
@@ -3402,11 +3755,11 @@ function useShopScripts(options) {
3402
3755
  }
3403
3756
 
3404
3757
  // src/react/hooks/use-shop-seo.ts
3405
- import { useQuery as useQuery31 } from "@tanstack/react-query";
3758
+ import { useQuery as useQuery34 } from "@tanstack/react-query";
3406
3759
  function useShopSeo(options) {
3407
3760
  const { client } = useBehio();
3408
3761
  const { locale, initialData, enabled = true } = options ?? {};
3409
- return useQuery31({
3762
+ return useQuery34({
3410
3763
  queryKey: ["behio", "shop-seo", locale ?? "_default"],
3411
3764
  queryFn: () => unwrap(client.getShopSeo(locale)),
3412
3765
  initialData,
@@ -3466,23 +3819,23 @@ function CurrencySwitcher({
3466
3819
  }
3467
3820
 
3468
3821
  // src/react/components/storefront-scripts.tsx
3469
- import { useEffect as useEffect5, useMemo as useMemo4, useState as useState9 } from "react";
3822
+ import { useEffect as useEffect6, useMemo as useMemo5, useState as useState10 } from "react";
3470
3823
 
3471
3824
  // src/react/hooks/use-consent.ts
3472
- import { useQuery as useQuery32, useMutation as useMutation15, useQueryClient as useQueryClient15 } from "@tanstack/react-query";
3825
+ import { useQuery as useQuery35, useMutation as useMutation16, useQueryClient as useQueryClient16 } from "@tanstack/react-query";
3473
3826
  function useCookieConsent(visitorId) {
3474
3827
  const { client } = useBehio();
3475
- const qc = useQueryClient15();
3476
- const query = useQuery32({
3828
+ const qc = useQueryClient16();
3829
+ const query = useQuery35({
3477
3830
  queryKey: ["behio", "consent", visitorId],
3478
3831
  queryFn: () => unwrap(client.consent.get(visitorId)),
3479
3832
  enabled: Boolean(visitorId)
3480
3833
  });
3481
- const recordMutation = useMutation15({
3834
+ const recordMutation = useMutation16({
3482
3835
  mutationFn: (input) => unwrap(client.consent.record(input)),
3483
3836
  onSuccess: () => qc.invalidateQueries({ queryKey: ["behio", "consent"] })
3484
3837
  });
3485
- const revokeMutation = useMutation15({
3838
+ const revokeMutation = useMutation16({
3486
3839
  mutationFn: () => unwrap(client.consent.revoke(visitorId)),
3487
3840
  onSuccess: () => qc.invalidateQueries({ queryKey: ["behio", "consent"] })
3488
3841
  });
@@ -3556,8 +3909,8 @@ function injectHtml(target, html) {
3556
3909
  }
3557
3910
  function StorefrontScripts({ visitorId: visitorIdProp } = {}) {
3558
3911
  const { data } = useShopScripts();
3559
- const [visitorId, setVisitorId] = useState9(visitorIdProp ?? "");
3560
- useEffect5(() => {
3912
+ const [visitorId, setVisitorId] = useState10(visitorIdProp ?? "");
3913
+ useEffect6(() => {
3561
3914
  if (visitorIdProp) return;
3562
3915
  try {
3563
3916
  const v = localStorage.getItem(VISITOR_KEY);
@@ -3567,15 +3920,15 @@ function StorefrontScripts({ visitorId: visitorIdProp } = {}) {
3567
3920
  }, [visitorIdProp]);
3568
3921
  const { data: consent } = useCookieConsent(visitorId || void 0);
3569
3922
  const analyticsOk = Boolean(consent?.analytics);
3570
- const scripts = useMemo4(
3923
+ const scripts = useMemo5(
3571
3924
  () => (data?.scripts ?? []).filter((s) => !s.consentRequired || analyticsOk),
3572
3925
  [data, analyticsOk]
3573
3926
  );
3574
- const signature = useMemo4(
3927
+ const signature = useMemo5(
3575
3928
  () => JSON.stringify(scripts.map((s) => [s.id, s.type, s.placement, s.value])),
3576
3929
  [scripts]
3577
3930
  );
3578
- useEffect5(() => {
3931
+ useEffect6(() => {
3579
3932
  if (typeof document === "undefined") return;
3580
3933
  const added = [];
3581
3934
  for (const s of scripts) {
@@ -3592,7 +3945,7 @@ function StorefrontScripts({ visitorId: visitorIdProp } = {}) {
3592
3945
  }
3593
3946
 
3594
3947
  // src/react/components/behio-analytics.tsx
3595
- import { useEffect as useEffect6 } from "react";
3948
+ import { useEffect as useEffect7 } from "react";
3596
3949
 
3597
3950
  // src/react/hooks/use-behio-client.ts
3598
3951
  function useBehioClient() {
@@ -3602,7 +3955,7 @@ function useBehioClient() {
3602
3955
  // src/react/components/behio-analytics.tsx
3603
3956
  function BehioAnalyticsTracker() {
3604
3957
  const client = useBehioClient();
3605
- useEffect6(() => {
3958
+ useEffect7(() => {
3606
3959
  if (typeof window === "undefined") return;
3607
3960
  const w = window;
3608
3961
  if (w.__behioAnalytics) return;
@@ -3841,10 +4194,10 @@ function utmFromSearch(search) {
3841
4194
  }
3842
4195
 
3843
4196
  // src/react/hooks/use-bundles.ts
3844
- import { useQuery as useQuery33 } from "@tanstack/react-query";
4197
+ import { useQuery as useQuery36 } from "@tanstack/react-query";
3845
4198
  function useBundles(options) {
3846
4199
  const { client } = useBehio();
3847
- return useQuery33({
4200
+ return useQuery36({
3848
4201
  queryKey: ["behio", "bundles"],
3849
4202
  queryFn: () => unwrap(client.catalog.getBundles()),
3850
4203
  enabled: options?.enabled ?? true,
@@ -3853,7 +4206,7 @@ function useBundles(options) {
3853
4206
  }
3854
4207
  function useBundle(slug, options) {
3855
4208
  const { client } = useBehio();
3856
- return useQuery33({
4209
+ return useQuery36({
3857
4210
  queryKey: ["behio", "bundle", slug],
3858
4211
  queryFn: () => unwrap(client.catalog.getBundle(slug)),
3859
4212
  enabled: Boolean(slug) && (options?.enabled ?? true),
@@ -3862,10 +4215,10 @@ function useBundle(slug, options) {
3862
4215
  }
3863
4216
 
3864
4217
  // src/react/hooks/use-product-group.ts
3865
- import { useQuery as useQuery34 } from "@tanstack/react-query";
4218
+ import { useQuery as useQuery37 } from "@tanstack/react-query";
3866
4219
  function useProductGroup(slug, options) {
3867
4220
  const { client } = useBehio();
3868
- return useQuery34({
4221
+ return useQuery37({
3869
4222
  queryKey: ["behio", "product-group", slug, options?.locale, options?.currency],
3870
4223
  queryFn: () => unwrap(
3871
4224
  client.catalog.getProductGroup(slug, {
@@ -3879,10 +4232,10 @@ function useProductGroup(slug, options) {
3879
4232
  }
3880
4233
 
3881
4234
  // src/react/hooks/use-cross-sell.ts
3882
- import { useQuery as useQuery35 } from "@tanstack/react-query";
4235
+ import { useQuery as useQuery38 } from "@tanstack/react-query";
3883
4236
  function useCrossSell(productSlug, options) {
3884
4237
  const { client } = useBehio();
3885
- return useQuery35({
4238
+ return useQuery38({
3886
4239
  queryKey: ["behio", "cross-sell", productSlug, options?.locale, options?.currency],
3887
4240
  queryFn: () => unwrap(
3888
4241
  client.catalog.getCrossSell(productSlug, {
@@ -3896,10 +4249,10 @@ function useCrossSell(productSlug, options) {
3896
4249
  }
3897
4250
 
3898
4251
  // src/react/hooks/use-product-promotions.ts
3899
- import { useQuery as useQuery36 } from "@tanstack/react-query";
4252
+ import { useQuery as useQuery39 } from "@tanstack/react-query";
3900
4253
  function useProductPromotions(productSlug, options) {
3901
4254
  const { client } = useBehio();
3902
- return useQuery36({
4255
+ return useQuery39({
3903
4256
  queryKey: ["behio", "product-promotions", productSlug],
3904
4257
  queryFn: () => unwrap(client.catalog.getProductPromotions(productSlug)),
3905
4258
  enabled: Boolean(productSlug) && (options?.enabled ?? true),
@@ -3908,11 +4261,11 @@ function useProductPromotions(productSlug, options) {
3908
4261
  }
3909
4262
 
3910
4263
  // src/react/hooks/use-gift-card.ts
3911
- import { useQuery as useQuery37 } from "@tanstack/react-query";
4264
+ import { useQuery as useQuery40 } from "@tanstack/react-query";
3912
4265
  function useGiftCardBalance(code, options) {
3913
4266
  const { client } = useBehio();
3914
4267
  const trimmed = code?.trim();
3915
- return useQuery37({
4268
+ return useQuery40({
3916
4269
  queryKey: ["behio", "gift-card-balance", trimmed],
3917
4270
  queryFn: () => unwrap(client.catalog.checkGiftCard(trimmed)),
3918
4271
  enabled: Boolean(trimmed && trimmed.length >= 6) && (options?.enabled ?? true)
@@ -3920,20 +4273,20 @@ function useGiftCardBalance(code, options) {
3920
4273
  }
3921
4274
 
3922
4275
  // src/react/hooks/use-wishlist.ts
3923
- import { useQuery as useQuery38, useMutation as useMutation16, useQueryClient as useQueryClient16 } from "@tanstack/react-query";
4276
+ import { useQuery as useQuery41, useMutation as useMutation17, useQueryClient as useQueryClient17 } from "@tanstack/react-query";
3924
4277
  function useWishlist(options) {
3925
4278
  const { client } = useBehio();
3926
- const qc = useQueryClient16();
3927
- const query = useQuery38({
4279
+ const qc = useQueryClient17();
4280
+ const query = useQuery41({
3928
4281
  queryKey: ["behio", "wishlist"],
3929
4282
  queryFn: () => unwrap(client.wishlist.get()),
3930
4283
  enabled: options?.enabled ?? true
3931
4284
  });
3932
- const addMutation = useMutation16({
4285
+ const addMutation = useMutation17({
3933
4286
  mutationFn: (productId) => unwrap(client.wishlist.add(productId)),
3934
4287
  onSuccess: () => qc.invalidateQueries({ queryKey: ["behio", "wishlist"] })
3935
4288
  });
3936
- const removeMutation = useMutation16({
4289
+ const removeMutation = useMutation17({
3937
4290
  mutationFn: (productId) => unwrap(client.wishlist.remove(productId)),
3938
4291
  onSuccess: () => qc.invalidateQueries({ queryKey: ["behio", "wishlist"] })
3939
4292
  });
@@ -3947,7 +4300,7 @@ function useWishlist(options) {
3947
4300
  }
3948
4301
  function useIsInWishlist(productId) {
3949
4302
  const { client } = useBehio();
3950
- return useQuery38({
4303
+ return useQuery41({
3951
4304
  queryKey: ["behio", "wishlist-check", productId],
3952
4305
  queryFn: () => unwrap(client.wishlist.isInWishlist(productId)),
3953
4306
  enabled: Boolean(productId)
@@ -3955,10 +4308,10 @@ function useIsInWishlist(productId) {
3955
4308
  }
3956
4309
 
3957
4310
  // src/react/hooks/use-reviews.ts
3958
- import { useQuery as useQuery39, useMutation as useMutation17, useQueryClient as useQueryClient17 } from "@tanstack/react-query";
4311
+ import { useQuery as useQuery42, useMutation as useMutation18, useQueryClient as useQueryClient18 } from "@tanstack/react-query";
3959
4312
  function useProductReviews(productId, options) {
3960
4313
  const { client } = useBehio();
3961
- return useQuery39({
4314
+ return useQuery42({
3962
4315
  queryKey: ["behio", "reviews", productId, options?.page ?? 1],
3963
4316
  queryFn: () => unwrap(client.reviews.getProductReviews(productId, options?.page, options?.limit)),
3964
4317
  enabled: Boolean(productId) && (options?.enabled ?? true)
@@ -3966,30 +4319,30 @@ function useProductReviews(productId, options) {
3966
4319
  }
3967
4320
  function useSubmitReview() {
3968
4321
  const { client } = useBehio();
3969
- const qc = useQueryClient17();
3970
- return useMutation17({
4322
+ const qc = useQueryClient18();
4323
+ return useMutation18({
3971
4324
  mutationFn: (input) => unwrap(client.reviews.submit(input)),
3972
4325
  onSuccess: (_, input) => qc.invalidateQueries({ queryKey: ["behio", "reviews", input.productId] })
3973
4326
  });
3974
4327
  }
3975
4328
 
3976
4329
  // src/react/hooks/use-returns.ts
3977
- import { useQuery as useQuery40, useMutation as useMutation18 } from "@tanstack/react-query";
4330
+ import { useQuery as useQuery43, useMutation as useMutation19 } from "@tanstack/react-query";
3978
4331
  function useLookupReturnableOrder() {
3979
4332
  const { client } = useBehio();
3980
- return useMutation18({
4333
+ return useMutation19({
3981
4334
  mutationFn: ({ orderNumber, email }) => unwrap(client.returns.lookupOrder(orderNumber, email))
3982
4335
  });
3983
4336
  }
3984
4337
  function useSubmitReturn() {
3985
4338
  const { client } = useBehio();
3986
- return useMutation18({
4339
+ return useMutation19({
3987
4340
  mutationFn: (input) => unwrap(client.returns.submit(input))
3988
4341
  });
3989
4342
  }
3990
4343
  function useReturnStatus(returnId, email) {
3991
4344
  const { client } = useBehio();
3992
- return useQuery40({
4345
+ return useQuery43({
3993
4346
  queryKey: ["behio", "return-status", returnId],
3994
4347
  queryFn: () => unwrap(client.returns.getStatus(returnId, email)),
3995
4348
  enabled: Boolean(returnId && email)
@@ -3997,16 +4350,16 @@ function useReturnStatus(returnId, email) {
3997
4350
  }
3998
4351
 
3999
4352
  // src/react/hooks/use-quotes.ts
4000
- import { useMutation as useMutation19, useQuery as useQuery41 } from "@tanstack/react-query";
4353
+ import { useMutation as useMutation20, useQuery as useQuery44 } from "@tanstack/react-query";
4001
4354
  function useSubmitQuote() {
4002
4355
  const { client } = useBehio();
4003
- return useMutation19({
4356
+ return useMutation20({
4004
4357
  mutationFn: (input) => unwrap(client.quotes.submit(input))
4005
4358
  });
4006
4359
  }
4007
4360
  function useQuoteStatus(quoteId, email) {
4008
4361
  const { client } = useBehio();
4009
- return useQuery41({
4362
+ return useQuery44({
4010
4363
  queryKey: ["behio", "quote-status", quoteId],
4011
4364
  queryFn: () => unwrap(client.quotes.getStatus(quoteId, email)),
4012
4365
  enabled: Boolean(quoteId && email)
@@ -4014,10 +4367,10 @@ function useQuoteStatus(quoteId, email) {
4014
4367
  }
4015
4368
 
4016
4369
  // src/react/hooks/use-back-in-stock.ts
4017
- import { useMutation as useMutation20 } from "@tanstack/react-query";
4370
+ import { useMutation as useMutation21 } from "@tanstack/react-query";
4018
4371
  function useNotifyWhenAvailable() {
4019
4372
  const { client } = useBehio();
4020
- return useMutation20({
4373
+ return useMutation21({
4021
4374
  mutationFn: ({ productId, email }) => unwrap(client.catalog.notifyWhenAvailable(productId, email))
4022
4375
  });
4023
4376
  }
@@ -4153,6 +4506,9 @@ export {
4153
4506
  useAuth,
4154
4507
  useBehio,
4155
4508
  useBehioClient,
4509
+ useBlogPost,
4510
+ useBlogPosts,
4511
+ useBlogs,
4156
4512
  useBundle,
4157
4513
  useBundles,
4158
4514
  useCart,
@@ -4206,9 +4562,12 @@ export {
4206
4562
  useShopInfo,
4207
4563
  useShopScripts,
4208
4564
  useShopSeo,
4565
+ useSiteForm,
4566
+ useSiteFormSubmit,
4209
4567
  useSubmitQuote,
4210
4568
  useSubmitReturn,
4211
4569
  useSubmitReview,
4212
4570
  useSubscriptions,
4571
+ useVisitorMessages,
4213
4572
  useWishlist
4214
4573
  };