@behio/storefront-sdk 1.18.0 → 2.0.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
@@ -150,6 +150,7 @@ var BehioStorefront = class {
150
150
  this.shopDomain = config.shopDomain;
151
151
  this.defaultLocale = config.locale;
152
152
  this.defaultCurrency = config.currency;
153
+ this.setCountry(config.country);
153
154
  this.fetchFn = config.fetch || globalThis.fetch.bind(globalThis);
154
155
  this.timeout = config.timeout ?? 3e4;
155
156
  this.retries = config.retries ?? 1;
@@ -311,6 +312,14 @@ var BehioStorefront = class {
311
312
  getCurrency() {
312
313
  return this.defaultCurrency;
313
314
  }
315
+ /** Set the catalog's delivery country. Use cart.setDestination separately for the cart. */
316
+ setCountry(country) {
317
+ const normalized = country?.trim().toUpperCase();
318
+ this.defaultCountry = normalized && /^[A-Z]{2}$/.test(normalized) ? normalized : void 0;
319
+ }
320
+ getCountry() {
321
+ return this.defaultCountry;
322
+ }
314
323
  /** Set the default locale sent on every catalog request (per-call wins). */
315
324
  setLocale(locale) {
316
325
  this.defaultLocale = locale || void 0;
@@ -466,12 +475,16 @@ var BehioStorefront = class {
466
475
  if (this.defaultCurrency && !params.has("currency")) {
467
476
  params.set("currency", this.defaultCurrency);
468
477
  }
478
+ if (this.defaultCountry && path.startsWith("/catalog/") && !params.has("country")) {
479
+ params.set("country", this.defaultCountry);
480
+ }
469
481
  const qs = params.toString();
470
482
  const url = `${this.baseUrl}/storefront/v1${path}${qs ? `?${qs}` : ""}`;
471
483
  const headers = {
472
484
  "X-Api-Key": this.apiKey,
473
485
  "Content-Type": "application/json"
474
486
  };
487
+ if (path === "/cart" || path.startsWith("/cart/")) headers["X-Behio-Cart-Contract"] = "2";
475
488
  if (this.shopDomain) {
476
489
  headers["X-Shop-Domain"] = this.shopDomain;
477
490
  }
@@ -598,42 +611,46 @@ var BehioStorefront = class {
598
611
  throw new BehioNetworkError("Request failed after retries");
599
612
  }
600
613
  };
614
+ function catalogProductsQuery(query) {
615
+ const q = {};
616
+ if (query) {
617
+ if (query.page !== void 0) q.page = query.page;
618
+ if (query.limit !== void 0) q.limit = query.limit;
619
+ if (query.category) q.category = query.category;
620
+ if (query.label) q.label = query.label;
621
+ if (query.priceMin !== void 0) q.priceMin = query.priceMin;
622
+ if (query.priceMax !== void 0) q.priceMax = query.priceMax;
623
+ if (query.currency) q.currency = query.currency;
624
+ if (query.country) q.country = query.country;
625
+ if (query.locale) q.locale = query.locale;
626
+ if (query.sort) q.sort = query.sort;
627
+ if (query.inStock !== void 0) q.inStock = query.inStock;
628
+ if (query.ratingMin !== void 0) q.ratingMin = query.ratingMin;
629
+ if (query.search) q.search = query.search;
630
+ if (query.parameters) q.parameters = JSON.stringify(query.parameters);
631
+ if (query.facets) q.facets = JSON.stringify(query.facets);
632
+ if (query.ids && query.ids.length > 0) q.ids = query.ids;
633
+ if (query.slugs && query.slugs.length > 0) q.slugs = query.slugs;
634
+ if (query.labels && query.labels.length > 0) q.labels = query.labels;
635
+ if (query.categories && query.categories.length > 0)
636
+ q.categories = query.categories;
637
+ if (query.excludeIds && query.excludeIds.length > 0)
638
+ q.excludeIds = query.excludeIds;
639
+ if (query.excludeCategories && query.excludeCategories.length > 0)
640
+ q.excludeCategories = query.excludeCategories;
641
+ if (query.hasDiscount !== void 0) q.hasDiscount = query.hasDiscount;
642
+ if (query.isFeatured !== void 0) q.isFeatured = query.isFeatured;
643
+ if (query.createdAfter !== void 0) q.createdAfter = query.createdAfter;
644
+ }
645
+ return q;
646
+ }
601
647
  var CatalogModule = class {
602
648
  constructor(client) {
603
649
  this.client = client;
604
650
  }
605
651
  /** List products with filtering, pagination, search */
606
652
  async getProducts(query) {
607
- const q = {};
608
- if (query) {
609
- if (query.page) q.page = query.page;
610
- if (query.limit) q.limit = query.limit;
611
- if (query.category) q.category = query.category;
612
- if (query.label) q.label = query.label;
613
- if (query.priceMin) q.priceMin = query.priceMin;
614
- if (query.priceMax) q.priceMax = query.priceMax;
615
- if (query.currency) q.currency = query.currency;
616
- if (query.country) q.country = query.country;
617
- if (query.locale) q.locale = query.locale;
618
- if (query.sort) q.sort = query.sort;
619
- if (query.inStock !== void 0) q.inStock = query.inStock;
620
- if (query.ratingMin !== void 0) q.ratingMin = query.ratingMin;
621
- if (query.search) q.search = query.search;
622
- if (query.parameters) q.parameters = JSON.stringify(query.parameters);
623
- if (query.facets) q.facets = JSON.stringify(query.facets);
624
- if (query.ids && query.ids.length > 0) q.ids = query.ids;
625
- if (query.slugs && query.slugs.length > 0) q.slugs = query.slugs;
626
- if (query.labels && query.labels.length > 0) q.labels = query.labels;
627
- if (query.categories && query.categories.length > 0)
628
- q.categories = query.categories;
629
- if (query.excludeIds && query.excludeIds.length > 0)
630
- q.excludeIds = query.excludeIds;
631
- if (query.excludeCategories && query.excludeCategories.length > 0)
632
- q.excludeCategories = query.excludeCategories;
633
- if (query.hasDiscount !== void 0) q.hasDiscount = query.hasDiscount;
634
- if (query.isFeatured !== void 0) q.isFeatured = query.isFeatured;
635
- if (query.createdAfter !== void 0) q.createdAfter = query.createdAfter;
636
- }
653
+ const q = catalogProductsQuery(query);
637
654
  return this.client.request(
638
655
  "GET",
639
656
  "/catalog/products",
@@ -675,14 +692,7 @@ var CatalogModule = class {
675
692
  }
676
693
  /** Get products in a category */
677
694
  async getCategoryProducts(slug, query) {
678
- const q = {};
679
- if (query) {
680
- if (query.page) q.page = query.page;
681
- if (query.limit) q.limit = query.limit;
682
- if (query.sort) q.sort = query.sort;
683
- if (query.locale) q.locale = query.locale;
684
- if (query.currency) q.currency = query.currency;
685
- }
695
+ const q = catalogProductsQuery(query);
686
696
  return this.client.request(
687
697
  "GET",
688
698
  `/catalog/categories/${slug}/products`,
@@ -716,7 +726,7 @@ var CatalogModule = class {
716
726
  "GET",
717
727
  "/catalog/featured",
718
728
  {
719
- query: { locale: options?.locale, currency: options?.currency }
729
+ query: { locale: options?.locale, currency: options?.currency, country: options?.country }
720
730
  }
721
731
  );
722
732
  }
@@ -774,9 +784,10 @@ var CatalogModule = class {
774
784
  if (query) {
775
785
  if (query.category) q.category = query.category;
776
786
  if (query.label) q.label = query.label;
777
- if (query.priceMin) q.priceMin = query.priceMin;
778
- if (query.priceMax) q.priceMax = query.priceMax;
787
+ if (query.priceMin !== void 0) q.priceMin = query.priceMin;
788
+ if (query.priceMax !== void 0) q.priceMax = query.priceMax;
779
789
  if (query.currency) q.currency = query.currency;
790
+ if (query.country) q.country = query.country;
780
791
  if (query.locale) q.locale = query.locale;
781
792
  if (query.inStock !== void 0) q.inStock = query.inStock;
782
793
  if (query.ratingMin !== void 0) q.ratingMin = query.ratingMin;
@@ -798,12 +809,12 @@ var CatalogModule = class {
798
809
  return this.getProducts({ search: query, ...options });
799
810
  }
800
811
  /** List all active bundles */
801
- async getBundles() {
802
- return this.client.request("GET", "/catalog/bundles");
812
+ async getBundles(options) {
813
+ return this.client.request("GET", "/catalog/bundles", { query: { ...options } });
803
814
  }
804
815
  /** Get a single bundle by slug */
805
- async getBundle(slug) {
806
- return this.client.request("GET", `/catalog/bundles/${slug}`);
816
+ async getBundle(slug, options) {
817
+ return this.client.request("GET", `/catalog/bundles/${encodeURIComponent(slug)}`, { query: { ...options } });
807
818
  }
808
819
  /**
809
820
  * One product group ("collection") by slug with its products as standard
@@ -816,7 +827,7 @@ var CatalogModule = class {
816
827
  "GET",
817
828
  `/catalog/product-groups/${encodeURIComponent(slug)}`,
818
829
  {
819
- query: { locale: options?.locale, currency: options?.currency }
830
+ query: { locale: options?.locale, currency: options?.currency, country: options?.country }
820
831
  }
821
832
  );
822
833
  }
@@ -831,7 +842,7 @@ var CatalogModule = class {
831
842
  return this.client.request(
832
843
  "GET",
833
844
  `/catalog/products/${encodeURIComponent(productSlug)}/cross-sell`,
834
- { query: { locale: options?.locale, currency: options?.currency } }
845
+ { query: { locale: options?.locale, currency: options?.currency, country: options?.country } }
835
846
  );
836
847
  }
837
848
  /** Active promotions applicable to a product (with countdown end time) */
@@ -1039,6 +1050,7 @@ var CartModule = class {
1039
1050
  body: { currency }
1040
1051
  });
1041
1052
  if (res.error) return res;
1053
+ if (res.data.sessionToken) this.client.setCartSession(res.data.sessionToken);
1042
1054
  this.client.emit("cart:updated", res.data);
1043
1055
  return res;
1044
1056
  }
@@ -1059,6 +1071,7 @@ var CartModule = class {
1059
1071
  body: input
1060
1072
  });
1061
1073
  if (res.error) return res;
1074
+ if (res.data.sessionToken) this.client.setCartSession(res.data.sessionToken);
1062
1075
  this.client.emit("cart:updated", res.data);
1063
1076
  return res;
1064
1077
  }
@@ -1143,14 +1156,15 @@ var CartModule = class {
1143
1156
  body
1144
1157
  });
1145
1158
  if (res.error) return res;
1159
+ if (res.data.sessionToken) this.client.setCartSession(res.data.sessionToken);
1146
1160
  this.client.emit("cart:updated", res.data);
1147
1161
  return res;
1148
1162
  }
1149
- /** Update quantity of a bundle already in the cart */
1150
- async updateBundleQuantity(bundleId, quantity) {
1163
+ /** Update by CartBundleLine.id; legacy catalog bundle IDs are accepted by the API */
1164
+ async updateBundleQuantity(lineId, quantity) {
1151
1165
  const res = await this.client.request(
1152
1166
  "PATCH",
1153
- `/cart/bundles/${bundleId}`,
1167
+ `/cart/bundles/${lineId}`,
1154
1168
  {
1155
1169
  body: { quantity }
1156
1170
  }
@@ -1159,11 +1173,11 @@ var CartModule = class {
1159
1173
  this.client.emit("cart:updated", res.data);
1160
1174
  return res;
1161
1175
  }
1162
- /** Remove a bundle from the cart */
1163
- async removeBundle(bundleId) {
1176
+ /** Remove by CartBundleLine.id; scoped to the current visitor cart */
1177
+ async removeBundle(lineId) {
1164
1178
  const res = await this.client.request(
1165
1179
  "DELETE",
1166
- `/cart/bundles/${bundleId}`
1180
+ `/cart/bundles/${lineId}`
1167
1181
  );
1168
1182
  if (res.error) return res;
1169
1183
  this.client.emit("cart:updated", res.data);
@@ -1197,6 +1211,12 @@ var CheckoutModule = class {
1197
1211
  constructor(client) {
1198
1212
  this.client = client;
1199
1213
  }
1214
+ /** Recalculate the current cart including shipping, fees, benefits and rounding.
1215
+ * No order, stock claim, payment or gift-card redemption is created. Required
1216
+ * legal consents are enforced only on createOrder, not while quoting. */
1217
+ async preview(input) {
1218
+ return this.client.request("POST", "/checkout/preview", { body: input });
1219
+ }
1200
1220
  /** Create order from cart */
1201
1221
  async createOrder(input) {
1202
1222
  const res = await this.client.request("POST", "/checkout", {
@@ -2091,6 +2111,7 @@ function BehioProvider({
2091
2111
  });
2092
2112
  }
2093
2113
  const client = clientRef.current;
2114
+ const [sessionRestored, setSessionRestored] = useState(false);
2094
2115
  useEffect(() => {
2095
2116
  const accessToken = storageAdapter.get(STORAGE_KEYS.ACCESS_TOKEN);
2096
2117
  const refreshToken = storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
@@ -2101,6 +2122,7 @@ function BehioProvider({
2101
2122
  if (cartSession) {
2102
2123
  client.setCartSession(cartSession);
2103
2124
  }
2125
+ setSessionRestored(true);
2104
2126
  }, [client, storageAdapter]);
2105
2127
  useEffect(() => {
2106
2128
  if (!persistCurrency) return;
@@ -2155,12 +2177,13 @@ function BehioProvider({
2155
2177
  () => ({
2156
2178
  client,
2157
2179
  storage: storageAdapter,
2180
+ sessionRestored,
2158
2181
  currency: activeCurrency,
2159
2182
  setCurrency,
2160
2183
  configuredDefaultCurrency: defaultCurrency,
2161
2184
  configuredCurrencies: currencies
2162
2185
  }),
2163
- [client, storageAdapter, activeCurrency, setCurrency, defaultCurrency, currencies]
2186
+ [client, storageAdapter, sessionRestored, activeCurrency, setCurrency, defaultCurrency, currencies]
2164
2187
  );
2165
2188
  return /* @__PURE__ */ jsx(QueryClientProvider, { client: qc, children: /* @__PURE__ */ jsx(BehioContext.Provider, { value: ctxValue, children }) });
2166
2189
  }
@@ -2410,6 +2433,15 @@ function useSearch(query, options) {
2410
2433
  // src/react/hooks/use-cart.ts
2411
2434
  import { useCallback as useCallback3 } from "react";
2412
2435
  import { useQuery as useQuery10, useMutation, useQueryClient } from "@tanstack/react-query";
2436
+
2437
+ // src/react/utils/refresh-bundle-queries.ts
2438
+ async function refreshBundleQueries(queryClient) {
2439
+ const keys = [["behio", "bundle"], ["behio", "bundles"]];
2440
+ await Promise.all(keys.map((queryKey) => queryClient.cancelQueries({ queryKey })));
2441
+ await Promise.all(keys.map((queryKey) => queryClient.invalidateQueries({ queryKey })));
2442
+ }
2443
+
2444
+ // src/react/hooks/use-cart.ts
2413
2445
  var CART_KEY = ["behio", "cart"];
2414
2446
  function useCart(options) {
2415
2447
  const { client, storage } = useBehio();
@@ -2433,32 +2465,21 @@ function useCart(options) {
2433
2465
  storage.set(STORAGE_KEYS.CART_SESSION, result.newSessionToken);
2434
2466
  }
2435
2467
  queryClient.setQueryData([...CART_KEY], result);
2468
+ void refreshBundleQueries(queryClient);
2436
2469
  }
2437
2470
  });
2471
+ const cancelCartRead = () => queryClient.cancelQueries({ queryKey: [...CART_KEY] });
2472
+ const acceptCart = async (result) => {
2473
+ await cancelCartRead();
2474
+ queryClient.setQueryData([...CART_KEY], result);
2475
+ void refreshBundleQueries(queryClient);
2476
+ };
2438
2477
  const updateMutation = useMutation({
2439
2478
  mutationFn: async ({ itemId, quantity }) => {
2440
2479
  return unwrap(client.cart.updateQuantity(itemId, quantity));
2441
2480
  },
2442
- onMutate: async ({ itemId, quantity }) => {
2443
- await queryClient.cancelQueries({ queryKey: [...CART_KEY] });
2444
- const previous = queryClient.getQueryData([...CART_KEY]);
2445
- if (previous) {
2446
- const updatedItems = previous.items.map(
2447
- (item) => item.id === itemId ? { ...item, quantity, totalPrice: item.unitPrice * quantity } : item
2448
- );
2449
- queryClient.setQueryData([...CART_KEY], {
2450
- ...previous,
2451
- items: updatedItems,
2452
- itemCount: updatedItems.reduce((sum, i) => sum + i.quantity, 0)
2453
- });
2454
- }
2455
- return { previous };
2456
- },
2457
- onError: (_err, _vars, context) => {
2458
- if (context?.previous) {
2459
- queryClient.setQueryData([...CART_KEY], context.previous);
2460
- }
2461
- },
2481
+ onMutate: cancelCartRead,
2482
+ onSuccess: acceptCart,
2462
2483
  onSettled: () => {
2463
2484
  queryClient.invalidateQueries({ queryKey: [...CART_KEY] });
2464
2485
  }
@@ -2467,24 +2488,8 @@ function useCart(options) {
2467
2488
  mutationFn: async (itemId) => {
2468
2489
  return unwrap(client.cart.removeItem(itemId));
2469
2490
  },
2470
- onMutate: async (itemId) => {
2471
- await queryClient.cancelQueries({ queryKey: [...CART_KEY] });
2472
- const previous = queryClient.getQueryData([...CART_KEY]);
2473
- if (previous) {
2474
- const updatedItems = previous.items.filter((item) => item.id !== itemId);
2475
- queryClient.setQueryData([...CART_KEY], {
2476
- ...previous,
2477
- items: updatedItems,
2478
- itemCount: updatedItems.reduce((sum, i) => sum + i.quantity, 0)
2479
- });
2480
- }
2481
- return { previous };
2482
- },
2483
- onError: (_err, _vars, context) => {
2484
- if (context?.previous) {
2485
- queryClient.setQueryData([...CART_KEY], context.previous);
2486
- }
2487
- },
2491
+ onMutate: cancelCartRead,
2492
+ onSuccess: acceptCart,
2488
2493
  onSettled: () => {
2489
2494
  queryClient.invalidateQueries({ queryKey: [...CART_KEY] });
2490
2495
  }
@@ -2495,6 +2500,7 @@ function useCart(options) {
2495
2500
  queryClient.setQueryData([...CART_KEY], null);
2496
2501
  client.clearCartSession();
2497
2502
  storage.remove(STORAGE_KEYS.CART_SESSION);
2503
+ void refreshBundleQueries(queryClient);
2498
2504
  }
2499
2505
  });
2500
2506
  const applyDiscountMutation = useMutation({
@@ -2515,6 +2521,7 @@ function useCart(options) {
2515
2521
  queryClient.setQueryData([...CART_KEY], result);
2516
2522
  client.clearCartSession();
2517
2523
  storage.remove(STORAGE_KEYS.CART_SESSION);
2524
+ void refreshBundleQueries(queryClient);
2518
2525
  }
2519
2526
  });
2520
2527
  const addItem = useCallback3(
@@ -2559,7 +2566,7 @@ function useCart(options) {
2559
2566
  merge,
2560
2567
  // Computed
2561
2568
  itemCount: cart?.itemCount ?? 0,
2562
- isEmpty: !cart || cart.items.length === 0,
2569
+ isEmpty: !cart || cart.items.length === 0 && (cart.bundleLines?.length ?? 0) === 0,
2563
2570
  // Mutation states
2564
2571
  isAdding: addMutation.isPending,
2565
2572
  isUpdating: updateMutation.isPending,
@@ -2615,6 +2622,7 @@ function useAuth() {
2615
2622
  queryClient.setQueryData([...CUSTOMER_KEY], null);
2616
2623
  queryClient.invalidateQueries({ queryKey: ["behio", "orders"] });
2617
2624
  queryClient.invalidateQueries({ queryKey: ["behio", "addresses"] });
2625
+ void refreshBundleQueries(queryClient);
2618
2626
  }, [client, storage, queryClient]);
2619
2627
  const postAuth = useCallback4(async () => {
2620
2628
  await queryClient.invalidateQueries({ queryKey: [...CUSTOMER_KEY] });
@@ -2627,6 +2635,7 @@ function useAuth() {
2627
2635
  }
2628
2636
  }
2629
2637
  queryClient.invalidateQueries({ queryKey: [...CART_KEY3] });
2638
+ void refreshBundleQueries(queryClient);
2630
2639
  }, [client, storage, queryClient]);
2631
2640
  const loginMutation = useMutation2({
2632
2641
  mutationFn: async ({ email, password }) => {
@@ -3590,6 +3599,11 @@ function useCheckout() {
3590
3599
  reset
3591
3600
  };
3592
3601
  }
3602
+ function useCheckoutPreview() {
3603
+ const { client } = useBehio();
3604
+ const mutation = useMutation14({ mutationFn: (input) => unwrap(client.checkout.preview(input)) });
3605
+ return { preview: mutation.mutateAsync, data: mutation.data, isPreviewing: mutation.isPending, error: mutation.error, reset: mutation.reset };
3606
+ }
3593
3607
 
3594
3608
  // src/react/hooks/use-pages.ts
3595
3609
  import { useQuery as useQuery29 } from "@tanstack/react-query";
@@ -3754,7 +3768,8 @@ function useShopScripts(options) {
3754
3768
  return useQuery33({
3755
3769
  queryKey: ["behio", "shop-scripts"],
3756
3770
  queryFn: () => unwrap(client.getShopScripts()),
3757
- enabled: options?.enabled !== false
3771
+ enabled: options?.enabled !== false,
3772
+ initialData: options?.initialData
3758
3773
  });
3759
3774
  }
3760
3775
 
@@ -3823,7 +3838,7 @@ function CurrencySwitcher({
3823
3838
  }
3824
3839
 
3825
3840
  // src/react/components/storefront-scripts.tsx
3826
- import { useEffect as useEffect6, useMemo as useMemo5, useState as useState10 } from "react";
3841
+ import { useEffect as useEffect6, useMemo as useMemo5, useRef as useRef4, useState as useState10 } from "react";
3827
3842
 
3828
3843
  // src/react/hooks/use-consent.ts
3829
3844
  import { useQuery as useQuery35, useMutation as useMutation16, useQueryClient as useQueryClient16 } from "@tanstack/react-query";
@@ -3888,31 +3903,32 @@ function partsFor(s) {
3888
3903
  return [{ target: "head", html: `<meta name="google-site-verification" content="${id}" />` }];
3889
3904
  case "RAW":
3890
3905
  default:
3891
- return [{ target: s.placement === "HEAD" ? "head" : "body", html: s.value }];
3906
+ return [{ target: s.placement === "HEAD" ? "head" : "body", start: s.placement === "BODY_START", html: s.value }];
3892
3907
  }
3893
3908
  }
3894
- function injectHtml(target, html) {
3909
+ function injectHtml(target, html, start = false) {
3895
3910
  const tpl = document.createElement("template");
3896
3911
  tpl.innerHTML = html;
3897
3912
  const added = [];
3913
+ const anchor = start ? target.firstChild : null;
3898
3914
  Array.from(tpl.content.childNodes).forEach((node) => {
3899
3915
  if (node.nodeName === "SCRIPT") {
3900
3916
  const old = node;
3901
3917
  const script = document.createElement("script");
3902
3918
  for (const attr of Array.from(old.attributes)) script.setAttribute(attr.name, attr.value);
3903
3919
  script.text = old.text;
3904
- target.appendChild(script);
3920
+ target.insertBefore(script, anchor);
3905
3921
  added.push(script);
3906
3922
  } else if (node.nodeType === Node.ELEMENT_NODE) {
3907
3923
  const clone = node.cloneNode(true);
3908
- target.appendChild(clone);
3924
+ target.insertBefore(clone, anchor);
3909
3925
  added.push(clone);
3910
3926
  }
3911
3927
  });
3912
3928
  return added;
3913
3929
  }
3914
- function StorefrontScripts({ visitorId: visitorIdProp } = {}) {
3915
- const { data } = useShopScripts();
3930
+ function StorefrontScripts({ visitorId: visitorIdProp, initialScripts } = {}) {
3931
+ const { data } = useShopScripts({ enabled: initialScripts === void 0, initialData: initialScripts ? { scripts: initialScripts } : void 0 });
3916
3932
  const [visitorId, setVisitorId] = useState10(visitorIdProp ?? "");
3917
3933
  useEffect6(() => {
3918
3934
  if (visitorIdProp) return;
@@ -3924,21 +3940,30 @@ function StorefrontScripts({ visitorId: visitorIdProp } = {}) {
3924
3940
  }, [visitorIdProp]);
3925
3941
  const { data: consent } = useCookieConsent(visitorId || void 0);
3926
3942
  const analyticsOk = Boolean(consent?.analytics);
3943
+ const marketingOk = Boolean(consent?.marketing);
3927
3944
  const scripts = useMemo5(
3928
- () => (data?.scripts ?? []).filter((s) => !s.consentRequired || analyticsOk),
3929
- [data, analyticsOk]
3945
+ () => (initialScripts ?? data?.scripts ?? []).filter((s) => !s.consentRequired || (s.type === "META_PIXEL" ? marketingOk : analyticsOk)),
3946
+ [initialScripts, data, analyticsOk, marketingOk]
3930
3947
  );
3931
3948
  const signature = useMemo5(
3932
3949
  () => JSON.stringify(scripts.map((s) => [s.id, s.type, s.placement, s.value])),
3933
3950
  [scripts]
3934
3951
  );
3952
+ const activeConsented = useRef4(/* @__PURE__ */ new Set());
3935
3953
  useEffect6(() => {
3936
3954
  if (typeof document === "undefined") return;
3955
+ const nextConsented = new Set(scripts.filter((s) => s.consentRequired).map((s) => s.id));
3956
+ const revoked = [...activeConsented.current].some((id) => !nextConsented.has(id));
3957
+ activeConsented.current = nextConsented;
3958
+ if (revoked) {
3959
+ window.location.reload();
3960
+ return;
3961
+ }
3937
3962
  const added = [];
3938
3963
  for (const s of scripts) {
3939
3964
  for (const part of partsFor(s)) {
3940
3965
  const target = part.target === "head" ? document.head : document.body;
3941
- added.push(...injectHtml(target, part.html));
3966
+ added.push(...injectHtml(target, part.html, part.start));
3942
3967
  }
3943
3968
  }
3944
3969
  return () => {
@@ -4200,20 +4225,22 @@ function utmFromSearch(search) {
4200
4225
  // src/react/hooks/use-bundles.ts
4201
4226
  import { useQuery as useQuery36 } from "@tanstack/react-query";
4202
4227
  function useBundles(options) {
4203
- const { client } = useBehio();
4228
+ const { client, sessionRestored } = useBehio();
4229
+ const query = { locale: options?.locale ?? client.getLocale(), currency: options?.currency ?? client.getCurrency(), country: options?.country ?? client.getCountry() };
4204
4230
  return useQuery36({
4205
- queryKey: ["behio", "bundles"],
4206
- queryFn: () => unwrap(client.catalog.getBundles()),
4207
- enabled: options?.enabled ?? true,
4231
+ queryKey: ["behio", "bundles", query],
4232
+ queryFn: () => unwrap(client.catalog.getBundles(query)),
4233
+ enabled: sessionRestored && (options?.enabled ?? true),
4208
4234
  initialData: options?.initialData
4209
4235
  });
4210
4236
  }
4211
4237
  function useBundle(slug, options) {
4212
- const { client } = useBehio();
4238
+ const { client, sessionRestored } = useBehio();
4239
+ const query = { locale: options?.locale ?? client.getLocale(), currency: options?.currency ?? client.getCurrency(), country: options?.country ?? client.getCountry() };
4213
4240
  return useQuery36({
4214
- queryKey: ["behio", "bundle", slug],
4215
- queryFn: () => unwrap(client.catalog.getBundle(slug)),
4216
- enabled: Boolean(slug) && (options?.enabled ?? true),
4241
+ queryKey: ["behio", "bundle", slug, query],
4242
+ queryFn: () => unwrap(client.catalog.getBundle(slug, query)),
4243
+ enabled: sessionRestored && Boolean(slug) && (options?.enabled ?? true),
4217
4244
  initialData: options?.initialData
4218
4245
  });
4219
4246
  }
@@ -4316,8 +4343,10 @@ import { useQuery as useQuery42, useMutation as useMutation18, useQueryClient as
4316
4343
  function useProductReviews(productId, options) {
4317
4344
  const { client } = useBehio();
4318
4345
  return useQuery42({
4319
- queryKey: ["behio", "reviews", productId, options?.page ?? 1],
4346
+ queryKey: ["behio", "reviews", productId, options?.page ?? 1, options?.limit ?? 20],
4320
4347
  queryFn: () => unwrap(client.reviews.getProductReviews(productId, options?.page, options?.limit)),
4348
+ initialData: options?.initialData,
4349
+ staleTime: 3e4,
4321
4350
  enabled: Boolean(productId) && (options?.enabled ?? true)
4322
4351
  });
4323
4352
  }
@@ -4380,15 +4409,23 @@ function useNotifyWhenAvailable() {
4380
4409
  }
4381
4410
 
4382
4411
  // src/react/utils/format-price.ts
4383
- function formatPrice(amount, currency, locale) {
4412
+ function formatPrice(amount, currency, locale, display) {
4384
4413
  const resolvedLocale = locale ?? "cs";
4385
4414
  try {
4386
- return new Intl.NumberFormat(resolvedLocale, {
4415
+ const formatter = new Intl.NumberFormat(resolvedLocale, {
4387
4416
  style: "currency",
4388
4417
  currency,
4418
+ currencyDisplay: display === "CODE" ? "code" : "symbol",
4389
4419
  minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,
4390
4420
  maximumFractionDigits: 2
4391
- }).format(amount);
4421
+ });
4422
+ if (display === "SYMBOL_BEFORE" || display === "SYMBOL_AFTER") {
4423
+ const parts = formatter.formatToParts(amount);
4424
+ const symbol = parts.find((p) => p.type === "currency")?.value ?? currency;
4425
+ const value = parts.filter((p) => p.type !== "currency").map((p) => p.value).join("").trim();
4426
+ return display === "SYMBOL_BEFORE" ? `${symbol}\xA0${value}` : `${value}\xA0${symbol}`;
4427
+ }
4428
+ return formatter.format(amount);
4392
4429
  } catch {
4393
4430
  return `${amount} ${currency}`;
4394
4431
  }
@@ -4521,6 +4558,7 @@ export {
4521
4558
  useCategory,
4522
4559
  useCertificateVerification,
4523
4560
  useCheckout,
4561
+ useCheckoutPreview,
4524
4562
  useCookieConsent,
4525
4563
  useCourse,
4526
4564
  useCourseCertificates,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@behio/storefront-sdk",
3
- "version": "1.18.0",
3
+ "version": "2.0.0",
4
4
  "description": "TypeScript SDK for Behio headless e-commerce: core client + React hooks",
5
5
  "author": "Behio",
6
6
  "license": "MIT",