@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.js CHANGED
@@ -53,6 +53,7 @@ __export(react_exports, {
53
53
  useCategory: () => useCategory,
54
54
  useCertificateVerification: () => useCertificateVerification,
55
55
  useCheckout: () => useCheckout,
56
+ useCheckoutPreview: () => useCheckoutPreview,
56
57
  useCookieConsent: () => useCookieConsent,
57
58
  useCourse: () => useCourse,
58
59
  useCourseCertificates: () => useCourseCertificates,
@@ -259,6 +260,7 @@ var BehioStorefront = class {
259
260
  this.shopDomain = config.shopDomain;
260
261
  this.defaultLocale = config.locale;
261
262
  this.defaultCurrency = config.currency;
263
+ this.setCountry(config.country);
262
264
  this.fetchFn = config.fetch || globalThis.fetch.bind(globalThis);
263
265
  this.timeout = config.timeout ?? 3e4;
264
266
  this.retries = config.retries ?? 1;
@@ -420,6 +422,14 @@ var BehioStorefront = class {
420
422
  getCurrency() {
421
423
  return this.defaultCurrency;
422
424
  }
425
+ /** Set the catalog's delivery country. Use cart.setDestination separately for the cart. */
426
+ setCountry(country) {
427
+ const normalized = country?.trim().toUpperCase();
428
+ this.defaultCountry = normalized && /^[A-Z]{2}$/.test(normalized) ? normalized : void 0;
429
+ }
430
+ getCountry() {
431
+ return this.defaultCountry;
432
+ }
423
433
  /** Set the default locale sent on every catalog request (per-call wins). */
424
434
  setLocale(locale) {
425
435
  this.defaultLocale = locale || void 0;
@@ -575,12 +585,16 @@ var BehioStorefront = class {
575
585
  if (this.defaultCurrency && !params.has("currency")) {
576
586
  params.set("currency", this.defaultCurrency);
577
587
  }
588
+ if (this.defaultCountry && path.startsWith("/catalog/") && !params.has("country")) {
589
+ params.set("country", this.defaultCountry);
590
+ }
578
591
  const qs = params.toString();
579
592
  const url = `${this.baseUrl}/storefront/v1${path}${qs ? `?${qs}` : ""}`;
580
593
  const headers = {
581
594
  "X-Api-Key": this.apiKey,
582
595
  "Content-Type": "application/json"
583
596
  };
597
+ if (path === "/cart" || path.startsWith("/cart/")) headers["X-Behio-Cart-Contract"] = "2";
584
598
  if (this.shopDomain) {
585
599
  headers["X-Shop-Domain"] = this.shopDomain;
586
600
  }
@@ -707,42 +721,46 @@ var BehioStorefront = class {
707
721
  throw new BehioNetworkError("Request failed after retries");
708
722
  }
709
723
  };
724
+ function catalogProductsQuery(query) {
725
+ const q = {};
726
+ if (query) {
727
+ if (query.page !== void 0) q.page = query.page;
728
+ if (query.limit !== void 0) q.limit = query.limit;
729
+ if (query.category) q.category = query.category;
730
+ if (query.label) q.label = query.label;
731
+ if (query.priceMin !== void 0) q.priceMin = query.priceMin;
732
+ if (query.priceMax !== void 0) q.priceMax = query.priceMax;
733
+ if (query.currency) q.currency = query.currency;
734
+ if (query.country) q.country = query.country;
735
+ if (query.locale) q.locale = query.locale;
736
+ if (query.sort) q.sort = query.sort;
737
+ if (query.inStock !== void 0) q.inStock = query.inStock;
738
+ if (query.ratingMin !== void 0) q.ratingMin = query.ratingMin;
739
+ if (query.search) q.search = query.search;
740
+ if (query.parameters) q.parameters = JSON.stringify(query.parameters);
741
+ if (query.facets) q.facets = JSON.stringify(query.facets);
742
+ if (query.ids && query.ids.length > 0) q.ids = query.ids;
743
+ if (query.slugs && query.slugs.length > 0) q.slugs = query.slugs;
744
+ if (query.labels && query.labels.length > 0) q.labels = query.labels;
745
+ if (query.categories && query.categories.length > 0)
746
+ q.categories = query.categories;
747
+ if (query.excludeIds && query.excludeIds.length > 0)
748
+ q.excludeIds = query.excludeIds;
749
+ if (query.excludeCategories && query.excludeCategories.length > 0)
750
+ q.excludeCategories = query.excludeCategories;
751
+ if (query.hasDiscount !== void 0) q.hasDiscount = query.hasDiscount;
752
+ if (query.isFeatured !== void 0) q.isFeatured = query.isFeatured;
753
+ if (query.createdAfter !== void 0) q.createdAfter = query.createdAfter;
754
+ }
755
+ return q;
756
+ }
710
757
  var CatalogModule = class {
711
758
  constructor(client) {
712
759
  this.client = client;
713
760
  }
714
761
  /** List products with filtering, pagination, search */
715
762
  async getProducts(query) {
716
- const q = {};
717
- if (query) {
718
- if (query.page) q.page = query.page;
719
- if (query.limit) q.limit = query.limit;
720
- if (query.category) q.category = query.category;
721
- if (query.label) q.label = query.label;
722
- if (query.priceMin) q.priceMin = query.priceMin;
723
- if (query.priceMax) q.priceMax = query.priceMax;
724
- if (query.currency) q.currency = query.currency;
725
- if (query.country) q.country = query.country;
726
- if (query.locale) q.locale = query.locale;
727
- if (query.sort) q.sort = query.sort;
728
- if (query.inStock !== void 0) q.inStock = query.inStock;
729
- if (query.ratingMin !== void 0) q.ratingMin = query.ratingMin;
730
- if (query.search) q.search = query.search;
731
- if (query.parameters) q.parameters = JSON.stringify(query.parameters);
732
- if (query.facets) q.facets = JSON.stringify(query.facets);
733
- if (query.ids && query.ids.length > 0) q.ids = query.ids;
734
- if (query.slugs && query.slugs.length > 0) q.slugs = query.slugs;
735
- if (query.labels && query.labels.length > 0) q.labels = query.labels;
736
- if (query.categories && query.categories.length > 0)
737
- q.categories = query.categories;
738
- if (query.excludeIds && query.excludeIds.length > 0)
739
- q.excludeIds = query.excludeIds;
740
- if (query.excludeCategories && query.excludeCategories.length > 0)
741
- q.excludeCategories = query.excludeCategories;
742
- if (query.hasDiscount !== void 0) q.hasDiscount = query.hasDiscount;
743
- if (query.isFeatured !== void 0) q.isFeatured = query.isFeatured;
744
- if (query.createdAfter !== void 0) q.createdAfter = query.createdAfter;
745
- }
763
+ const q = catalogProductsQuery(query);
746
764
  return this.client.request(
747
765
  "GET",
748
766
  "/catalog/products",
@@ -784,14 +802,7 @@ var CatalogModule = class {
784
802
  }
785
803
  /** Get products in a category */
786
804
  async getCategoryProducts(slug, query) {
787
- const q = {};
788
- if (query) {
789
- if (query.page) q.page = query.page;
790
- if (query.limit) q.limit = query.limit;
791
- if (query.sort) q.sort = query.sort;
792
- if (query.locale) q.locale = query.locale;
793
- if (query.currency) q.currency = query.currency;
794
- }
805
+ const q = catalogProductsQuery(query);
795
806
  return this.client.request(
796
807
  "GET",
797
808
  `/catalog/categories/${slug}/products`,
@@ -825,7 +836,7 @@ var CatalogModule = class {
825
836
  "GET",
826
837
  "/catalog/featured",
827
838
  {
828
- query: { locale: options?.locale, currency: options?.currency }
839
+ query: { locale: options?.locale, currency: options?.currency, country: options?.country }
829
840
  }
830
841
  );
831
842
  }
@@ -883,9 +894,10 @@ var CatalogModule = class {
883
894
  if (query) {
884
895
  if (query.category) q.category = query.category;
885
896
  if (query.label) q.label = query.label;
886
- if (query.priceMin) q.priceMin = query.priceMin;
887
- if (query.priceMax) q.priceMax = query.priceMax;
897
+ if (query.priceMin !== void 0) q.priceMin = query.priceMin;
898
+ if (query.priceMax !== void 0) q.priceMax = query.priceMax;
888
899
  if (query.currency) q.currency = query.currency;
900
+ if (query.country) q.country = query.country;
889
901
  if (query.locale) q.locale = query.locale;
890
902
  if (query.inStock !== void 0) q.inStock = query.inStock;
891
903
  if (query.ratingMin !== void 0) q.ratingMin = query.ratingMin;
@@ -907,12 +919,12 @@ var CatalogModule = class {
907
919
  return this.getProducts({ search: query, ...options });
908
920
  }
909
921
  /** List all active bundles */
910
- async getBundles() {
911
- return this.client.request("GET", "/catalog/bundles");
922
+ async getBundles(options) {
923
+ return this.client.request("GET", "/catalog/bundles", { query: { ...options } });
912
924
  }
913
925
  /** Get a single bundle by slug */
914
- async getBundle(slug) {
915
- return this.client.request("GET", `/catalog/bundles/${slug}`);
926
+ async getBundle(slug, options) {
927
+ return this.client.request("GET", `/catalog/bundles/${encodeURIComponent(slug)}`, { query: { ...options } });
916
928
  }
917
929
  /**
918
930
  * One product group ("collection") by slug with its products as standard
@@ -925,7 +937,7 @@ var CatalogModule = class {
925
937
  "GET",
926
938
  `/catalog/product-groups/${encodeURIComponent(slug)}`,
927
939
  {
928
- query: { locale: options?.locale, currency: options?.currency }
940
+ query: { locale: options?.locale, currency: options?.currency, country: options?.country }
929
941
  }
930
942
  );
931
943
  }
@@ -940,7 +952,7 @@ var CatalogModule = class {
940
952
  return this.client.request(
941
953
  "GET",
942
954
  `/catalog/products/${encodeURIComponent(productSlug)}/cross-sell`,
943
- { query: { locale: options?.locale, currency: options?.currency } }
955
+ { query: { locale: options?.locale, currency: options?.currency, country: options?.country } }
944
956
  );
945
957
  }
946
958
  /** Active promotions applicable to a product (with countdown end time) */
@@ -1148,6 +1160,7 @@ var CartModule = class {
1148
1160
  body: { currency }
1149
1161
  });
1150
1162
  if (res.error) return res;
1163
+ if (res.data.sessionToken) this.client.setCartSession(res.data.sessionToken);
1151
1164
  this.client.emit("cart:updated", res.data);
1152
1165
  return res;
1153
1166
  }
@@ -1168,6 +1181,7 @@ var CartModule = class {
1168
1181
  body: input
1169
1182
  });
1170
1183
  if (res.error) return res;
1184
+ if (res.data.sessionToken) this.client.setCartSession(res.data.sessionToken);
1171
1185
  this.client.emit("cart:updated", res.data);
1172
1186
  return res;
1173
1187
  }
@@ -1252,14 +1266,15 @@ var CartModule = class {
1252
1266
  body
1253
1267
  });
1254
1268
  if (res.error) return res;
1269
+ if (res.data.sessionToken) this.client.setCartSession(res.data.sessionToken);
1255
1270
  this.client.emit("cart:updated", res.data);
1256
1271
  return res;
1257
1272
  }
1258
- /** Update quantity of a bundle already in the cart */
1259
- async updateBundleQuantity(bundleId, quantity) {
1273
+ /** Update by CartBundleLine.id; legacy catalog bundle IDs are accepted by the API */
1274
+ async updateBundleQuantity(lineId, quantity) {
1260
1275
  const res = await this.client.request(
1261
1276
  "PATCH",
1262
- `/cart/bundles/${bundleId}`,
1277
+ `/cart/bundles/${lineId}`,
1263
1278
  {
1264
1279
  body: { quantity }
1265
1280
  }
@@ -1268,11 +1283,11 @@ var CartModule = class {
1268
1283
  this.client.emit("cart:updated", res.data);
1269
1284
  return res;
1270
1285
  }
1271
- /** Remove a bundle from the cart */
1272
- async removeBundle(bundleId) {
1286
+ /** Remove by CartBundleLine.id; scoped to the current visitor cart */
1287
+ async removeBundle(lineId) {
1273
1288
  const res = await this.client.request(
1274
1289
  "DELETE",
1275
- `/cart/bundles/${bundleId}`
1290
+ `/cart/bundles/${lineId}`
1276
1291
  );
1277
1292
  if (res.error) return res;
1278
1293
  this.client.emit("cart:updated", res.data);
@@ -1306,6 +1321,12 @@ var CheckoutModule = class {
1306
1321
  constructor(client) {
1307
1322
  this.client = client;
1308
1323
  }
1324
+ /** Recalculate the current cart including shipping, fees, benefits and rounding.
1325
+ * No order, stock claim, payment or gift-card redemption is created. Required
1326
+ * legal consents are enforced only on createOrder, not while quoting. */
1327
+ async preview(input) {
1328
+ return this.client.request("POST", "/checkout/preview", { body: input });
1329
+ }
1309
1330
  /** Create order from cart */
1310
1331
  async createOrder(input) {
1311
1332
  const res = await this.client.request("POST", "/checkout", {
@@ -2200,6 +2221,7 @@ function BehioProvider({
2200
2221
  });
2201
2222
  }
2202
2223
  const client = clientRef.current;
2224
+ const [sessionRestored, setSessionRestored] = (0, import_react2.useState)(false);
2203
2225
  (0, import_react2.useEffect)(() => {
2204
2226
  const accessToken = storageAdapter.get(STORAGE_KEYS.ACCESS_TOKEN);
2205
2227
  const refreshToken = storageAdapter.get(STORAGE_KEYS.REFRESH_TOKEN);
@@ -2210,6 +2232,7 @@ function BehioProvider({
2210
2232
  if (cartSession) {
2211
2233
  client.setCartSession(cartSession);
2212
2234
  }
2235
+ setSessionRestored(true);
2213
2236
  }, [client, storageAdapter]);
2214
2237
  (0, import_react2.useEffect)(() => {
2215
2238
  if (!persistCurrency) return;
@@ -2264,12 +2287,13 @@ function BehioProvider({
2264
2287
  () => ({
2265
2288
  client,
2266
2289
  storage: storageAdapter,
2290
+ sessionRestored,
2267
2291
  currency: activeCurrency,
2268
2292
  setCurrency,
2269
2293
  configuredDefaultCurrency: defaultCurrency,
2270
2294
  configuredCurrencies: currencies
2271
2295
  }),
2272
- [client, storageAdapter, activeCurrency, setCurrency, defaultCurrency, currencies]
2296
+ [client, storageAdapter, sessionRestored, activeCurrency, setCurrency, defaultCurrency, currencies]
2273
2297
  );
2274
2298
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_query.QueryClientProvider, { client: qc, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BehioContext.Provider, { value: ctxValue, children }) });
2275
2299
  }
@@ -2519,6 +2543,15 @@ function useSearch(query, options) {
2519
2543
  // src/react/hooks/use-cart.ts
2520
2544
  var import_react5 = require("react");
2521
2545
  var import_react_query12 = require("@tanstack/react-query");
2546
+
2547
+ // src/react/utils/refresh-bundle-queries.ts
2548
+ async function refreshBundleQueries(queryClient) {
2549
+ const keys = [["behio", "bundle"], ["behio", "bundles"]];
2550
+ await Promise.all(keys.map((queryKey) => queryClient.cancelQueries({ queryKey })));
2551
+ await Promise.all(keys.map((queryKey) => queryClient.invalidateQueries({ queryKey })));
2552
+ }
2553
+
2554
+ // src/react/hooks/use-cart.ts
2522
2555
  var CART_KEY = ["behio", "cart"];
2523
2556
  function useCart(options) {
2524
2557
  const { client, storage } = useBehio();
@@ -2533,41 +2566,30 @@ function useCart(options) {
2533
2566
  // Only fetch if we have a cart session or are logged in
2534
2567
  enabled: options?.enabled !== false && (!!client.getCartSession() || !!client.getAccessToken())
2535
2568
  });
2569
+ const cancelCartRead = () => queryClient.cancelQueries({ queryKey: [...CART_KEY] });
2570
+ const acceptCart = async (result) => {
2571
+ await cancelCartRead();
2572
+ queryClient.setQueryData([...CART_KEY], result);
2573
+ void refreshBundleQueries(queryClient);
2574
+ };
2536
2575
  const addMutation = (0, import_react_query12.useMutation)({
2537
2576
  mutationFn: async ({ productId, quantity }) => {
2538
2577
  return unwrap(client.cart.addItem({ productId, quantity: quantity ?? 1 }));
2539
2578
  },
2540
- onSuccess: (result) => {
2579
+ onMutate: cancelCartRead,
2580
+ onSuccess: async (result) => {
2541
2581
  if (result.newSessionToken) {
2542
2582
  storage.set(STORAGE_KEYS.CART_SESSION, result.newSessionToken);
2543
2583
  }
2544
- queryClient.setQueryData([...CART_KEY], result);
2584
+ await acceptCart(result);
2545
2585
  }
2546
2586
  });
2547
2587
  const updateMutation = (0, import_react_query12.useMutation)({
2548
2588
  mutationFn: async ({ itemId, quantity }) => {
2549
2589
  return unwrap(client.cart.updateQuantity(itemId, quantity));
2550
2590
  },
2551
- onMutate: async ({ itemId, quantity }) => {
2552
- await queryClient.cancelQueries({ queryKey: [...CART_KEY] });
2553
- const previous = queryClient.getQueryData([...CART_KEY]);
2554
- if (previous) {
2555
- const updatedItems = previous.items.map(
2556
- (item) => item.id === itemId ? { ...item, quantity, totalPrice: item.unitPrice * quantity } : item
2557
- );
2558
- queryClient.setQueryData([...CART_KEY], {
2559
- ...previous,
2560
- items: updatedItems,
2561
- itemCount: updatedItems.reduce((sum, i) => sum + i.quantity, 0)
2562
- });
2563
- }
2564
- return { previous };
2565
- },
2566
- onError: (_err, _vars, context) => {
2567
- if (context?.previous) {
2568
- queryClient.setQueryData([...CART_KEY], context.previous);
2569
- }
2570
- },
2591
+ onMutate: cancelCartRead,
2592
+ onSuccess: acceptCart,
2571
2593
  onSettled: () => {
2572
2594
  queryClient.invalidateQueries({ queryKey: [...CART_KEY] });
2573
2595
  }
@@ -2576,54 +2598,42 @@ function useCart(options) {
2576
2598
  mutationFn: async (itemId) => {
2577
2599
  return unwrap(client.cart.removeItem(itemId));
2578
2600
  },
2579
- onMutate: async (itemId) => {
2580
- await queryClient.cancelQueries({ queryKey: [...CART_KEY] });
2581
- const previous = queryClient.getQueryData([...CART_KEY]);
2582
- if (previous) {
2583
- const updatedItems = previous.items.filter((item) => item.id !== itemId);
2584
- queryClient.setQueryData([...CART_KEY], {
2585
- ...previous,
2586
- items: updatedItems,
2587
- itemCount: updatedItems.reduce((sum, i) => sum + i.quantity, 0)
2588
- });
2589
- }
2590
- return { previous };
2591
- },
2592
- onError: (_err, _vars, context) => {
2593
- if (context?.previous) {
2594
- queryClient.setQueryData([...CART_KEY], context.previous);
2595
- }
2596
- },
2601
+ onMutate: cancelCartRead,
2602
+ onSuccess: acceptCart,
2597
2603
  onSettled: () => {
2598
2604
  queryClient.invalidateQueries({ queryKey: [...CART_KEY] });
2599
2605
  }
2600
2606
  });
2601
2607
  const clearMutation = (0, import_react_query12.useMutation)({
2602
2608
  mutationFn: () => unwrap(client.cart.clear()),
2603
- onSuccess: () => {
2609
+ onMutate: cancelCartRead,
2610
+ onSuccess: async () => {
2611
+ await cancelCartRead();
2604
2612
  queryClient.setQueryData([...CART_KEY], null);
2605
2613
  client.clearCartSession();
2606
2614
  storage.remove(STORAGE_KEYS.CART_SESSION);
2615
+ void refreshBundleQueries(queryClient);
2607
2616
  }
2608
2617
  });
2609
2618
  const applyDiscountMutation = (0, import_react_query12.useMutation)({
2610
2619
  mutationFn: (code) => unwrap(client.cart.applyDiscount(code)),
2611
- onSuccess: (result) => {
2612
- queryClient.setQueryData([...CART_KEY], result);
2613
- }
2620
+ onMutate: cancelCartRead,
2621
+ onSuccess: acceptCart
2614
2622
  });
2615
2623
  const removeDiscountMutation = (0, import_react_query12.useMutation)({
2616
2624
  mutationFn: () => unwrap(client.cart.removeDiscount()),
2617
- onSuccess: (result) => {
2618
- queryClient.setQueryData([...CART_KEY], result);
2619
- }
2625
+ onMutate: cancelCartRead,
2626
+ onSuccess: acceptCart
2620
2627
  });
2621
2628
  const mergeMutation = (0, import_react_query12.useMutation)({
2622
2629
  mutationFn: () => unwrap(client.cart.merge()),
2623
- onSuccess: (result) => {
2630
+ onMutate: cancelCartRead,
2631
+ onSuccess: async (result) => {
2632
+ await cancelCartRead();
2624
2633
  queryClient.setQueryData([...CART_KEY], result);
2625
2634
  client.clearCartSession();
2626
2635
  storage.remove(STORAGE_KEYS.CART_SESSION);
2636
+ void refreshBundleQueries(queryClient);
2627
2637
  }
2628
2638
  });
2629
2639
  const addItem = (0, import_react5.useCallback)(
@@ -2668,7 +2678,7 @@ function useCart(options) {
2668
2678
  merge,
2669
2679
  // Computed
2670
2680
  itemCount: cart?.itemCount ?? 0,
2671
- isEmpty: !cart || cart.items.length === 0,
2681
+ isEmpty: !cart || cart.items.length === 0 && (cart.bundleLines?.length ?? 0) === 0,
2672
2682
  // Mutation states
2673
2683
  isAdding: addMutation.isPending,
2674
2684
  isUpdating: updateMutation.isPending,
@@ -2724,6 +2734,7 @@ function useAuth() {
2724
2734
  queryClient.setQueryData([...CUSTOMER_KEY], null);
2725
2735
  queryClient.invalidateQueries({ queryKey: ["behio", "orders"] });
2726
2736
  queryClient.invalidateQueries({ queryKey: ["behio", "addresses"] });
2737
+ void refreshBundleQueries(queryClient);
2727
2738
  }, [client, storage, queryClient]);
2728
2739
  const postAuth = (0, import_react6.useCallback)(async () => {
2729
2740
  await queryClient.invalidateQueries({ queryKey: [...CUSTOMER_KEY] });
@@ -2736,6 +2747,7 @@ function useAuth() {
2736
2747
  }
2737
2748
  }
2738
2749
  queryClient.invalidateQueries({ queryKey: [...CART_KEY3] });
2750
+ void refreshBundleQueries(queryClient);
2739
2751
  }, [client, storage, queryClient]);
2740
2752
  const loginMutation = (0, import_react_query14.useMutation)({
2741
2753
  mutationFn: async ({ email, password }) => {
@@ -3699,6 +3711,11 @@ function useCheckout() {
3699
3711
  reset
3700
3712
  };
3701
3713
  }
3714
+ function useCheckoutPreview() {
3715
+ const { client } = useBehio();
3716
+ const mutation = (0, import_react_query34.useMutation)({ mutationFn: (input) => unwrap(client.checkout.preview(input)) });
3717
+ return { preview: mutation.mutateAsync, data: mutation.data, isPreviewing: mutation.isPending, error: mutation.error, reset: mutation.reset };
3718
+ }
3702
3719
 
3703
3720
  // src/react/hooks/use-pages.ts
3704
3721
  var import_react_query35 = require("@tanstack/react-query");
@@ -3863,7 +3880,8 @@ function useShopScripts(options) {
3863
3880
  return (0, import_react_query39.useQuery)({
3864
3881
  queryKey: ["behio", "shop-scripts"],
3865
3882
  queryFn: () => unwrap(client.getShopScripts()),
3866
- enabled: options?.enabled !== false
3883
+ enabled: options?.enabled !== false,
3884
+ initialData: options?.initialData
3867
3885
  });
3868
3886
  }
3869
3887
 
@@ -3997,31 +4015,32 @@ function partsFor(s) {
3997
4015
  return [{ target: "head", html: `<meta name="google-site-verification" content="${id}" />` }];
3998
4016
  case "RAW":
3999
4017
  default:
4000
- return [{ target: s.placement === "HEAD" ? "head" : "body", html: s.value }];
4018
+ return [{ target: s.placement === "HEAD" ? "head" : "body", start: s.placement === "BODY_START", html: s.value }];
4001
4019
  }
4002
4020
  }
4003
- function injectHtml(target, html) {
4021
+ function injectHtml(target, html, start = false) {
4004
4022
  const tpl = document.createElement("template");
4005
4023
  tpl.innerHTML = html;
4006
4024
  const added = [];
4025
+ const anchor = start ? target.firstChild : null;
4007
4026
  Array.from(tpl.content.childNodes).forEach((node) => {
4008
4027
  if (node.nodeName === "SCRIPT") {
4009
4028
  const old = node;
4010
4029
  const script = document.createElement("script");
4011
4030
  for (const attr of Array.from(old.attributes)) script.setAttribute(attr.name, attr.value);
4012
4031
  script.text = old.text;
4013
- target.appendChild(script);
4032
+ target.insertBefore(script, anchor);
4014
4033
  added.push(script);
4015
4034
  } else if (node.nodeType === Node.ELEMENT_NODE) {
4016
4035
  const clone = node.cloneNode(true);
4017
- target.appendChild(clone);
4036
+ target.insertBefore(clone, anchor);
4018
4037
  added.push(clone);
4019
4038
  }
4020
4039
  });
4021
4040
  return added;
4022
4041
  }
4023
- function StorefrontScripts({ visitorId: visitorIdProp } = {}) {
4024
- const { data } = useShopScripts();
4042
+ function StorefrontScripts({ visitorId: visitorIdProp, initialScripts } = {}) {
4043
+ const { data } = useShopScripts({ enabled: initialScripts === void 0, initialData: initialScripts ? { scripts: initialScripts } : void 0 });
4025
4044
  const [visitorId, setVisitorId] = (0, import_react18.useState)(visitorIdProp ?? "");
4026
4045
  (0, import_react18.useEffect)(() => {
4027
4046
  if (visitorIdProp) return;
@@ -4033,21 +4052,30 @@ function StorefrontScripts({ visitorId: visitorIdProp } = {}) {
4033
4052
  }, [visitorIdProp]);
4034
4053
  const { data: consent } = useCookieConsent(visitorId || void 0);
4035
4054
  const analyticsOk = Boolean(consent?.analytics);
4055
+ const marketingOk = Boolean(consent?.marketing);
4036
4056
  const scripts = (0, import_react18.useMemo)(
4037
- () => (data?.scripts ?? []).filter((s) => !s.consentRequired || analyticsOk),
4038
- [data, analyticsOk]
4057
+ () => (initialScripts ?? data?.scripts ?? []).filter((s) => !s.consentRequired || (s.type === "META_PIXEL" ? marketingOk : analyticsOk)),
4058
+ [initialScripts, data, analyticsOk, marketingOk]
4039
4059
  );
4040
4060
  const signature = (0, import_react18.useMemo)(
4041
4061
  () => JSON.stringify(scripts.map((s) => [s.id, s.type, s.placement, s.value])),
4042
4062
  [scripts]
4043
4063
  );
4064
+ const activeConsented = (0, import_react18.useRef)(/* @__PURE__ */ new Set());
4044
4065
  (0, import_react18.useEffect)(() => {
4045
4066
  if (typeof document === "undefined") return;
4067
+ const nextConsented = new Set(scripts.filter((s) => s.consentRequired).map((s) => s.id));
4068
+ const revoked = [...activeConsented.current].some((id) => !nextConsented.has(id));
4069
+ activeConsented.current = nextConsented;
4070
+ if (revoked) {
4071
+ window.location.reload();
4072
+ return;
4073
+ }
4046
4074
  const added = [];
4047
4075
  for (const s of scripts) {
4048
4076
  for (const part of partsFor(s)) {
4049
4077
  const target = part.target === "head" ? document.head : document.body;
4050
- added.push(...injectHtml(target, part.html));
4078
+ added.push(...injectHtml(target, part.html, part.start));
4051
4079
  }
4052
4080
  }
4053
4081
  return () => {
@@ -4309,20 +4337,22 @@ function utmFromSearch(search) {
4309
4337
  // src/react/hooks/use-bundles.ts
4310
4338
  var import_react_query42 = require("@tanstack/react-query");
4311
4339
  function useBundles(options) {
4312
- const { client } = useBehio();
4340
+ const { client, sessionRestored } = useBehio();
4341
+ const query = { locale: options?.locale ?? client.getLocale(), currency: options?.currency ?? client.getCurrency(), country: options?.country ?? client.getCountry() };
4313
4342
  return (0, import_react_query42.useQuery)({
4314
- queryKey: ["behio", "bundles"],
4315
- queryFn: () => unwrap(client.catalog.getBundles()),
4316
- enabled: options?.enabled ?? true,
4343
+ queryKey: ["behio", "bundles", query],
4344
+ queryFn: () => unwrap(client.catalog.getBundles(query)),
4345
+ enabled: sessionRestored && (options?.enabled ?? true),
4317
4346
  initialData: options?.initialData
4318
4347
  });
4319
4348
  }
4320
4349
  function useBundle(slug, options) {
4321
- const { client } = useBehio();
4350
+ const { client, sessionRestored } = useBehio();
4351
+ const query = { locale: options?.locale ?? client.getLocale(), currency: options?.currency ?? client.getCurrency(), country: options?.country ?? client.getCountry() };
4322
4352
  return (0, import_react_query42.useQuery)({
4323
- queryKey: ["behio", "bundle", slug],
4324
- queryFn: () => unwrap(client.catalog.getBundle(slug)),
4325
- enabled: Boolean(slug) && (options?.enabled ?? true),
4353
+ queryKey: ["behio", "bundle", slug, query],
4354
+ queryFn: () => unwrap(client.catalog.getBundle(slug, query)),
4355
+ enabled: sessionRestored && Boolean(slug) && (options?.enabled ?? true),
4326
4356
  initialData: options?.initialData
4327
4357
  });
4328
4358
  }
@@ -4425,8 +4455,10 @@ var import_react_query48 = require("@tanstack/react-query");
4425
4455
  function useProductReviews(productId, options) {
4426
4456
  const { client } = useBehio();
4427
4457
  return (0, import_react_query48.useQuery)({
4428
- queryKey: ["behio", "reviews", productId, options?.page ?? 1],
4458
+ queryKey: ["behio", "reviews", productId, options?.page ?? 1, options?.limit ?? 20],
4429
4459
  queryFn: () => unwrap(client.reviews.getProductReviews(productId, options?.page, options?.limit)),
4460
+ initialData: options?.initialData,
4461
+ staleTime: 3e4,
4430
4462
  enabled: Boolean(productId) && (options?.enabled ?? true)
4431
4463
  });
4432
4464
  }
@@ -4489,15 +4521,23 @@ function useNotifyWhenAvailable() {
4489
4521
  }
4490
4522
 
4491
4523
  // src/react/utils/format-price.ts
4492
- function formatPrice(amount, currency, locale) {
4524
+ function formatPrice(amount, currency, locale, display) {
4493
4525
  const resolvedLocale = locale ?? "cs";
4494
4526
  try {
4495
- return new Intl.NumberFormat(resolvedLocale, {
4527
+ const formatter = new Intl.NumberFormat(resolvedLocale, {
4496
4528
  style: "currency",
4497
4529
  currency,
4530
+ currencyDisplay: display === "CODE" ? "code" : "symbol",
4498
4531
  minimumFractionDigits: Number.isInteger(amount) ? 0 : 2,
4499
4532
  maximumFractionDigits: 2
4500
- }).format(amount);
4533
+ });
4534
+ if (display === "SYMBOL_BEFORE" || display === "SYMBOL_AFTER") {
4535
+ const parts = formatter.formatToParts(amount);
4536
+ const symbol = parts.find((p) => p.type === "currency")?.value ?? currency;
4537
+ const value = parts.filter((p) => p.type !== "currency").map((p) => p.value).join("").trim();
4538
+ return display === "SYMBOL_BEFORE" ? `${symbol}\xA0${value}` : `${value}\xA0${symbol}`;
4539
+ }
4540
+ return formatter.format(amount);
4501
4541
  } catch {
4502
4542
  return `${amount} ${currency}`;
4503
4543
  }
@@ -4631,6 +4671,7 @@ async function revokeAnalyticsConsent(client) {
4631
4671
  useCategory,
4632
4672
  useCertificateVerification,
4633
4673
  useCheckout,
4674
+ useCheckoutPreview,
4634
4675
  useCookieConsent,
4635
4676
  useCourse,
4636
4677
  useCourseCertificates,