@ecomiq/storefront 1.1.3 → 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 (39) 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 +173 -0
  12. package/dist/chunks/client.css.map +1 -1
  13. package/dist/chunks/client.d.ts +622 -26
  14. package/dist/chunks/client.js +662 -173
  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/checkout-page.css +855 -0
  31. package/dist/react/client.css +984 -0
  32. package/dist/react/payment-provider.css +91 -0
  33. package/dist/react/thank-you-page.css +105 -0
  34. package/dist/tanstack/api.js +3 -2
  35. package/dist/tanstack/api.js.map +1 -1
  36. package/package.json +6 -2
  37. package/dist/chunk-3URTJY6X.js.map +0 -1
  38. package/dist/chunk-EADDSNJS.js.map +0 -1
  39. 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
 
@@ -1936,13 +1966,281 @@ async function signInWithEcomiqCredentials(authClient, settings, credentials) {
1936
1966
  return response;
1937
1967
  }
1938
1968
 
1939
- // src/core/constants/currency.ts
1940
- var countryCurrency = {
1941
- "PEN": "es-PE",
1942
- "USD": "en-US",
1943
- "COP": "es-CO",
1944
- "MXN": "es-MX"
1945
- };
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
2137
+ function buildCheckoutPath(cartId, pathPrefix = "/checkout") {
2138
+ const base = pathPrefix.replace(/\/$/, "");
2139
+ return `${base}/${encodeURIComponent(cartId.trim())}`;
2140
+ }
2141
+ function useProceedToCheckout(options = {}) {
2142
+ const {
2143
+ checkoutPathPrefix = "/checkout",
2144
+ currencyCode: currencyCodeOption,
2145
+ salesChannelId = "web",
2146
+ guestEmail: guestEmailOption,
2147
+ syncLocalCart = true,
2148
+ navigate: navigateOption,
2149
+ onError
2150
+ } = options;
2151
+ const storeCurrency = useStore((s) => s.data?.store?.currency ?? DEFAULT_CURRENCY_CODE);
2152
+ const currencyCode = currencyCodeOption ?? storeCurrency;
2153
+ const { useCreateCart } = useStoreCart();
2154
+ const createCartMutation = useCreateCart();
2155
+ const { cartTokenId, setCartTokenId } = useCart(
2156
+ useShallow((s) => ({
2157
+ cartTokenId: s.cartTokenId,
2158
+ setCartTokenId: s.setCartTokenId
2159
+ }))
2160
+ );
2161
+ const [isProceedingToCheckout, setIsProceedingToCheckout] = useState(false);
2162
+ const inFlightRef = useRef(false);
2163
+ const navigate = navigateOption ?? (typeof window !== "undefined" ? (path) => window.location.assign(path) : () => {
2164
+ });
2165
+ const proceedToCheckout = useCallback(async () => {
2166
+ if (inFlightRef.current) return null;
2167
+ inFlightRef.current = true;
2168
+ setIsProceedingToCheckout(true);
2169
+ const fail = (error) => {
2170
+ console.error("Error starting checkout", error);
2171
+ onError?.(error);
2172
+ return null;
2173
+ };
2174
+ try {
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
+ }
2188
+ if (!checkoutCartId) {
2189
+ const guestEmail = guestEmailOption ?? useCart.getState().checkoutCustomerLocal?.customer.email ?? "cambiar@ecomiq.pe";
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
+ }
2200
+ checkoutCartId = created.id;
2201
+ if (!checkoutCartId) {
2202
+ return fail(new Error("Cart created without id"));
2203
+ }
2204
+ setCartTokenId(checkoutCartId);
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
+ }
2218
+ navigate(buildCheckoutPath(checkoutCartId, checkoutPathPrefix));
2219
+ return checkoutCartId;
2220
+ } catch (error) {
2221
+ return fail(error);
2222
+ } finally {
2223
+ inFlightRef.current = false;
2224
+ setIsProceedingToCheckout(false);
2225
+ }
2226
+ }, [
2227
+ cartTokenId,
2228
+ checkoutPathPrefix,
2229
+ createCartMutation,
2230
+ currencyCode,
2231
+ guestEmailOption,
2232
+ navigate,
2233
+ onError,
2234
+ salesChannelId,
2235
+ setCartTokenId,
2236
+ syncLocalCart
2237
+ ]);
2238
+ return {
2239
+ proceedToCheckout,
2240
+ isProceedingToCheckout: isProceedingToCheckout || createCartMutation.isPending,
2241
+ cartTokenId
2242
+ };
2243
+ }
1946
2244
 
1947
2245
  // src/core/utils/format-currency.ts
1948
2246
  function formatCurrency(amount, currency = "USD") {
@@ -1983,12 +2281,12 @@ var zodPathToFieldId = {
1983
2281
  selectedPaymentCode: "checkout-payment-method"
1984
2282
  };
1985
2283
  function buildCheckoutFormValidationSchema(currency) {
1986
- const phoneDigits = currency === "PEN" ? 9 : 10;
2284
+ const phoneDigits = currency === DEFAULT_CURRENCY_CODE ? 9 : 10;
1987
2285
  return z.object({
1988
2286
  email: z.string().trim().min(1, "El email es requerido").email("Ingresa un email v\xE1lido"),
1989
2287
  firstName: z.string().trim().min(1, "El nombre es requerido").min(2, "El nombre debe tener al menos 2 caracteres"),
1990
2288
  lastName: z.string().trim().min(1, "El apellido es requerido").min(2, "El apellido debe tener al menos 2 caracteres"),
1991
- countryCode: z.string().trim().min(2, "Selecciona un pa\xEDs"),
2289
+ countryCode: z.string().trim().min(2, "Selecciona un pa\xEDs").max(2),
1992
2290
  address: z.string().trim().min(1, "La direcci\xF3n es requerida").min(5, "La direcci\xF3n debe tener al menos 5 caracteres"),
1993
2291
  phone: z.string().trim().min(1, "El tel\xE9fono es requerido").regex(/^\d+$/, "El tel\xE9fono solo debe contener n\xFAmeros").length(
1994
2292
  phoneDigits,
@@ -2005,7 +2303,7 @@ function buildCheckoutFormValidationSchema(currency) {
2005
2303
  paymentMethodsCount: z.number().int().nonnegative()
2006
2304
  }).superRefine((data, ctx) => {
2007
2305
  const country = data.countryCode.toUpperCase();
2008
- if (country === "PE") {
2306
+ if (country === DEFAULT_COUNTRY_CODE) {
2009
2307
  if (!data.departmentCode.trim()) {
2010
2308
  ctx.addIssue({
2011
2309
  code: z.ZodIssueCode.custom,
@@ -2122,7 +2420,7 @@ function focusCheckoutField(fieldId) {
2122
2420
  }
2123
2421
 
2124
2422
  // src/react/pages/checkout-page/hooks/use-checkout-form-validation.ts
2125
- function useCheckoutFormValidation(currency = "PEN") {
2423
+ function useCheckoutFormValidation(currency = DEFAULT_CURRENCY_CODE) {
2126
2424
  const schema = useMemo(
2127
2425
  () => buildCheckoutFormValidationSchema(currency),
2128
2426
  [currency]
@@ -2193,7 +2491,9 @@ function loadStorefrontGeoDepartments(headers) {
2193
2491
  if (departmentsCache) return Promise.resolve(departmentsCache);
2194
2492
  if (departmentsRequest) return departmentsRequest;
2195
2493
  departmentsRequest = fetch(
2196
- `/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`,
2197
2497
  { cache: "no-store", headers }
2198
2498
  ).then((r) => r.json()).then((data) => {
2199
2499
  const items = toGeoItems(data);
@@ -2227,10 +2527,10 @@ async function fetchStorefrontGeoDistricts(headers, countryCode, provinceCode) {
2227
2527
  const data = await res.json();
2228
2528
  return toGeoItems(data);
2229
2529
  }
2230
- async function resolveIneiCodesFromLabels(storeId, labels, countryCode = "PE") {
2530
+ async function resolveIneiCodesFromLabels(storeId, labels, countryCode = DEFAULT_COUNTRY_CODE) {
2231
2531
  const id = storeId.trim();
2232
2532
  if (!id) return null;
2233
- const headers = { "X-Store-Id": id };
2533
+ const headers = { [STOREFRONT_HEADERS.storeId]: id };
2234
2534
  const departments = await loadStorefrontGeoDepartments(headers);
2235
2535
  const dept = departments.find((d) => d.name.trim() === labels.department.trim());
2236
2536
  if (!dept) return null;
@@ -2268,12 +2568,12 @@ function useCheckoutDeliveryForm({
2268
2568
  const patchContact = useCallback((patch) => {
2269
2569
  onChangeRef.current(patch);
2270
2570
  }, []);
2271
- const countryCode = (contact.countryCode || "PE").toUpperCase();
2272
- const isPeru = countryCode === "PE";
2571
+ const countryCode = (contact.countryCode || DEFAULT_COUNTRY_CODE).toUpperCase();
2572
+ const isPeru = countryCode === DEFAULT_COUNTRY_CODE;
2273
2573
  const storeRequestHeaders = useMemo(() => {
2274
2574
  const id = storeId.trim();
2275
2575
  if (!id) return null;
2276
- return { "X-Store-Id": id };
2576
+ return { [STOREFRONT_HEADERS.storeId]: id };
2277
2577
  }, [storeId]);
2278
2578
  useEffect(() => {
2279
2579
  if (!isPeru || !storeRequestHeaders) {
@@ -2621,13 +2921,13 @@ function CheckoutDeliveryForm({
2621
2921
  CheckoutSelectField,
2622
2922
  {
2623
2923
  id: "countryCode",
2624
- label: "Country/Region",
2924
+ label: "Pa\xEDs/Regi\xF3n",
2625
2925
  value: contact.countryCode,
2626
2926
  error: getFieldError("countryCode"),
2627
2927
  onClearError: () => clearFieldError("countryCode"),
2628
2928
  onChange: (v) => onContactChange({ countryCode: v }),
2629
2929
  children: [
2630
- /* @__PURE__ */ jsx("option", { value: "PE", children: "Peru" }),
2930
+ /* @__PURE__ */ jsx("option", { value: DEFAULT_COUNTRY_CODE, children: "Per\xFA" }),
2631
2931
  /* @__PURE__ */ jsx("option", { value: "US", children: "United States" }),
2632
2932
  /* @__PURE__ */ jsx("option", { value: "MX", children: "Mexico" }),
2633
2933
  /* @__PURE__ */ jsx("option", { value: "CO", children: "Colombia" })
@@ -3070,7 +3370,7 @@ function StripePaymentProvider({ checkout }) {
3070
3370
  return;
3071
3371
  }
3072
3372
  let cancelled = false;
3073
- const ensureStripeScript = () => new Promise((resolve, reject) => {
3373
+ const loadStripeScript = () => new Promise((resolve, reject) => {
3074
3374
  const maybeStripe = globalThis.Stripe;
3075
3375
  if (maybeStripe) return resolve();
3076
3376
  const existing = document.querySelector('script[src="https://js.stripe.com/v3/"]');
@@ -3095,7 +3395,7 @@ function StripePaymentProvider({ checkout }) {
3095
3395
  document.head.appendChild(script);
3096
3396
  });
3097
3397
  const mountStripePaymentElement = async () => {
3098
- await ensureStripeScript();
3398
+ await loadStripeScript();
3099
3399
  if (cancelled) return;
3100
3400
  const StripeCtor = globalThis.Stripe;
3101
3401
  if (!StripeCtor) throw new Error("Stripe is not available");
@@ -3235,21 +3535,21 @@ function useCheckoutPayment({
3235
3535
  const requiresProviderUi = Boolean(
3236
3536
  selectedMethod && methodUsesProviderCheckoutUi(selectedMethod)
3237
3537
  );
3238
- const effectiveCheckout = checkoutMethodCode && checkoutMethodCode === selectedMethod?.code ? checkoutResponse ?? null : null;
3538
+ const activeCheckout = checkoutMethodCode && checkoutMethodCode === selectedMethod?.code ? checkoutResponse ?? null : null;
3239
3539
  const activeLoadingCheckout = !!checkoutMethodCode && checkoutMethodCode === selectedMethod?.code && loadingCheckout;
3240
3540
  const activeCheckoutError = checkoutMethodCode && checkoutMethodCode === selectedMethod?.code ? checkoutError : null;
3241
3541
  const manualPaymentDetails = useMemo(() => {
3242
3542
  if (!selectedMethod?.isManual) return null;
3243
- const action = getPaymentFromCheckout(effectiveCheckout)?.action ?? null;
3543
+ const action = getPaymentFromCheckout(activeCheckout)?.action ?? null;
3244
3544
  const publicConfiguration = action?.publicConfiguration ?? {};
3245
3545
  const phoneNumber = selectedMethod.phoneNumber ?? publicConfiguration.phoneNumber ?? null;
3246
3546
  const accountHolder = selectedMethod.accountHolder ?? publicConfiguration.accountHolder ?? null;
3247
3547
  const qrImageUrl = action?.qrCode ?? selectedMethod.qrImageUrl ?? publicConfiguration.qrImageUrl ?? null;
3248
3548
  if (!phoneNumber && !accountHolder && !qrImageUrl) return null;
3249
3549
  return { phoneNumber, accountHolder, qrImageUrl };
3250
- }, [selectedMethod, effectiveCheckout]);
3550
+ }, [selectedMethod, activeCheckout]);
3251
3551
  const needsReceipt = Boolean(selectedMethod?.isManual && manualPaymentDetails);
3252
- const showProviderPanel = requiresProviderUi && !!selectedMethod && !!effectiveCheckout && !activeLoadingCheckout && !activeCheckoutError;
3552
+ const showProviderPanel = requiresProviderUi && !!selectedMethod && !!activeCheckout && !activeLoadingCheckout && !activeCheckoutError;
3253
3553
  const [receiptFile, setReceiptFile] = useState(null);
3254
3554
  const [receiptUrl, setReceiptUrl] = useState(null);
3255
3555
  const [receiptError, setReceiptError] = useState(null);
@@ -3387,7 +3687,7 @@ function useCheckoutPayment({
3387
3687
  return {
3388
3688
  activeMethods,
3389
3689
  selectedMethod,
3390
- effectiveCheckout,
3690
+ activeCheckout,
3391
3691
  activeLoadingCheckout,
3392
3692
  activeCheckoutError,
3393
3693
  manualPaymentDetails,
@@ -3515,8 +3815,8 @@ function CheckoutPaymentSectionInner({
3515
3815
  ] })
3516
3816
  ] })
3517
3817
  ] }) }) : null,
3518
- payment.showProviderPanel && payment.effectiveCheckout ? /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__payment-provider", children: [
3519
- /* @__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 }),
3520
3820
  !payment.providerPaymentCompleted ? /* @__PURE__ */ jsx(
3521
3821
  "button",
3522
3822
  {
@@ -3929,128 +4229,6 @@ function CheckoutPageSkeleton({ header }) {
3929
4229
  }
3930
4230
  );
3931
4231
  }
3932
-
3933
- // src/react/pages/checkout-page/lib/cart-mappers.ts
3934
- function mapApiItemsToCartItems(items) {
3935
- if (!items?.length) return [];
3936
- return items.map((raw) => {
3937
- const item = raw;
3938
- const price = Number(item.unitPrice);
3939
- const quantity = Number(item.quantity);
3940
- const variantId = String(item.variantId ?? "");
3941
- if (!Number.isFinite(price) || !Number.isFinite(quantity) || !variantId) return null;
3942
- return {
3943
- id: item.id,
3944
- title: item.title,
3945
- image: String(item.thumbnail ?? ""),
3946
- size: String(item.variantLabel ?? "\u2014"),
3947
- price,
3948
- quantity,
3949
- variantId,
3950
- variantSku: item.variantSku ?? void 0,
3951
- variantLabel: item.variantLabel ?? void 0
3952
- };
3953
- }).filter(Boolean);
3954
- }
3955
- function shippingValuesFromCartAddress(cartData) {
3956
- const addr = cartData.shippingAddress;
3957
- if (!addr) return void 0;
3958
- const geo = addr.geo;
3959
- return {
3960
- address: addr.address1,
3961
- zipCode: addr.postalCode ?? "",
3962
- countryCode: addr.countryCode ?? "PE",
3963
- departmentCode: geo?.departmentCode ?? "",
3964
- departmentName: "",
3965
- provinceCode: geo?.provinceCode ?? "",
3966
- provinceName: addr.province ?? "",
3967
- districtCode: geo?.districtCode ?? "",
3968
- districtName: addr.district ?? addr.city ?? ""
3969
- };
3970
- }
3971
-
3972
- // src/react/pages/checkout-page/lib/cart-sync.ts
3973
- var checkoutReadQueryKey = (cartId) => ["storefront", "cart", cartId, "checkout"];
3974
- function cartItemsSignature(items) {
3975
- return items.map((i) => `${i.variantId ?? i.id}:${i.quantity}`).sort().join("|");
3976
- }
3977
- function apiCartItemsFromResponse(cart) {
3978
- return mapApiItemsToCartItems(cart?.items);
3979
- }
3980
- function groupCartQuantitiesByVariant(items) {
3981
- const map = /* @__PURE__ */ new Map();
3982
- for (const item of items) {
3983
- const variantId = item.variantId ?? item.id;
3984
- map.set(variantId, (map.get(variantId) ?? 0) + Math.max(0, item.quantity));
3985
- }
3986
- return map;
3987
- }
3988
- function groupApiLinesByVariant(items) {
3989
- const map = /* @__PURE__ */ new Map();
3990
- for (const item of items) {
3991
- const variantId = item.variantId ?? item.id;
3992
- const lines = map.get(variantId) ?? [];
3993
- lines.push(item);
3994
- map.set(variantId, lines);
3995
- }
3996
- return map;
3997
- }
3998
- function findCartReconciliationActions(local, api) {
3999
- const actions = [];
4000
- const localQty = groupCartQuantitiesByVariant(local);
4001
- const apiLinesByVariant = groupApiLinesByVariant(api);
4002
- const variantIds = /* @__PURE__ */ new Set([
4003
- ...localQty.keys(),
4004
- ...apiLinesByVariant.keys()
4005
- ]);
4006
- for (const variantId of variantIds) {
4007
- const targetQty = localQty.get(variantId) ?? 0;
4008
- const apiLines = apiLinesByVariant.get(variantId) ?? [];
4009
- const apiQty = apiLines.reduce((sum, line) => sum + line.quantity, 0);
4010
- if (targetQty > apiQty) {
4011
- actions.push({
4012
- action: "add",
4013
- variantId,
4014
- quantity: targetQty - apiQty
4015
- });
4016
- continue;
4017
- }
4018
- if (targetQty >= apiQty) continue;
4019
- let excess = apiQty - targetQty;
4020
- for (const line of [...apiLines].reverse()) {
4021
- if (excess <= 0) break;
4022
- if (line.quantity <= excess) {
4023
- actions.push({ action: "remove", itemId: line.id });
4024
- excess -= line.quantity;
4025
- } else {
4026
- actions.push({
4027
- action: "update",
4028
- itemId: line.id,
4029
- quantity: line.quantity - excess
4030
- });
4031
- excess = 0;
4032
- }
4033
- }
4034
- }
4035
- return actions;
4036
- }
4037
- function areCartSignaturesEqual(a, b) {
4038
- return cartItemsSignature(a) === cartItemsSignature(b);
4039
- }
4040
- function cartSessionSignature(cartTokenId, items) {
4041
- return `${cartTokenId ?? ""}|${cartItemsSignature(items)}`;
4042
- }
4043
- function readCartCheckoutStatus(cart) {
4044
- if (!cart) return null;
4045
- const status = cart.status ?? null;
4046
- return typeof status === "string" && status.trim() ? status.trim() : null;
4047
- }
4048
- function isCheckoutCartAlreadyPurchased(cart, localCartItemCount) {
4049
- if (localCartItemCount > 0) return false;
4050
- const status = readCartCheckoutStatus(cart);
4051
- if (!status) return false;
4052
- return status.toLowerCase() === "inprogress";
4053
- }
4054
4232
  function useCartListener({
4055
4233
  cartId,
4056
4234
  enabled,
@@ -4098,7 +4276,7 @@ function useCartListener({
4098
4276
  function useCheckoutSession(cartId) {
4099
4277
  const queryClient = useQueryClient();
4100
4278
  const storeId = useStore((s) => s.data?.store?.id ?? "");
4101
- const currency = useStore((s) => s.data?.store?.currency ?? "PEN");
4279
+ const currency = useStore((s) => s.data?.store?.currency ?? DEFAULT_CURRENCY_CODE);
4102
4280
  const { setCartTokenId, clearCartSession, cart: localCart } = useCart(
4103
4281
  useShallow((s) => ({
4104
4282
  setCartTokenId: s.setCartTokenId,
@@ -4168,7 +4346,7 @@ function useCheckoutSession(cartId) {
4168
4346
  province: "",
4169
4347
  postalCode: "",
4170
4348
  phone: "",
4171
- countryCode: "PE",
4349
+ countryCode: DEFAULT_COUNTRY_CODE,
4172
4350
  departmentCode: "",
4173
4351
  departmentName: "",
4174
4352
  provinceCode: "",
@@ -4337,7 +4515,7 @@ function useCheckoutSession(cartId) {
4337
4515
  }
4338
4516
  });
4339
4517
  mergeCartIntoCheckoutCache(buyerCart);
4340
- const countryCode = (contact.countryCode || "PE").toUpperCase();
4518
+ const countryCode = (contact.countryCode || DEFAULT_COUNTRY_CODE).toUpperCase();
4341
4519
  const districtName = contact.districtName || contact.city;
4342
4520
  const provinceName = contact.provinceName || contact.province;
4343
4521
  const baseAddress = {
@@ -4760,6 +4938,96 @@ function CheckoutPage({
4760
4938
  ] })
4761
4939
  ] });
4762
4940
  }
4941
+ function DefaultLink2({ href, className, children }) {
4942
+ return /* @__PURE__ */ jsx("a", { href, className, children });
4943
+ }
4944
+ function formatOrderLabel(orderNumber) {
4945
+ const trimmed = orderNumber.trim();
4946
+ if (!trimmed) return trimmed;
4947
+ return trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
4948
+ }
4949
+ function ThankYouPage({
4950
+ orderNumber,
4951
+ email,
4952
+ storeName: storeNameProp,
4953
+ logoUrl,
4954
+ logoHref = "/",
4955
+ homeHref = "/",
4956
+ Link: LinkComponent = DefaultLink2
4957
+ }) {
4958
+ const Link = LinkComponent;
4959
+ const brandingName = useStore((s) => s.data?.store?.name);
4960
+ const logoFromCms = useStore(
4961
+ (s) => s.data?.cms?.settings?.branding?.logoUrl
4962
+ );
4963
+ const storeName = storeNameProp ?? brandingName ?? "Store";
4964
+ const resolvedLogo = logoUrl ?? logoFromCms;
4965
+ const orderLabel = orderNumber?.trim() ? formatOrderLabel(orderNumber) : null;
4966
+ const emailLabel = email?.trim() || null;
4967
+ return /* @__PURE__ */ jsxs("div", { className: "ecomiq-thank-you-page", children: [
4968
+ /* @__PURE__ */ jsx("header", { className: "ecomiq-thank-you-page__top", children: /* @__PURE__ */ jsx(Link, { href: logoHref, className: "ecomiq-thank-you-page__logo", children: resolvedLogo ? /* @__PURE__ */ jsx("img", { src: resolvedLogo, alt: storeName }) : storeName }) }),
4969
+ /* @__PURE__ */ jsx("main", { className: "ecomiq-thank-you-page__main", children: /* @__PURE__ */ jsxs("div", { className: "ecomiq-thank-you-page__card", children: [
4970
+ /* @__PURE__ */ jsx("div", { className: "ecomiq-thank-you-page__icon", "aria-hidden": true, children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", children: /* @__PURE__ */ jsx(
4971
+ "path",
4972
+ {
4973
+ d: "M5 13l4 4L19 7",
4974
+ strokeLinecap: "round",
4975
+ strokeLinejoin: "round"
4976
+ }
4977
+ ) }) }),
4978
+ /* @__PURE__ */ jsx("h1", { className: "ecomiq-thank-you-page__title", children: "Thank you for your purchase!" }),
4979
+ orderLabel ? /* @__PURE__ */ jsxs("p", { className: "ecomiq-thank-you-page__text", children: [
4980
+ "We received your order ",
4981
+ /* @__PURE__ */ jsx("strong", { children: orderLabel }),
4982
+ " and we're working on it."
4983
+ ] }) : /* @__PURE__ */ jsx("p", { className: "ecomiq-thank-you-page__text", children: "We received your order and we're working on it." }),
4984
+ emailLabel ? /* @__PURE__ */ jsxs("p", { className: "ecomiq-thank-you-page__text ecomiq-thank-you-page__text--muted", children: [
4985
+ "You should receive a confirmation email sent to",
4986
+ " ",
4987
+ /* @__PURE__ */ jsx("strong", { children: emailLabel })
4988
+ ] }) : /* @__PURE__ */ jsx("p", { className: "ecomiq-thank-you-page__text ecomiq-thank-you-page__text--muted", children: "You should receive a confirmation email shortly." }),
4989
+ /* @__PURE__ */ jsx("div", { className: "ecomiq-thank-you-page__actions", children: /* @__PURE__ */ jsx(Link, { href: homeHref, className: "ecomiq-thank-you-page__link", children: "Continue shopping" }) })
4990
+ ] }) })
4991
+ ] });
4992
+ }
4993
+
4994
+ // src/react/pages/thank-you-page/lib/thank-you-url.ts
4995
+ function buildCheckoutThankYouUrl(pathname, params) {
4996
+ const q = new URLSearchParams();
4997
+ if (params.orderId?.trim()) q.set("order", params.orderId.trim());
4998
+ if (params.email?.trim()) q.set("email", params.email.trim());
4999
+ if (params.transactionId?.trim()) q.set("txn", params.transactionId.trim());
5000
+ const query = q.toString();
5001
+ return query ? `${pathname}?${query}` : pathname;
5002
+ }
5003
+ function orderIdFromCheckoutResult(result) {
5004
+ if (result.orderId != null && String(result.orderId).trim()) {
5005
+ return String(result.orderId).trim();
5006
+ }
5007
+ const first = result.orderIds?.[0];
5008
+ if (first != null && String(first).trim()) return String(first).trim();
5009
+ const ref = result.payment?.reference?.trim();
5010
+ if (ref) return ref;
5011
+ const txn = result.payment?.transactionId?.trim();
5012
+ if (txn) return txn;
5013
+ return void 0;
5014
+ }
5015
+ function emailFromCheckoutResult(result) {
5016
+ const cart = result.cart;
5017
+ const fromBuyer = cart?.buyer?.email?.trim();
5018
+ if (fromBuyer) return fromBuyer;
5019
+ const direct = cart?.email?.trim();
5020
+ return direct || void 0;
5021
+ }
5022
+ function thankYouParamsFromCheckoutResult(result, overrides) {
5023
+ return {
5024
+ orderId: overrides?.orderId ?? orderIdFromCheckoutResult(result),
5025
+ email: overrides?.email ?? emailFromCheckoutResult(result),
5026
+ transactionId: overrides?.transactionId ?? result.payment?.transactionId ?? void 0
5027
+ };
5028
+ }
5029
+
5030
+ // src/react/shell/components/product-variant-selector/product-variant-selection.ts
4763
5031
  function buildOptionGroups(variants) {
4764
5032
  const map = /* @__PURE__ */ new Map();
4765
5033
  for (const v of variants) {
@@ -4799,6 +5067,54 @@ function getInitialOptions(variants, optionGroups) {
4799
5067
  }
4800
5068
  return selected;
4801
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
+ }
4802
5118
  function ProductVariantSelector({
4803
5119
  product,
4804
5120
  currency = "USD",
@@ -4868,7 +5184,180 @@ function ProductVariantSelector({
4868
5184
  })
4869
5185
  ] });
4870
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
+ }
4871
5360
 
4872
- export { CheckoutPage, CustomerAddressSchema, CustomerAddressUpsertSchema, CustomerMeSchema, CustomerOrderDetailSchema, CustomerOrderListItemSchema, CustomerProfilePatchSchema, CustomerRegisterRequestSchema, EcomiqProvider, ProductVariantSelector, QueryProvider, StoreApiClient, StorefrontAuthProvider, addCartCoupon, checkoutCart, createCart, createCustomerAddress, createStoreApiClient, customerMeToProfilePatch, deleteCustomerAddress, ecomiqOAuthPasswordClient, getCart, getCartCheckout, getCartPaymentMethods, getCustomerAddresses, getCustomerMe, getCustomerOrder, getCustomerOrders, getSharedOrderDraft, getShippingOptions, getStoreApiBaseUrl, getStoreApiClient, getStorefrontCollectionBySlug, getStorefrontCollections, getStorefrontPageBySlug, getStorefrontPagePreview, getStorefrontPages, getStorefrontProductBySlug, getStorefrontProducts, isCartNotActiveApiError, patchCartAddresses, patchCartBuyer, patchCustomer, putCartShippingMethod, registerCustomer, removeCartCoupon, setClientStoreId, setServerRequestOrigin, setServerStoreId, signInWithEcomiqCredentials, storeApiClient, storefrontAccessTokenNeedsRefresh, storefrontSessionEndsSoon, updateCustomerAddress, uploadPaymentReceipt, useCart, useCheckoutSession, 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 };
4873
5362
  //# sourceMappingURL=client.js.map
4874
5363
  //# sourceMappingURL=client.js.map