@behio/storefront-sdk 1.19.0 → 2.0.1

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();
@@ -2424,41 +2456,30 @@ function useCart(options) {
2424
2456
  // Only fetch if we have a cart session or are logged in
2425
2457
  enabled: options?.enabled !== false && (!!client.getCartSession() || !!client.getAccessToken())
2426
2458
  });
2459
+ const cancelCartRead = () => queryClient.cancelQueries({ queryKey: [...CART_KEY] });
2460
+ const acceptCart = async (result) => {
2461
+ await cancelCartRead();
2462
+ queryClient.setQueryData([...CART_KEY], result);
2463
+ void refreshBundleQueries(queryClient);
2464
+ };
2427
2465
  const addMutation = useMutation({
2428
2466
  mutationFn: async ({ productId, quantity }) => {
2429
2467
  return unwrap(client.cart.addItem({ productId, quantity: quantity ?? 1 }));
2430
2468
  },
2431
- onSuccess: (result) => {
2469
+ onMutate: cancelCartRead,
2470
+ onSuccess: async (result) => {
2432
2471
  if (result.newSessionToken) {
2433
2472
  storage.set(STORAGE_KEYS.CART_SESSION, result.newSessionToken);
2434
2473
  }
2435
- queryClient.setQueryData([...CART_KEY], result);
2474
+ await acceptCart(result);
2436
2475
  }
2437
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,54 +2488,42 @@ 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
  }
2491
2496
  });
2492
2497
  const clearMutation = useMutation({
2493
2498
  mutationFn: () => unwrap(client.cart.clear()),
2494
- onSuccess: () => {
2499
+ onMutate: cancelCartRead,
2500
+ onSuccess: async () => {
2501
+ await cancelCartRead();
2495
2502
  queryClient.setQueryData([...CART_KEY], null);
2496
2503
  client.clearCartSession();
2497
2504
  storage.remove(STORAGE_KEYS.CART_SESSION);
2505
+ void refreshBundleQueries(queryClient);
2498
2506
  }
2499
2507
  });
2500
2508
  const applyDiscountMutation = useMutation({
2501
2509
  mutationFn: (code) => unwrap(client.cart.applyDiscount(code)),
2502
- onSuccess: (result) => {
2503
- queryClient.setQueryData([...CART_KEY], result);
2504
- }
2510
+ onMutate: cancelCartRead,
2511
+ onSuccess: acceptCart
2505
2512
  });
2506
2513
  const removeDiscountMutation = useMutation({
2507
2514
  mutationFn: () => unwrap(client.cart.removeDiscount()),
2508
- onSuccess: (result) => {
2509
- queryClient.setQueryData([...CART_KEY], result);
2510
- }
2515
+ onMutate: cancelCartRead,
2516
+ onSuccess: acceptCart
2511
2517
  });
2512
2518
  const mergeMutation = useMutation({
2513
2519
  mutationFn: () => unwrap(client.cart.merge()),
2514
- onSuccess: (result) => {
2520
+ onMutate: cancelCartRead,
2521
+ onSuccess: async (result) => {
2522
+ await cancelCartRead();
2515
2523
  queryClient.setQueryData([...CART_KEY], result);
2516
2524
  client.clearCartSession();
2517
2525
  storage.remove(STORAGE_KEYS.CART_SESSION);
2526
+ void refreshBundleQueries(queryClient);
2518
2527
  }
2519
2528
  });
2520
2529
  const addItem = useCallback3(
@@ -2559,7 +2568,7 @@ function useCart(options) {
2559
2568
  merge,
2560
2569
  // Computed
2561
2570
  itemCount: cart?.itemCount ?? 0,
2562
- isEmpty: !cart || cart.items.length === 0,
2571
+ isEmpty: !cart || cart.items.length === 0 && (cart.bundleLines?.length ?? 0) === 0,
2563
2572
  // Mutation states
2564
2573
  isAdding: addMutation.isPending,
2565
2574
  isUpdating: updateMutation.isPending,
@@ -2615,6 +2624,7 @@ function useAuth() {
2615
2624
  queryClient.setQueryData([...CUSTOMER_KEY], null);
2616
2625
  queryClient.invalidateQueries({ queryKey: ["behio", "orders"] });
2617
2626
  queryClient.invalidateQueries({ queryKey: ["behio", "addresses"] });
2627
+ void refreshBundleQueries(queryClient);
2618
2628
  }, [client, storage, queryClient]);
2619
2629
  const postAuth = useCallback4(async () => {
2620
2630
  await queryClient.invalidateQueries({ queryKey: [...CUSTOMER_KEY] });
@@ -2627,6 +2637,7 @@ function useAuth() {
2627
2637
  }
2628
2638
  }
2629
2639
  queryClient.invalidateQueries({ queryKey: [...CART_KEY3] });
2640
+ void refreshBundleQueries(queryClient);
2630
2641
  }, [client, storage, queryClient]);
2631
2642
  const loginMutation = useMutation2({
2632
2643
  mutationFn: async ({ email, password }) => {
@@ -3590,6 +3601,11 @@ function useCheckout() {
3590
3601
  reset
3591
3602
  };
3592
3603
  }
3604
+ function useCheckoutPreview() {
3605
+ const { client } = useBehio();
3606
+ const mutation = useMutation14({ mutationFn: (input) => unwrap(client.checkout.preview(input)) });
3607
+ return { preview: mutation.mutateAsync, data: mutation.data, isPreviewing: mutation.isPending, error: mutation.error, reset: mutation.reset };
3608
+ }
3593
3609
 
3594
3610
  // src/react/hooks/use-pages.ts
3595
3611
  import { useQuery as useQuery29 } from "@tanstack/react-query";
@@ -3754,7 +3770,8 @@ function useShopScripts(options) {
3754
3770
  return useQuery33({
3755
3771
  queryKey: ["behio", "shop-scripts"],
3756
3772
  queryFn: () => unwrap(client.getShopScripts()),
3757
- enabled: options?.enabled !== false
3773
+ enabled: options?.enabled !== false,
3774
+ initialData: options?.initialData
3758
3775
  });
3759
3776
  }
3760
3777
 
@@ -3823,7 +3840,7 @@ function CurrencySwitcher({
3823
3840
  }
3824
3841
 
3825
3842
  // src/react/components/storefront-scripts.tsx
3826
- import { useEffect as useEffect6, useMemo as useMemo5, useState as useState10 } from "react";
3843
+ import { useEffect as useEffect6, useMemo as useMemo5, useRef as useRef4, useState as useState10 } from "react";
3827
3844
 
3828
3845
  // src/react/hooks/use-consent.ts
3829
3846
  import { useQuery as useQuery35, useMutation as useMutation16, useQueryClient as useQueryClient16 } from "@tanstack/react-query";
@@ -3888,31 +3905,32 @@ function partsFor(s) {
3888
3905
  return [{ target: "head", html: `<meta name="google-site-verification" content="${id}" />` }];
3889
3906
  case "RAW":
3890
3907
  default:
3891
- return [{ target: s.placement === "HEAD" ? "head" : "body", html: s.value }];
3908
+ return [{ target: s.placement === "HEAD" ? "head" : "body", start: s.placement === "BODY_START", html: s.value }];
3892
3909
  }
3893
3910
  }
3894
- function injectHtml(target, html) {
3911
+ function injectHtml(target, html, start = false) {
3895
3912
  const tpl = document.createElement("template");
3896
3913
  tpl.innerHTML = html;
3897
3914
  const added = [];
3915
+ const anchor = start ? target.firstChild : null;
3898
3916
  Array.from(tpl.content.childNodes).forEach((node) => {
3899
3917
  if (node.nodeName === "SCRIPT") {
3900
3918
  const old = node;
3901
3919
  const script = document.createElement("script");
3902
3920
  for (const attr of Array.from(old.attributes)) script.setAttribute(attr.name, attr.value);
3903
3921
  script.text = old.text;
3904
- target.appendChild(script);
3922
+ target.insertBefore(script, anchor);
3905
3923
  added.push(script);
3906
3924
  } else if (node.nodeType === Node.ELEMENT_NODE) {
3907
3925
  const clone = node.cloneNode(true);
3908
- target.appendChild(clone);
3926
+ target.insertBefore(clone, anchor);
3909
3927
  added.push(clone);
3910
3928
  }
3911
3929
  });
3912
3930
  return added;
3913
3931
  }
3914
- function StorefrontScripts({ visitorId: visitorIdProp } = {}) {
3915
- const { data } = useShopScripts();
3932
+ function StorefrontScripts({ visitorId: visitorIdProp, initialScripts } = {}) {
3933
+ const { data } = useShopScripts({ enabled: initialScripts === void 0, initialData: initialScripts ? { scripts: initialScripts } : void 0 });
3916
3934
  const [visitorId, setVisitorId] = useState10(visitorIdProp ?? "");
3917
3935
  useEffect6(() => {
3918
3936
  if (visitorIdProp) return;
@@ -3924,21 +3942,30 @@ function StorefrontScripts({ visitorId: visitorIdProp } = {}) {
3924
3942
  }, [visitorIdProp]);
3925
3943
  const { data: consent } = useCookieConsent(visitorId || void 0);
3926
3944
  const analyticsOk = Boolean(consent?.analytics);
3945
+ const marketingOk = Boolean(consent?.marketing);
3927
3946
  const scripts = useMemo5(
3928
- () => (data?.scripts ?? []).filter((s) => !s.consentRequired || analyticsOk),
3929
- [data, analyticsOk]
3947
+ () => (initialScripts ?? data?.scripts ?? []).filter((s) => !s.consentRequired || (s.type === "META_PIXEL" ? marketingOk : analyticsOk)),
3948
+ [initialScripts, data, analyticsOk, marketingOk]
3930
3949
  );
3931
3950
  const signature = useMemo5(
3932
3951
  () => JSON.stringify(scripts.map((s) => [s.id, s.type, s.placement, s.value])),
3933
3952
  [scripts]
3934
3953
  );
3954
+ const activeConsented = useRef4(/* @__PURE__ */ new Set());
3935
3955
  useEffect6(() => {
3936
3956
  if (typeof document === "undefined") return;
3957
+ const nextConsented = new Set(scripts.filter((s) => s.consentRequired).map((s) => s.id));
3958
+ const revoked = [...activeConsented.current].some((id) => !nextConsented.has(id));
3959
+ activeConsented.current = nextConsented;
3960
+ if (revoked) {
3961
+ window.location.reload();
3962
+ return;
3963
+ }
3937
3964
  const added = [];
3938
3965
  for (const s of scripts) {
3939
3966
  for (const part of partsFor(s)) {
3940
3967
  const target = part.target === "head" ? document.head : document.body;
3941
- added.push(...injectHtml(target, part.html));
3968
+ added.push(...injectHtml(target, part.html, part.start));
3942
3969
  }
3943
3970
  }
3944
3971
  return () => {
@@ -4200,20 +4227,22 @@ function utmFromSearch(search) {
4200
4227
  // src/react/hooks/use-bundles.ts
4201
4228
  import { useQuery as useQuery36 } from "@tanstack/react-query";
4202
4229
  function useBundles(options) {
4203
- const { client } = useBehio();
4230
+ const { client, sessionRestored } = useBehio();
4231
+ const query = { locale: options?.locale ?? client.getLocale(), currency: options?.currency ?? client.getCurrency(), country: options?.country ?? client.getCountry() };
4204
4232
  return useQuery36({
4205
- queryKey: ["behio", "bundles"],
4206
- queryFn: () => unwrap(client.catalog.getBundles()),
4207
- enabled: options?.enabled ?? true,
4233
+ queryKey: ["behio", "bundles", query],
4234
+ queryFn: () => unwrap(client.catalog.getBundles(query)),
4235
+ enabled: sessionRestored && (options?.enabled ?? true),
4208
4236
  initialData: options?.initialData
4209
4237
  });
4210
4238
  }
4211
4239
  function useBundle(slug, options) {
4212
- const { client } = useBehio();
4240
+ const { client, sessionRestored } = useBehio();
4241
+ const query = { locale: options?.locale ?? client.getLocale(), currency: options?.currency ?? client.getCurrency(), country: options?.country ?? client.getCountry() };
4213
4242
  return useQuery36({
4214
- queryKey: ["behio", "bundle", slug],
4215
- queryFn: () => unwrap(client.catalog.getBundle(slug)),
4216
- enabled: Boolean(slug) && (options?.enabled ?? true),
4243
+ queryKey: ["behio", "bundle", slug, query],
4244
+ queryFn: () => unwrap(client.catalog.getBundle(slug, query)),
4245
+ enabled: sessionRestored && Boolean(slug) && (options?.enabled ?? true),
4217
4246
  initialData: options?.initialData
4218
4247
  });
4219
4248
  }
@@ -4316,8 +4345,10 @@ import { useQuery as useQuery42, useMutation as useMutation18, useQueryClient as
4316
4345
  function useProductReviews(productId, options) {
4317
4346
  const { client } = useBehio();
4318
4347
  return useQuery42({
4319
- queryKey: ["behio", "reviews", productId, options?.page ?? 1],
4348
+ queryKey: ["behio", "reviews", productId, options?.page ?? 1, options?.limit ?? 20],
4320
4349
  queryFn: () => unwrap(client.reviews.getProductReviews(productId, options?.page, options?.limit)),
4350
+ initialData: options?.initialData,
4351
+ staleTime: 3e4,
4321
4352
  enabled: Boolean(productId) && (options?.enabled ?? true)
4322
4353
  });
4323
4354
  }
@@ -4380,15 +4411,23 @@ function useNotifyWhenAvailable() {
4380
4411
  }
4381
4412
 
4382
4413
  // src/react/utils/format-price.ts
4383
- function formatPrice(amount, currency, locale) {
4414
+ function formatPrice(amount, currency, locale, display) {
4384
4415
  const resolvedLocale = locale ?? "cs";
4385
4416
  try {
4386
- return new Intl.NumberFormat(resolvedLocale, {
4417
+ const formatter = new Intl.NumberFormat(resolvedLocale, {
4387
4418
  style: "currency",
4388
4419
  currency,
4420
+ currencyDisplay: display === "CODE" ? "code" : "symbol",
4389
4421
  minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,
4390
4422
  maximumFractionDigits: 2
4391
- }).format(amount);
4423
+ });
4424
+ if (display === "SYMBOL_BEFORE" || display === "SYMBOL_AFTER") {
4425
+ const parts = formatter.formatToParts(amount);
4426
+ const symbol = parts.find((p) => p.type === "currency")?.value ?? currency;
4427
+ const value = parts.filter((p) => p.type !== "currency").map((p) => p.value).join("").trim();
4428
+ return display === "SYMBOL_BEFORE" ? `${symbol}\xA0${value}` : `${value}\xA0${symbol}`;
4429
+ }
4430
+ return formatter.format(amount);
4392
4431
  } catch {
4393
4432
  return `${amount} ${currency}`;
4394
4433
  }
@@ -4521,6 +4560,7 @@ export {
4521
4560
  useCategory,
4522
4561
  useCertificateVerification,
4523
4562
  useCheckout,
4563
+ useCheckoutPreview,
4524
4564
  useCookieConsent,
4525
4565
  useCourse,
4526
4566
  useCourseCertificates,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@behio/storefront-sdk",
3
- "version": "1.19.0",
3
+ "version": "2.0.1",
4
4
  "description": "TypeScript SDK for Behio headless e-commerce: core client + React hooks",
5
5
  "author": "Behio",
6
6
  "license": "MIT",