@behio/storefront-sdk 2.8.0 → 2.10.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.d.ts CHANGED
@@ -228,6 +228,8 @@ interface CheckoutSettings {
228
228
  type PriceDisplay = "INCL_VAT" | "EXCL_VAT" | "CUSTOMER_CHOICE";
229
229
  type CurrencyDisplayFormat = "SYMBOL_BEFORE" | "SYMBOL_AFTER" | "CODE";
230
230
  interface ShopInfo {
231
+ /** Merchant default catalog order, normalized to the public sort values. */
232
+ defaultProductSort?: ProductSortValue;
231
233
  currencyDisplayFormat?: CurrencyDisplayFormat;
232
234
  account?: {
233
235
  passwordMinLength: number;
@@ -561,6 +563,8 @@ interface ProductLabel {
561
563
  slug: string;
562
564
  name: string;
563
565
  color?: string;
566
+ /** Merchant icon name. Render a trusted local icon, with a safe fallback. */
567
+ iconName?: string | null;
564
568
  }
565
569
  /**
566
570
  * Resolved availability status of a product. Either a merchant preset
@@ -1128,6 +1132,7 @@ interface FacetLabel {
1128
1132
  slug: string;
1129
1133
  name: string;
1130
1134
  color?: string | null;
1135
+ iconName?: string | null;
1131
1136
  count: number;
1132
1137
  }
1133
1138
  interface FacetCategory {
@@ -2542,6 +2547,7 @@ interface PickupPointsInput {
2542
2547
  }
2543
2548
  /** Public terms of the eshop's loyalty program. */
2544
2549
  interface LoyaltyProgram {
2550
+ enrollmentType?: "AUTO_ON_REGISTER" | "OPT_IN" | "INVITE_ONLY";
2545
2551
  name: string;
2546
2552
  pointName: string;
2547
2553
  pointNamePlural: string;
@@ -2556,6 +2562,8 @@ interface LoyaltyProgram {
2556
2562
  }
2557
2563
  interface LoyaltyBalance {
2558
2564
  currentPoints: number;
2565
+ /** Revoked points to offset before new rewards become spendable. Absent on older servers. */
2566
+ pointsDebt?: number;
2559
2567
  /** `currentPoints` valued in the program currency. */
2560
2568
  currentPointsValue: number;
2561
2569
  lifetimePointsEarned: number;
@@ -2587,10 +2595,13 @@ interface LoyaltyNextTier {
2587
2595
  progressPercent: number;
2588
2596
  }
2589
2597
  interface LoyaltyTransaction {
2598
+ /** Stable identifier on servers supporting cursor history. */
2599
+ id?: string;
2590
2600
  type: string;
2591
2601
  /** Positive = earned, negative = redeemed / expired. */
2592
2602
  points: number;
2593
2603
  balanceAfter: number;
2604
+ debtAfter?: number;
2594
2605
  eventType: string | null;
2595
2606
  description: string | null;
2596
2607
  createdAt: number;
@@ -2602,6 +2613,7 @@ interface LoyaltyTransaction {
2602
2613
  * "join and earn" CTA).
2603
2614
  */
2604
2615
  interface LoyaltySummary {
2616
+ canJoin?: boolean;
2605
2617
  hasProgram: boolean;
2606
2618
  enrolled: boolean;
2607
2619
  program: LoyaltyProgram | null;
@@ -2610,6 +2622,18 @@ interface LoyaltySummary {
2610
2622
  nextTier: LoyaltyNextTier | null;
2611
2623
  referralCode: string | null;
2612
2624
  transactions: LoyaltyTransaction[];
2625
+ /** Follow using getLoyalty({transactionsCursor}); null is the last page. */
2626
+ transactionsNextCursor?: string | null;
2627
+ canApplyReferral?: boolean;
2628
+ }
2629
+ interface LoyaltyReferralResult {
2630
+ alreadyApplied: boolean;
2631
+ recipientPoints: number;
2632
+ }
2633
+ interface LoyaltyQuery {
2634
+ transactionsCursor?: string;
2635
+ /** 1 to 50, default 20. */
2636
+ transactionsLimit?: number;
2613
2637
  }
2614
2638
  interface CrossSellItem {
2615
2639
  productId: string;
@@ -2938,6 +2962,55 @@ interface CartLinkClaimed extends Cart {
2938
2962
  /** Code carried by the link, including a refused code. Null means no code was offered. */
2939
2963
  linkDiscountCode: string | null;
2940
2964
  }
2965
+ /** Earned merchant badges for the current authenticated customer. */
2966
+ interface CustomerBadge {
2967
+ id: string;
2968
+ name: string;
2969
+ slug: string;
2970
+ description: string | null;
2971
+ iconUrl: string | null;
2972
+ type: "MILESTONE" | "CHALLENGE" | "STREAK" | "ACHIEVEMENT" | "CUSTOM";
2973
+ earnedAt: number;
2974
+ }
2975
+ interface CustomerChallenge {
2976
+ id: string;
2977
+ name: string;
2978
+ slug: string;
2979
+ description: string | null;
2980
+ targetType: string;
2981
+ targetValue: number;
2982
+ /** TOTAL_SPEND uses the shop's default currency and stored reference FX rates. */
2983
+ currency: string;
2984
+ startsAt: number;
2985
+ endsAt: number;
2986
+ rewardPoints: number | null;
2987
+ rewardBadgeName: string | null;
2988
+ active: boolean;
2989
+ joined: boolean;
2990
+ currentProgress: number;
2991
+ completedAt: number | null;
2992
+ canJoin: boolean;
2993
+ rewardMembershipRequired: boolean;
2994
+ }
2995
+ interface CustomerGamificationQuery {
2996
+ badgesPage?: number;
2997
+ challengesPage?: number;
2998
+ /** 1..50, default 20. */
2999
+ limit?: number;
3000
+ }
3001
+ interface CustomerGamification {
3002
+ badges: CustomerBadge[];
3003
+ badgesTotal: number;
3004
+ badgesPage: number;
3005
+ challenges: CustomerChallenge[];
3006
+ challengesTotal: number;
3007
+ challengesPage: number;
3008
+ limit: number;
3009
+ }
3010
+ interface GamificationRefreshResult {
3011
+ awarded: number;
3012
+ completed: number;
3013
+ }
2941
3014
 
2942
3015
  declare class BehioStorefront {
2943
3016
  private baseUrl;
@@ -3561,7 +3634,17 @@ declare class CustomerModule {
3561
3634
  * value, current tier, next-tier progress, point ratio, referral code,
3562
3635
  * recent transactions). Requires an authenticated customer session.
3563
3636
  */
3564
- getLoyalty(): Promise<SdkResult<LoyaltySummary>>;
3637
+ getLoyalty(query?: LoyaltyQuery): Promise<SdkResult<LoyaltySummary>>;
3638
+ /** Apply an invitation to the caller's active loyalty membership, once. */
3639
+ applyReferral(code: string): Promise<SdkResult<LoyaltyReferralResult>>;
3640
+ /** Explicit enrollment; invite-only programs require the shop administrator. */
3641
+ joinLoyalty(): Promise<SdkResult<LoyaltySummary>>;
3642
+ /** Authenticated account rewards. Both collections have independent pages. */
3643
+ getGamification(query?: CustomerGamificationQuery): Promise<SdkResult<CustomerGamification>>;
3644
+ /** Join an active challenge; a repeated call preserves the original joinedAt. */
3645
+ joinChallenge(challengeId: string): Promise<SdkResult<GamificationRefreshResult>>;
3646
+ /** Reconcile persisted qualifying activity. Never supplies progress or points. */
3647
+ refreshGamification(): Promise<SdkResult<GamificationRefreshResult>>;
3565
3648
  /**
3566
3649
  * Digital product delivery (GAP-07): list the logged-in customer's download
3567
3650
  * grants across all their orders (file name, product, remaining downloads,
@@ -4070,6 +4153,13 @@ declare function useCart(options?: UseCartOptions): {
4070
4153
  updateQuantity: (itemId: string, quantity: number) => Promise<Cart>;
4071
4154
  removeItem: (itemId: string) => Promise<Cart>;
4072
4155
  clear: () => Promise<void>;
4156
+ addBundle: (bundle: string | {
4157
+ id: string;
4158
+ } | {
4159
+ slug: string;
4160
+ }, quantity?: number) => Promise<Cart>;
4161
+ updateBundleQuantity: (lineId: string, quantity: number) => Promise<Cart>;
4162
+ removeBundle: (lineId: string) => Promise<Cart>;
4073
4163
  applyDiscount: (code: string) => Promise<Cart>;
4074
4164
  removeDiscount: () => Promise<Cart>;
4075
4165
  merge: () => Promise<Cart>;
@@ -4078,6 +4168,7 @@ declare function useCart(options?: UseCartOptions): {
4078
4168
  isAdding: boolean;
4079
4169
  isUpdating: boolean;
4080
4170
  isRemoving: boolean;
4171
+ isUpdatingBundle: boolean;
4081
4172
  };
4082
4173
 
4083
4174
  interface UseCartCountOptions {
package/dist/react.js CHANGED
@@ -1655,8 +1655,28 @@ var CustomerModule = class {
1655
1655
  * value, current tier, next-tier progress, point ratio, referral code,
1656
1656
  * recent transactions). Requires an authenticated customer session.
1657
1657
  */
1658
- async getLoyalty() {
1659
- return this.client.request("GET", "/customer/loyalty");
1658
+ async getLoyalty(query = {}) {
1659
+ return this.client.request("GET", "/customer/loyalty", { query: { ...query } });
1660
+ }
1661
+ /** Apply an invitation to the caller's active loyalty membership, once. */
1662
+ async applyReferral(code) {
1663
+ return this.client.request("POST", "/customer/loyalty/referral", { body: { code } });
1664
+ }
1665
+ /** Explicit enrollment; invite-only programs require the shop administrator. */
1666
+ async joinLoyalty() {
1667
+ return this.client.request("POST", "/customer/loyalty/join", { body: { consent: true } });
1668
+ }
1669
+ /** Authenticated account rewards. Both collections have independent pages. */
1670
+ async getGamification(query = {}) {
1671
+ return this.client.request("GET", "/customer/gamification", { query: { ...query } });
1672
+ }
1673
+ /** Join an active challenge; a repeated call preserves the original joinedAt. */
1674
+ async joinChallenge(challengeId) {
1675
+ return this.client.request("POST", `/customer/gamification/challenges/${encodeURIComponent(challengeId)}/join`);
1676
+ }
1677
+ /** Reconcile persisted qualifying activity. Never supplies progress or points. */
1678
+ async refreshGamification() {
1679
+ return this.client.request("POST", "/customer/gamification/refresh");
1660
1680
  }
1661
1681
  /**
1662
1682
  * Digital product delivery (GAP-07): list the logged-in customer's download
@@ -2779,6 +2799,16 @@ function useCart(options) {
2779
2799
  queryClient.setQueryData([...CART_KEY], result);
2780
2800
  void refreshBundleQueries(queryClient);
2781
2801
  };
2802
+ (0, import_react5.useEffect)(
2803
+ () => client.on("cart:updated", (data) => {
2804
+ if (!data || typeof data !== "object" || !("items" in data)) return;
2805
+ const session = client.getCartSession();
2806
+ if (session) storage.set(STORAGE_KEYS.CART_SESSION, session);
2807
+ void acceptCart(data);
2808
+ }),
2809
+ // acceptCart only closes over queryClient, which is stable per provider.
2810
+ [client, queryClient, storage]
2811
+ );
2782
2812
  const addMutation = (0, import_react_query12.useMutation)({
2783
2813
  mutationFn: async ({ productId, quantity }) => {
2784
2814
  return unwrap(client.cart.addItem({ productId, quantity: quantity ?? 1 }));
@@ -2811,6 +2841,26 @@ function useCart(options) {
2811
2841
  queryClient.invalidateQueries({ queryKey: [...CART_KEY] });
2812
2842
  }
2813
2843
  });
2844
+ const acceptBundleCart = async (result) => {
2845
+ const session = result.newSessionToken ?? result.sessionToken;
2846
+ if (session) storage.set(STORAGE_KEYS.CART_SESSION, session);
2847
+ await acceptCart(result);
2848
+ };
2849
+ const addBundleMutation = (0, import_react_query12.useMutation)({
2850
+ mutationFn: async ({ bundle, quantity }) => unwrap(client.cart.addBundle(bundle, quantity ?? 1)),
2851
+ onMutate: cancelCartRead,
2852
+ onSuccess: acceptBundleCart
2853
+ });
2854
+ const updateBundleMutation = (0, import_react_query12.useMutation)({
2855
+ mutationFn: async ({ lineId, quantity }) => unwrap(client.cart.updateBundleQuantity(lineId, quantity)),
2856
+ onMutate: cancelCartRead,
2857
+ onSuccess: acceptBundleCart
2858
+ });
2859
+ const removeBundleMutation = (0, import_react_query12.useMutation)({
2860
+ mutationFn: async (lineId) => unwrap(client.cart.removeBundle(lineId)),
2861
+ onMutate: cancelCartRead,
2862
+ onSuccess: acceptBundleCart
2863
+ });
2814
2864
  const clearMutation = (0, import_react_query12.useMutation)({
2815
2865
  mutationFn: () => unwrap(client.cart.clear()),
2816
2866
  onMutate: cancelCartRead,
@@ -2859,6 +2909,18 @@ function useCart(options) {
2859
2909
  () => clearMutation.mutateAsync(),
2860
2910
  [clearMutation]
2861
2911
  );
2912
+ const addBundle = (0, import_react5.useCallback)(
2913
+ (bundle, quantity) => addBundleMutation.mutateAsync({ bundle, quantity }),
2914
+ [addBundleMutation]
2915
+ );
2916
+ const updateBundleQuantity = (0, import_react5.useCallback)(
2917
+ (lineId, quantity) => updateBundleMutation.mutateAsync({ lineId, quantity }),
2918
+ [updateBundleMutation]
2919
+ );
2920
+ const removeBundle = (0, import_react5.useCallback)(
2921
+ (lineId) => removeBundleMutation.mutateAsync(lineId),
2922
+ [removeBundleMutation]
2923
+ );
2862
2924
  const applyDiscount = (0, import_react5.useCallback)(
2863
2925
  (code) => applyDiscountMutation.mutateAsync(code),
2864
2926
  [applyDiscountMutation]
@@ -2880,6 +2942,9 @@ function useCart(options) {
2880
2942
  updateQuantity,
2881
2943
  removeItem,
2882
2944
  clear,
2945
+ addBundle,
2946
+ updateBundleQuantity,
2947
+ removeBundle,
2883
2948
  applyDiscount,
2884
2949
  removeDiscount,
2885
2950
  merge,
@@ -2889,7 +2954,8 @@ function useCart(options) {
2889
2954
  // Mutation states
2890
2955
  isAdding: addMutation.isPending,
2891
2956
  isUpdating: updateMutation.isPending,
2892
- isRemoving: removeMutation.isPending
2957
+ isRemoving: removeMutation.isPending,
2958
+ isUpdatingBundle: addBundleMutation.isPending || updateBundleMutation.isPending || removeBundleMutation.isPending
2893
2959
  };
2894
2960
  }
2895
2961
 
@@ -4019,16 +4085,19 @@ function errorParams(err) {
4019
4085
  const params = body?.params;
4020
4086
  return params && typeof params === "object" && !Array.isArray(params) ? params : {};
4021
4087
  }
4022
- var FORM_FIELD_ERROR_CODES = /* @__PURE__ */ new Set([
4023
- "required",
4024
- "invalid",
4025
- "tooShort",
4026
- "tooLong",
4027
- "min",
4028
- "max",
4029
- "notOption",
4030
- "pattern"
4031
- ]);
4088
+ var FORM_FIELD_ERROR_CODES = {
4089
+ required: true,
4090
+ invalid: true,
4091
+ tooShort: true,
4092
+ tooLong: true,
4093
+ min: true,
4094
+ max: true,
4095
+ notOption: true,
4096
+ pattern: true,
4097
+ dateNotFuture: true,
4098
+ dateNotPast: true,
4099
+ tooManyFiles: true
4100
+ };
4032
4101
  function formFieldErrors(err) {
4033
4102
  if (errorCode(err) !== "be.forms.validationFailed") return [];
4034
4103
  const raw = errorParams(err).errors;
@@ -4040,7 +4109,7 @@ function formFieldErrors(err) {
4040
4109
  if (idx <= 0) continue;
4041
4110
  const key = entry.slice(0, idx);
4042
4111
  const code = entry.slice(idx + 1);
4043
- if (!FORM_FIELD_ERROR_CODES.has(code)) continue;
4112
+ if (!Object.prototype.hasOwnProperty.call(FORM_FIELD_ERROR_CODES, code)) continue;
4044
4113
  out.push({ key, code });
4045
4114
  }
4046
4115
  return out;
package/dist/react.mjs CHANGED
@@ -1545,8 +1545,28 @@ var CustomerModule = class {
1545
1545
  * value, current tier, next-tier progress, point ratio, referral code,
1546
1546
  * recent transactions). Requires an authenticated customer session.
1547
1547
  */
1548
- async getLoyalty() {
1549
- return this.client.request("GET", "/customer/loyalty");
1548
+ async getLoyalty(query = {}) {
1549
+ return this.client.request("GET", "/customer/loyalty", { query: { ...query } });
1550
+ }
1551
+ /** Apply an invitation to the caller's active loyalty membership, once. */
1552
+ async applyReferral(code) {
1553
+ return this.client.request("POST", "/customer/loyalty/referral", { body: { code } });
1554
+ }
1555
+ /** Explicit enrollment; invite-only programs require the shop administrator. */
1556
+ async joinLoyalty() {
1557
+ return this.client.request("POST", "/customer/loyalty/join", { body: { consent: true } });
1558
+ }
1559
+ /** Authenticated account rewards. Both collections have independent pages. */
1560
+ async getGamification(query = {}) {
1561
+ return this.client.request("GET", "/customer/gamification", { query: { ...query } });
1562
+ }
1563
+ /** Join an active challenge; a repeated call preserves the original joinedAt. */
1564
+ async joinChallenge(challengeId) {
1565
+ return this.client.request("POST", `/customer/gamification/challenges/${encodeURIComponent(challengeId)}/join`);
1566
+ }
1567
+ /** Reconcile persisted qualifying activity. Never supplies progress or points. */
1568
+ async refreshGamification() {
1569
+ return this.client.request("POST", "/customer/gamification/refresh");
1550
1570
  }
1551
1571
  /**
1552
1572
  * Digital product delivery (GAP-07): list the logged-in customer's download
@@ -2638,7 +2658,7 @@ function useSearch(query, options) {
2638
2658
  }
2639
2659
 
2640
2660
  // src/react/hooks/use-cart.ts
2641
- import { useCallback as useCallback3 } from "react";
2661
+ import { useCallback as useCallback3, useEffect as useEffect3 } from "react";
2642
2662
  import { useQuery as useQuery10, useMutation, useQueryClient } from "@tanstack/react-query";
2643
2663
 
2644
2664
  // src/react/utils/refresh-bundle-queries.ts
@@ -2669,6 +2689,16 @@ function useCart(options) {
2669
2689
  queryClient.setQueryData([...CART_KEY], result);
2670
2690
  void refreshBundleQueries(queryClient);
2671
2691
  };
2692
+ useEffect3(
2693
+ () => client.on("cart:updated", (data) => {
2694
+ if (!data || typeof data !== "object" || !("items" in data)) return;
2695
+ const session = client.getCartSession();
2696
+ if (session) storage.set(STORAGE_KEYS.CART_SESSION, session);
2697
+ void acceptCart(data);
2698
+ }),
2699
+ // acceptCart only closes over queryClient, which is stable per provider.
2700
+ [client, queryClient, storage]
2701
+ );
2672
2702
  const addMutation = useMutation({
2673
2703
  mutationFn: async ({ productId, quantity }) => {
2674
2704
  return unwrap(client.cart.addItem({ productId, quantity: quantity ?? 1 }));
@@ -2701,6 +2731,26 @@ function useCart(options) {
2701
2731
  queryClient.invalidateQueries({ queryKey: [...CART_KEY] });
2702
2732
  }
2703
2733
  });
2734
+ const acceptBundleCart = async (result) => {
2735
+ const session = result.newSessionToken ?? result.sessionToken;
2736
+ if (session) storage.set(STORAGE_KEYS.CART_SESSION, session);
2737
+ await acceptCart(result);
2738
+ };
2739
+ const addBundleMutation = useMutation({
2740
+ mutationFn: async ({ bundle, quantity }) => unwrap(client.cart.addBundle(bundle, quantity ?? 1)),
2741
+ onMutate: cancelCartRead,
2742
+ onSuccess: acceptBundleCart
2743
+ });
2744
+ const updateBundleMutation = useMutation({
2745
+ mutationFn: async ({ lineId, quantity }) => unwrap(client.cart.updateBundleQuantity(lineId, quantity)),
2746
+ onMutate: cancelCartRead,
2747
+ onSuccess: acceptBundleCart
2748
+ });
2749
+ const removeBundleMutation = useMutation({
2750
+ mutationFn: async (lineId) => unwrap(client.cart.removeBundle(lineId)),
2751
+ onMutate: cancelCartRead,
2752
+ onSuccess: acceptBundleCart
2753
+ });
2704
2754
  const clearMutation = useMutation({
2705
2755
  mutationFn: () => unwrap(client.cart.clear()),
2706
2756
  onMutate: cancelCartRead,
@@ -2749,6 +2799,18 @@ function useCart(options) {
2749
2799
  () => clearMutation.mutateAsync(),
2750
2800
  [clearMutation]
2751
2801
  );
2802
+ const addBundle = useCallback3(
2803
+ (bundle, quantity) => addBundleMutation.mutateAsync({ bundle, quantity }),
2804
+ [addBundleMutation]
2805
+ );
2806
+ const updateBundleQuantity = useCallback3(
2807
+ (lineId, quantity) => updateBundleMutation.mutateAsync({ lineId, quantity }),
2808
+ [updateBundleMutation]
2809
+ );
2810
+ const removeBundle = useCallback3(
2811
+ (lineId) => removeBundleMutation.mutateAsync(lineId),
2812
+ [removeBundleMutation]
2813
+ );
2752
2814
  const applyDiscount = useCallback3(
2753
2815
  (code) => applyDiscountMutation.mutateAsync(code),
2754
2816
  [applyDiscountMutation]
@@ -2770,6 +2832,9 @@ function useCart(options) {
2770
2832
  updateQuantity,
2771
2833
  removeItem,
2772
2834
  clear,
2835
+ addBundle,
2836
+ updateBundleQuantity,
2837
+ removeBundle,
2773
2838
  applyDiscount,
2774
2839
  removeDiscount,
2775
2840
  merge,
@@ -2779,7 +2844,8 @@ function useCart(options) {
2779
2844
  // Mutation states
2780
2845
  isAdding: addMutation.isPending,
2781
2846
  isUpdating: updateMutation.isPending,
2782
- isRemoving: removeMutation.isPending
2847
+ isRemoving: removeMutation.isPending,
2848
+ isUpdatingBundle: addBundleMutation.isPending || updateBundleMutation.isPending || removeBundleMutation.isPending
2783
2849
  };
2784
2850
  }
2785
2851
 
@@ -3044,7 +3110,7 @@ function useAddresses(options) {
3044
3110
  }
3045
3111
 
3046
3112
  // src/react/hooks/use-address-autocomplete.ts
3047
- import { useState as useState4, useEffect as useEffect3, useRef as useRef2, useCallback as useCallback7 } from "react";
3113
+ import { useState as useState4, useEffect as useEffect4, useRef as useRef2, useCallback as useCallback7 } from "react";
3048
3114
  import { useQuery as useQuery15 } from "@tanstack/react-query";
3049
3115
  var AC_KEY = ["behio", "address-autocomplete"];
3050
3116
  function useAddressAutocomplete(options) {
@@ -3055,7 +3121,7 @@ function useAddressAutocomplete(options) {
3055
3121
  const [selected, setSelected] = useState4(null);
3056
3122
  const [isSelecting, setIsSelecting] = useState4(false);
3057
3123
  const timerRef = useRef2(void 0);
3058
- useEffect3(() => {
3124
+ useEffect4(() => {
3059
3125
  if (timerRef.current) clearTimeout(timerRef.current);
3060
3126
  if (query.length < minChars) {
3061
3127
  setDebouncedQuery("");
@@ -3483,14 +3549,14 @@ function usePaymentMethods(options) {
3483
3549
  }
3484
3550
 
3485
3551
  // src/react/hooks/use-personal-offers.ts
3486
- import { useCallback as useCallback8, useEffect as useEffect4, useState as useState5 } from "react";
3552
+ import { useCallback as useCallback8, useEffect as useEffect5, useState as useState5 } from "react";
3487
3553
  import { useMutation as useMutation10, useQuery as useQuery26, useQueryClient as useQueryClient12 } from "@tanstack/react-query";
3488
3554
  function usePersonalOffers(options) {
3489
3555
  const { client } = useBehio();
3490
3556
  const queryClient = useQueryClient12();
3491
3557
  const [polledVid, setPolledVid] = useState5(null);
3492
3558
  const explicitVid = options?.visitorId;
3493
- useEffect4(() => {
3559
+ useEffect5(() => {
3494
3560
  if (explicitVid) return;
3495
3561
  const read = () => {
3496
3562
  const vid = client.getAnalyticsVisitorId();
@@ -3542,7 +3608,7 @@ function usePersonalOffers(options) {
3542
3608
  }
3543
3609
 
3544
3610
  // src/react/hooks/use-visitor-messages.ts
3545
- import { useCallback as useCallback9, useEffect as useEffect5, useRef as useRef3, useState as useState6 } from "react";
3611
+ import { useCallback as useCallback9, useEffect as useEffect6, useRef as useRef3, useState as useState6 } from "react";
3546
3612
  import { useQuery as useQuery27, useQueryClient as useQueryClient13 } from "@tanstack/react-query";
3547
3613
  function useVisitorMessages(options) {
3548
3614
  const { client } = useBehio();
@@ -3550,7 +3616,7 @@ function useVisitorMessages(options) {
3550
3616
  const [polledVid, setPolledVid] = useState6(null);
3551
3617
  const seenRef = useRef3(/* @__PURE__ */ new Set());
3552
3618
  const explicitVid = options?.visitorId;
3553
- useEffect5(() => {
3619
+ useEffect6(() => {
3554
3620
  if (explicitVid) return;
3555
3621
  const read = () => {
3556
3622
  const vid = client.getAnalyticsVisitorId();
@@ -3587,7 +3653,7 @@ function useVisitorMessages(options) {
3587
3653
  );
3588
3654
  const onMessage = options?.onMessage;
3589
3655
  const dispatchDom = options?.dispatchDomEvents !== false;
3590
- useEffect5(() => {
3656
+ useEffect6(() => {
3591
3657
  for (const message of data?.items ?? []) {
3592
3658
  if (seenRef.current.has(message.id)) continue;
3593
3659
  seenRef.current.add(message.id);
@@ -3909,16 +3975,19 @@ function errorParams(err) {
3909
3975
  const params = body?.params;
3910
3976
  return params && typeof params === "object" && !Array.isArray(params) ? params : {};
3911
3977
  }
3912
- var FORM_FIELD_ERROR_CODES = /* @__PURE__ */ new Set([
3913
- "required",
3914
- "invalid",
3915
- "tooShort",
3916
- "tooLong",
3917
- "min",
3918
- "max",
3919
- "notOption",
3920
- "pattern"
3921
- ]);
3978
+ var FORM_FIELD_ERROR_CODES = {
3979
+ required: true,
3980
+ invalid: true,
3981
+ tooShort: true,
3982
+ tooLong: true,
3983
+ min: true,
3984
+ max: true,
3985
+ notOption: true,
3986
+ pattern: true,
3987
+ dateNotFuture: true,
3988
+ dateNotPast: true,
3989
+ tooManyFiles: true
3990
+ };
3922
3991
  function formFieldErrors(err) {
3923
3992
  if (errorCode(err) !== "be.forms.validationFailed") return [];
3924
3993
  const raw = errorParams(err).errors;
@@ -3930,7 +3999,7 @@ function formFieldErrors(err) {
3930
3999
  if (idx <= 0) continue;
3931
4000
  const key = entry.slice(0, idx);
3932
4001
  const code = entry.slice(idx + 1);
3933
- if (!FORM_FIELD_ERROR_CODES.has(code)) continue;
4002
+ if (!Object.prototype.hasOwnProperty.call(FORM_FIELD_ERROR_CODES, code)) continue;
3934
4003
  out.push({ key, code });
3935
4004
  }
3936
4005
  return out;
@@ -4070,7 +4139,7 @@ function CurrencySwitcher({
4070
4139
  }
4071
4140
 
4072
4141
  // src/react/components/storefront-scripts.tsx
4073
- import { useEffect as useEffect6, useMemo as useMemo5, useRef as useRef4, useState as useState10 } from "react";
4142
+ import { useEffect as useEffect7, useMemo as useMemo5, useRef as useRef4, useState as useState10 } from "react";
4074
4143
 
4075
4144
  // src/react/hooks/use-consent.ts
4076
4145
  import { useQuery as useQuery35, useMutation as useMutation16, useQueryClient as useQueryClient16 } from "@tanstack/react-query";
@@ -4162,7 +4231,7 @@ function injectHtml(target, html, start = false) {
4162
4231
  function StorefrontScripts({ visitorId: visitorIdProp, initialScripts } = {}) {
4163
4232
  const { data } = useShopScripts({ enabled: initialScripts === void 0, initialData: initialScripts ? { scripts: initialScripts } : void 0 });
4164
4233
  const [visitorId, setVisitorId] = useState10(visitorIdProp ?? "");
4165
- useEffect6(() => {
4234
+ useEffect7(() => {
4166
4235
  if (visitorIdProp) return;
4167
4236
  try {
4168
4237
  const v = localStorage.getItem(VISITOR_KEY);
@@ -4182,7 +4251,7 @@ function StorefrontScripts({ visitorId: visitorIdProp, initialScripts } = {}) {
4182
4251
  [scripts]
4183
4252
  );
4184
4253
  const activeConsented = useRef4(/* @__PURE__ */ new Set());
4185
- useEffect6(() => {
4254
+ useEffect7(() => {
4186
4255
  if (typeof document === "undefined") return;
4187
4256
  const nextConsented = new Set(scripts.filter((s) => s.consentRequired).map((s) => s.id));
4188
4257
  const revoked = [...activeConsented.current].some((id) => !nextConsented.has(id));
@@ -4206,7 +4275,7 @@ function StorefrontScripts({ visitorId: visitorIdProp, initialScripts } = {}) {
4206
4275
  }
4207
4276
 
4208
4277
  // src/react/components/behio-analytics.tsx
4209
- import { useEffect as useEffect7 } from "react";
4278
+ import { useEffect as useEffect8 } from "react";
4210
4279
 
4211
4280
  // src/react/hooks/use-behio-client.ts
4212
4281
  function useBehioClient() {
@@ -4216,7 +4285,7 @@ function useBehioClient() {
4216
4285
  // src/react/components/behio-analytics.tsx
4217
4286
  function BehioAnalyticsTracker() {
4218
4287
  const client = useBehioClient();
4219
- useEffect7(() => {
4288
+ useEffect8(() => {
4220
4289
  if (typeof window === "undefined") return;
4221
4290
  const w = window;
4222
4291
  if (w.__behioAnalytics) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@behio/storefront-sdk",
3
- "version": "2.8.0",
3
+ "version": "2.10.0",
4
4
  "description": "TypeScript SDK for Behio headless e-commerce: core client + React hooks",
5
5
  "author": "Behio",
6
6
  "license": "MIT",