@ecomiq/storefront 1.1.4 → 1.1.9

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 (44) hide show
  1. package/README.md +3 -2
  2. package/dist/{chunk-EADDSNJS.js → chunk-64GLOWMJ.js} +14 -4
  3. package/dist/chunk-64GLOWMJ.js.map +1 -0
  4. package/dist/{chunk-3URTJY6X.js → chunk-AKD5IDEB.js} +16 -13
  5. package/dist/chunk-AKD5IDEB.js.map +1 -0
  6. package/dist/{chunk-YSD62KQM.js → chunk-EFEVJJWF.js} +102 -36
  7. package/dist/chunk-EFEVJJWF.js.map +1 -0
  8. package/dist/{chunk-6TMU4OXP.js → chunk-NAJQB5DN.js} +5 -7
  9. package/dist/chunk-NAJQB5DN.js.map +1 -0
  10. package/dist/{chunk-ASFU32ZM.js → chunk-OABDGTBI.js} +25 -8
  11. package/dist/chunk-OABDGTBI.js.map +1 -0
  12. package/dist/chunk-Q4U5OEUT.js +21 -0
  13. package/dist/chunk-Q4U5OEUT.js.map +1 -0
  14. package/dist/chunks/client.css +1 -1
  15. package/dist/chunks/client.css.map +1 -1
  16. package/dist/chunks/client.d.ts +1456 -228
  17. package/dist/chunks/client.js +559 -200
  18. package/dist/chunks/client.js.map +1 -1
  19. package/dist/chunks/config.js +3 -2
  20. package/dist/chunks/core-public.d.ts +2 -2
  21. package/dist/chunks/core-public.js +6 -12
  22. package/dist/chunks/core-public.js.map +1 -1
  23. package/dist/chunks/server-nextjs.d.ts +3 -3
  24. package/dist/chunks/server-nextjs.js +40 -14
  25. package/dist/chunks/server-nextjs.js.map +1 -1
  26. package/dist/chunks/server-tanstack.d.ts +3 -3
  27. package/dist/chunks/server-tanstack.js +15 -8
  28. package/dist/chunks/server-tanstack.js.map +1 -1
  29. package/dist/{collection-BHD4hhQa.d.ts → collection-DU7gREXS.d.ts} +491 -38
  30. package/dist/{ecomiq-oauth-password-plugin-gw2dPyYb.d.ts → ecomiq-oauth-password-plugin-B_gMdGuc.d.ts} +33 -31
  31. package/dist/nextjs/api.js +2 -1
  32. package/dist/nextjs/api.js.map +1 -1
  33. package/dist/nextjs.server.d.ts +17 -0
  34. package/dist/react/client.css +1 -1
  35. package/dist/tanstack/api.js +3 -2
  36. package/dist/tanstack/api.js.map +1 -1
  37. package/dist/tanstack.d.ts +0 -1
  38. package/dist/tanstack.server.d.ts +17 -0
  39. package/package.json +13 -3
  40. package/dist/chunk-3URTJY6X.js.map +0 -1
  41. package/dist/chunk-6TMU4OXP.js.map +0 -1
  42. package/dist/chunk-ASFU32ZM.js.map +0 -1
  43. package/dist/chunk-EADDSNJS.js.map +0 -1
  44. 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;
@@ -62,14 +78,13 @@ var StoreApiClient = class {
62
78
  if (!isFormData) {
63
79
  headers.set("Content-Type", "application/json");
64
80
  }
65
- headers.set("Accept-Encoding", "identity");
66
81
  const explicitStoreId = options?.xStoreId?.trim();
67
82
  const runtimeStoreId = explicitStoreId || (typeof window === "undefined" ? serverStoreIdGetter : clientStoreIdGetter);
68
- if (runtimeStoreId && !headers.has("x-store-id")) {
69
- headers.set("X-Store-Id", runtimeStoreId);
83
+ if (runtimeStoreId && !headers.has(STOREFRONT_HEADERS.storeIdLower)) {
84
+ headers.set(STOREFRONT_HEADERS.storeId, runtimeStoreId);
70
85
  }
71
86
  if (options?.xStoreDomain) {
72
- headers.set("X-Store-Domain", options.xStoreDomain);
87
+ headers.set(STOREFRONT_HEADERS.storeDomain, options.xStoreDomain);
73
88
  }
74
89
  if (options?.xCartId) {
75
90
  headers.set("X-Cart-Token", options.xCartId);
@@ -242,7 +257,19 @@ var StorefrontProductSchema = z.object({
242
257
  });
243
258
  var StorefrontFacetSchema = z.object({
244
259
  value: z.string(),
245
- count: z.number().int().nonnegative()
260
+ count: z.number().int().nonnegative(),
261
+ label: z.string().optional().nullable(),
262
+ selected: z.boolean().optional().default(false)
263
+ });
264
+ var StorefrontSearchFilterSchema = z.object({
265
+ id: z.string(),
266
+ label: z.string(),
267
+ type: z.string(),
268
+ values: z.array(StorefrontFacetSchema).optional().default([]),
269
+ min: z.number().nonnegative().optional().nullable(),
270
+ max: z.number().nonnegative().optional().nullable(),
271
+ selectedMin: z.number().nonnegative().optional().nullable(),
272
+ selectedMax: z.number().nonnegative().optional().nullable()
246
273
  });
247
274
  var StorefrontProductsResponseSchema = z.object({
248
275
  pageIndex: z.number().int().positive(),
@@ -250,10 +277,7 @@ var StorefrontProductsResponseSchema = z.object({
250
277
  totalCount: z.number().int().nonnegative(),
251
278
  totalPages: z.number().int().nonnegative(),
252
279
  products: z.array(StorefrontProductSchema),
253
- categories: z.array(StorefrontFacetSchema).optional().default([]),
254
- brands: z.array(StorefrontFacetSchema).optional().default([]),
255
- minPrice: z.number().nonnegative().optional().nullable(),
256
- maxPrice: z.number().nonnegative().optional().nullable()
280
+ filters: z.array(StorefrontSearchFilterSchema).optional().default([])
257
281
  });
258
282
  var StorefrontProductsParamsSchema = z.object({
259
283
  page: z.number().int().positive().optional().default(1),
@@ -262,9 +286,17 @@ var StorefrontProductsParamsSchema = z.object({
262
286
  sellerId: z.string().optional(),
263
287
  search: z.string().optional(),
264
288
  collection: z.string().optional(),
289
+ collections: z.array(z.string()).optional(),
265
290
  category: z.string().optional(),
291
+ categories: z.array(z.string()).optional(),
266
292
  brand: z.string().optional(),
293
+ brands: z.array(z.string()).optional(),
267
294
  productType: z.string().optional(),
295
+ productTypes: z.array(z.string()).optional(),
296
+ tag: z.string().optional(),
297
+ tags: z.array(z.string()).optional(),
298
+ filters: z.array(z.string()).optional(),
299
+ locationId: z.string().optional(),
268
300
  inStock: z.boolean().optional(),
269
301
  minPrice: z.number().nonnegative().optional(),
270
302
  maxPrice: z.number().nonnegative().optional()
@@ -343,25 +375,42 @@ async function getStorefrontProducts(params, cacheKey) {
343
375
  if (parsed.sellerId) searchParams.set("sellerId", parsed.sellerId);
344
376
  if (parsed.search) searchParams.set("search", parsed.search);
345
377
  if (parsed.collection) searchParams.set("collection", parsed.collection);
378
+ appendValues(searchParams, "collections", parsed.collections);
346
379
  if (parsed.category) searchParams.set("category", parsed.category);
380
+ appendValues(searchParams, "categories", parsed.categories);
347
381
  if (parsed.brand) searchParams.set("brand", parsed.brand);
382
+ appendValues(searchParams, "brands", parsed.brands);
348
383
  if (parsed.productType) searchParams.set("productType", parsed.productType);
384
+ appendValues(searchParams, "productTypes", parsed.productTypes);
385
+ if (parsed.tag) searchParams.set("tag", parsed.tag);
386
+ appendValues(searchParams, "tags", parsed.tags);
387
+ appendValues(searchParams, "filters", parsed.filters);
388
+ if (parsed.locationId) searchParams.set("locationId", parsed.locationId);
349
389
  if (parsed.inStock != null) searchParams.set("inStock", String(parsed.inStock));
350
390
  if (parsed.minPrice != null) searchParams.set("minPrice", String(parsed.minPrice));
351
391
  if (parsed.maxPrice != null) searchParams.set("maxPrice", String(parsed.maxPrice));
352
392
  const path = `products?${searchParams.toString()}`;
353
393
  const client = await getStoreApiClient();
354
394
  const response = await client.get(path, {
355
- cacheKey
395
+ cacheKey,
396
+ noStore: !cacheKey
356
397
  });
357
398
  return StorefrontProductsResponseSchema.parse(response);
358
399
  }
359
- async function getStorefrontProductBySlug(slug) {
400
+ async function getStorefrontProductBySlug(slug, options) {
360
401
  const path = `products/${encodeURIComponent(slug)}`;
361
402
  const client = await getStoreApiClient();
362
- const response = await client.get(path);
403
+ const response = await client.get(path, {
404
+ cacheKey: options?.cacheKey,
405
+ noStore: !options?.cacheKey
406
+ });
363
407
  return StorefrontProductDetailSchema.parse(response);
364
408
  }
409
+ function appendValues(searchParams, key, values) {
410
+ for (const value of values ?? []) {
411
+ if (value) searchParams.append(key, value);
412
+ }
413
+ }
365
414
  var StorefrontCollectionSchema = z.object({
366
415
  id: z.coerce.string(),
367
416
  slug: z.string(),
@@ -455,7 +504,7 @@ var CartAddressSchema = z.object({
455
504
  name: z.string().min(1),
456
505
  address1: z.string().min(1),
457
506
  city: z.string().min(1),
458
- countryCode: z.string().min(2),
507
+ countryCode: z.string().min(2).max(2),
459
508
  company: z.string().nullable().optional(),
460
509
  address2: z.string().nullable().optional(),
461
510
  province: z.string().nullable().optional(),
@@ -500,7 +549,7 @@ z.object({
500
549
  name: z.string().nullable().optional(),
501
550
  address1: z.string(),
502
551
  city: z.string(),
503
- countryCode: z.string(),
552
+ countryCode: z.string().min(2).max(2),
504
553
  company: z.string().nullable().optional(),
505
554
  address2: z.string().nullable().optional(),
506
555
  province: z.string().nullable().optional(),
@@ -914,7 +963,7 @@ function customerMeToProfilePatch(me) {
914
963
  companyName: me.companyName,
915
964
  dateOfBirth: me.dateOfBirth,
916
965
  consent: me.consent,
917
- countryIsoCode: me.countryIsoCode || "PE"
966
+ countryIsoCode: me.countryIsoCode || DEFAULT_COUNTRY_CODE
918
967
  };
919
968
  }
920
969
  function customerAuthOptions(accessToken) {
@@ -977,7 +1026,7 @@ var CustomerOrderShippingAddressSchema = z.object({
977
1026
  address1: z.string(),
978
1027
  address2: z.string().nullable().optional(),
979
1028
  city: z.string().nullable().optional(),
980
- countryCode: z.string(),
1029
+ countryCode: z.string().min(2).max(2),
981
1030
  province: z.string().nullable().optional(),
982
1031
  postalCode: z.string().nullable().optional(),
983
1032
  district: z.string().nullable().optional(),
@@ -1142,7 +1191,7 @@ var OrderDraftSchema = z.object({
1142
1191
  customerId: z.coerce.string().nullable().optional(),
1143
1192
  cartId: z.string().nullable().optional(),
1144
1193
  draftNumber: z.string(),
1145
- currencyCode: z.string().optional().default("PEN"),
1194
+ currencyCode: z.string().optional().default(DEFAULT_CURRENCY_CODE),
1146
1195
  source: z.string().nullable().optional(),
1147
1196
  notes: z.string().nullable().optional(),
1148
1197
  email: z.string().nullable().optional(),
@@ -1290,7 +1339,9 @@ function useStoreProducts() {
1290
1339
  const useStorefrontProductsList = (params) => {
1291
1340
  return useQuery({
1292
1341
  queryKey: ["storefront", "products", params],
1293
- queryFn: () => getStorefrontProducts(params)
1342
+ queryFn: () => getStorefrontProducts(params),
1343
+ staleTime: 6e4,
1344
+ gcTime: 5 * 6e4
1294
1345
  });
1295
1346
  };
1296
1347
  const useStorefrontInfiniteProducts = (params) => {
@@ -1298,14 +1349,18 @@ function useStoreProducts() {
1298
1349
  queryKey: ["storefront", "products", "infinite", params],
1299
1350
  queryFn: ({ pageParam = 1 }) => getStorefrontProducts({ ...params, page: pageParam }),
1300
1351
  getNextPageParam: (lastPage) => lastPage.pageIndex < lastPage.totalPages ? lastPage.pageIndex + 1 : void 0,
1301
- initialPageParam: 1
1352
+ initialPageParam: 1,
1353
+ staleTime: 6e4,
1354
+ gcTime: 5 * 6e4
1302
1355
  });
1303
1356
  };
1304
1357
  const useProductBySlug = (slug) => {
1305
1358
  return useQuery({
1306
1359
  queryKey: ["storefront", "product", "slug", slug],
1307
1360
  queryFn: () => getStorefrontProductBySlug(slug),
1308
- enabled: !!slug
1361
+ enabled: !!slug,
1362
+ staleTime: 5 * 6e4,
1363
+ gcTime: 10 * 6e4
1309
1364
  });
1310
1365
  };
1311
1366
  const useRefreshStorefrontProducts = () => {
@@ -1757,8 +1812,17 @@ var StorefrontCmsCustomScriptsSchema = z.object({
1757
1812
  head: z.string().nullable().optional(),
1758
1813
  bodyEnd: z.string().nullable().optional()
1759
1814
  });
1815
+ var StorefrontCmsChannelsSchema = z.object({
1816
+ storefront: z.object({
1817
+ enabled: z.boolean().optional()
1818
+ }).optional(),
1819
+ pos: z.object({
1820
+ enabled: z.boolean().optional()
1821
+ }).optional()
1822
+ });
1760
1823
  var StorefrontCmsSettingsSchema = z.object({
1761
1824
  themeKey: z.string().optional(),
1825
+ channels: StorefrontCmsChannelsSchema.optional(),
1762
1826
  branding: StorefrontCmsBrandingSchema.optional(),
1763
1827
  seo: StorefrontCmsSeoSchema.optional(),
1764
1828
  contact: StorefrontCmsContactSchema.optional(),
@@ -1770,6 +1834,7 @@ var StorefrontCmsPageSchema = z.object({
1770
1834
  id: z.string(),
1771
1835
  name: z.string(),
1772
1836
  slug: z.string(),
1837
+ channel: z.string().transform((value) => value.toLowerCase()).pipe(z.enum(["storefront", "pos"])).default("storefront"),
1773
1838
  layout: z.string().optional(),
1774
1839
  version: z.number().optional(),
1775
1840
  settings: z.record(z.string(), z.unknown()).optional().nullable(),
@@ -1821,7 +1886,7 @@ function normalizeStorefrontSettingsPayload(v) {
1821
1886
  const parsed = StorefrontSettingsLegacyFlatSchema.safeParse(raw);
1822
1887
  if (!parsed.success) return body;
1823
1888
  const l = parsed.data;
1824
- const currency = l.currency?.trim() ? l.currency : "PEN";
1889
+ const currency = l.currency?.trim() ? l.currency : DEFAULT_CURRENCY_CODE;
1825
1890
  const language = l.language?.trim() ? l.language : "es";
1826
1891
  const slug = l.slug?.trim() ? l.slug : l.id;
1827
1892
  return {
@@ -1916,10 +1981,10 @@ function buildEcomiqOAuthIdentityHeaders(settings) {
1916
1981
  const checkout = record.checkoutSettings;
1917
1982
  const identityScope = checkout?.identityScope === "tenant" ? "tenant" : "store";
1918
1983
  const headers = {
1919
- "x-Identity-Scope": identityScope,
1920
- "x-Store-Id": record.id
1984
+ [STOREFRONT_HEADERS.identityScope]: identityScope,
1985
+ [STOREFRONT_HEADERS.storeId]: record.id
1921
1986
  };
1922
- if (record.tenantId) headers["x-Tenant-Id"] = record.tenantId;
1987
+ if (record.tenantId) headers[STOREFRONT_HEADERS.tenantId] = record.tenantId;
1923
1988
  return headers;
1924
1989
  }
1925
1990
 
@@ -1935,6 +2000,175 @@ async function signInWithEcomiqCredentials(authClient, settings, credentials) {
1935
2000
  }
1936
2001
  return response;
1937
2002
  }
2003
+
2004
+ // src/react/pages/checkout-page/lib/cart-mappers.ts
2005
+ function mapApiItemsToCartItems(items) {
2006
+ if (!items?.length) return [];
2007
+ return items.map((raw) => {
2008
+ const item = raw;
2009
+ const price = Number(item.unitPrice);
2010
+ const quantity = Number(item.quantity);
2011
+ const variantId = String(item.variantId ?? "");
2012
+ if (!Number.isFinite(price) || !Number.isFinite(quantity) || !variantId) return null;
2013
+ return {
2014
+ id: item.id,
2015
+ title: item.title,
2016
+ image: String(item.thumbnail ?? ""),
2017
+ size: String(item.variantLabel ?? "\u2014"),
2018
+ price,
2019
+ quantity,
2020
+ variantId,
2021
+ variantSku: item.variantSku ?? void 0,
2022
+ variantLabel: item.variantLabel ?? void 0
2023
+ };
2024
+ }).filter(Boolean);
2025
+ }
2026
+ function shippingValuesFromCartAddress(cartData) {
2027
+ const addr = cartData.shippingAddress;
2028
+ if (!addr) return void 0;
2029
+ const geo = addr.geo;
2030
+ return {
2031
+ address: addr.address1,
2032
+ zipCode: addr.postalCode ?? "",
2033
+ countryCode: addr.countryCode ?? DEFAULT_COUNTRY_CODE,
2034
+ departmentCode: geo?.departmentCode ?? "",
2035
+ departmentName: "",
2036
+ provinceCode: geo?.provinceCode ?? "",
2037
+ provinceName: addr.province ?? "",
2038
+ districtCode: geo?.districtCode ?? "",
2039
+ districtName: addr.district ?? addr.city ?? ""
2040
+ };
2041
+ }
2042
+
2043
+ // src/react/pages/checkout-page/lib/cart-sync.ts
2044
+ var checkoutReadQueryKey = (cartId) => ["storefront", "cart", cartId, "checkout"];
2045
+ function cartItemsSignature(items) {
2046
+ return items.map((i) => `${i.variantId ?? i.id}:${i.quantity}`).sort().join("|");
2047
+ }
2048
+ function apiCartItemsFromResponse(cart) {
2049
+ return mapApiItemsToCartItems(cart?.items);
2050
+ }
2051
+ function groupCartQuantitiesByVariant(items) {
2052
+ const map = /* @__PURE__ */ new Map();
2053
+ for (const item of items) {
2054
+ const variantId = item.variantId ?? item.id;
2055
+ map.set(variantId, (map.get(variantId) ?? 0) + Math.max(0, item.quantity));
2056
+ }
2057
+ return map;
2058
+ }
2059
+ function groupApiLinesByVariant(items) {
2060
+ const map = /* @__PURE__ */ new Map();
2061
+ for (const item of items) {
2062
+ const variantId = item.variantId ?? item.id;
2063
+ const lines = map.get(variantId) ?? [];
2064
+ lines.push(item);
2065
+ map.set(variantId, lines);
2066
+ }
2067
+ return map;
2068
+ }
2069
+ function findCartReconciliationActions(local, api) {
2070
+ const actions = [];
2071
+ const localQty = groupCartQuantitiesByVariant(local);
2072
+ const apiLinesByVariant = groupApiLinesByVariant(api);
2073
+ const variantIds = /* @__PURE__ */ new Set([
2074
+ ...localQty.keys(),
2075
+ ...apiLinesByVariant.keys()
2076
+ ]);
2077
+ for (const variantId of variantIds) {
2078
+ const targetQty = localQty.get(variantId) ?? 0;
2079
+ const apiLines = apiLinesByVariant.get(variantId) ?? [];
2080
+ const apiQty = apiLines.reduce((sum, line) => sum + line.quantity, 0);
2081
+ if (targetQty > apiQty) {
2082
+ actions.push({
2083
+ action: "add",
2084
+ variantId,
2085
+ quantity: targetQty - apiQty
2086
+ });
2087
+ continue;
2088
+ }
2089
+ if (targetQty >= apiQty) continue;
2090
+ let excess = apiQty - targetQty;
2091
+ for (const line of [...apiLines].reverse()) {
2092
+ if (excess <= 0) break;
2093
+ if (line.quantity <= excess) {
2094
+ actions.push({ action: "remove", itemId: line.id });
2095
+ excess -= line.quantity;
2096
+ } else {
2097
+ actions.push({
2098
+ action: "update",
2099
+ itemId: line.id,
2100
+ quantity: line.quantity - excess
2101
+ });
2102
+ excess = 0;
2103
+ }
2104
+ }
2105
+ }
2106
+ return actions;
2107
+ }
2108
+ function areCartSignaturesEqual(a, b) {
2109
+ return cartItemsSignature(a) === cartItemsSignature(b);
2110
+ }
2111
+ function cartSessionSignature(cartTokenId, items) {
2112
+ return `${cartTokenId ?? ""}|${cartItemsSignature(items)}`;
2113
+ }
2114
+ function readCartCheckoutStatus(cart) {
2115
+ if (!cart) return null;
2116
+ const status = cart.status ?? null;
2117
+ return typeof status === "string" && status.trim() ? status.trim() : null;
2118
+ }
2119
+ function isCheckoutCartAlreadyPurchased(cart, localCartItemCount) {
2120
+ if (localCartItemCount > 0) return false;
2121
+ const status = readCartCheckoutStatus(cart);
2122
+ if (!status) return false;
2123
+ return status.toLowerCase() === "inprogress";
2124
+ }
2125
+
2126
+ // src/react/lib/reconcile-local-cart-to-api.ts
2127
+ async function reconcileLocalCartWithApi(cartId, localCart, options) {
2128
+ let cart = await getCart(cartId);
2129
+ if (localCart.length === 0) return cart;
2130
+ let apiItems = apiCartItemsFromResponse(cart);
2131
+ if (areCartSignaturesEqual(localCart, apiItems)) return cart;
2132
+ const actions = findCartReconciliationActions(localCart, apiItems);
2133
+ const reconciliationErrors = [];
2134
+ for (const action of actions) {
2135
+ if (action.action !== "remove" && action.action !== "update") continue;
2136
+ try {
2137
+ cart = action.action === "remove" ? await deleteCartItem(cartId, action.itemId) : await updateCartItem(cartId, action.itemId, {
2138
+ quantity: action.quantity
2139
+ });
2140
+ apiItems = apiCartItemsFromResponse(cart);
2141
+ } catch (error) {
2142
+ {
2143
+ reconciliationErrors.push(error);
2144
+ }
2145
+ }
2146
+ }
2147
+ for (const action of actions) {
2148
+ if (action.action !== "add") continue;
2149
+ try {
2150
+ cart = await addCartItem(
2151
+ { variantId: action.variantId, quantity: action.quantity },
2152
+ cartId
2153
+ );
2154
+ apiItems = apiCartItemsFromResponse(cart);
2155
+ } catch (error) {
2156
+ {
2157
+ reconciliationErrors.push(error);
2158
+ }
2159
+ }
2160
+ }
2161
+ if (reconciliationErrors.length > 0) {
2162
+ const first = reconciliationErrors[0];
2163
+ throw first instanceof Error ? first : new Error("No se pudo sincronizar el carrito con el servidor.");
2164
+ }
2165
+ return cart;
2166
+ }
2167
+ function isCartNotFoundError(error) {
2168
+ return error.status === 404;
2169
+ }
2170
+
2171
+ // src/react/hooks/use-proceed-to-checkout.ts
1938
2172
  function buildCheckoutPath(cartId, pathPrefix = "/checkout") {
1939
2173
  const base = pathPrefix.replace(/\/$/, "");
1940
2174
  return `${base}/${encodeURIComponent(cartId.trim())}`;
@@ -1945,10 +2179,11 @@ function useProceedToCheckout(options = {}) {
1945
2179
  currencyCode: currencyCodeOption,
1946
2180
  salesChannelId = "web",
1947
2181
  guestEmail: guestEmailOption,
2182
+ syncLocalCart = true,
1948
2183
  navigate: navigateOption,
1949
2184
  onError
1950
2185
  } = options;
1951
- const storeCurrency = useStore((s) => s.data?.store?.currency ?? "PEN");
2186
+ const storeCurrency = useStore((s) => s.data?.store?.currency ?? DEFAULT_CURRENCY_CODE);
1952
2187
  const currencyCode = currencyCodeOption ?? storeCurrency;
1953
2188
  const { useCreateCart } = useStoreCart();
1954
2189
  const createCartMutation = useCreateCart();
@@ -1959,39 +2194,68 @@ function useProceedToCheckout(options = {}) {
1959
2194
  }))
1960
2195
  );
1961
2196
  const [isProceedingToCheckout, setIsProceedingToCheckout] = useState(false);
2197
+ const inFlightRef = useRef(false);
1962
2198
  const navigate = navigateOption ?? (typeof window !== "undefined" ? (path) => window.location.assign(path) : () => {
1963
2199
  });
1964
2200
  const proceedToCheckout = useCallback(async () => {
1965
- if (isProceedingToCheckout || createCartMutation.isPending) {
1966
- return cartTokenId;
1967
- }
2201
+ if (inFlightRef.current) return null;
2202
+ inFlightRef.current = true;
1968
2203
  setIsProceedingToCheckout(true);
2204
+ const fail = (error) => {
2205
+ console.error("Error starting checkout", error);
2206
+ onError?.(error);
2207
+ return null;
2208
+ };
1969
2209
  try {
1970
2210
  let checkoutCartId = cartTokenId;
2211
+ if (checkoutCartId) {
2212
+ try {
2213
+ await getCart(checkoutCartId);
2214
+ } catch (error) {
2215
+ if (isCartNotFoundError(error)) {
2216
+ checkoutCartId = null;
2217
+ setCartTokenId(null);
2218
+ } else {
2219
+ return fail(error);
2220
+ }
2221
+ }
2222
+ }
1971
2223
  if (!checkoutCartId) {
1972
2224
  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
- });
2225
+ let created;
2226
+ try {
2227
+ created = await createCartMutation.mutateAsync({
2228
+ currencyCode,
2229
+ email: guestEmail,
2230
+ salesChannelId
2231
+ });
2232
+ } catch (error) {
2233
+ return fail(error);
2234
+ }
1978
2235
  checkoutCartId = created.id;
1979
2236
  if (!checkoutCartId) {
1980
- throw new Error("Cart created without id");
2237
+ return fail(new Error("Cart created without id"));
1981
2238
  }
1982
2239
  setCartTokenId(checkoutCartId);
1983
2240
  }
2241
+ if (syncLocalCart) {
2242
+ const localCart = useCart.getState().cart;
2243
+ if (localCart.length > 0) {
2244
+ try {
2245
+ await reconcileLocalCartWithApi(checkoutCartId, localCart, {
2246
+ strict: true
2247
+ });
2248
+ } catch (error) {
2249
+ return fail(error);
2250
+ }
2251
+ }
2252
+ }
1984
2253
  navigate(buildCheckoutPath(checkoutCartId, checkoutPathPrefix));
1985
2254
  return checkoutCartId;
1986
2255
  } 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;
2256
+ return fail(error);
1994
2257
  } finally {
2258
+ inFlightRef.current = false;
1995
2259
  setIsProceedingToCheckout(false);
1996
2260
  }
1997
2261
  }, [
@@ -2000,11 +2264,11 @@ function useProceedToCheckout(options = {}) {
2000
2264
  createCartMutation,
2001
2265
  currencyCode,
2002
2266
  guestEmailOption,
2003
- isProceedingToCheckout,
2004
2267
  navigate,
2005
2268
  onError,
2006
2269
  salesChannelId,
2007
- setCartTokenId
2270
+ setCartTokenId,
2271
+ syncLocalCart
2008
2272
  ]);
2009
2273
  return {
2010
2274
  proceedToCheckout,
@@ -2013,14 +2277,6 @@ function useProceedToCheckout(options = {}) {
2013
2277
  };
2014
2278
  }
2015
2279
 
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
2280
  // src/core/utils/format-currency.ts
2025
2281
  function formatCurrency(amount, currency = "USD") {
2026
2282
  const locale = countryCurrency[currency] ?? "en-US";
@@ -2060,12 +2316,12 @@ var zodPathToFieldId = {
2060
2316
  selectedPaymentCode: "checkout-payment-method"
2061
2317
  };
2062
2318
  function buildCheckoutFormValidationSchema(currency) {
2063
- const phoneDigits = currency === "PEN" ? 9 : 10;
2319
+ const phoneDigits = currency === DEFAULT_CURRENCY_CODE ? 9 : 10;
2064
2320
  return z.object({
2065
2321
  email: z.string().trim().min(1, "El email es requerido").email("Ingresa un email v\xE1lido"),
2066
2322
  firstName: z.string().trim().min(1, "El nombre es requerido").min(2, "El nombre debe tener al menos 2 caracteres"),
2067
2323
  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"),
2324
+ countryCode: z.string().trim().min(2, "Selecciona un pa\xEDs").max(2),
2069
2325
  address: z.string().trim().min(1, "La direcci\xF3n es requerida").min(5, "La direcci\xF3n debe tener al menos 5 caracteres"),
2070
2326
  phone: z.string().trim().min(1, "El tel\xE9fono es requerido").regex(/^\d+$/, "El tel\xE9fono solo debe contener n\xFAmeros").length(
2071
2327
  phoneDigits,
@@ -2082,7 +2338,7 @@ function buildCheckoutFormValidationSchema(currency) {
2082
2338
  paymentMethodsCount: z.number().int().nonnegative()
2083
2339
  }).superRefine((data, ctx) => {
2084
2340
  const country = data.countryCode.toUpperCase();
2085
- if (country === "PE") {
2341
+ if (country === DEFAULT_COUNTRY_CODE) {
2086
2342
  if (!data.departmentCode.trim()) {
2087
2343
  ctx.addIssue({
2088
2344
  code: z.ZodIssueCode.custom,
@@ -2199,7 +2455,7 @@ function focusCheckoutField(fieldId) {
2199
2455
  }
2200
2456
 
2201
2457
  // src/react/pages/checkout-page/hooks/use-checkout-form-validation.ts
2202
- function useCheckoutFormValidation(currency = "PEN") {
2458
+ function useCheckoutFormValidation(currency = DEFAULT_CURRENCY_CODE) {
2203
2459
  const schema = useMemo(
2204
2460
  () => buildCheckoutFormValidationSchema(currency),
2205
2461
  [currency]
@@ -2270,7 +2526,9 @@ function loadStorefrontGeoDepartments(headers) {
2270
2526
  if (departmentsCache) return Promise.resolve(departmentsCache);
2271
2527
  if (departmentsRequest) return departmentsRequest;
2272
2528
  departmentsRequest = fetch(
2273
- `/api/store/geo/departments?countryCode=PE&source=inei&limit=200`,
2529
+ `/api/store/geo/departments?countryCode=${encodeURIComponent(
2530
+ DEFAULT_COUNTRY_CODE
2531
+ )}&source=inei&limit=200`,
2274
2532
  { cache: "no-store", headers }
2275
2533
  ).then((r) => r.json()).then((data) => {
2276
2534
  const items = toGeoItems(data);
@@ -2304,10 +2562,10 @@ async function fetchStorefrontGeoDistricts(headers, countryCode, provinceCode) {
2304
2562
  const data = await res.json();
2305
2563
  return toGeoItems(data);
2306
2564
  }
2307
- async function resolveIneiCodesFromLabels(storeId, labels, countryCode = "PE") {
2565
+ async function resolveIneiCodesFromLabels(storeId, labels, countryCode = DEFAULT_COUNTRY_CODE) {
2308
2566
  const id = storeId.trim();
2309
2567
  if (!id) return null;
2310
- const headers = { "X-Store-Id": id };
2568
+ const headers = { [STOREFRONT_HEADERS.storeId]: id };
2311
2569
  const departments = await loadStorefrontGeoDepartments(headers);
2312
2570
  const dept = departments.find((d) => d.name.trim() === labels.department.trim());
2313
2571
  if (!dept) return null;
@@ -2345,12 +2603,12 @@ function useCheckoutDeliveryForm({
2345
2603
  const patchContact = useCallback((patch) => {
2346
2604
  onChangeRef.current(patch);
2347
2605
  }, []);
2348
- const countryCode = (contact.countryCode || "PE").toUpperCase();
2349
- const isPeru = countryCode === "PE";
2606
+ const countryCode = (contact.countryCode || DEFAULT_COUNTRY_CODE).toUpperCase();
2607
+ const isPeru = countryCode === DEFAULT_COUNTRY_CODE;
2350
2608
  const storeRequestHeaders = useMemo(() => {
2351
2609
  const id = storeId.trim();
2352
2610
  if (!id) return null;
2353
- return { "X-Store-Id": id };
2611
+ return { [STOREFRONT_HEADERS.storeId]: id };
2354
2612
  }, [storeId]);
2355
2613
  useEffect(() => {
2356
2614
  if (!isPeru || !storeRequestHeaders) {
@@ -2698,13 +2956,13 @@ function CheckoutDeliveryForm({
2698
2956
  CheckoutSelectField,
2699
2957
  {
2700
2958
  id: "countryCode",
2701
- label: "Country/Region",
2959
+ label: "Pa\xEDs/Regi\xF3n",
2702
2960
  value: contact.countryCode,
2703
2961
  error: getFieldError("countryCode"),
2704
2962
  onClearError: () => clearFieldError("countryCode"),
2705
2963
  onChange: (v) => onContactChange({ countryCode: v }),
2706
2964
  children: [
2707
- /* @__PURE__ */ jsx("option", { value: "PE", children: "Peru" }),
2965
+ /* @__PURE__ */ jsx("option", { value: DEFAULT_COUNTRY_CODE, children: "Per\xFA" }),
2708
2966
  /* @__PURE__ */ jsx("option", { value: "US", children: "United States" }),
2709
2967
  /* @__PURE__ */ jsx("option", { value: "MX", children: "Mexico" }),
2710
2968
  /* @__PURE__ */ jsx("option", { value: "CO", children: "Colombia" })
@@ -3147,7 +3405,7 @@ function StripePaymentProvider({ checkout }) {
3147
3405
  return;
3148
3406
  }
3149
3407
  let cancelled = false;
3150
- const ensureStripeScript = () => new Promise((resolve, reject) => {
3408
+ const loadStripeScript = () => new Promise((resolve, reject) => {
3151
3409
  const maybeStripe = globalThis.Stripe;
3152
3410
  if (maybeStripe) return resolve();
3153
3411
  const existing = document.querySelector('script[src="https://js.stripe.com/v3/"]');
@@ -3172,7 +3430,7 @@ function StripePaymentProvider({ checkout }) {
3172
3430
  document.head.appendChild(script);
3173
3431
  });
3174
3432
  const mountStripePaymentElement = async () => {
3175
- await ensureStripeScript();
3433
+ await loadStripeScript();
3176
3434
  if (cancelled) return;
3177
3435
  const StripeCtor = globalThis.Stripe;
3178
3436
  if (!StripeCtor) throw new Error("Stripe is not available");
@@ -3312,21 +3570,21 @@ function useCheckoutPayment({
3312
3570
  const requiresProviderUi = Boolean(
3313
3571
  selectedMethod && methodUsesProviderCheckoutUi(selectedMethod)
3314
3572
  );
3315
- const effectiveCheckout = checkoutMethodCode && checkoutMethodCode === selectedMethod?.code ? checkoutResponse ?? null : null;
3573
+ const activeCheckout = checkoutMethodCode && checkoutMethodCode === selectedMethod?.code ? checkoutResponse ?? null : null;
3316
3574
  const activeLoadingCheckout = !!checkoutMethodCode && checkoutMethodCode === selectedMethod?.code && loadingCheckout;
3317
3575
  const activeCheckoutError = checkoutMethodCode && checkoutMethodCode === selectedMethod?.code ? checkoutError : null;
3318
3576
  const manualPaymentDetails = useMemo(() => {
3319
3577
  if (!selectedMethod?.isManual) return null;
3320
- const action = getPaymentFromCheckout(effectiveCheckout)?.action ?? null;
3578
+ const action = getPaymentFromCheckout(activeCheckout)?.action ?? null;
3321
3579
  const publicConfiguration = action?.publicConfiguration ?? {};
3322
3580
  const phoneNumber = selectedMethod.phoneNumber ?? publicConfiguration.phoneNumber ?? null;
3323
3581
  const accountHolder = selectedMethod.accountHolder ?? publicConfiguration.accountHolder ?? null;
3324
3582
  const qrImageUrl = action?.qrCode ?? selectedMethod.qrImageUrl ?? publicConfiguration.qrImageUrl ?? null;
3325
3583
  if (!phoneNumber && !accountHolder && !qrImageUrl) return null;
3326
3584
  return { phoneNumber, accountHolder, qrImageUrl };
3327
- }, [selectedMethod, effectiveCheckout]);
3585
+ }, [selectedMethod, activeCheckout]);
3328
3586
  const needsReceipt = Boolean(selectedMethod?.isManual && manualPaymentDetails);
3329
- const showProviderPanel = requiresProviderUi && !!selectedMethod && !!effectiveCheckout && !activeLoadingCheckout && !activeCheckoutError;
3587
+ const showProviderPanel = requiresProviderUi && !!selectedMethod && !!activeCheckout && !activeLoadingCheckout && !activeCheckoutError;
3330
3588
  const [receiptFile, setReceiptFile] = useState(null);
3331
3589
  const [receiptUrl, setReceiptUrl] = useState(null);
3332
3590
  const [receiptError, setReceiptError] = useState(null);
@@ -3464,7 +3722,7 @@ function useCheckoutPayment({
3464
3722
  return {
3465
3723
  activeMethods,
3466
3724
  selectedMethod,
3467
- effectiveCheckout,
3725
+ activeCheckout,
3468
3726
  activeLoadingCheckout,
3469
3727
  activeCheckoutError,
3470
3728
  manualPaymentDetails,
@@ -3592,8 +3850,8 @@ function CheckoutPaymentSectionInner({
3592
3850
  ] })
3593
3851
  ] })
3594
3852
  ] }) }) : null,
3595
- payment.showProviderPanel && payment.effectiveCheckout ? /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__payment-provider", children: [
3596
- /* @__PURE__ */ jsx(PaymentProviderRouter, { checkout: payment.effectiveCheckout }),
3853
+ payment.showProviderPanel && payment.activeCheckout ? /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__payment-provider", children: [
3854
+ /* @__PURE__ */ jsx(PaymentProviderRouter, { checkout: payment.activeCheckout }),
3597
3855
  !payment.providerPaymentCompleted ? /* @__PURE__ */ jsx(
3598
3856
  "button",
3599
3857
  {
@@ -4006,128 +4264,6 @@ function CheckoutPageSkeleton({ header }) {
4006
4264
  }
4007
4265
  );
4008
4266
  }
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
4267
  function useCartListener({
4132
4268
  cartId,
4133
4269
  enabled,
@@ -4175,7 +4311,7 @@ function useCartListener({
4175
4311
  function useCheckoutSession(cartId) {
4176
4312
  const queryClient = useQueryClient();
4177
4313
  const storeId = useStore((s) => s.data?.store?.id ?? "");
4178
- const currency = useStore((s) => s.data?.store?.currency ?? "PEN");
4314
+ const currency = useStore((s) => s.data?.store?.currency ?? DEFAULT_CURRENCY_CODE);
4179
4315
  const { setCartTokenId, clearCartSession, cart: localCart } = useCart(
4180
4316
  useShallow((s) => ({
4181
4317
  setCartTokenId: s.setCartTokenId,
@@ -4245,7 +4381,7 @@ function useCheckoutSession(cartId) {
4245
4381
  province: "",
4246
4382
  postalCode: "",
4247
4383
  phone: "",
4248
- countryCode: "PE",
4384
+ countryCode: DEFAULT_COUNTRY_CODE,
4249
4385
  departmentCode: "",
4250
4386
  departmentName: "",
4251
4387
  provinceCode: "",
@@ -4414,7 +4550,7 @@ function useCheckoutSession(cartId) {
4414
4550
  }
4415
4551
  });
4416
4552
  mergeCartIntoCheckoutCache(buyerCart);
4417
- const countryCode = (contact.countryCode || "PE").toUpperCase();
4553
+ const countryCode = (contact.countryCode || DEFAULT_COUNTRY_CODE).toUpperCase();
4418
4554
  const districtName = contact.districtName || contact.city;
4419
4555
  const provinceName = contact.provinceName || contact.province;
4420
4556
  const baseAddress = {
@@ -4925,6 +5061,8 @@ function thankYouParamsFromCheckoutResult(result, overrides) {
4925
5061
  transactionId: overrides?.transactionId ?? result.payment?.transactionId ?? void 0
4926
5062
  };
4927
5063
  }
5064
+
5065
+ // src/react/shell/components/product-variant-selector/product-variant-selection.ts
4928
5066
  function buildOptionGroups(variants) {
4929
5067
  const map = /* @__PURE__ */ new Map();
4930
5068
  for (const v of variants) {
@@ -4964,6 +5102,54 @@ function getInitialOptions(variants, optionGroups) {
4964
5102
  }
4965
5103
  return selected;
4966
5104
  }
5105
+ function buildVariantLabel(variant) {
5106
+ if (!variant?.options?.length) return void 0;
5107
+ return variant.options.map((o) => `${o.key}: ${o.value}`).join(" \u2022 ");
5108
+ }
5109
+ function getPriceTiersForVariant(product, variantId) {
5110
+ if (!variantId) return [];
5111
+ return (product.priceTiers ?? []).filter((tier) => tier.variantId === variantId).sort((a, b) => a.minQuantity - b.minQuantity);
5112
+ }
5113
+ function resolveProductImageUrl(product, selectedVariant) {
5114
+ return product.imageUrl ?? selectedVariant?.images?.[0]?.url ?? product.images?.[0]?.url ?? "";
5115
+ }
5116
+ function buildVariantSelectionPayload(product, selectedVariant, displayPrice) {
5117
+ if (!selectedVariant?.id) return null;
5118
+ return {
5119
+ productId: product.id,
5120
+ productName: product.name,
5121
+ variant: selectedVariant,
5122
+ displayPrice,
5123
+ compareAtPrice: selectedVariant.compareAtPrice ?? product.compareAtPrice,
5124
+ variantLabel: buildVariantLabel(selectedVariant),
5125
+ variantSku: selectedVariant.sku,
5126
+ imageUrl: resolveProductImageUrl(product, selectedVariant)
5127
+ };
5128
+ }
5129
+ function buildCartItemFromVariant(product, selectedVariant, displayPrice) {
5130
+ if (!selectedVariant?.id) return null;
5131
+ const sizeOption = selectedVariant.options?.find(
5132
+ (o) => o.key.toLowerCase() === "size"
5133
+ )?.value;
5134
+ const variantLabel = buildVariantLabel(selectedVariant);
5135
+ return {
5136
+ id: selectedVariant.id,
5137
+ title: product.name,
5138
+ price: displayPrice,
5139
+ quantity: 1,
5140
+ size: sizeOption ?? "\u2014",
5141
+ image: resolveProductImageUrl(product, selectedVariant),
5142
+ variantId: selectedVariant.id,
5143
+ variantSku: selectedVariant.sku,
5144
+ variantLabel
5145
+ };
5146
+ }
5147
+ function isVariantSelectionReady(selectedVariant, isAvailable) {
5148
+ return Boolean(selectedVariant?.id && isAvailable);
5149
+ }
5150
+ function canAddVariantToCart(selectedVariant, isAvailable) {
5151
+ return isVariantSelectionReady(selectedVariant, isAvailable);
5152
+ }
4967
5153
  function ProductVariantSelector({
4968
5154
  product,
4969
5155
  currency = "USD",
@@ -5033,7 +5219,180 @@ function ProductVariantSelector({
5033
5219
  })
5034
5220
  ] });
5035
5221
  }
5222
+ function DefaultVariantPrice({
5223
+ displayPrice,
5224
+ compareAtPrice,
5225
+ currency
5226
+ }) {
5227
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
5228
+ /* @__PURE__ */ jsx(
5229
+ "span",
5230
+ {
5231
+ className: "inline-flex items-center rounded-md bg-primary/10 px-2.5 py-0.5 text-base font-semibold text-primary",
5232
+ "data-testid": "product-display-price",
5233
+ children: formatCurrency(displayPrice, currency)
5234
+ }
5235
+ ),
5236
+ compareAtPrice != null && compareAtPrice > displayPrice && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground line-through", children: formatCurrency(compareAtPrice, currency) })
5237
+ ] });
5238
+ }
5239
+ function DefaultVariantOptions(slot) {
5240
+ if (!slot.hasVariants) return null;
5241
+ return /* @__PURE__ */ jsx(Fragment, { children: slot.optionGroups.map((group) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
5242
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: group.name }),
5243
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: group.values.map((value) => {
5244
+ const isSelected = slot.selectedOptions[group.key] === value;
5245
+ const isColorLike = group.key.toLowerCase().includes("color") || group.key.toLowerCase().includes("colour");
5246
+ return /* @__PURE__ */ jsx(
5247
+ "button",
5248
+ {
5249
+ type: "button",
5250
+ onClick: () => slot.selectOption(group.key, value),
5251
+ 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"}`,
5252
+ style: isColorLike && isSelected && value.toLowerCase() === "black" ? { backgroundColor: "#1a1a1a", borderColor: "#1a1a1a", color: "#fff" } : isColorLike && value.toLowerCase() === "white" ? { backgroundColor: "#fafafa", color: "#1a1a1a", borderColor: "#e5e5e5" } : void 0,
5253
+ "data-testid": `variant-option-${group.key}-${value}`,
5254
+ children: value
5255
+ },
5256
+ value
5257
+ );
5258
+ }) })
5259
+ ] }, group.key)) });
5260
+ }
5261
+ function DefaultPriceTiers({
5262
+ priceTiers,
5263
+ currency
5264
+ }) {
5265
+ if (priceTiers.length === 0) return null;
5266
+ return /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-md border border-border text-sm", children: [
5267
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 bg-muted/40 px-3 py-2 text-xs font-semibold uppercase text-muted-foreground", children: [
5268
+ /* @__PURE__ */ jsx("span", { children: "Cantidad" }),
5269
+ /* @__PURE__ */ jsx("span", { className: "text-right", children: "Precio" })
5270
+ ] }),
5271
+ priceTiers.map((priceTier) => /* @__PURE__ */ jsxs(
5272
+ "div",
5273
+ {
5274
+ className: "grid grid-cols-2 border-t border-border px-3 py-2",
5275
+ children: [
5276
+ /* @__PURE__ */ jsx("span", { children: priceTier.maxQuantity ? `${priceTier.minQuantity} - ${priceTier.maxQuantity}` : `${priceTier.minQuantity}+` }),
5277
+ /* @__PURE__ */ jsx("span", { className: "text-right font-medium", children: formatCurrency(priceTier.unitPrice, currency) })
5278
+ ]
5279
+ },
5280
+ `${priceTier.variantId}-${priceTier.minQuantity}`
5281
+ ))
5282
+ ] });
5283
+ }
5284
+ function DefaultProductFeatures({ features }) {
5285
+ if (features.length === 0) return null;
5286
+ 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)) });
5287
+ }
5288
+ function useProductVariantSelection({
5289
+ product,
5290
+ onVariantChange,
5291
+ onSelectionChange
5292
+ }) {
5293
+ const variants = useMemo(() => product.variants ?? [], [product.variants]);
5294
+ const optionGroups = useMemo(() => buildOptionGroups(variants), [variants]);
5295
+ const hasVariants = optionGroups.length > 0;
5296
+ const [selectedOptions, setSelectedOptions] = useState(
5297
+ () => getInitialOptions(variants, optionGroups)
5298
+ );
5299
+ const selectedVariant = useMemo(
5300
+ () => findVariant(variants, selectedOptions, hasVariants),
5301
+ [variants, selectedOptions, hasVariants]
5302
+ );
5303
+ const displayPrice = selectedVariant?.price ?? product.price ?? 0;
5304
+ const compareAtPrice = selectedVariant?.compareAtPrice ?? product.compareAtPrice;
5305
+ const isAvailable = (selectedVariant ? (selectedVariant.quantity ?? 0) > 0 : product.isAvailable) ?? true;
5306
+ const selection = useMemo(
5307
+ () => buildVariantSelectionPayload(product, selectedVariant, displayPrice),
5308
+ [product, selectedVariant, displayPrice]
5309
+ );
5310
+ const priceTiers = useMemo(
5311
+ () => getPriceTiersForVariant(product, selectedVariant?.id),
5312
+ [product, selectedVariant?.id]
5313
+ );
5314
+ const isReady = isVariantSelectionReady(selectedVariant, isAvailable);
5315
+ useEffect(() => {
5316
+ onVariantChange?.(selectedVariant, displayPrice);
5317
+ }, [selectedVariant, displayPrice, onVariantChange]);
5318
+ useEffect(() => {
5319
+ onSelectionChange?.(selection);
5320
+ }, [onSelectionChange, selection]);
5321
+ const selectOption = useCallback((key, value) => {
5322
+ setSelectedOptions((prev) => ({ ...prev, [key]: value }));
5323
+ }, []);
5324
+ return {
5325
+ product,
5326
+ variants,
5327
+ optionGroups,
5328
+ selectedOptions,
5329
+ selectOption,
5330
+ hasVariants,
5331
+ selectedVariant,
5332
+ displayPrice,
5333
+ compareAtPrice,
5334
+ isAvailable,
5335
+ isReady,
5336
+ selection,
5337
+ priceTiers
5338
+ };
5339
+ }
5340
+ function ProductVariantSelectorComplete({
5341
+ product,
5342
+ currency = "PEN",
5343
+ features = [],
5344
+ className,
5345
+ onVariantChange,
5346
+ onSelectionChange,
5347
+ renderPrice,
5348
+ renderVariants,
5349
+ renderPriceTiers,
5350
+ renderAction,
5351
+ renderFeatures
5352
+ }) {
5353
+ const state = useProductVariantSelection({
5354
+ product,
5355
+ onVariantChange,
5356
+ onSelectionChange
5357
+ });
5358
+ const priceSlot = {
5359
+ displayPrice: state.displayPrice,
5360
+ compareAtPrice: state.compareAtPrice,
5361
+ currency,
5362
+ selectedVariant: state.selectedVariant
5363
+ };
5364
+ const variantsSlot = {
5365
+ optionGroups: state.optionGroups,
5366
+ selectedOptions: state.selectedOptions,
5367
+ selectOption: state.selectOption,
5368
+ hasVariants: state.hasVariants,
5369
+ selectedVariant: state.selectedVariant,
5370
+ isAvailable: state.isAvailable
5371
+ };
5372
+ const priceTiersSlot = {
5373
+ priceTiers: state.priceTiers,
5374
+ currency,
5375
+ selectedVariant: state.selectedVariant
5376
+ };
5377
+ const actionSlot = {
5378
+ product: state.product,
5379
+ selectedVariant: state.selectedVariant,
5380
+ displayPrice: state.displayPrice,
5381
+ compareAtPrice: state.compareAtPrice,
5382
+ currency,
5383
+ isAvailable: state.isAvailable,
5384
+ isReady: state.isReady,
5385
+ selection: state.selection
5386
+ };
5387
+ return /* @__PURE__ */ jsxs("div", { className: className ?? "w-full max-w-[320px] space-y-5", children: [
5388
+ renderPrice?.(priceSlot) ?? /* @__PURE__ */ jsx(DefaultVariantPrice, { ...priceSlot }),
5389
+ renderVariants?.(variantsSlot) ?? /* @__PURE__ */ jsx(DefaultVariantOptions, { ...variantsSlot }),
5390
+ renderFeatures?.(features) ?? /* @__PURE__ */ jsx(DefaultProductFeatures, { features }),
5391
+ state.priceTiers.length > 0 && (renderPriceTiers?.(priceTiersSlot) ?? /* @__PURE__ */ jsx(DefaultPriceTiers, { ...priceTiersSlot })),
5392
+ renderAction?.(actionSlot)
5393
+ ] });
5394
+ }
5036
5395
 
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 };
5396
+ 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
5397
  //# sourceMappingURL=client.js.map
5039
5398
  //# sourceMappingURL=client.js.map