@ecomiq/storefront 1.1.4 → 1.1.5

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.
Files changed (36) hide show
  1. package/dist/{chunk-6TMU4OXP.js → chunk-3QGB55R6.js} +3 -3
  2. package/dist/{chunk-6TMU4OXP.js.map → chunk-3QGB55R6.js.map} +1 -1
  3. package/dist/{chunk-EADDSNJS.js → chunk-64GLOWMJ.js} +14 -4
  4. package/dist/chunk-64GLOWMJ.js.map +1 -0
  5. package/dist/chunk-Q4U5OEUT.js +21 -0
  6. package/dist/chunk-Q4U5OEUT.js.map +1 -0
  7. package/dist/{chunk-3URTJY6X.js → chunk-QRR7EPCH.js} +14 -12
  8. package/dist/chunk-QRR7EPCH.js.map +1 -0
  9. package/dist/{chunk-YSD62KQM.js → chunk-ZG2XUYGZ.js} +23 -21
  10. package/dist/chunk-ZG2XUYGZ.js.map +1 -0
  11. package/dist/chunks/client.css +1 -1
  12. package/dist/chunks/client.css.map +1 -1
  13. package/dist/chunks/client.d.ts +535 -25
  14. package/dist/chunks/client.js +517 -193
  15. package/dist/chunks/client.js.map +1 -1
  16. package/dist/chunks/config.js +3 -2
  17. package/dist/chunks/core-public.d.ts +2 -2
  18. package/dist/chunks/core-public.js +4 -10
  19. package/dist/chunks/core-public.js.map +1 -1
  20. package/dist/chunks/server-nextjs.d.ts +3 -3
  21. package/dist/chunks/server-nextjs.js +27 -13
  22. package/dist/chunks/server-nextjs.js.map +1 -1
  23. package/dist/chunks/server-tanstack.d.ts +3 -3
  24. package/dist/chunks/server-tanstack.js +6 -5
  25. package/dist/chunks/server-tanstack.js.map +1 -1
  26. package/dist/{collection-BHD4hhQa.d.ts → collection-C6bWDLiw.d.ts} +318 -1
  27. package/dist/{ecomiq-oauth-password-plugin-gw2dPyYb.d.ts → ecomiq-oauth-password-plugin-J5wDFqzL.d.ts} +2 -2
  28. package/dist/nextjs/api.js +2 -1
  29. package/dist/nextjs/api.js.map +1 -1
  30. package/dist/react/client.css +1 -1
  31. package/dist/tanstack/api.js +3 -2
  32. package/dist/tanstack/api.js.map +1 -1
  33. package/package.json +2 -2
  34. package/dist/chunk-3URTJY6X.js.map +0 -1
  35. package/dist/chunk-EADDSNJS.js.map +0 -1
  36. package/dist/chunk-YSD62KQM.js.map +0 -1
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { z } from 'zod';
3
- import { QueryClient, hydrate, QueryClientProvider, useMutation, useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
3
+ import { QueryClient, hydrate, QueryClientProvider, useQueryClient, useQuery, useMutation, useInfiniteQuery } from '@tanstack/react-query';
4
4
  import React2, { createContext, forwardRef, useRef, useState, useCallback, useMemo, useImperativeHandle, useEffect, useContext } from 'react';
5
- import { jsx, jsxs } from 'react/jsx-runtime';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  import { toast } from 'sonner';
7
7
  import { create } from 'zustand';
8
8
  import { persist } from 'zustand/middleware';
@@ -18,6 +18,22 @@ function isCartNotActiveApiError(error) {
18
18
  return keys.includes("Carts.NotActive");
19
19
  }
20
20
 
21
+ // src/core/constants/currency.ts
22
+ var DEFAULT_COUNTRY_CODE = "PE";
23
+ var DEFAULT_CURRENCY_CODE = "PEN";
24
+ var STOREFRONT_HEADERS = {
25
+ storeId: "X-Store-Id",
26
+ storeDomain: "X-Store-Domain",
27
+ identityScope: "x-Identity-Scope",
28
+ tenantId: "x-Tenant-Id",
29
+ storeIdLower: "x-store-id"};
30
+ var countryCurrency = {
31
+ "PEN": "es-PE",
32
+ "USD": "en-US",
33
+ "COP": "es-CO",
34
+ "MXN": "es-MX"
35
+ };
36
+
21
37
  // src/core/client/store-api-client.ts
22
38
  var STORE_API_PATH = "/api/store";
23
39
  var serverRequestOriginGetter = null;
@@ -65,11 +81,11 @@ var StoreApiClient = class {
65
81
  headers.set("Accept-Encoding", "identity");
66
82
  const explicitStoreId = options?.xStoreId?.trim();
67
83
  const runtimeStoreId = explicitStoreId || (typeof window === "undefined" ? serverStoreIdGetter : clientStoreIdGetter);
68
- if (runtimeStoreId && !headers.has("x-store-id")) {
69
- headers.set("X-Store-Id", runtimeStoreId);
84
+ if (runtimeStoreId && !headers.has(STOREFRONT_HEADERS.storeIdLower)) {
85
+ headers.set(STOREFRONT_HEADERS.storeId, runtimeStoreId);
70
86
  }
71
87
  if (options?.xStoreDomain) {
72
- headers.set("X-Store-Domain", options.xStoreDomain);
88
+ headers.set(STOREFRONT_HEADERS.storeDomain, options.xStoreDomain);
73
89
  }
74
90
  if (options?.xCartId) {
75
91
  headers.set("X-Cart-Token", options.xCartId);
@@ -352,14 +368,15 @@ async function getStorefrontProducts(params, cacheKey) {
352
368
  const path = `products?${searchParams.toString()}`;
353
369
  const client = await getStoreApiClient();
354
370
  const response = await client.get(path, {
355
- cacheKey
371
+ cacheKey,
372
+ noStore: true
356
373
  });
357
374
  return StorefrontProductsResponseSchema.parse(response);
358
375
  }
359
376
  async function getStorefrontProductBySlug(slug) {
360
377
  const path = `products/${encodeURIComponent(slug)}`;
361
378
  const client = await getStoreApiClient();
362
- const response = await client.get(path);
379
+ const response = await client.get(path, { noStore: true });
363
380
  return StorefrontProductDetailSchema.parse(response);
364
381
  }
365
382
  var StorefrontCollectionSchema = z.object({
@@ -455,7 +472,7 @@ var CartAddressSchema = z.object({
455
472
  name: z.string().min(1),
456
473
  address1: z.string().min(1),
457
474
  city: z.string().min(1),
458
- countryCode: z.string().min(2),
475
+ countryCode: z.string().min(2).max(2),
459
476
  company: z.string().nullable().optional(),
460
477
  address2: z.string().nullable().optional(),
461
478
  province: z.string().nullable().optional(),
@@ -500,7 +517,7 @@ z.object({
500
517
  name: z.string().nullable().optional(),
501
518
  address1: z.string(),
502
519
  city: z.string(),
503
- countryCode: z.string(),
520
+ countryCode: z.string().min(2).max(2),
504
521
  company: z.string().nullable().optional(),
505
522
  address2: z.string().nullable().optional(),
506
523
  province: z.string().nullable().optional(),
@@ -914,7 +931,7 @@ function customerMeToProfilePatch(me) {
914
931
  companyName: me.companyName,
915
932
  dateOfBirth: me.dateOfBirth,
916
933
  consent: me.consent,
917
- countryIsoCode: me.countryIsoCode || "PE"
934
+ countryIsoCode: me.countryIsoCode || DEFAULT_COUNTRY_CODE
918
935
  };
919
936
  }
920
937
  function customerAuthOptions(accessToken) {
@@ -977,7 +994,7 @@ var CustomerOrderShippingAddressSchema = z.object({
977
994
  address1: z.string(),
978
995
  address2: z.string().nullable().optional(),
979
996
  city: z.string().nullable().optional(),
980
- countryCode: z.string(),
997
+ countryCode: z.string().min(2).max(2),
981
998
  province: z.string().nullable().optional(),
982
999
  postalCode: z.string().nullable().optional(),
983
1000
  district: z.string().nullable().optional(),
@@ -1142,7 +1159,7 @@ var OrderDraftSchema = z.object({
1142
1159
  customerId: z.coerce.string().nullable().optional(),
1143
1160
  cartId: z.string().nullable().optional(),
1144
1161
  draftNumber: z.string(),
1145
- currencyCode: z.string().optional().default("PEN"),
1162
+ currencyCode: z.string().optional().default(DEFAULT_CURRENCY_CODE),
1146
1163
  source: z.string().nullable().optional(),
1147
1164
  notes: z.string().nullable().optional(),
1148
1165
  email: z.string().nullable().optional(),
@@ -1290,7 +1307,8 @@ function useStoreProducts() {
1290
1307
  const useStorefrontProductsList = (params) => {
1291
1308
  return useQuery({
1292
1309
  queryKey: ["storefront", "products", params],
1293
- queryFn: () => getStorefrontProducts(params)
1310
+ queryFn: () => getStorefrontProducts(params),
1311
+ staleTime: 0
1294
1312
  });
1295
1313
  };
1296
1314
  const useStorefrontInfiniteProducts = (params) => {
@@ -1298,14 +1316,16 @@ function useStoreProducts() {
1298
1316
  queryKey: ["storefront", "products", "infinite", params],
1299
1317
  queryFn: ({ pageParam = 1 }) => getStorefrontProducts({ ...params, page: pageParam }),
1300
1318
  getNextPageParam: (lastPage) => lastPage.pageIndex < lastPage.totalPages ? lastPage.pageIndex + 1 : void 0,
1301
- initialPageParam: 1
1319
+ initialPageParam: 1,
1320
+ staleTime: 0
1302
1321
  });
1303
1322
  };
1304
1323
  const useProductBySlug = (slug) => {
1305
1324
  return useQuery({
1306
1325
  queryKey: ["storefront", "product", "slug", slug],
1307
1326
  queryFn: () => getStorefrontProductBySlug(slug),
1308
- enabled: !!slug
1327
+ enabled: !!slug,
1328
+ staleTime: 0
1309
1329
  });
1310
1330
  };
1311
1331
  const useRefreshStorefrontProducts = () => {
@@ -1757,8 +1777,17 @@ var StorefrontCmsCustomScriptsSchema = z.object({
1757
1777
  head: z.string().nullable().optional(),
1758
1778
  bodyEnd: z.string().nullable().optional()
1759
1779
  });
1780
+ var StorefrontCmsChannelsSchema = z.object({
1781
+ storefront: z.object({
1782
+ enabled: z.boolean().optional()
1783
+ }).optional(),
1784
+ pos: z.object({
1785
+ enabled: z.boolean().optional()
1786
+ }).optional()
1787
+ });
1760
1788
  var StorefrontCmsSettingsSchema = z.object({
1761
1789
  themeKey: z.string().optional(),
1790
+ channels: StorefrontCmsChannelsSchema.optional(),
1762
1791
  branding: StorefrontCmsBrandingSchema.optional(),
1763
1792
  seo: StorefrontCmsSeoSchema.optional(),
1764
1793
  contact: StorefrontCmsContactSchema.optional(),
@@ -1770,6 +1799,7 @@ var StorefrontCmsPageSchema = z.object({
1770
1799
  id: z.string(),
1771
1800
  name: z.string(),
1772
1801
  slug: z.string(),
1802
+ channel: z.string().transform((value) => value.toLowerCase()).pipe(z.enum(["storefront", "pos"])).default("storefront"),
1773
1803
  layout: z.string().optional(),
1774
1804
  version: z.number().optional(),
1775
1805
  settings: z.record(z.string(), z.unknown()).optional().nullable(),
@@ -1821,7 +1851,7 @@ function normalizeStorefrontSettingsPayload(v) {
1821
1851
  const parsed = StorefrontSettingsLegacyFlatSchema.safeParse(raw);
1822
1852
  if (!parsed.success) return body;
1823
1853
  const l = parsed.data;
1824
- const currency = l.currency?.trim() ? l.currency : "PEN";
1854
+ const currency = l.currency?.trim() ? l.currency : DEFAULT_CURRENCY_CODE;
1825
1855
  const language = l.language?.trim() ? l.language : "es";
1826
1856
  const slug = l.slug?.trim() ? l.slug : l.id;
1827
1857
  return {
@@ -1916,10 +1946,10 @@ function buildEcomiqOAuthIdentityHeaders(settings) {
1916
1946
  const checkout = record.checkoutSettings;
1917
1947
  const identityScope = checkout?.identityScope === "tenant" ? "tenant" : "store";
1918
1948
  const headers = {
1919
- "x-Identity-Scope": identityScope,
1920
- "x-Store-Id": record.id
1949
+ [STOREFRONT_HEADERS.identityScope]: identityScope,
1950
+ [STOREFRONT_HEADERS.storeId]: record.id
1921
1951
  };
1922
- if (record.tenantId) headers["x-Tenant-Id"] = record.tenantId;
1952
+ if (record.tenantId) headers[STOREFRONT_HEADERS.tenantId] = record.tenantId;
1923
1953
  return headers;
1924
1954
  }
1925
1955
 
@@ -1935,6 +1965,175 @@ async function signInWithEcomiqCredentials(authClient, settings, credentials) {
1935
1965
  }
1936
1966
  return response;
1937
1967
  }
1968
+
1969
+ // src/react/pages/checkout-page/lib/cart-mappers.ts
1970
+ function mapApiItemsToCartItems(items) {
1971
+ if (!items?.length) return [];
1972
+ return items.map((raw) => {
1973
+ const item = raw;
1974
+ const price = Number(item.unitPrice);
1975
+ const quantity = Number(item.quantity);
1976
+ const variantId = String(item.variantId ?? "");
1977
+ if (!Number.isFinite(price) || !Number.isFinite(quantity) || !variantId) return null;
1978
+ return {
1979
+ id: item.id,
1980
+ title: item.title,
1981
+ image: String(item.thumbnail ?? ""),
1982
+ size: String(item.variantLabel ?? "\u2014"),
1983
+ price,
1984
+ quantity,
1985
+ variantId,
1986
+ variantSku: item.variantSku ?? void 0,
1987
+ variantLabel: item.variantLabel ?? void 0
1988
+ };
1989
+ }).filter(Boolean);
1990
+ }
1991
+ function shippingValuesFromCartAddress(cartData) {
1992
+ const addr = cartData.shippingAddress;
1993
+ if (!addr) return void 0;
1994
+ const geo = addr.geo;
1995
+ return {
1996
+ address: addr.address1,
1997
+ zipCode: addr.postalCode ?? "",
1998
+ countryCode: addr.countryCode ?? DEFAULT_COUNTRY_CODE,
1999
+ departmentCode: geo?.departmentCode ?? "",
2000
+ departmentName: "",
2001
+ provinceCode: geo?.provinceCode ?? "",
2002
+ provinceName: addr.province ?? "",
2003
+ districtCode: geo?.districtCode ?? "",
2004
+ districtName: addr.district ?? addr.city ?? ""
2005
+ };
2006
+ }
2007
+
2008
+ // src/react/pages/checkout-page/lib/cart-sync.ts
2009
+ var checkoutReadQueryKey = (cartId) => ["storefront", "cart", cartId, "checkout"];
2010
+ function cartItemsSignature(items) {
2011
+ return items.map((i) => `${i.variantId ?? i.id}:${i.quantity}`).sort().join("|");
2012
+ }
2013
+ function apiCartItemsFromResponse(cart) {
2014
+ return mapApiItemsToCartItems(cart?.items);
2015
+ }
2016
+ function groupCartQuantitiesByVariant(items) {
2017
+ const map = /* @__PURE__ */ new Map();
2018
+ for (const item of items) {
2019
+ const variantId = item.variantId ?? item.id;
2020
+ map.set(variantId, (map.get(variantId) ?? 0) + Math.max(0, item.quantity));
2021
+ }
2022
+ return map;
2023
+ }
2024
+ function groupApiLinesByVariant(items) {
2025
+ const map = /* @__PURE__ */ new Map();
2026
+ for (const item of items) {
2027
+ const variantId = item.variantId ?? item.id;
2028
+ const lines = map.get(variantId) ?? [];
2029
+ lines.push(item);
2030
+ map.set(variantId, lines);
2031
+ }
2032
+ return map;
2033
+ }
2034
+ function findCartReconciliationActions(local, api) {
2035
+ const actions = [];
2036
+ const localQty = groupCartQuantitiesByVariant(local);
2037
+ const apiLinesByVariant = groupApiLinesByVariant(api);
2038
+ const variantIds = /* @__PURE__ */ new Set([
2039
+ ...localQty.keys(),
2040
+ ...apiLinesByVariant.keys()
2041
+ ]);
2042
+ for (const variantId of variantIds) {
2043
+ const targetQty = localQty.get(variantId) ?? 0;
2044
+ const apiLines = apiLinesByVariant.get(variantId) ?? [];
2045
+ const apiQty = apiLines.reduce((sum, line) => sum + line.quantity, 0);
2046
+ if (targetQty > apiQty) {
2047
+ actions.push({
2048
+ action: "add",
2049
+ variantId,
2050
+ quantity: targetQty - apiQty
2051
+ });
2052
+ continue;
2053
+ }
2054
+ if (targetQty >= apiQty) continue;
2055
+ let excess = apiQty - targetQty;
2056
+ for (const line of [...apiLines].reverse()) {
2057
+ if (excess <= 0) break;
2058
+ if (line.quantity <= excess) {
2059
+ actions.push({ action: "remove", itemId: line.id });
2060
+ excess -= line.quantity;
2061
+ } else {
2062
+ actions.push({
2063
+ action: "update",
2064
+ itemId: line.id,
2065
+ quantity: line.quantity - excess
2066
+ });
2067
+ excess = 0;
2068
+ }
2069
+ }
2070
+ }
2071
+ return actions;
2072
+ }
2073
+ function areCartSignaturesEqual(a, b) {
2074
+ return cartItemsSignature(a) === cartItemsSignature(b);
2075
+ }
2076
+ function cartSessionSignature(cartTokenId, items) {
2077
+ return `${cartTokenId ?? ""}|${cartItemsSignature(items)}`;
2078
+ }
2079
+ function readCartCheckoutStatus(cart) {
2080
+ if (!cart) return null;
2081
+ const status = cart.status ?? null;
2082
+ return typeof status === "string" && status.trim() ? status.trim() : null;
2083
+ }
2084
+ function isCheckoutCartAlreadyPurchased(cart, localCartItemCount) {
2085
+ if (localCartItemCount > 0) return false;
2086
+ const status = readCartCheckoutStatus(cart);
2087
+ if (!status) return false;
2088
+ return status.toLowerCase() === "inprogress";
2089
+ }
2090
+
2091
+ // src/react/lib/reconcile-local-cart-to-api.ts
2092
+ async function reconcileLocalCartWithApi(cartId, localCart, options) {
2093
+ let cart = await getCart(cartId);
2094
+ if (localCart.length === 0) return cart;
2095
+ let apiItems = apiCartItemsFromResponse(cart);
2096
+ if (areCartSignaturesEqual(localCart, apiItems)) return cart;
2097
+ const actions = findCartReconciliationActions(localCart, apiItems);
2098
+ const reconciliationErrors = [];
2099
+ for (const action of actions) {
2100
+ if (action.action !== "remove" && action.action !== "update") continue;
2101
+ try {
2102
+ cart = action.action === "remove" ? await deleteCartItem(cartId, action.itemId) : await updateCartItem(cartId, action.itemId, {
2103
+ quantity: action.quantity
2104
+ });
2105
+ apiItems = apiCartItemsFromResponse(cart);
2106
+ } catch (error) {
2107
+ {
2108
+ reconciliationErrors.push(error);
2109
+ }
2110
+ }
2111
+ }
2112
+ for (const action of actions) {
2113
+ if (action.action !== "add") continue;
2114
+ try {
2115
+ cart = await addCartItem(
2116
+ { variantId: action.variantId, quantity: action.quantity },
2117
+ cartId
2118
+ );
2119
+ apiItems = apiCartItemsFromResponse(cart);
2120
+ } catch (error) {
2121
+ {
2122
+ reconciliationErrors.push(error);
2123
+ }
2124
+ }
2125
+ }
2126
+ if (reconciliationErrors.length > 0) {
2127
+ const first = reconciliationErrors[0];
2128
+ throw first instanceof Error ? first : new Error("No se pudo sincronizar el carrito con el servidor.");
2129
+ }
2130
+ return cart;
2131
+ }
2132
+ function isCartNotFoundError(error) {
2133
+ return error.status === 404;
2134
+ }
2135
+
2136
+ // src/react/hooks/use-proceed-to-checkout.ts
1938
2137
  function buildCheckoutPath(cartId, pathPrefix = "/checkout") {
1939
2138
  const base = pathPrefix.replace(/\/$/, "");
1940
2139
  return `${base}/${encodeURIComponent(cartId.trim())}`;
@@ -1945,10 +2144,11 @@ function useProceedToCheckout(options = {}) {
1945
2144
  currencyCode: currencyCodeOption,
1946
2145
  salesChannelId = "web",
1947
2146
  guestEmail: guestEmailOption,
2147
+ syncLocalCart = true,
1948
2148
  navigate: navigateOption,
1949
2149
  onError
1950
2150
  } = options;
1951
- const storeCurrency = useStore((s) => s.data?.store?.currency ?? "PEN");
2151
+ const storeCurrency = useStore((s) => s.data?.store?.currency ?? DEFAULT_CURRENCY_CODE);
1952
2152
  const currencyCode = currencyCodeOption ?? storeCurrency;
1953
2153
  const { useCreateCart } = useStoreCart();
1954
2154
  const createCartMutation = useCreateCart();
@@ -1959,39 +2159,68 @@ function useProceedToCheckout(options = {}) {
1959
2159
  }))
1960
2160
  );
1961
2161
  const [isProceedingToCheckout, setIsProceedingToCheckout] = useState(false);
2162
+ const inFlightRef = useRef(false);
1962
2163
  const navigate = navigateOption ?? (typeof window !== "undefined" ? (path) => window.location.assign(path) : () => {
1963
2164
  });
1964
2165
  const proceedToCheckout = useCallback(async () => {
1965
- if (isProceedingToCheckout || createCartMutation.isPending) {
1966
- return cartTokenId;
1967
- }
2166
+ if (inFlightRef.current) return null;
2167
+ inFlightRef.current = true;
1968
2168
  setIsProceedingToCheckout(true);
2169
+ const fail = (error) => {
2170
+ console.error("Error starting checkout", error);
2171
+ onError?.(error);
2172
+ return null;
2173
+ };
1969
2174
  try {
1970
2175
  let checkoutCartId = cartTokenId;
2176
+ if (checkoutCartId) {
2177
+ try {
2178
+ await getCart(checkoutCartId);
2179
+ } catch (error) {
2180
+ if (isCartNotFoundError(error)) {
2181
+ checkoutCartId = null;
2182
+ setCartTokenId(null);
2183
+ } else {
2184
+ return fail(error);
2185
+ }
2186
+ }
2187
+ }
1971
2188
  if (!checkoutCartId) {
1972
2189
  const guestEmail = guestEmailOption ?? useCart.getState().checkoutCustomerLocal?.customer.email ?? "cambiar@ecomiq.pe";
1973
- const created = await createCartMutation.mutateAsync({
1974
- currencyCode,
1975
- email: guestEmail,
1976
- salesChannelId
1977
- });
2190
+ let created;
2191
+ try {
2192
+ created = await createCartMutation.mutateAsync({
2193
+ currencyCode,
2194
+ email: guestEmail,
2195
+ salesChannelId
2196
+ });
2197
+ } catch (error) {
2198
+ return fail(error);
2199
+ }
1978
2200
  checkoutCartId = created.id;
1979
2201
  if (!checkoutCartId) {
1980
- throw new Error("Cart created without id");
2202
+ return fail(new Error("Cart created without id"));
1981
2203
  }
1982
2204
  setCartTokenId(checkoutCartId);
1983
2205
  }
2206
+ if (syncLocalCart) {
2207
+ const localCart = useCart.getState().cart;
2208
+ if (localCart.length > 0) {
2209
+ try {
2210
+ await reconcileLocalCartWithApi(checkoutCartId, localCart, {
2211
+ strict: true
2212
+ });
2213
+ } catch (error) {
2214
+ return fail(error);
2215
+ }
2216
+ }
2217
+ }
1984
2218
  navigate(buildCheckoutPath(checkoutCartId, checkoutPathPrefix));
1985
2219
  return checkoutCartId;
1986
2220
  } catch (error) {
1987
- console.error("Error starting checkout", error);
1988
- if (onError) {
1989
- onError(error);
1990
- } else {
1991
- console.error("Error starting checkout", error);
1992
- }
1993
- return null;
2221
+ return fail(error);
1994
2222
  } finally {
2223
+ inFlightRef.current = false;
1995
2224
  setIsProceedingToCheckout(false);
1996
2225
  }
1997
2226
  }, [
@@ -2000,11 +2229,11 @@ function useProceedToCheckout(options = {}) {
2000
2229
  createCartMutation,
2001
2230
  currencyCode,
2002
2231
  guestEmailOption,
2003
- isProceedingToCheckout,
2004
2232
  navigate,
2005
2233
  onError,
2006
2234
  salesChannelId,
2007
- setCartTokenId
2235
+ setCartTokenId,
2236
+ syncLocalCart
2008
2237
  ]);
2009
2238
  return {
2010
2239
  proceedToCheckout,
@@ -2013,14 +2242,6 @@ function useProceedToCheckout(options = {}) {
2013
2242
  };
2014
2243
  }
2015
2244
 
2016
- // src/core/constants/currency.ts
2017
- var countryCurrency = {
2018
- "PEN": "es-PE",
2019
- "USD": "en-US",
2020
- "COP": "es-CO",
2021
- "MXN": "es-MX"
2022
- };
2023
-
2024
2245
  // src/core/utils/format-currency.ts
2025
2246
  function formatCurrency(amount, currency = "USD") {
2026
2247
  const locale = countryCurrency[currency] ?? "en-US";
@@ -2060,12 +2281,12 @@ var zodPathToFieldId = {
2060
2281
  selectedPaymentCode: "checkout-payment-method"
2061
2282
  };
2062
2283
  function buildCheckoutFormValidationSchema(currency) {
2063
- const phoneDigits = currency === "PEN" ? 9 : 10;
2284
+ const phoneDigits = currency === DEFAULT_CURRENCY_CODE ? 9 : 10;
2064
2285
  return z.object({
2065
2286
  email: z.string().trim().min(1, "El email es requerido").email("Ingresa un email v\xE1lido"),
2066
2287
  firstName: z.string().trim().min(1, "El nombre es requerido").min(2, "El nombre debe tener al menos 2 caracteres"),
2067
2288
  lastName: z.string().trim().min(1, "El apellido es requerido").min(2, "El apellido debe tener al menos 2 caracteres"),
2068
- countryCode: z.string().trim().min(2, "Selecciona un pa\xEDs"),
2289
+ countryCode: z.string().trim().min(2, "Selecciona un pa\xEDs").max(2),
2069
2290
  address: z.string().trim().min(1, "La direcci\xF3n es requerida").min(5, "La direcci\xF3n debe tener al menos 5 caracteres"),
2070
2291
  phone: z.string().trim().min(1, "El tel\xE9fono es requerido").regex(/^\d+$/, "El tel\xE9fono solo debe contener n\xFAmeros").length(
2071
2292
  phoneDigits,
@@ -2082,7 +2303,7 @@ function buildCheckoutFormValidationSchema(currency) {
2082
2303
  paymentMethodsCount: z.number().int().nonnegative()
2083
2304
  }).superRefine((data, ctx) => {
2084
2305
  const country = data.countryCode.toUpperCase();
2085
- if (country === "PE") {
2306
+ if (country === DEFAULT_COUNTRY_CODE) {
2086
2307
  if (!data.departmentCode.trim()) {
2087
2308
  ctx.addIssue({
2088
2309
  code: z.ZodIssueCode.custom,
@@ -2199,7 +2420,7 @@ function focusCheckoutField(fieldId) {
2199
2420
  }
2200
2421
 
2201
2422
  // src/react/pages/checkout-page/hooks/use-checkout-form-validation.ts
2202
- function useCheckoutFormValidation(currency = "PEN") {
2423
+ function useCheckoutFormValidation(currency = DEFAULT_CURRENCY_CODE) {
2203
2424
  const schema = useMemo(
2204
2425
  () => buildCheckoutFormValidationSchema(currency),
2205
2426
  [currency]
@@ -2270,7 +2491,9 @@ function loadStorefrontGeoDepartments(headers) {
2270
2491
  if (departmentsCache) return Promise.resolve(departmentsCache);
2271
2492
  if (departmentsRequest) return departmentsRequest;
2272
2493
  departmentsRequest = fetch(
2273
- `/api/store/geo/departments?countryCode=PE&source=inei&limit=200`,
2494
+ `/api/store/geo/departments?countryCode=${encodeURIComponent(
2495
+ DEFAULT_COUNTRY_CODE
2496
+ )}&source=inei&limit=200`,
2274
2497
  { cache: "no-store", headers }
2275
2498
  ).then((r) => r.json()).then((data) => {
2276
2499
  const items = toGeoItems(data);
@@ -2304,10 +2527,10 @@ async function fetchStorefrontGeoDistricts(headers, countryCode, provinceCode) {
2304
2527
  const data = await res.json();
2305
2528
  return toGeoItems(data);
2306
2529
  }
2307
- async function resolveIneiCodesFromLabels(storeId, labels, countryCode = "PE") {
2530
+ async function resolveIneiCodesFromLabels(storeId, labels, countryCode = DEFAULT_COUNTRY_CODE) {
2308
2531
  const id = storeId.trim();
2309
2532
  if (!id) return null;
2310
- const headers = { "X-Store-Id": id };
2533
+ const headers = { [STOREFRONT_HEADERS.storeId]: id };
2311
2534
  const departments = await loadStorefrontGeoDepartments(headers);
2312
2535
  const dept = departments.find((d) => d.name.trim() === labels.department.trim());
2313
2536
  if (!dept) return null;
@@ -2345,12 +2568,12 @@ function useCheckoutDeliveryForm({
2345
2568
  const patchContact = useCallback((patch) => {
2346
2569
  onChangeRef.current(patch);
2347
2570
  }, []);
2348
- const countryCode = (contact.countryCode || "PE").toUpperCase();
2349
- const isPeru = countryCode === "PE";
2571
+ const countryCode = (contact.countryCode || DEFAULT_COUNTRY_CODE).toUpperCase();
2572
+ const isPeru = countryCode === DEFAULT_COUNTRY_CODE;
2350
2573
  const storeRequestHeaders = useMemo(() => {
2351
2574
  const id = storeId.trim();
2352
2575
  if (!id) return null;
2353
- return { "X-Store-Id": id };
2576
+ return { [STOREFRONT_HEADERS.storeId]: id };
2354
2577
  }, [storeId]);
2355
2578
  useEffect(() => {
2356
2579
  if (!isPeru || !storeRequestHeaders) {
@@ -2698,13 +2921,13 @@ function CheckoutDeliveryForm({
2698
2921
  CheckoutSelectField,
2699
2922
  {
2700
2923
  id: "countryCode",
2701
- label: "Country/Region",
2924
+ label: "Pa\xEDs/Regi\xF3n",
2702
2925
  value: contact.countryCode,
2703
2926
  error: getFieldError("countryCode"),
2704
2927
  onClearError: () => clearFieldError("countryCode"),
2705
2928
  onChange: (v) => onContactChange({ countryCode: v }),
2706
2929
  children: [
2707
- /* @__PURE__ */ jsx("option", { value: "PE", children: "Peru" }),
2930
+ /* @__PURE__ */ jsx("option", { value: DEFAULT_COUNTRY_CODE, children: "Per\xFA" }),
2708
2931
  /* @__PURE__ */ jsx("option", { value: "US", children: "United States" }),
2709
2932
  /* @__PURE__ */ jsx("option", { value: "MX", children: "Mexico" }),
2710
2933
  /* @__PURE__ */ jsx("option", { value: "CO", children: "Colombia" })
@@ -3147,7 +3370,7 @@ function StripePaymentProvider({ checkout }) {
3147
3370
  return;
3148
3371
  }
3149
3372
  let cancelled = false;
3150
- const ensureStripeScript = () => new Promise((resolve, reject) => {
3373
+ const loadStripeScript = () => new Promise((resolve, reject) => {
3151
3374
  const maybeStripe = globalThis.Stripe;
3152
3375
  if (maybeStripe) return resolve();
3153
3376
  const existing = document.querySelector('script[src="https://js.stripe.com/v3/"]');
@@ -3172,7 +3395,7 @@ function StripePaymentProvider({ checkout }) {
3172
3395
  document.head.appendChild(script);
3173
3396
  });
3174
3397
  const mountStripePaymentElement = async () => {
3175
- await ensureStripeScript();
3398
+ await loadStripeScript();
3176
3399
  if (cancelled) return;
3177
3400
  const StripeCtor = globalThis.Stripe;
3178
3401
  if (!StripeCtor) throw new Error("Stripe is not available");
@@ -3312,21 +3535,21 @@ function useCheckoutPayment({
3312
3535
  const requiresProviderUi = Boolean(
3313
3536
  selectedMethod && methodUsesProviderCheckoutUi(selectedMethod)
3314
3537
  );
3315
- const effectiveCheckout = checkoutMethodCode && checkoutMethodCode === selectedMethod?.code ? checkoutResponse ?? null : null;
3538
+ const activeCheckout = checkoutMethodCode && checkoutMethodCode === selectedMethod?.code ? checkoutResponse ?? null : null;
3316
3539
  const activeLoadingCheckout = !!checkoutMethodCode && checkoutMethodCode === selectedMethod?.code && loadingCheckout;
3317
3540
  const activeCheckoutError = checkoutMethodCode && checkoutMethodCode === selectedMethod?.code ? checkoutError : null;
3318
3541
  const manualPaymentDetails = useMemo(() => {
3319
3542
  if (!selectedMethod?.isManual) return null;
3320
- const action = getPaymentFromCheckout(effectiveCheckout)?.action ?? null;
3543
+ const action = getPaymentFromCheckout(activeCheckout)?.action ?? null;
3321
3544
  const publicConfiguration = action?.publicConfiguration ?? {};
3322
3545
  const phoneNumber = selectedMethod.phoneNumber ?? publicConfiguration.phoneNumber ?? null;
3323
3546
  const accountHolder = selectedMethod.accountHolder ?? publicConfiguration.accountHolder ?? null;
3324
3547
  const qrImageUrl = action?.qrCode ?? selectedMethod.qrImageUrl ?? publicConfiguration.qrImageUrl ?? null;
3325
3548
  if (!phoneNumber && !accountHolder && !qrImageUrl) return null;
3326
3549
  return { phoneNumber, accountHolder, qrImageUrl };
3327
- }, [selectedMethod, effectiveCheckout]);
3550
+ }, [selectedMethod, activeCheckout]);
3328
3551
  const needsReceipt = Boolean(selectedMethod?.isManual && manualPaymentDetails);
3329
- const showProviderPanel = requiresProviderUi && !!selectedMethod && !!effectiveCheckout && !activeLoadingCheckout && !activeCheckoutError;
3552
+ const showProviderPanel = requiresProviderUi && !!selectedMethod && !!activeCheckout && !activeLoadingCheckout && !activeCheckoutError;
3330
3553
  const [receiptFile, setReceiptFile] = useState(null);
3331
3554
  const [receiptUrl, setReceiptUrl] = useState(null);
3332
3555
  const [receiptError, setReceiptError] = useState(null);
@@ -3464,7 +3687,7 @@ function useCheckoutPayment({
3464
3687
  return {
3465
3688
  activeMethods,
3466
3689
  selectedMethod,
3467
- effectiveCheckout,
3690
+ activeCheckout,
3468
3691
  activeLoadingCheckout,
3469
3692
  activeCheckoutError,
3470
3693
  manualPaymentDetails,
@@ -3592,8 +3815,8 @@ function CheckoutPaymentSectionInner({
3592
3815
  ] })
3593
3816
  ] })
3594
3817
  ] }) }) : null,
3595
- payment.showProviderPanel && payment.effectiveCheckout ? /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__payment-provider", children: [
3596
- /* @__PURE__ */ jsx(PaymentProviderRouter, { checkout: payment.effectiveCheckout }),
3818
+ payment.showProviderPanel && payment.activeCheckout ? /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__payment-provider", children: [
3819
+ /* @__PURE__ */ jsx(PaymentProviderRouter, { checkout: payment.activeCheckout }),
3597
3820
  !payment.providerPaymentCompleted ? /* @__PURE__ */ jsx(
3598
3821
  "button",
3599
3822
  {
@@ -4006,128 +4229,6 @@ function CheckoutPageSkeleton({ header }) {
4006
4229
  }
4007
4230
  );
4008
4231
  }
4009
-
4010
- // src/react/pages/checkout-page/lib/cart-mappers.ts
4011
- function mapApiItemsToCartItems(items) {
4012
- if (!items?.length) return [];
4013
- return items.map((raw) => {
4014
- const item = raw;
4015
- const price = Number(item.unitPrice);
4016
- const quantity = Number(item.quantity);
4017
- const variantId = String(item.variantId ?? "");
4018
- if (!Number.isFinite(price) || !Number.isFinite(quantity) || !variantId) return null;
4019
- return {
4020
- id: item.id,
4021
- title: item.title,
4022
- image: String(item.thumbnail ?? ""),
4023
- size: String(item.variantLabel ?? "\u2014"),
4024
- price,
4025
- quantity,
4026
- variantId,
4027
- variantSku: item.variantSku ?? void 0,
4028
- variantLabel: item.variantLabel ?? void 0
4029
- };
4030
- }).filter(Boolean);
4031
- }
4032
- function shippingValuesFromCartAddress(cartData) {
4033
- const addr = cartData.shippingAddress;
4034
- if (!addr) return void 0;
4035
- const geo = addr.geo;
4036
- return {
4037
- address: addr.address1,
4038
- zipCode: addr.postalCode ?? "",
4039
- countryCode: addr.countryCode ?? "PE",
4040
- departmentCode: geo?.departmentCode ?? "",
4041
- departmentName: "",
4042
- provinceCode: geo?.provinceCode ?? "",
4043
- provinceName: addr.province ?? "",
4044
- districtCode: geo?.districtCode ?? "",
4045
- districtName: addr.district ?? addr.city ?? ""
4046
- };
4047
- }
4048
-
4049
- // src/react/pages/checkout-page/lib/cart-sync.ts
4050
- var checkoutReadQueryKey = (cartId) => ["storefront", "cart", cartId, "checkout"];
4051
- function cartItemsSignature(items) {
4052
- return items.map((i) => `${i.variantId ?? i.id}:${i.quantity}`).sort().join("|");
4053
- }
4054
- function apiCartItemsFromResponse(cart) {
4055
- return mapApiItemsToCartItems(cart?.items);
4056
- }
4057
- function groupCartQuantitiesByVariant(items) {
4058
- const map = /* @__PURE__ */ new Map();
4059
- for (const item of items) {
4060
- const variantId = item.variantId ?? item.id;
4061
- map.set(variantId, (map.get(variantId) ?? 0) + Math.max(0, item.quantity));
4062
- }
4063
- return map;
4064
- }
4065
- function groupApiLinesByVariant(items) {
4066
- const map = /* @__PURE__ */ new Map();
4067
- for (const item of items) {
4068
- const variantId = item.variantId ?? item.id;
4069
- const lines = map.get(variantId) ?? [];
4070
- lines.push(item);
4071
- map.set(variantId, lines);
4072
- }
4073
- return map;
4074
- }
4075
- function findCartReconciliationActions(local, api) {
4076
- const actions = [];
4077
- const localQty = groupCartQuantitiesByVariant(local);
4078
- const apiLinesByVariant = groupApiLinesByVariant(api);
4079
- const variantIds = /* @__PURE__ */ new Set([
4080
- ...localQty.keys(),
4081
- ...apiLinesByVariant.keys()
4082
- ]);
4083
- for (const variantId of variantIds) {
4084
- const targetQty = localQty.get(variantId) ?? 0;
4085
- const apiLines = apiLinesByVariant.get(variantId) ?? [];
4086
- const apiQty = apiLines.reduce((sum, line) => sum + line.quantity, 0);
4087
- if (targetQty > apiQty) {
4088
- actions.push({
4089
- action: "add",
4090
- variantId,
4091
- quantity: targetQty - apiQty
4092
- });
4093
- continue;
4094
- }
4095
- if (targetQty >= apiQty) continue;
4096
- let excess = apiQty - targetQty;
4097
- for (const line of [...apiLines].reverse()) {
4098
- if (excess <= 0) break;
4099
- if (line.quantity <= excess) {
4100
- actions.push({ action: "remove", itemId: line.id });
4101
- excess -= line.quantity;
4102
- } else {
4103
- actions.push({
4104
- action: "update",
4105
- itemId: line.id,
4106
- quantity: line.quantity - excess
4107
- });
4108
- excess = 0;
4109
- }
4110
- }
4111
- }
4112
- return actions;
4113
- }
4114
- function areCartSignaturesEqual(a, b) {
4115
- return cartItemsSignature(a) === cartItemsSignature(b);
4116
- }
4117
- function cartSessionSignature(cartTokenId, items) {
4118
- return `${cartTokenId ?? ""}|${cartItemsSignature(items)}`;
4119
- }
4120
- function readCartCheckoutStatus(cart) {
4121
- if (!cart) return null;
4122
- const status = cart.status ?? null;
4123
- return typeof status === "string" && status.trim() ? status.trim() : null;
4124
- }
4125
- function isCheckoutCartAlreadyPurchased(cart, localCartItemCount) {
4126
- if (localCartItemCount > 0) return false;
4127
- const status = readCartCheckoutStatus(cart);
4128
- if (!status) return false;
4129
- return status.toLowerCase() === "inprogress";
4130
- }
4131
4232
  function useCartListener({
4132
4233
  cartId,
4133
4234
  enabled,
@@ -4175,7 +4276,7 @@ function useCartListener({
4175
4276
  function useCheckoutSession(cartId) {
4176
4277
  const queryClient = useQueryClient();
4177
4278
  const storeId = useStore((s) => s.data?.store?.id ?? "");
4178
- const currency = useStore((s) => s.data?.store?.currency ?? "PEN");
4279
+ const currency = useStore((s) => s.data?.store?.currency ?? DEFAULT_CURRENCY_CODE);
4179
4280
  const { setCartTokenId, clearCartSession, cart: localCart } = useCart(
4180
4281
  useShallow((s) => ({
4181
4282
  setCartTokenId: s.setCartTokenId,
@@ -4245,7 +4346,7 @@ function useCheckoutSession(cartId) {
4245
4346
  province: "",
4246
4347
  postalCode: "",
4247
4348
  phone: "",
4248
- countryCode: "PE",
4349
+ countryCode: DEFAULT_COUNTRY_CODE,
4249
4350
  departmentCode: "",
4250
4351
  departmentName: "",
4251
4352
  provinceCode: "",
@@ -4414,7 +4515,7 @@ function useCheckoutSession(cartId) {
4414
4515
  }
4415
4516
  });
4416
4517
  mergeCartIntoCheckoutCache(buyerCart);
4417
- const countryCode = (contact.countryCode || "PE").toUpperCase();
4518
+ const countryCode = (contact.countryCode || DEFAULT_COUNTRY_CODE).toUpperCase();
4418
4519
  const districtName = contact.districtName || contact.city;
4419
4520
  const provinceName = contact.provinceName || contact.province;
4420
4521
  const baseAddress = {
@@ -4925,6 +5026,8 @@ function thankYouParamsFromCheckoutResult(result, overrides) {
4925
5026
  transactionId: overrides?.transactionId ?? result.payment?.transactionId ?? void 0
4926
5027
  };
4927
5028
  }
5029
+
5030
+ // src/react/shell/components/product-variant-selector/product-variant-selection.ts
4928
5031
  function buildOptionGroups(variants) {
4929
5032
  const map = /* @__PURE__ */ new Map();
4930
5033
  for (const v of variants) {
@@ -4964,6 +5067,54 @@ function getInitialOptions(variants, optionGroups) {
4964
5067
  }
4965
5068
  return selected;
4966
5069
  }
5070
+ function buildVariantLabel(variant) {
5071
+ if (!variant?.options?.length) return void 0;
5072
+ return variant.options.map((o) => `${o.key}: ${o.value}`).join(" \u2022 ");
5073
+ }
5074
+ function getPriceTiersForVariant(product, variantId) {
5075
+ if (!variantId) return [];
5076
+ return (product.priceTiers ?? []).filter((tier) => tier.variantId === variantId).sort((a, b) => a.minQuantity - b.minQuantity);
5077
+ }
5078
+ function resolveProductImageUrl(product, selectedVariant) {
5079
+ return product.imageUrl ?? selectedVariant?.images?.[0]?.url ?? product.images?.[0]?.url ?? "";
5080
+ }
5081
+ function buildVariantSelectionPayload(product, selectedVariant, displayPrice) {
5082
+ if (!selectedVariant?.id) return null;
5083
+ return {
5084
+ productId: product.id,
5085
+ productName: product.name,
5086
+ variant: selectedVariant,
5087
+ displayPrice,
5088
+ compareAtPrice: selectedVariant.compareAtPrice ?? product.compareAtPrice,
5089
+ variantLabel: buildVariantLabel(selectedVariant),
5090
+ variantSku: selectedVariant.sku,
5091
+ imageUrl: resolveProductImageUrl(product, selectedVariant)
5092
+ };
5093
+ }
5094
+ function buildCartItemFromVariant(product, selectedVariant, displayPrice) {
5095
+ if (!selectedVariant?.id) return null;
5096
+ const sizeOption = selectedVariant.options?.find(
5097
+ (o) => o.key.toLowerCase() === "size"
5098
+ )?.value;
5099
+ const variantLabel = buildVariantLabel(selectedVariant);
5100
+ return {
5101
+ id: selectedVariant.id,
5102
+ title: product.name,
5103
+ price: displayPrice,
5104
+ quantity: 1,
5105
+ size: sizeOption ?? "\u2014",
5106
+ image: resolveProductImageUrl(product, selectedVariant),
5107
+ variantId: selectedVariant.id,
5108
+ variantSku: selectedVariant.sku,
5109
+ variantLabel
5110
+ };
5111
+ }
5112
+ function isVariantSelectionReady(selectedVariant, isAvailable) {
5113
+ return Boolean(selectedVariant?.id && isAvailable);
5114
+ }
5115
+ function canAddVariantToCart(selectedVariant, isAvailable) {
5116
+ return isVariantSelectionReady(selectedVariant, isAvailable);
5117
+ }
4967
5118
  function ProductVariantSelector({
4968
5119
  product,
4969
5120
  currency = "USD",
@@ -5033,7 +5184,180 @@ function ProductVariantSelector({
5033
5184
  })
5034
5185
  ] });
5035
5186
  }
5187
+ function DefaultVariantPrice({
5188
+ displayPrice,
5189
+ compareAtPrice,
5190
+ currency
5191
+ }) {
5192
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
5193
+ /* @__PURE__ */ jsx(
5194
+ "span",
5195
+ {
5196
+ className: "inline-flex items-center rounded-md bg-primary/10 px-2.5 py-0.5 text-base font-semibold text-primary",
5197
+ "data-testid": "product-display-price",
5198
+ children: formatCurrency(displayPrice, currency)
5199
+ }
5200
+ ),
5201
+ compareAtPrice != null && compareAtPrice > displayPrice && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground line-through", children: formatCurrency(compareAtPrice, currency) })
5202
+ ] });
5203
+ }
5204
+ function DefaultVariantOptions(slot) {
5205
+ if (!slot.hasVariants) return null;
5206
+ return /* @__PURE__ */ jsx(Fragment, { children: slot.optionGroups.map((group) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
5207
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: group.name }),
5208
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: group.values.map((value) => {
5209
+ const isSelected = slot.selectedOptions[group.key] === value;
5210
+ const isColorLike = group.key.toLowerCase().includes("color") || group.key.toLowerCase().includes("colour");
5211
+ return /* @__PURE__ */ jsx(
5212
+ "button",
5213
+ {
5214
+ type: "button",
5215
+ onClick: () => slot.selectOption(group.key, value),
5216
+ className: isColorLike ? `min-w-[72px] rounded-md border-2 px-3 py-2 text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 ${isSelected ? "border-primary bg-primary text-foreground" : "border-border bg-muted/30 text-foreground hover:border-muted-foreground/50"}` : `rounded-md border px-3 py-2 text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 ${isSelected ? "border-primary bg-muted ring-1 ring-primary" : "border-border bg-transparent text-muted-foreground hover:border-muted-foreground/50 hover:text-foreground"}`,
5217
+ style: isColorLike && isSelected && value.toLowerCase() === "black" ? { backgroundColor: "#1a1a1a", borderColor: "#1a1a1a", color: "#fff" } : isColorLike && value.toLowerCase() === "white" ? { backgroundColor: "#fafafa", color: "#1a1a1a", borderColor: "#e5e5e5" } : void 0,
5218
+ "data-testid": `variant-option-${group.key}-${value}`,
5219
+ children: value
5220
+ },
5221
+ value
5222
+ );
5223
+ }) })
5224
+ ] }, group.key)) });
5225
+ }
5226
+ function DefaultPriceTiers({
5227
+ priceTiers,
5228
+ currency
5229
+ }) {
5230
+ if (priceTiers.length === 0) return null;
5231
+ return /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-md border border-border text-sm", children: [
5232
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 bg-muted/40 px-3 py-2 text-xs font-semibold uppercase text-muted-foreground", children: [
5233
+ /* @__PURE__ */ jsx("span", { children: "Cantidad" }),
5234
+ /* @__PURE__ */ jsx("span", { className: "text-right", children: "Precio" })
5235
+ ] }),
5236
+ priceTiers.map((priceTier) => /* @__PURE__ */ jsxs(
5237
+ "div",
5238
+ {
5239
+ className: "grid grid-cols-2 border-t border-border px-3 py-2",
5240
+ children: [
5241
+ /* @__PURE__ */ jsx("span", { children: priceTier.maxQuantity ? `${priceTier.minQuantity} - ${priceTier.maxQuantity}` : `${priceTier.minQuantity}+` }),
5242
+ /* @__PURE__ */ jsx("span", { className: "text-right font-medium", children: formatCurrency(priceTier.unitPrice, currency) })
5243
+ ]
5244
+ },
5245
+ `${priceTier.variantId}-${priceTier.minQuantity}`
5246
+ ))
5247
+ ] });
5248
+ }
5249
+ function DefaultProductFeatures({ features }) {
5250
+ if (features.length === 0) return null;
5251
+ return /* @__PURE__ */ jsx("ul", { className: "list-disc list-inside space-y-1 text-sm text-muted-foreground", children: features.map((feature, index) => /* @__PURE__ */ jsx("li", { children: feature }, index)) });
5252
+ }
5253
+ function useProductVariantSelection({
5254
+ product,
5255
+ onVariantChange,
5256
+ onSelectionChange
5257
+ }) {
5258
+ const variants = useMemo(() => product.variants ?? [], [product.variants]);
5259
+ const optionGroups = useMemo(() => buildOptionGroups(variants), [variants]);
5260
+ const hasVariants = optionGroups.length > 0;
5261
+ const [selectedOptions, setSelectedOptions] = useState(
5262
+ () => getInitialOptions(variants, optionGroups)
5263
+ );
5264
+ const selectedVariant = useMemo(
5265
+ () => findVariant(variants, selectedOptions, hasVariants),
5266
+ [variants, selectedOptions, hasVariants]
5267
+ );
5268
+ const displayPrice = selectedVariant?.price ?? product.price ?? 0;
5269
+ const compareAtPrice = selectedVariant?.compareAtPrice ?? product.compareAtPrice;
5270
+ const isAvailable = (selectedVariant ? (selectedVariant.quantity ?? 0) > 0 : product.isAvailable) ?? true;
5271
+ const selection = useMemo(
5272
+ () => buildVariantSelectionPayload(product, selectedVariant, displayPrice),
5273
+ [product, selectedVariant, displayPrice]
5274
+ );
5275
+ const priceTiers = useMemo(
5276
+ () => getPriceTiersForVariant(product, selectedVariant?.id),
5277
+ [product, selectedVariant?.id]
5278
+ );
5279
+ const isReady = isVariantSelectionReady(selectedVariant, isAvailable);
5280
+ useEffect(() => {
5281
+ onVariantChange?.(selectedVariant, displayPrice);
5282
+ }, [selectedVariant, displayPrice, onVariantChange]);
5283
+ useEffect(() => {
5284
+ onSelectionChange?.(selection);
5285
+ }, [onSelectionChange, selection]);
5286
+ const selectOption = useCallback((key, value) => {
5287
+ setSelectedOptions((prev) => ({ ...prev, [key]: value }));
5288
+ }, []);
5289
+ return {
5290
+ product,
5291
+ variants,
5292
+ optionGroups,
5293
+ selectedOptions,
5294
+ selectOption,
5295
+ hasVariants,
5296
+ selectedVariant,
5297
+ displayPrice,
5298
+ compareAtPrice,
5299
+ isAvailable,
5300
+ isReady,
5301
+ selection,
5302
+ priceTiers
5303
+ };
5304
+ }
5305
+ function ProductVariantSelectorComplete({
5306
+ product,
5307
+ currency = "PEN",
5308
+ features = [],
5309
+ className,
5310
+ onVariantChange,
5311
+ onSelectionChange,
5312
+ renderPrice,
5313
+ renderVariants,
5314
+ renderPriceTiers,
5315
+ renderAction,
5316
+ renderFeatures
5317
+ }) {
5318
+ const state = useProductVariantSelection({
5319
+ product,
5320
+ onVariantChange,
5321
+ onSelectionChange
5322
+ });
5323
+ const priceSlot = {
5324
+ displayPrice: state.displayPrice,
5325
+ compareAtPrice: state.compareAtPrice,
5326
+ currency,
5327
+ selectedVariant: state.selectedVariant
5328
+ };
5329
+ const variantsSlot = {
5330
+ optionGroups: state.optionGroups,
5331
+ selectedOptions: state.selectedOptions,
5332
+ selectOption: state.selectOption,
5333
+ hasVariants: state.hasVariants,
5334
+ selectedVariant: state.selectedVariant,
5335
+ isAvailable: state.isAvailable
5336
+ };
5337
+ const priceTiersSlot = {
5338
+ priceTiers: state.priceTiers,
5339
+ currency,
5340
+ selectedVariant: state.selectedVariant
5341
+ };
5342
+ const actionSlot = {
5343
+ product: state.product,
5344
+ selectedVariant: state.selectedVariant,
5345
+ displayPrice: state.displayPrice,
5346
+ compareAtPrice: state.compareAtPrice,
5347
+ currency,
5348
+ isAvailable: state.isAvailable,
5349
+ isReady: state.isReady,
5350
+ selection: state.selection
5351
+ };
5352
+ return /* @__PURE__ */ jsxs("div", { className: className ?? "w-full max-w-[320px] space-y-5", children: [
5353
+ renderPrice?.(priceSlot) ?? /* @__PURE__ */ jsx(DefaultVariantPrice, { ...priceSlot }),
5354
+ renderVariants?.(variantsSlot) ?? /* @__PURE__ */ jsx(DefaultVariantOptions, { ...variantsSlot }),
5355
+ renderFeatures?.(features) ?? /* @__PURE__ */ jsx(DefaultProductFeatures, { features }),
5356
+ state.priceTiers.length > 0 && (renderPriceTiers?.(priceTiersSlot) ?? /* @__PURE__ */ jsx(DefaultPriceTiers, { ...priceTiersSlot })),
5357
+ renderAction?.(actionSlot)
5358
+ ] });
5359
+ }
5036
5360
 
5037
- export { CheckoutPage, CustomerAddressSchema, CustomerAddressUpsertSchema, CustomerMeSchema, CustomerOrderDetailSchema, CustomerOrderListItemSchema, CustomerProfilePatchSchema, CustomerRegisterRequestSchema, EcomiqProvider, ProductVariantSelector, QueryProvider, StoreApiClient, StorefrontAuthProvider, ThankYouPage, addCartCoupon, buildCheckoutPath, buildCheckoutThankYouUrl, checkoutCart, createCart, createCustomerAddress, createStoreApiClient, customerMeToProfilePatch, deleteCustomerAddress, ecomiqOAuthPasswordClient, emailFromCheckoutResult, getCart, getCartCheckout, getCartPaymentMethods, getCustomerAddresses, getCustomerMe, getCustomerOrder, getCustomerOrders, getSharedOrderDraft, getShippingOptions, getStoreApiBaseUrl, getStoreApiClient, getStorefrontCollectionBySlug, getStorefrontCollections, getStorefrontPageBySlug, getStorefrontPagePreview, getStorefrontPages, getStorefrontProductBySlug, getStorefrontProducts, isCartNotActiveApiError, orderIdFromCheckoutResult, patchCartAddresses, patchCartBuyer, patchCustomer, putCartShippingMethod, registerCustomer, removeCartCoupon, setClientStoreId, setServerRequestOrigin, setServerStoreId, signInWithEcomiqCredentials, storeApiClient, storefrontAccessTokenNeedsRefresh, storefrontSessionEndsSoon, thankYouParamsFromCheckoutResult, updateCustomerAddress, uploadPaymentReceipt, useCart, useCheckoutSession, useProceedToCheckout, useStore, useStoreCart, useStoreCollections, useStorePages, useStoreProducts, useStorefrontAuthClient, useStorefrontSession };
5361
+ export { CheckoutPage, CustomerAddressSchema, CustomerAddressUpsertSchema, CustomerMeSchema, CustomerOrderDetailSchema, CustomerOrderListItemSchema, CustomerProfilePatchSchema, CustomerRegisterRequestSchema, EcomiqProvider, ProductVariantSelector, ProductVariantSelectorComplete, QueryProvider, StoreApiClient, StorefrontAuthProvider, ThankYouPage, addCartCoupon, buildCartItemFromVariant, buildCheckoutPath, buildCheckoutThankYouUrl, buildOptionGroups, buildVariantLabel, buildVariantSelectionPayload, canAddVariantToCart, checkoutCart, createCart, createCustomerAddress, createStoreApiClient, customerMeToProfilePatch, deleteCustomerAddress, ecomiqOAuthPasswordClient, emailFromCheckoutResult, findVariant, getCart, getCartCheckout, getCartPaymentMethods, getCustomerAddresses, getCustomerMe, getCustomerOrder, getCustomerOrders, getInitialOptions, getPriceTiersForVariant, getSharedOrderDraft, getShippingOptions, getStoreApiBaseUrl, getStoreApiClient, getStorefrontCollectionBySlug, getStorefrontCollections, getStorefrontPageBySlug, getStorefrontPagePreview, getStorefrontPages, getStorefrontProductBySlug, getStorefrontProducts, isCartNotActiveApiError, isVariantSelectionReady, orderIdFromCheckoutResult, patchCartAddresses, patchCartBuyer, patchCustomer, putCartShippingMethod, registerCustomer, removeCartCoupon, setClientStoreId, setServerRequestOrigin, setServerStoreId, signInWithEcomiqCredentials, storeApiClient, storefrontAccessTokenNeedsRefresh, storefrontSessionEndsSoon, thankYouParamsFromCheckoutResult, updateCustomerAddress, uploadPaymentReceipt, useCart, useCheckoutSession, useProceedToCheckout, useProductVariantSelection, useStore, useStoreCart, useStoreCollections, useStorePages, useStoreProducts, useStorefrontAuthClient, useStorefrontSession };
5038
5362
  //# sourceMappingURL=client.js.map
5039
5363
  //# sourceMappingURL=client.js.map