@ecomiq/storefront 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/nextjs.js ADDED
@@ -0,0 +1,2405 @@
1
+ import { headers } from 'next/headers';
2
+ import { StorefrontSettingsSchema, StorefrontProductsParamsSchema, StorefrontProductsResponseSchema, StorefrontProductDetailSchema, StorefrontCollectionsResponseSchema, StorefrontCollectionDetailSchema, StorefrontPageListItemSchema, StorefrontPageDetailSchema, CartCreateRequestSchema, CartResponseSchema, CartBuyerRequestSchema, CartAddressesRequestSchema, CartCouponRequestSchema, CartCheckoutRequestSchema, CartCheckoutResponseSchema, CartPaymentReceiptResponseSchema, CartCheckoutReadResponseSchema, ShippingOptionsResponseSchema, CartShippingMethodPutRequestSchema, CartItemRequestSchema } from '@ecomiq/storefront/core';
3
+ import { getCloudflareContext } from '@opennextjs/cloudflare';
4
+ import { z } from 'zod';
5
+ import { useMutation, useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
6
+ import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
7
+ import { toast } from 'sonner';
8
+ import { StorefrontPageDetailSchema as StorefrontPageDetailSchema$1, formatCurrency, useCart } from '@ecomiq/storefront/react';
9
+ import { create } from 'zustand';
10
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
+ import Link from 'next/link';
12
+
13
+ // src/core/client/store-api-client.ts
14
+ var STORE_API_PATH = "/api/store";
15
+ var serverRequestOriginGetter = null;
16
+ var serverStoreIdGetter = null;
17
+ var clientStoreIdGetter = null;
18
+ function setServerRequestOrigin(getter) {
19
+ serverRequestOriginGetter = getter;
20
+ }
21
+ function setServerStoreId(storeId) {
22
+ serverStoreIdGetter = storeId?.trim() || null;
23
+ }
24
+ function setClientStoreId(storeId) {
25
+ if (typeof window === "undefined") return;
26
+ clientStoreIdGetter = storeId?.trim() || null;
27
+ }
28
+ async function getStoreApiBaseUrl() {
29
+ if (typeof window !== "undefined") {
30
+ return STORE_API_PATH;
31
+ }
32
+ if (serverRequestOriginGetter) {
33
+ return `${serverRequestOriginGetter.replace(/\/$/, "")}/${STORE_API_PATH.replace(/^\//, "")}`;
34
+ }
35
+ return STORE_API_PATH;
36
+ }
37
+ function createStoreApiClient(baseUrl) {
38
+ return new StoreApiClient(baseUrl);
39
+ }
40
+ async function getStoreApiClient() {
41
+ const baseUrl = await getStoreApiBaseUrl();
42
+ return new StoreApiClient(baseUrl);
43
+ }
44
+ var StoreApiClient = class {
45
+ baseUrl;
46
+ constructor(baseUrl = STORE_API_PATH) {
47
+ this.baseUrl = baseUrl.replace(/\/$/, "");
48
+ }
49
+ async request(path, init, options) {
50
+ const pathNorm = path.replace(/^\/+/, "");
51
+ const url = path.startsWith("http") ? path : `${this.baseUrl}/${pathNorm}`;
52
+ const headers3 = new Headers(init.headers ?? {});
53
+ headers3.set("Content-Type", "application/json");
54
+ headers3.set("Accept-Encoding", "identity");
55
+ const explicitStoreId = options?.xStoreId?.trim();
56
+ const runtimeStoreId = explicitStoreId || (typeof window === "undefined" ? serverStoreIdGetter : clientStoreIdGetter);
57
+ if (runtimeStoreId && !headers3.has("x-store-id")) {
58
+ headers3.set("X-Store-Id", runtimeStoreId);
59
+ }
60
+ if (options?.xStoreDomain) {
61
+ headers3.set("X-Store-Domain", options.xStoreDomain);
62
+ }
63
+ if (options?.xCartId) {
64
+ headers3.set("X-Cart-Token", options.xCartId);
65
+ }
66
+ const bearer = typeof options?.bearerToken === "string" && options.bearerToken.trim().length > 0 ? options.bearerToken.trim() : void 0;
67
+ if (bearer) {
68
+ headers3.set(
69
+ "Authorization",
70
+ bearer.toLowerCase().startsWith("bearer ") ? bearer : `Bearer ${bearer}`
71
+ );
72
+ }
73
+ const isServer = typeof window === "undefined";
74
+ const res = await fetch(url, {
75
+ ...init,
76
+ headers: headers3,
77
+ credentials: isServer ? "same-origin" : "include",
78
+ ...options?.noStore ? { cache: "no-store" } : {},
79
+ ...options?.cacheKey && isServer && !options?.noStore ? {
80
+ next: {
81
+ revalidate: 60,
82
+ tags: [options.cacheKey]
83
+ }
84
+ } : {}
85
+ });
86
+ if (!res.ok) {
87
+ let body;
88
+ try {
89
+ const errorText = await res.text();
90
+ body = errorText ? JSON.parse(errorText) : void 0;
91
+ } catch {
92
+ body = void 0;
93
+ }
94
+ const msg = typeof body === "object" && body !== null && "title" in body && typeof body.title === "string" ? body.title : res.statusText || "Store API error";
95
+ const err = new Error(msg);
96
+ err.status = res.status;
97
+ err.body = body;
98
+ throw err;
99
+ }
100
+ const text = await res.text();
101
+ if (!text) return void 0;
102
+ return JSON.parse(text);
103
+ }
104
+ get(path, options) {
105
+ return this.request(path, { method: "GET" }, options);
106
+ }
107
+ post(path, data, options) {
108
+ return this.request(
109
+ path,
110
+ {
111
+ method: "POST",
112
+ body: data != null ? JSON.stringify(data) : void 0
113
+ },
114
+ options
115
+ );
116
+ }
117
+ patch(path, data, options) {
118
+ return this.request(path, {
119
+ method: "PATCH",
120
+ body: data != null ? JSON.stringify(data) : void 0
121
+ }, options);
122
+ }
123
+ put(path, data, options) {
124
+ return this.request(
125
+ path,
126
+ {
127
+ method: "PUT",
128
+ body: data != null ? JSON.stringify(data) : void 0
129
+ },
130
+ options
131
+ );
132
+ }
133
+ delete(path, options) {
134
+ return this.request(path, { method: "DELETE" }, options);
135
+ }
136
+ };
137
+ var storeApiClient = new StoreApiClient(STORE_API_PATH);
138
+ async function getHostname() {
139
+ const hd = await headers();
140
+ const host = hd.get("host");
141
+ const hostname = host?.replace(/^www\./, "") ?? null;
142
+ const proto = hd.get("x-forwarded-proto") ?? hd.get("x-forwarded-ssl") ?? "http";
143
+ const secure = proto === "https" || (hd.get("x-forwarded-ssl") ?? "") === "on";
144
+ setServerRequestOrigin(`${secure ? "https" : "http"}://${hostname}`);
145
+ return hostname;
146
+ }
147
+ async function getStorefrontSettings(subdomain, cacheKey) {
148
+ const client = await getStoreApiClient();
149
+ const response = await client.get("settings", {
150
+ xStoreDomain: subdomain?.trim() || void 0,
151
+ cacheKey,
152
+ noStore: true
153
+ });
154
+ return StorefrontSettingsSchema.parse(response);
155
+ }
156
+ var ECOMIQ_CLIENTS_STORE_KV_BINDING = "ecomiq_clients_store";
157
+ var ECOMIQ_APP_APEX = "ecomiq.app";
158
+ var ECOMIQ_APP_SUFFIX = [`.${ECOMIQ_APP_APEX}`, `.localhost`];
159
+ function normalizeStoreHost(hostHeader) {
160
+ let h = hostHeader.trim().toLowerCase();
161
+ const colon = h.indexOf(":");
162
+ if (colon !== -1) h = h.slice(0, colon);
163
+ if (h.startsWith("www.")) h = h.slice(4);
164
+ return h;
165
+ }
166
+ function resolveStorefrontSettingsHost(hostHeader) {
167
+ const host = normalizeStoreHost(hostHeader);
168
+ if (!host) {
169
+ throw Object.assign(new Error("Host inv\xE1lido para tienda"), { status: 400 });
170
+ }
171
+ if (host === ECOMIQ_APP_APEX) {
172
+ throw Object.assign(new Error("Host apex ecomiq.app sin slug de tienda"), { status: 400 });
173
+ }
174
+ if (ECOMIQ_APP_SUFFIX.some((suffix) => host.endsWith(suffix))) {
175
+ const slug = host.slice(0, -(ECOMIQ_APP_SUFFIX.find((suffix) => host.endsWith(suffix))?.length ?? 0));
176
+ if (!slug) {
177
+ throw Object.assign(new Error("Host sin slug para ecomiq.app"), { status: 400 });
178
+ }
179
+ return slug;
180
+ }
181
+ return host;
182
+ }
183
+ function buildStorefrontKvSnapshotKey(hostHeader) {
184
+ return `storefront:snapshots:store:${resolveStorefrontSettingsHost(hostHeader)}`;
185
+ }
186
+ function getKvNamespace(env) {
187
+ if (!env || typeof env !== "object") return void 0;
188
+ const ns = env[ECOMIQ_CLIENTS_STORE_KV_BINDING];
189
+ if (!ns || typeof ns !== "object" || typeof ns.get !== "function") {
190
+ return void 0;
191
+ }
192
+ return ns;
193
+ }
194
+ async function getStorefrontSettingsFromKv(hostHeader) {
195
+ const { env } = await getCloudflareContext({ async: true });
196
+ const kv = getKvNamespace(env);
197
+ if (!kv) {
198
+ const err = new Error(
199
+ `Binding KV "${ECOMIQ_CLIENTS_STORE_KV_BINDING}" no encontrado. Configura kv_namespaces en wrangler.`
200
+ );
201
+ throw Object.assign(err, { status: 500 });
202
+ }
203
+ const cacheKey = buildStorefrontKvSnapshotKey(hostHeader);
204
+ const raw = await kv.get(cacheKey, { type: "json" });
205
+ if (raw == null) {
206
+ const err = new Error("Store settings not found in KV");
207
+ throw Object.assign(err, { status: 404 });
208
+ }
209
+ return StorefrontSettingsSchema.parse(raw);
210
+ }
211
+
212
+ // src/react/shell/lib/public-store-settings-source.ts
213
+ var ENV_PUBLIC_STORE_SETTINGS_SOURCE = "PUBLIC_STORE_SETTINGS_SOURCE";
214
+ function resolvePublicStoreSettingsSource(envValue = process.env[ENV_PUBLIC_STORE_SETTINGS_SOURCE]) {
215
+ const v = (envValue ?? "api").trim().toLowerCase();
216
+ return v === "kv" ? "kv" : "api";
217
+ }
218
+
219
+ // src/react/nextjs/actions/getPublicStore.ts
220
+ async function getPublicStore(options) {
221
+ const headerList = await headers();
222
+ const host = headerList.get("host");
223
+ if (!host?.trim()) {
224
+ return {
225
+ data: null,
226
+ error: {
227
+ message: "Host is required",
228
+ statusCode: 400,
229
+ isNotFound: true,
230
+ subdomain: void 0
231
+ }
232
+ };
233
+ }
234
+ const storeHost = normalizeStoreHost(host);
235
+ const proto = headerList.get("x-forwarded-proto") ?? headerList.get("x-forwarded-ssl") ?? "http";
236
+ const secure = proto === "https" || (headerList.get("x-forwarded-ssl") ?? "") === "on";
237
+ setServerRequestOrigin(`${secure ? "https" : "http"}://${host}`);
238
+ const source = options?.source ?? resolvePublicStoreSettingsSource();
239
+ let storeDomain;
240
+ try {
241
+ storeDomain = resolveStorefrontSettingsHost(host);
242
+ setServerStoreId(null);
243
+ const data = source === "kv" ? await getStorefrontSettingsFromKv(storeHost) : await getStorefrontSettings(storeDomain, `storefront-settings-${storeDomain}`);
244
+ setServerStoreId(data.store.id);
245
+ return { data, error: null };
246
+ } catch (error) {
247
+ const err = error;
248
+ const statusCode = err.status ?? 500;
249
+ return {
250
+ data: null,
251
+ error: {
252
+ message: err.message ?? "Error fetching store settings",
253
+ statusCode,
254
+ isNotFound: statusCode === 404,
255
+ subdomain: storeDomain
256
+ }
257
+ };
258
+ }
259
+ }
260
+
261
+ // src/core/client/store-api-errors.ts
262
+ function isCartNotActiveApiError(error) {
263
+ if (!error || typeof error !== "object") return false;
264
+ const e = error;
265
+ if (e.status !== 400) return false;
266
+ const body = e.body;
267
+ const keys = body?.errors ? Object.keys(body.errors) : [];
268
+ return keys.includes("Carts.NotActive");
269
+ }
270
+ async function getStorefrontProducts(params, cacheKey) {
271
+ const parsed = StorefrontProductsParamsSchema.parse(params ?? {});
272
+ const searchParams = new URLSearchParams();
273
+ searchParams.set("page", String(parsed.page));
274
+ searchParams.set("pageSize", String(parsed.pageSize));
275
+ if (parsed.sort) searchParams.set("sort", parsed.sort);
276
+ if (parsed.sellerId) searchParams.set("sellerId", parsed.sellerId);
277
+ if (parsed.search) searchParams.set("search", parsed.search);
278
+ if (parsed.collection) searchParams.set("collection", parsed.collection);
279
+ if (parsed.category) searchParams.set("category", parsed.category);
280
+ if (parsed.brand) searchParams.set("brand", parsed.brand);
281
+ if (parsed.productType) searchParams.set("productType", parsed.productType);
282
+ if (parsed.inStock != null) searchParams.set("inStock", String(parsed.inStock));
283
+ if (parsed.minPrice != null) searchParams.set("minPrice", String(parsed.minPrice));
284
+ if (parsed.maxPrice != null) searchParams.set("maxPrice", String(parsed.maxPrice));
285
+ const path = `products?${searchParams.toString()}`;
286
+ const client = await getStoreApiClient();
287
+ const response = await client.get(path, {
288
+ cacheKey
289
+ });
290
+ return StorefrontProductsResponseSchema.parse(response);
291
+ }
292
+ async function getStorefrontProductBySlug(slug) {
293
+ const path = `products/${encodeURIComponent(slug)}`;
294
+ const client = await getStoreApiClient();
295
+ const response = await client.get(path);
296
+ return StorefrontProductDetailSchema.parse(response);
297
+ }
298
+ async function getStorefrontCollections() {
299
+ const client = await getStoreApiClient();
300
+ const response = await client.get("collections");
301
+ return StorefrontCollectionsResponseSchema.parse(response);
302
+ }
303
+ async function getStorefrontCollectionBySlug(slug) {
304
+ const client = await getStoreApiClient();
305
+ const response = await client.get(
306
+ `collections/by-slug/${encodeURIComponent(slug)}`
307
+ );
308
+ return StorefrontCollectionDetailSchema.parse(response);
309
+ }
310
+ async function getStorefrontPages() {
311
+ const client = await getStoreApiClient();
312
+ const response = await client.get("pages");
313
+ return z.array(StorefrontPageListItemSchema).parse(response);
314
+ }
315
+ async function getStorefrontPageBySlug(slug) {
316
+ const path = `pages/${encodeURIComponent(slug)}`;
317
+ const client = await getStoreApiClient();
318
+ const response = await client.get(path);
319
+ return StorefrontPageDetailSchema.parse(response);
320
+ }
321
+ async function getStorefrontPagePreview(slug, token) {
322
+ const path = `pages/${encodeURIComponent(slug)}/preview?token=${encodeURIComponent(token)}`;
323
+ const client = await getStoreApiClient();
324
+ const response = await client.get(path);
325
+ return StorefrontPageDetailSchema.parse(response);
326
+ }
327
+ async function createCart(data) {
328
+ const payload = CartCreateRequestSchema.parse(data);
329
+ const client = await getStoreApiClient();
330
+ const response = await client.post("carts", payload);
331
+ return CartResponseSchema.parse(response);
332
+ }
333
+ async function getCart(cartId) {
334
+ if (!cartId) throw new Error("cartId is required");
335
+ const client = await getStoreApiClient();
336
+ const response = await client.get(`carts/${cartId}`);
337
+ return CartResponseSchema.parse(response);
338
+ }
339
+ async function addCartItem(data, cartId) {
340
+ if (!cartId) throw new Error("cartId is required");
341
+ const payload = CartItemRequestSchema.parse(data);
342
+ const variantId = Number(payload.variantId);
343
+ if (!Number.isSafeInteger(variantId) || variantId <= 0) {
344
+ throw new Error("variantId must be a valid public id");
345
+ }
346
+ const modifiers = payload.modifiers?.map((modifier) => {
347
+ const modifierGroupId = Number(modifier.modifierGroupId);
348
+ const optionIds = modifier.optionIds.map((optionId) => Number(optionId));
349
+ if (!Number.isSafeInteger(modifierGroupId) || modifierGroupId <= 0 || optionIds.some((optionId) => !Number.isSafeInteger(optionId) || optionId <= 0)) {
350
+ throw new Error("modifiers must use valid public ids");
351
+ }
352
+ return { modifierGroupId, optionIds };
353
+ });
354
+ const client = await getStoreApiClient();
355
+ const response = await client.post(`carts/${cartId}/items`, {
356
+ ...payload,
357
+ variantId,
358
+ ...modifiers ? { modifiers } : {}
359
+ });
360
+ return CartResponseSchema.parse(response);
361
+ }
362
+ async function patchCartBuyer(data, cartId) {
363
+ if (!cartId) throw new Error("cartId is required");
364
+ const payload = CartBuyerRequestSchema.parse(data);
365
+ const client = await getStoreApiClient();
366
+ const response = await client.patch(
367
+ `carts/${cartId}/buyer`,
368
+ payload
369
+ );
370
+ return CartResponseSchema.parse(response);
371
+ }
372
+ async function patchCartAddresses(data, cartId) {
373
+ if (!cartId) throw new Error("cartId is required");
374
+ const payload = CartAddressesRequestSchema.parse(data);
375
+ const client = await getStoreApiClient();
376
+ const response = await client.patch(
377
+ `carts/${cartId}/addresses`,
378
+ payload
379
+ );
380
+ return CartResponseSchema.parse(response);
381
+ }
382
+ async function addCartCoupon(cartId, data) {
383
+ if (!cartId) throw new Error("cartId is required");
384
+ const payload = CartCouponRequestSchema.parse(data);
385
+ const client = await getStoreApiClient();
386
+ const response = await client.post(
387
+ `carts/${cartId}/promotion-codes`,
388
+ payload
389
+ );
390
+ return CartResponseSchema.parse(response);
391
+ }
392
+ async function removeCartCoupon(cartId, code) {
393
+ if (!cartId) throw new Error("cartId is required");
394
+ if (!code.trim()) throw new Error("code is required");
395
+ const client = await getStoreApiClient();
396
+ const response = await client.delete(
397
+ `carts/${cartId}/promotion-codes/${encodeURIComponent(code.trim())}`
398
+ );
399
+ return CartResponseSchema.parse(response);
400
+ }
401
+ async function checkoutCart(data, cartId) {
402
+ if (!cartId) throw new Error("cartId is required");
403
+ const payload = CartCheckoutRequestSchema.parse(data);
404
+ const client = await getStoreApiClient();
405
+ const response = await client.post(
406
+ `carts/${cartId}/checkout`,
407
+ payload
408
+ );
409
+ return CartCheckoutResponseSchema.parse(response);
410
+ }
411
+ async function uploadPaymentReceipt(cartId, file) {
412
+ if (!cartId) throw new Error("cartId is required");
413
+ const baseUrl = await getStoreApiBaseUrl();
414
+ const form = new FormData();
415
+ form.set("file", file);
416
+ const response = await fetch(
417
+ `${baseUrl.replace(/\/$/, "")}/carts/${encodeURIComponent(cartId)}/payment-receipt`,
418
+ {
419
+ method: "POST",
420
+ body: form,
421
+ ...typeof window === "undefined" && { cache: "no-store" }
422
+ }
423
+ );
424
+ if (!response.ok) {
425
+ let body;
426
+ try {
427
+ const errorText = await response.text();
428
+ body = errorText ? JSON.parse(errorText) : void 0;
429
+ } catch {
430
+ body = void 0;
431
+ }
432
+ const message = typeof body === "object" && body !== null && "title" in body && typeof body.title === "string" ? body.title : response.statusText || "Store API error";
433
+ const error = new Error(message);
434
+ error.status = response.status;
435
+ error.body = body;
436
+ throw error;
437
+ }
438
+ return CartPaymentReceiptResponseSchema.parse(await response.json());
439
+ }
440
+ async function getCartCheckout(cartId) {
441
+ if (!cartId) throw new Error("cartId is required");
442
+ const client = await getStoreApiClient();
443
+ const response = await client.get(`carts/${cartId}/checkout`);
444
+ return CartCheckoutReadResponseSchema.parse(response);
445
+ }
446
+ async function getShippingOptions(cartId, postalCode) {
447
+ if (!cartId) throw new Error("cartId is required");
448
+ if (!postalCode?.trim()) throw new Error("postalCode is required");
449
+ const client = await getStoreApiClient();
450
+ const q = new URLSearchParams({ postalCode: postalCode.trim() });
451
+ const response = await client.get(
452
+ `carts/${cartId}/shipping-options?${q.toString()}`
453
+ );
454
+ return ShippingOptionsResponseSchema.parse(response);
455
+ }
456
+ async function putCartShippingMethod(cartId, data) {
457
+ if (!cartId) throw new Error("cartId is required");
458
+ const payload = CartShippingMethodPutRequestSchema.parse(data);
459
+ const client = await getStoreApiClient();
460
+ const response = await client.put(
461
+ `carts/${cartId}/shipping-method`,
462
+ payload
463
+ );
464
+ return CartResponseSchema.parse(response);
465
+ }
466
+ async function getCartPaymentMethods(cartId) {
467
+ if (!cartId) throw new Error("cartId is required");
468
+ const { paymentMethods } = await getCartCheckout(cartId);
469
+ return paymentMethods;
470
+ }
471
+ var CustomerRegisterRequestSchema = z.object({
472
+ email: z.string().email(),
473
+ firstName: z.string().trim().min(1),
474
+ lastName: z.string().trim().min(1),
475
+ countryIsoCode: z.string().trim().length(2),
476
+ password: z.string(),
477
+ phone: z.string().trim().optional()
478
+ });
479
+ var CustomerAddressSchema = z.object({
480
+ /** Presente en listados cuando el upstream expone el id para PUT/DELETE. */
481
+ id: z.string().optional(),
482
+ addressKey: z.string(),
483
+ countryIsoCode: z.string(),
484
+ addressLine: z.string(),
485
+ addressNumber: z.string(),
486
+ department: z.string(),
487
+ province: z.string(),
488
+ district: z.string(),
489
+ phone: z.string(),
490
+ cellPhone: z.string(),
491
+ postalCode: z.string(),
492
+ reference: z.string(),
493
+ latitude: z.number().nullable(),
494
+ longitude: z.number().nullable(),
495
+ isDefault: z.boolean()
496
+ });
497
+ var CustomerAddressUpsertSchema = CustomerAddressSchema.omit({ id: true });
498
+ var CustomerMeSchema = z.object({
499
+ id: z.string(),
500
+ storeId: z.string(),
501
+ email: z.string().email(),
502
+ firstName: z.string(),
503
+ lastName: z.string(),
504
+ phone: z.string(),
505
+ identificationNumber: z.string().nullable(),
506
+ identificationType: z.string().nullable(),
507
+ entityName: z.string().nullable(),
508
+ dateOfBirth: z.string().nullable(),
509
+ gender: z.string().nullable(),
510
+ countryIsoCode: z.string(),
511
+ role: z.string().nullable(),
512
+ companyName: z.string().nullable(),
513
+ consent: z.boolean(),
514
+ userId: z.string(),
515
+ customerType: z.string(),
516
+ status: z.string(),
517
+ entityIdentificationNumber: z.string().nullable(),
518
+ defaultAddressId: z.string().nullable(),
519
+ metadata: z.unknown().nullable(),
520
+ totalOrders: z.number(),
521
+ avgOrders: z.number(),
522
+ lastOrderId: z.string().nullable(),
523
+ lastOrderDate: z.string().nullable(),
524
+ loyaltyPoints: z.number(),
525
+ addresses: z.array(CustomerAddressSchema),
526
+ createdOn: z.string(),
527
+ updatedOn: z.string().nullable()
528
+ });
529
+ var CustomerProfilePatchSchema = z.object({
530
+ firstName: z.string().trim().min(1),
531
+ lastName: z.string().trim().min(1),
532
+ phone: z.string().trim(),
533
+ identificationNumber: z.string().nullable(),
534
+ identificationType: z.string().nullable(),
535
+ entityName: z.string().nullable(),
536
+ entityIdentificationNumber: z.string().nullable(),
537
+ gender: z.string().nullable(),
538
+ role: z.string().nullable(),
539
+ companyName: z.string().nullable(),
540
+ dateOfBirth: z.string().nullable(),
541
+ consent: z.boolean(),
542
+ countryIsoCode: z.string().trim().length(2)
543
+ });
544
+ function customerMeToProfilePatch(me) {
545
+ return {
546
+ firstName: me.firstName,
547
+ lastName: me.lastName,
548
+ phone: me.phone ?? "",
549
+ identificationNumber: me.identificationNumber,
550
+ identificationType: me.identificationType,
551
+ entityName: me.entityName,
552
+ entityIdentificationNumber: me.entityIdentificationNumber,
553
+ gender: me.gender,
554
+ role: me.role,
555
+ companyName: me.companyName,
556
+ dateOfBirth: me.dateOfBirth,
557
+ consent: me.consent,
558
+ countryIsoCode: me.countryIsoCode || "PE"
559
+ };
560
+ }
561
+ function customerAuthOptions(accessToken) {
562
+ const token = typeof accessToken === "string" && accessToken.trim().length > 0 ? accessToken.trim() : void 0;
563
+ return token ? { bearerToken: token } : {};
564
+ }
565
+ async function registerCustomer(data) {
566
+ const payload = CustomerRegisterRequestSchema.parse(data);
567
+ const client = await getStoreApiClient();
568
+ return client.post("customer/register", payload);
569
+ }
570
+ async function getCustomerAddresses(accessToken) {
571
+ const client = await getStoreApiClient();
572
+ const token = typeof accessToken === "string" && accessToken.trim().length > 0 ? accessToken.trim() : void 0;
573
+ const response = await client.get("customer/addresses", {
574
+ ...token ? { bearerToken: token } : {}
575
+ });
576
+ return z.array(CustomerAddressSchema).parse(response);
577
+ }
578
+ async function createCustomerAddress(data, accessToken) {
579
+ const client = await getStoreApiClient();
580
+ const token = typeof accessToken === "string" && accessToken.trim().length > 0 ? accessToken.trim() : void 0;
581
+ const payload = CustomerAddressUpsertSchema.parse(data);
582
+ return client.post("customer/addresses", payload, {
583
+ ...token ? { bearerToken: token } : {}
584
+ });
585
+ }
586
+ async function updateCustomerAddress(addressId, data, accessToken) {
587
+ const client = await getStoreApiClient();
588
+ const token = typeof accessToken === "string" && accessToken.trim().length > 0 ? accessToken.trim() : void 0;
589
+ const payload = CustomerAddressUpsertSchema.parse(data);
590
+ const id = addressId.trim();
591
+ if (!id) throw new Error("addressId requerido");
592
+ return client.put(`customer/addresses/${encodeURIComponent(id)}`, payload, {
593
+ ...token ? { bearerToken: token } : {}
594
+ });
595
+ }
596
+ async function deleteCustomerAddress(addressId, accessToken) {
597
+ const client = await getStoreApiClient();
598
+ const token = typeof accessToken === "string" && accessToken.trim().length > 0 ? accessToken.trim() : void 0;
599
+ const id = addressId.trim();
600
+ if (!id) throw new Error("addressId requerido");
601
+ return client.delete(`customer/addresses/${encodeURIComponent(id)}`, {
602
+ ...token ? { bearerToken: token } : {}
603
+ });
604
+ }
605
+ async function getCustomerMe(accessToken) {
606
+ const client = await getStoreApiClient();
607
+ const response = await client.get("customer", customerAuthOptions(accessToken));
608
+ return CustomerMeSchema.parse(response);
609
+ }
610
+ async function patchCustomer(data, accessToken) {
611
+ const client = await getStoreApiClient();
612
+ const payload = CustomerProfilePatchSchema.parse(data);
613
+ await client.patch("customer", payload, customerAuthOptions(accessToken));
614
+ }
615
+ var CustomerOrderShippingAddressSchema = z.object({
616
+ name: z.string().nullable().optional(),
617
+ company: z.string().nullable().optional(),
618
+ address1: z.string(),
619
+ address2: z.string().nullable().optional(),
620
+ city: z.string().nullable().optional(),
621
+ countryCode: z.string(),
622
+ province: z.string().nullable().optional(),
623
+ postalCode: z.string().nullable().optional(),
624
+ district: z.string().nullable().optional(),
625
+ phone: z.string().nullable().optional(),
626
+ latitude: z.number().nullable().optional(),
627
+ longitude: z.number().nullable().optional(),
628
+ metadata: z.record(z.unknown()).nullable().optional()
629
+ }).passthrough();
630
+ var CustomerOrderLineItemSchema = z.object({
631
+ id: z.string(),
632
+ orderId: z.coerce.string(),
633
+ productId: z.coerce.string().nullable(),
634
+ variantId: z.coerce.string().nullable(),
635
+ title: z.string(),
636
+ subtitle: z.string().nullable().optional(),
637
+ thumbnail: z.string().nullable().optional(),
638
+ variantTitle: z.string().nullable().optional(),
639
+ productHandle: z.string().nullable().optional(),
640
+ quantity: z.number(),
641
+ unitPrice: z.number(),
642
+ total: z.number(),
643
+ status: z.string()
644
+ });
645
+ var CustomerOrderListItemSchema = z.object({
646
+ orderId: z.coerce.string(),
647
+ orderNumber: z.string(),
648
+ orderGroup: z.string(),
649
+ orderIds: z.array(z.coerce.string()),
650
+ status: z.string(),
651
+ total: z.number(),
652
+ totalPaid: z.number(),
653
+ currencyCode: z.string(),
654
+ orderDate: z.string(),
655
+ createdOn: z.string(),
656
+ updatedOn: z.string().nullable().optional(),
657
+ paymentAt: z.string().nullable().optional(),
658
+ shippingAddress: CustomerOrderShippingAddressSchema.nullable().optional(),
659
+ shippingAddressSummary: z.string().nullable().optional(),
660
+ items: z.array(CustomerOrderLineItemSchema)
661
+ });
662
+ var CustomerOrderPaymentSchema = z.object({
663
+ id: z.string(),
664
+ paymentMethod: z.string(),
665
+ paymentCode: z.string(),
666
+ amount: z.number(),
667
+ paymentStatus: z.string(),
668
+ receiptUrl: z.string().nullable().optional(),
669
+ createdOn: z.string()
670
+ });
671
+ var CustomerOrderStatusTimelineSchema = z.object({
672
+ key: z.string(),
673
+ label: z.string(),
674
+ occurredAt: z.string(),
675
+ description: z.string().nullable().optional(),
676
+ status: z.string(),
677
+ source: z.string()
678
+ });
679
+ var CustomerOrderDetailSchema = z.object({
680
+ id: z.coerce.string(),
681
+ orderNumber: z.string(),
682
+ orderGroup: z.string(),
683
+ status: z.string(),
684
+ total: z.number(),
685
+ totalPaid: z.number(),
686
+ currencyCode: z.string(),
687
+ orderDate: z.string(),
688
+ createdOn: z.string(),
689
+ updatedOn: z.string().nullable().optional(),
690
+ paymentAt: z.string().nullable().optional(),
691
+ subTotal: z.number(),
692
+ discount: z.number(),
693
+ shipping: z.number(),
694
+ tax: z.number(),
695
+ email: z.string(),
696
+ phone: z.string(),
697
+ firstName: z.string(),
698
+ lastName: z.string(),
699
+ shippingAddress: CustomerOrderShippingAddressSchema.nullable().optional(),
700
+ shippingAddressSummary: z.string().nullable().optional(),
701
+ billingAddress: CustomerOrderShippingAddressSchema.nullable().optional(),
702
+ items: z.array(CustomerOrderLineItemSchema),
703
+ payments: z.array(CustomerOrderPaymentSchema),
704
+ statusTimeline: z.array(CustomerOrderStatusTimelineSchema)
705
+ });
706
+ function authOptions(accessToken) {
707
+ const token = typeof accessToken === "string" && accessToken.trim().length > 0 ? accessToken.trim() : void 0;
708
+ return token ? { bearerToken: token } : {};
709
+ }
710
+ async function getCustomerOrders(accessToken) {
711
+ const client = await getStoreApiClient();
712
+ const response = await client.get("customer/orders", authOptions(accessToken));
713
+ return z.array(CustomerOrderListItemSchema).parse(response);
714
+ }
715
+ async function getCustomerOrder(orderId, accessToken) {
716
+ const client = await getStoreApiClient();
717
+ const id = orderId.trim();
718
+ if (!id) throw new Error("orderId requerido");
719
+ const response = await client.get(
720
+ `customer/orders/${encodeURIComponent(id)}`,
721
+ authOptions(accessToken)
722
+ );
723
+ return CustomerOrderDetailSchema.parse(response);
724
+ }
725
+ var StorefrontOrderAddressSchema = z.object({
726
+ address1: z.string().optional(),
727
+ address2: z.string().optional(),
728
+ city: z.string().optional(),
729
+ province: z.string().optional(),
730
+ country: z.string().optional(),
731
+ zip: z.string().optional(),
732
+ phone: z.string().optional(),
733
+ firstName: z.string().optional(),
734
+ lastName: z.string().optional()
735
+ }).passthrough();
736
+ var OrderDraftItemSchema = z.object({
737
+ id: z.string(),
738
+ productId: z.coerce.string().nullable().optional(),
739
+ sellerId: z.string().nullable().optional(),
740
+ shippingProfileId: z.string().nullable().optional(),
741
+ variantId: z.coerce.string().nullable().optional(),
742
+ title: z.string(),
743
+ subtitle: z.string().nullable().optional(),
744
+ thumbnail: z.string().nullable().optional(),
745
+ quantity: z.number().optional().default(1),
746
+ unitPrice: z.number().optional().default(0),
747
+ subTotal: z.number().optional().default(0),
748
+ discount: z.number().optional().default(0),
749
+ shipping: z.number().optional().default(0),
750
+ tax: z.number().optional().default(0),
751
+ warranty: z.number().optional().default(0),
752
+ total: z.number().optional().default(0),
753
+ requiresShipping: z.boolean().optional().default(true),
754
+ isTaxInclusive: z.boolean().optional().default(true),
755
+ metadata: z.record(z.unknown()).optional().default({})
756
+ });
757
+ var OrderDraftShippingMethodSchema = z.object({
758
+ id: z.string(),
759
+ name: z.string().optional().default(""),
760
+ description: z.string().nullable().optional(),
761
+ subtotal: z.number().optional().default(0),
762
+ discountTotal: z.number().optional().default(0),
763
+ taxTotal: z.number().optional().default(0),
764
+ total: z.number().optional().default(0),
765
+ isTaxInclusive: z.boolean().optional().default(true),
766
+ sellerId: z.string().nullable().optional(),
767
+ shippingProfileId: z.string().nullable().optional(),
768
+ shippingMethodId: z.string().nullable().optional(),
769
+ connectionId: z.string().nullable().optional(),
770
+ providerName: z.string().nullable().optional(),
771
+ trackingCode: z.string().nullable().optional(),
772
+ estimatedDays: z.number().nullable().optional(),
773
+ zoneCode: z.string().nullable().optional(),
774
+ locationId: z.string().nullable().optional(),
775
+ pickupPointId: z.string().nullable().optional(),
776
+ scheduledDate: z.string().nullable().optional(),
777
+ scheduledTime: z.string().nullable().optional(),
778
+ data: z.record(z.unknown()).optional().default({})
779
+ });
780
+ var OrderDraftSchema = z.object({
781
+ id: z.string(),
782
+ storeId: z.string().optional(),
783
+ customerId: z.coerce.string().nullable().optional(),
784
+ cartId: z.string().nullable().optional(),
785
+ draftNumber: z.string(),
786
+ currencyCode: z.string().optional().default("PEN"),
787
+ source: z.string().nullable().optional(),
788
+ notes: z.string().nullable().optional(),
789
+ email: z.string().nullable().optional(),
790
+ phone: z.string().nullable().optional(),
791
+ firstName: z.string().nullable().optional(),
792
+ lastName: z.string().nullable().optional(),
793
+ identificationType: z.string().nullable().optional(),
794
+ identificationNumber: z.string().nullable().optional(),
795
+ entityTaxId: z.string().nullable().optional(),
796
+ entityName: z.string().nullable().optional(),
797
+ status: z.string(),
798
+ draftDate: z.string().nullable().optional(),
799
+ sentAt: z.string().nullable().optional(),
800
+ viewedAt: z.string().nullable().optional(),
801
+ acceptedAt: z.string().nullable().optional(),
802
+ rejectedAt: z.string().nullable().optional(),
803
+ expiredAt: z.string().nullable().optional(),
804
+ cancelledAt: z.string().nullable().optional(),
805
+ convertedAt: z.string().nullable().optional(),
806
+ shareToken: z.string().nullable().optional(),
807
+ shareExpiresAt: z.string().nullable().optional(),
808
+ allowAnonymousShareAccess: z.boolean().optional().default(false),
809
+ sharedAt: z.string().nullable().optional(),
810
+ billingAddress: StorefrontOrderAddressSchema.optional().nullable(),
811
+ shippingAddress: StorefrontOrderAddressSchema.optional().nullable(),
812
+ metadata: z.record(z.unknown()).optional().default({}),
813
+ subTotal: z.number().optional().default(0),
814
+ discount: z.number().optional().default(0),
815
+ shipping: z.number().optional().default(0),
816
+ tax: z.number().optional().default(0),
817
+ warranty: z.number().optional().default(0),
818
+ total: z.number().optional().default(0),
819
+ paymentMethod: z.string().optional().default(""),
820
+ paymentCode: z.string().optional().default(""),
821
+ items: z.array(OrderDraftItemSchema).optional().default([]),
822
+ shippingMethods: z.array(OrderDraftShippingMethodSchema).optional().default([]),
823
+ createdOn: z.string().optional(),
824
+ updatedOn: z.string().nullable().optional()
825
+ });
826
+
827
+ // src/core/client/store-api-order-draft.service.ts
828
+ async function getSharedOrderDraft(shareToken) {
829
+ const client = await getStoreApiClient();
830
+ const response = await client.get(
831
+ `order-drafts/share/${encodeURIComponent(shareToken)}`,
832
+ { noStore: true }
833
+ );
834
+ return OrderDraftSchema.parse(response);
835
+ }
836
+ function useStoreProducts() {
837
+ const useStorefrontProductsList = (params) => {
838
+ return useQuery({
839
+ queryKey: ["storefront", "products", params],
840
+ queryFn: () => getStorefrontProducts(params)
841
+ });
842
+ };
843
+ const useStorefrontInfiniteProducts = (params) => {
844
+ return useInfiniteQuery({
845
+ queryKey: ["storefront", "products", "infinite", params],
846
+ queryFn: ({ pageParam = 1 }) => getStorefrontProducts({ ...params, page: pageParam }),
847
+ getNextPageParam: (lastPage) => lastPage.pageIndex < lastPage.totalPages ? lastPage.pageIndex + 1 : void 0,
848
+ initialPageParam: 1
849
+ });
850
+ };
851
+ const useProductBySlug = (slug) => {
852
+ return useQuery({
853
+ queryKey: ["storefront", "product", "slug", slug],
854
+ queryFn: () => getStorefrontProductBySlug(slug),
855
+ enabled: !!slug
856
+ });
857
+ };
858
+ const useRefreshStorefrontProducts = () => {
859
+ const queryClient = useQueryClient();
860
+ return useMutation({
861
+ mutationFn: (params) => getStorefrontProducts(params),
862
+ onSuccess: () => {
863
+ queryClient.invalidateQueries({ queryKey: ["storefront", "products"] });
864
+ }
865
+ });
866
+ };
867
+ return {
868
+ useStorefrontProductsList,
869
+ useStorefrontInfiniteProducts,
870
+ useProductBySlug,
871
+ useRefreshStorefrontProducts
872
+ };
873
+ }
874
+ function useStoreCollections() {
875
+ const useStorefrontCollectionsList = () => {
876
+ return useQuery({
877
+ queryKey: ["storefront", "collections"],
878
+ queryFn: () => getStorefrontCollections()
879
+ });
880
+ };
881
+ const useCollectionBySlug = (slug) => {
882
+ return useQuery({
883
+ queryKey: ["storefront", "collection", "slug", slug],
884
+ queryFn: () => getStorefrontCollectionBySlug(slug),
885
+ enabled: !!slug
886
+ });
887
+ };
888
+ const useRefreshStorefrontCollections = () => {
889
+ const queryClient = useQueryClient();
890
+ return useMutation({
891
+ mutationFn: () => getStorefrontCollections(),
892
+ onSuccess: () => {
893
+ queryClient.invalidateQueries({ queryKey: ["storefront", "collections"] });
894
+ }
895
+ });
896
+ };
897
+ return {
898
+ useStorefrontCollectionsList,
899
+ useCollectionBySlug,
900
+ useRefreshStorefrontCollections
901
+ };
902
+ }
903
+ function useStorePages() {
904
+ const useStorefrontPagesList = () => {
905
+ return useQuery({
906
+ queryKey: ["storefront", "pages"],
907
+ queryFn: () => getStorefrontPages()
908
+ });
909
+ };
910
+ const usePageBySlug = (slug) => {
911
+ return useQuery({
912
+ queryKey: ["storefront", "page", "slug", slug],
913
+ queryFn: () => getStorefrontPageBySlug(slug),
914
+ enabled: !!slug
915
+ });
916
+ };
917
+ const useRefreshStorefrontPages = () => {
918
+ const queryClient = useQueryClient();
919
+ return useMutation({
920
+ mutationFn: () => getStorefrontPages(),
921
+ onSuccess: () => {
922
+ queryClient.invalidateQueries({ queryKey: ["storefront", "pages"] });
923
+ }
924
+ });
925
+ };
926
+ return {
927
+ useStorefrontPagesList,
928
+ usePageBySlug,
929
+ useRefreshStorefrontPages
930
+ };
931
+ }
932
+ function useStoreCart() {
933
+ const useGetCart = (cartId) => useQuery({
934
+ queryKey: ["storefront", "cart", cartId ?? null],
935
+ queryFn: () => getCart(cartId ?? void 0),
936
+ // Si devuelve 404 no reintentar; para otros errores solo un intento adicional como máximo.
937
+ retry: (failureCount, error) => {
938
+ const status = error.status;
939
+ if (status === 404) return false;
940
+ return failureCount < 1;
941
+ },
942
+ // Evitar refetch automático al enfocar la ventana; el carrito se carga una sola vez aquí.
943
+ refetchOnWindowFocus: false,
944
+ enabled: !!cartId
945
+ });
946
+ const useCreateCart = () => useMutation({
947
+ mutationFn: (data) => createCart(data)
948
+ });
949
+ const useUpdateCartBuyer = () => useMutation({
950
+ mutationFn: ({ data, cartId }) => patchCartBuyer(data, cartId)
951
+ });
952
+ const useUpdateCartAddresses = () => useMutation({
953
+ mutationFn: ({ data, cartId }) => patchCartAddresses(data, cartId)
954
+ });
955
+ const useAddCartItem = () => useMutation({
956
+ mutationFn: ({ cartId, ...data }) => addCartItem(data, cartId)
957
+ });
958
+ const useCheckoutCart = () => useMutation({
959
+ mutationFn: ({ data, cartId }) => checkoutCart(data, cartId)
960
+ });
961
+ const useUploadPaymentReceipt = () => useMutation({
962
+ mutationFn: ({ cartId, file }) => uploadPaymentReceipt(cartId, file)
963
+ });
964
+ const useGetCartCheckout = (cartId) => {
965
+ const inactiveHandledRef = useRef(null);
966
+ const query = useQuery({
967
+ queryKey: ["storefront", "cart", cartId ?? null, "checkout"],
968
+ queryFn: () => getCartCheckout(cartId),
969
+ retry: (failureCount, error) => {
970
+ const status = error.status;
971
+ if (status === 404) return false;
972
+ if (status === 400 && isCartNotActiveApiError(error)) return false;
973
+ return failureCount < 1;
974
+ },
975
+ /** Evita GET repetidos al cambiar de paso / re-render; `refetch()` sigue forzando actualización tras mutaciones. */
976
+ staleTime: 6e4,
977
+ refetchOnMount: false,
978
+ refetchOnReconnect: false,
979
+ refetchOnWindowFocus: false,
980
+ enabled: !!cartId
981
+ });
982
+ useEffect(() => {
983
+ inactiveHandledRef.current = null;
984
+ }, [cartId]);
985
+ useEffect(() => {
986
+ if (!cartId) return;
987
+ if (!query.isError || !query.error) return;
988
+ if (!isCartNotActiveApiError(query.error)) return;
989
+ if (inactiveHandledRef.current === cartId) return;
990
+ inactiveHandledRef.current = cartId;
991
+ toast.error(
992
+ "Este carrito ya no est\xE1 disponible. Se ha vaciado tu bolsa para que puedas empezar de nuevo."
993
+ );
994
+ useCart.getState().clearCartSession();
995
+ }, [cartId, query.isError, query.error]);
996
+ return query;
997
+ };
998
+ const useGetCartPaymentMethods = (cartId) => useQuery({
999
+ queryKey: ["storefront", "cart", cartId ?? null, "payment-methods"],
1000
+ queryFn: () => getCartPaymentMethods(cartId ?? void 0),
1001
+ retry: (failureCount, error) => {
1002
+ const status = error.status;
1003
+ if (status === 404) return false;
1004
+ return failureCount < 1;
1005
+ },
1006
+ refetchOnWindowFocus: false,
1007
+ enabled: !!cartId
1008
+ });
1009
+ const useGetShippingOptions = (cartId, postalCode, enabled) => useQuery({
1010
+ queryKey: [
1011
+ "storefront",
1012
+ "cart",
1013
+ cartId ?? null,
1014
+ "shipping-options",
1015
+ postalCode ?? ""
1016
+ ],
1017
+ queryFn: () => getShippingOptions(cartId, postalCode),
1018
+ retry: (failureCount, error) => {
1019
+ const status = error.status;
1020
+ if (status === 404) return false;
1021
+ return failureCount < 1;
1022
+ },
1023
+ refetchOnWindowFocus: false,
1024
+ enabled: !!cartId && !!postalCode?.trim() && enabled
1025
+ });
1026
+ const usePutCartShippingMethod = () => useMutation({
1027
+ mutationFn: ({ cartId, data }) => putCartShippingMethod(cartId, data)
1028
+ });
1029
+ const useAddCartCoupon = () => useMutation({
1030
+ mutationFn: ({ cartId, data }) => addCartCoupon(cartId, data)
1031
+ });
1032
+ const useRemoveCartCoupon = () => useMutation({
1033
+ mutationFn: ({ cartId, code }) => removeCartCoupon(cartId, code)
1034
+ });
1035
+ return {
1036
+ useCreateCart,
1037
+ useGetCart,
1038
+ useGetCartCheckout,
1039
+ useUpdateCartBuyer,
1040
+ useUpdateCartAddresses,
1041
+ useAddCartItem,
1042
+ useCheckoutCart,
1043
+ useUploadPaymentReceipt,
1044
+ useGetCartPaymentMethods,
1045
+ useGetShippingOptions,
1046
+ usePutCartShippingMethod,
1047
+ useAddCartCoupon,
1048
+ useRemoveCartCoupon
1049
+ };
1050
+ }
1051
+ var useStore = create()(
1052
+ (set) => ({
1053
+ data: null,
1054
+ drawers: {
1055
+ cart: false,
1056
+ search: false,
1057
+ menu: false,
1058
+ filter: false
1059
+ },
1060
+ openDrawer: (drawer) => set((state) => ({
1061
+ drawers: {
1062
+ ...state.drawers,
1063
+ [drawer]: true
1064
+ }
1065
+ })),
1066
+ closeDrawer: (drawer) => set((state) => ({
1067
+ drawers: {
1068
+ ...state.drawers,
1069
+ [drawer]: false
1070
+ }
1071
+ })),
1072
+ toggleDrawer: (drawer) => set((state) => ({
1073
+ drawers: {
1074
+ ...state.drawers,
1075
+ [drawer]: !state.drawers[drawer]
1076
+ }
1077
+ })),
1078
+ closeAllDrawers: () => set((state) => ({
1079
+ drawers: Object.keys(state.drawers).reduce(
1080
+ (acc, key) => ({ ...acc, [key]: false }),
1081
+ {}
1082
+ )
1083
+ })),
1084
+ modals: {
1085
+ login: false,
1086
+ signup: false
1087
+ },
1088
+ openModal: (modal) => set((state) => ({
1089
+ modals: {
1090
+ ...state.modals,
1091
+ login: false,
1092
+ signup: false,
1093
+ [modal]: true
1094
+ }
1095
+ })),
1096
+ closeModal: (modal) => set((state) => ({
1097
+ modals: {
1098
+ ...state.modals,
1099
+ login: false,
1100
+ signup: false,
1101
+ [modal]: false
1102
+ }
1103
+ })),
1104
+ toggleModal: (modal) => set((state) => ({
1105
+ modals: {
1106
+ ...state.modals,
1107
+ [modal]: !state.modals[modal]
1108
+ }
1109
+ })),
1110
+ closeAllModals: () => set((state) => ({
1111
+ modals: Object.keys(state.modals).reduce(
1112
+ (acc, key) => ({ ...acc, [key]: false }),
1113
+ {}
1114
+ )
1115
+ }))
1116
+ })
1117
+ );
1118
+ function isRenderableCmsPage(p) {
1119
+ if (!p.status) return true;
1120
+ return p.status === "published";
1121
+ }
1122
+ function findStorefrontCmsPageBySlug(settings, slug) {
1123
+ const pages = settings?.cms?.pages;
1124
+ if (!pages?.length) return null;
1125
+ const raw = pages.find(
1126
+ (p) => p.slug === slug && isRenderableCmsPage(p)
1127
+ );
1128
+ if (!raw) return null;
1129
+ const parsed = StorefrontPageDetailSchema$1.safeParse(raw);
1130
+ return parsed.success ? parsed.data : null;
1131
+ }
1132
+ function SafeThemeInjector({ theme, className }) {
1133
+ try {
1134
+ if (!theme || typeof theme !== "object") {
1135
+ console.warn("ThemeInjector: Invalid theme provided");
1136
+ return null;
1137
+ }
1138
+ if (!theme.colors || typeof theme.colors !== "object") {
1139
+ console.warn("ThemeInjector: Theme colors are required");
1140
+ return null;
1141
+ }
1142
+ const generateSafeCSSVariables = () => {
1143
+ const cssRules = [":root {"];
1144
+ const toKebabCase = (input) => input.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/_/g, "-").toLowerCase();
1145
+ const sanitizeCSSValue = (value) => {
1146
+ if (typeof value !== "string") return null;
1147
+ const trimmed = value.trim();
1148
+ if (!trimmed) return null;
1149
+ const isValidCSSValue = /^[a-zA-Z0-9\s,()#.-]+$/.test(trimmed);
1150
+ return isValidCSSValue ? trimmed : null;
1151
+ };
1152
+ if (theme.colors && typeof theme.colors === "object") {
1153
+ Object.entries(theme.colors).forEach(([key, value]) => {
1154
+ const sanitizedValue = sanitizeCSSValue(value);
1155
+ if (sanitizedValue) {
1156
+ cssRules.push(` --color-${key}: ${sanitizedValue};`);
1157
+ cssRules.push(` --theme-${key}: ${sanitizedValue};`);
1158
+ cssRules.push(` --${key}: ${sanitizedValue};`);
1159
+ cssRules.push(` --${toKebabCase(key)}: ${sanitizedValue};`);
1160
+ }
1161
+ });
1162
+ }
1163
+ if (theme.fonts && typeof theme.fonts === "object") {
1164
+ Object.entries(theme.fonts).forEach(([key, value]) => {
1165
+ const sanitizedValue = sanitizeCSSValue(value);
1166
+ if (sanitizedValue) {
1167
+ cssRules.push(` --font-${key}: ${sanitizedValue};`);
1168
+ }
1169
+ });
1170
+ }
1171
+ if (theme.spacing && typeof theme.spacing === "object") {
1172
+ Object.entries(theme.spacing).forEach(([key, value]) => {
1173
+ const sanitizedValue = sanitizeCSSValue(value);
1174
+ if (sanitizedValue) {
1175
+ cssRules.push(` --spacing-${key}: ${sanitizedValue};`);
1176
+ }
1177
+ });
1178
+ }
1179
+ if (theme.borderRadius && typeof theme.borderRadius === "object") {
1180
+ Object.entries(theme.borderRadius).forEach(([key, value]) => {
1181
+ const sanitizedValue = sanitizeCSSValue(value);
1182
+ if (sanitizedValue) {
1183
+ cssRules.push(` --radius-${key}: ${sanitizedValue};`);
1184
+ }
1185
+ });
1186
+ }
1187
+ if (theme.shadows && typeof theme.shadows === "object") {
1188
+ Object.entries(theme.shadows).forEach(([key, value]) => {
1189
+ const sanitizedValue = sanitizeCSSValue(value);
1190
+ if (sanitizedValue) {
1191
+ cssRules.push(` --shadow-${key}: ${sanitizedValue};`);
1192
+ }
1193
+ });
1194
+ }
1195
+ cssRules.push("}");
1196
+ return cssRules.join("\n");
1197
+ };
1198
+ const cssVariables = generateSafeCSSVariables();
1199
+ return /* @__PURE__ */ jsx(
1200
+ "style",
1201
+ {
1202
+ dangerouslySetInnerHTML: { __html: cssVariables },
1203
+ "data-theme-injector": theme.id,
1204
+ "data-theme-name": theme.name,
1205
+ className,
1206
+ suppressHydrationWarning: true
1207
+ }
1208
+ );
1209
+ } catch (error) {
1210
+ console.error("ThemeInjector: Error generating theme styles", error);
1211
+ return null;
1212
+ }
1213
+ }
1214
+ function buildOptionGroups(variants) {
1215
+ const map = /* @__PURE__ */ new Map();
1216
+ for (const v of variants) {
1217
+ for (const opt of v.options ?? []) {
1218
+ const key = opt.key;
1219
+ const name = opt.name ?? key;
1220
+ if (!map.has(key)) map.set(key, /* @__PURE__ */ new Map());
1221
+ map.get(key).set(opt.value, name);
1222
+ }
1223
+ }
1224
+ return Array.from(map.entries()).map(([key, valueMap]) => ({
1225
+ key,
1226
+ name: Array.from(valueMap.values())[0] ?? key,
1227
+ values: Array.from(valueMap.keys())
1228
+ }));
1229
+ }
1230
+ function findVariant(variants, selected, hasConfigurableOptions) {
1231
+ if (!hasConfigurableOptions && variants.length > 0) {
1232
+ return variants[0] ?? null;
1233
+ }
1234
+ if (Object.keys(selected).length === 0) return null;
1235
+ for (const v of variants) {
1236
+ const opts = v.options ?? [];
1237
+ const match = opts.length > 0 && opts.every((o) => selected[o.key] === o.value);
1238
+ if (match) return v;
1239
+ }
1240
+ return null;
1241
+ }
1242
+ function getInitialOptions(variants, optionGroups) {
1243
+ const first = variants[0];
1244
+ if (!first) return {};
1245
+ const opts = first.options ?? [];
1246
+ const selected = {};
1247
+ for (const g of optionGroups) {
1248
+ const o = opts.find((x) => x.key === g.key);
1249
+ if (o) selected[g.key] = o.value;
1250
+ }
1251
+ return selected;
1252
+ }
1253
+ function ProductVariantSelector({
1254
+ product,
1255
+ currency = "USD",
1256
+ features = [],
1257
+ onVariantChange,
1258
+ children
1259
+ }) {
1260
+ const variants = useMemo(() => product.variants ?? [], [product.variants]);
1261
+ const optionGroups = useMemo(() => buildOptionGroups(variants), [variants]);
1262
+ const hasConfigurableOptions = optionGroups.length > 0;
1263
+ const [selectedOptions, setSelectedOptions] = useState(
1264
+ () => getInitialOptions(variants, optionGroups)
1265
+ );
1266
+ const selectedVariant = useMemo(
1267
+ () => findVariant(variants, selectedOptions, hasConfigurableOptions),
1268
+ [variants, selectedOptions, hasConfigurableOptions]
1269
+ );
1270
+ const displayPrice = selectedVariant?.price ?? product.price ?? 0;
1271
+ const compareAtPrice = selectedVariant?.compareAtPrice ?? product.compareAtPrice;
1272
+ const isAvailable = (selectedVariant ? (selectedVariant.quantity ?? 0) > 0 : product.isAvailable) ?? true;
1273
+ useEffect(() => {
1274
+ onVariantChange?.(selectedVariant, displayPrice);
1275
+ }, [selectedVariant, displayPrice, onVariantChange]);
1276
+ const setOption = useCallback((key, value) => {
1277
+ setSelectedOptions((prev) => ({ ...prev, [key]: value }));
1278
+ }, []);
1279
+ const hasVariants = hasConfigurableOptions;
1280
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
1281
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
1282
+ /* @__PURE__ */ jsx(
1283
+ "span",
1284
+ {
1285
+ className: "inline-flex items-center rounded-md bg-primary/10 px-2.5 py-0.5 text-base font-semibold text-primary",
1286
+ "data-testid": "product-display-price",
1287
+ children: formatCurrency(displayPrice, currency)
1288
+ }
1289
+ ),
1290
+ compareAtPrice != null && compareAtPrice > displayPrice && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground line-through", children: formatCurrency(compareAtPrice, currency) })
1291
+ ] }),
1292
+ hasVariants && optionGroups.map((group) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1293
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: group.name }),
1294
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: group.values.map((value) => {
1295
+ const isSelected = selectedOptions[group.key] === value;
1296
+ const isColorLike = group.key.toLowerCase().includes("color") || group.key.toLowerCase().includes("colour");
1297
+ return /* @__PURE__ */ jsx(
1298
+ "button",
1299
+ {
1300
+ type: "button",
1301
+ onClick: () => setOption(group.key, value),
1302
+ 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"}`,
1303
+ style: isColorLike && isSelected && value.toLowerCase() === "black" ? { backgroundColor: "#1a1a1a", borderColor: "#1a1a1a", color: "#fff" } : isColorLike && value.toLowerCase() === "white" ? { backgroundColor: "#fafafa", color: "#1a1a1a", borderColor: "#e5e5e5" } : void 0,
1304
+ "data-testid": `variant-option-${group.key}-${value}`,
1305
+ children: value
1306
+ },
1307
+ value
1308
+ );
1309
+ }) })
1310
+ ] }, group.key)),
1311
+ features.length > 0 && /* @__PURE__ */ jsx("ul", { className: "list-disc list-inside space-y-1 text-sm text-muted-foreground", children: features.map((f, i) => /* @__PURE__ */ jsx("li", { children: f }, i)) }),
1312
+ children?.({
1313
+ product,
1314
+ selectedVariant,
1315
+ displayPrice,
1316
+ compareAtPrice,
1317
+ isAvailable,
1318
+ selectedOptions
1319
+ })
1320
+ ] });
1321
+ }
1322
+
1323
+ // src/react/components/cms-sections/utils.ts
1324
+ var PRODUCT_SORTS = /* @__PURE__ */ new Set([
1325
+ "price_asc",
1326
+ "price_desc",
1327
+ "name_asc",
1328
+ "name_desc",
1329
+ "newest"
1330
+ ]);
1331
+ function orderedBlocks(blocks) {
1332
+ return [...blocks ?? []].sort((a, b) => a.order - b.order);
1333
+ }
1334
+ function getCmsSectionKey(section, index) {
1335
+ return section.id ?? `${canonicalSectionType(section.type)}-${section.order}-${index}`;
1336
+ }
1337
+ function getCmsBlockKey(block, index) {
1338
+ return block.id ?? `${block.type}-${block.order}-${index}`;
1339
+ }
1340
+ function canonicalSectionType(type) {
1341
+ if (type === "page-hero") {
1342
+ return "hero";
1343
+ }
1344
+ if (type === "featured-products") {
1345
+ return "product-list";
1346
+ }
1347
+ return type;
1348
+ }
1349
+ function storefrontHref(value) {
1350
+ const href = value.trim();
1351
+ if (!href) {
1352
+ return "#";
1353
+ }
1354
+ if (href === "#" || href.startsWith("http://") || href.startsWith("https://") || href.startsWith("mailto:") || href.startsWith("tel:")) {
1355
+ return href;
1356
+ }
1357
+ if (href === "/") {
1358
+ return "/store";
1359
+ }
1360
+ if (href.startsWith("/store") || href.startsWith("/product")) {
1361
+ return href;
1362
+ }
1363
+ if (href.startsWith("/collections") || href.startsWith("/pages") || href.startsWith("/account")) {
1364
+ return `/store${href}`;
1365
+ }
1366
+ return href;
1367
+ }
1368
+ function sectionTitleContent(section) {
1369
+ return settingContent(section.settings, "title") || settingContent(section.settings, "name");
1370
+ }
1371
+ function settingString(settings, key) {
1372
+ const value = settings?.[key];
1373
+ return typeof value === "string" ? value : "";
1374
+ }
1375
+ function settingContent(settings, key) {
1376
+ const value = settings?.[key];
1377
+ if (typeof value === "string" || typeof value === "number") {
1378
+ return value;
1379
+ }
1380
+ if (value == null || typeof value === "boolean") {
1381
+ return "";
1382
+ }
1383
+ return value;
1384
+ }
1385
+ function settingNumber(settings, key, fallback) {
1386
+ const value = settings?.[key];
1387
+ if (typeof value === "number" && Number.isFinite(value)) {
1388
+ return value;
1389
+ }
1390
+ if (typeof value === "string") {
1391
+ const parsed = Number(value);
1392
+ return Number.isFinite(parsed) ? parsed : fallback;
1393
+ }
1394
+ return fallback;
1395
+ }
1396
+ function layoutChildren(settings, key) {
1397
+ const children = settings.children;
1398
+ if (typeof children !== "object" || children === null || Array.isArray(children)) {
1399
+ return [];
1400
+ }
1401
+ const value = children[key];
1402
+ return Array.isArray(value) ? value : [];
1403
+ }
1404
+ function layoutPadding(value) {
1405
+ switch (value) {
1406
+ case "none":
1407
+ return "py-0";
1408
+ case "small":
1409
+ return "py-6";
1410
+ case "large":
1411
+ return "py-16";
1412
+ default:
1413
+ return "py-10";
1414
+ }
1415
+ }
1416
+ function layoutMaxWidth(value) {
1417
+ switch (value) {
1418
+ case "full":
1419
+ return "max-w-none";
1420
+ case "narrow":
1421
+ return "max-w-3xl";
1422
+ default:
1423
+ return "max-w-7xl";
1424
+ }
1425
+ }
1426
+ function layoutBackground(value) {
1427
+ switch (value) {
1428
+ case "muted":
1429
+ return "bg-neutral-50";
1430
+ case "dark":
1431
+ return "bg-neutral-950 text-white";
1432
+ default:
1433
+ return "bg-white";
1434
+ }
1435
+ }
1436
+ function layoutGap(value) {
1437
+ switch (value) {
1438
+ case "none":
1439
+ return "gap-0";
1440
+ case "small":
1441
+ return "gap-3";
1442
+ case "large":
1443
+ return "gap-8";
1444
+ default:
1445
+ return "gap-5";
1446
+ }
1447
+ }
1448
+ function layoutAlign(value) {
1449
+ switch (value) {
1450
+ case "start":
1451
+ return "items-start";
1452
+ case "center":
1453
+ return "items-center";
1454
+ case "end":
1455
+ return "items-end";
1456
+ default:
1457
+ return "items-stretch";
1458
+ }
1459
+ }
1460
+ function layoutJustify(value) {
1461
+ switch (value) {
1462
+ case "center":
1463
+ return "justify-center";
1464
+ case "end":
1465
+ return "justify-end";
1466
+ case "between":
1467
+ return "justify-between";
1468
+ default:
1469
+ return "justify-start";
1470
+ }
1471
+ }
1472
+ function productSortValue(value) {
1473
+ return PRODUCT_SORTS.has(value) ? value : "newest";
1474
+ }
1475
+ function sanitizeHtml(html) {
1476
+ return html.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?>[\s\S]*?<\/style>/gi, "").replace(/<(iframe|object|embed|link|meta)[\s\S]*?>/gi, "").replace(/\son\w+\s*=\s*(['"]).*?\1/gi, "").replace(/\s(href|src)\s*=\s*(['"])\s*javascript:[\s\S]*?\2/gi, "");
1477
+ }
1478
+ var DEFAULT_CMS_IMAGE_URL = "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(`
1479
+ <svg xmlns="http://www.w3.org/2000/svg" width="640" height="360" viewBox="0 0 640 360">
1480
+ <rect width="640" height="360" fill="#f4f4f5"/>
1481
+ <rect x="40" y="36" width="560" height="288" rx="20" fill="#ffffff" stroke="#d4d4d8"/>
1482
+ <path d="M148 246l102-102 78 78 48-48 120 120H148z" fill="#d4d4d8"/>
1483
+ <circle cx="456" cy="118" r="32" fill="#e4e4e7"/>
1484
+ </svg>
1485
+ `);
1486
+ function SectionHeading({ section }) {
1487
+ const title = sectionTitleContent(section);
1488
+ const subtitle = settingContent(section.settings, "subtitle");
1489
+ if (!title && !subtitle) {
1490
+ return null;
1491
+ }
1492
+ return /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
1493
+ title ? /* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold tracking-tight text-neutral-950", children: title }) : null,
1494
+ subtitle ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-neutral-600", children: subtitle }) : null
1495
+ ] });
1496
+ }
1497
+ function EmptyState({ children }) {
1498
+ return /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-dashed p-6 text-center text-sm text-neutral-500", children });
1499
+ }
1500
+ function DefaultLink({ href, className, children }) {
1501
+ return /* @__PURE__ */ jsx("a", { href, className, children });
1502
+ }
1503
+ function CmsImage({
1504
+ src,
1505
+ fallbackSrc = DEFAULT_CMS_IMAGE_URL,
1506
+ onError,
1507
+ ...props
1508
+ }) {
1509
+ const imageSrc = src?.trim() || fallbackSrc;
1510
+ return /* @__PURE__ */ jsx(
1511
+ "img",
1512
+ {
1513
+ src: imageSrc,
1514
+ onError: (event) => {
1515
+ onError?.(event);
1516
+ const image = event.currentTarget;
1517
+ if (image.dataset.cmsFallback === "true") {
1518
+ return;
1519
+ }
1520
+ image.dataset.cmsFallback = "true";
1521
+ image.src = fallbackSrc;
1522
+ },
1523
+ ...props
1524
+ }
1525
+ );
1526
+ }
1527
+ function RichTextSection({ section }) {
1528
+ const html = settingString(section.settings, "html") || settingString(section.settings, "content") || settingString(section.settings, "richText");
1529
+ return /* @__PURE__ */ jsxs("section", { className: "container mx-auto px-4 py-10", children: [
1530
+ /* @__PURE__ */ jsx(SectionHeading, { section }),
1531
+ html ? /* @__PURE__ */ jsx(
1532
+ "div",
1533
+ {
1534
+ className: "prose prose-neutral max-w-none",
1535
+ dangerouslySetInnerHTML: { __html: sanitizeHtml(html) }
1536
+ }
1537
+ ) : null
1538
+ ] });
1539
+ }
1540
+ function CustomHtmlSection({ section }) {
1541
+ const html = settingString(section.settings, "html") || settingString(section.settings, "content");
1542
+ if (!html) {
1543
+ return null;
1544
+ }
1545
+ return /* @__PURE__ */ jsx(
1546
+ "section",
1547
+ {
1548
+ className: "cms-custom-html",
1549
+ dangerouslySetInnerHTML: { __html: sanitizeHtml(html) }
1550
+ }
1551
+ );
1552
+ }
1553
+ function FloatingBlockSection({
1554
+ section,
1555
+ linkComponent: LinkComponent
1556
+ }) {
1557
+ const settings = section.settings ?? {};
1558
+ const title = settingContent(settings, "title");
1559
+ const text = settingContent(settings, "text");
1560
+ const imageUrl = settingString(settings, "imageUrl");
1561
+ const linkText = settingContent(settings, "linkText");
1562
+ const linkUrl = storefrontHref(settingString(settings, "linkUrl"));
1563
+ const x = boundedPercent(settingNumber(settings, "x", 8));
1564
+ const y = boundedPercent(settingNumber(settings, "y", 12));
1565
+ const width = Math.min(
1566
+ Math.max(boundedPercent(settingNumber(settings, "width", 28)), 12),
1567
+ 90
1568
+ );
1569
+ const zIndex = Math.max(Math.round(settingNumber(settings, "zIndex", 20)), 1);
1570
+ if (!title && !text && !imageUrl) {
1571
+ return null;
1572
+ }
1573
+ const content = /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-lg border border-black/10 bg-white text-neutral-950 shadow-lg", children: [
1574
+ imageUrl ? /* @__PURE__ */ jsx("div", { className: "aspect-[16/9] bg-neutral-100", children: /* @__PURE__ */ jsx(
1575
+ CmsImage,
1576
+ {
1577
+ src: imageUrl,
1578
+ alt: "",
1579
+ className: "h-full w-full object-cover",
1580
+ loading: "lazy"
1581
+ }
1582
+ ) }) : null,
1583
+ /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
1584
+ title ? /* @__PURE__ */ jsx("p", { className: "text-base font-semibold leading-6", children: title }) : null,
1585
+ text ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm leading-5 text-neutral-600", children: text }) : null,
1586
+ linkText && linkUrl !== "#" ? /* @__PURE__ */ jsx("span", { className: "mt-3 inline-flex text-sm font-medium underline underline-offset-4", children: linkText }) : null
1587
+ ] })
1588
+ ] });
1589
+ return /* @__PURE__ */ jsx(
1590
+ "div",
1591
+ {
1592
+ className: "absolute",
1593
+ style: {
1594
+ left: `${x}%`,
1595
+ top: `${y}%`,
1596
+ width: `${width}%`,
1597
+ zIndex
1598
+ },
1599
+ children: linkText && linkUrl !== "#" ? /* @__PURE__ */ jsx(LinkComponent, { href: linkUrl, children: content }) : content
1600
+ }
1601
+ );
1602
+ }
1603
+ function FaqSection({ section }) {
1604
+ const items = orderedBlocks(section.blocks);
1605
+ return /* @__PURE__ */ jsxs("section", { className: "container mx-auto px-4 py-12", children: [
1606
+ /* @__PURE__ */ jsx(SectionHeading, { section }),
1607
+ /* @__PURE__ */ jsx("div", { className: "divide-y rounded-lg border bg-white", children: items.map((item, index) => /* @__PURE__ */ jsxs("details", { className: "group p-4", children: [
1608
+ /* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-sm font-medium text-neutral-950", children: settingContent(item.settings, "question") || settingContent(item.settings, "title") }),
1609
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm leading-6 text-neutral-600", children: settingContent(item.settings, "answer") || settingContent(item.settings, "content") })
1610
+ ] }, getCmsBlockKey(item, index))) })
1611
+ ] });
1612
+ }
1613
+ function boundedPercent(value) {
1614
+ if (!Number.isFinite(value)) {
1615
+ return 0;
1616
+ }
1617
+ return Math.min(Math.max(value, 0), 100);
1618
+ }
1619
+ function NewsletterSection({ section }) {
1620
+ const title = sectionTitleContent(section) || "Recibe novedades";
1621
+ const subtitle = settingContent(section.settings, "subtitle");
1622
+ const placeholder = settingString(section.settings, "placeholder") || "tu@email.com";
1623
+ const buttonText = settingContent(section.settings, "buttonText") || "Suscribirme";
1624
+ return /* @__PURE__ */ jsx("section", { className: "bg-neutral-950 px-4 py-14 text-white", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-2xl text-center", children: [
1625
+ /* @__PURE__ */ jsx("h2", { className: "text-3xl font-semibold", children: title }),
1626
+ subtitle ? /* @__PURE__ */ jsx("p", { className: "mt-3 text-white/70", children: subtitle }) : null,
1627
+ /* @__PURE__ */ jsxs("form", { className: "mt-6 flex flex-col gap-2 sm:flex-row", children: [
1628
+ /* @__PURE__ */ jsx(
1629
+ "input",
1630
+ {
1631
+ type: "email",
1632
+ placeholder,
1633
+ className: "h-10 flex-1 rounded-md border border-white/20 bg-white px-3 text-sm text-neutral-950 outline-none"
1634
+ }
1635
+ ),
1636
+ /* @__PURE__ */ jsx(
1637
+ "button",
1638
+ {
1639
+ type: "submit",
1640
+ className: "h-10 rounded-md bg-white px-5 text-sm font-medium text-neutral-950",
1641
+ children: buttonText
1642
+ }
1643
+ )
1644
+ ] })
1645
+ ] }) });
1646
+ }
1647
+ function IconRowSection({
1648
+ section,
1649
+ linkComponent: LinkComponent
1650
+ }) {
1651
+ return /* @__PURE__ */ jsxs("section", { className: "container mx-auto px-4 py-10", children: [
1652
+ /* @__PURE__ */ jsx(SectionHeading, { section }),
1653
+ /* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-4", children: orderedBlocks(section.blocks).map((block, index) => {
1654
+ const settings = block.settings ?? {};
1655
+ const href = storefrontHref(settingString(settings, "linkUrl"));
1656
+ const content = /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-white p-4", children: [
1657
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-neutral-950", children: settingContent(settings, "title") || settingContent(settings, "label") }),
1658
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-neutral-600", children: settingContent(settings, "subtitle") || settingContent(settings, "description") })
1659
+ ] });
1660
+ return href === "#" ? /* @__PURE__ */ jsx("div", { children: content }, getCmsBlockKey(block, index)) : /* @__PURE__ */ jsx(LinkComponent, { href, children: content }, getCmsBlockKey(block, index));
1661
+ }) })
1662
+ ] });
1663
+ }
1664
+ function TestimonialsSection({
1665
+ section
1666
+ }) {
1667
+ return /* @__PURE__ */ jsxs("section", { className: "container mx-auto px-4 py-12", children: [
1668
+ /* @__PURE__ */ jsx(SectionHeading, { section }),
1669
+ /* @__PURE__ */ jsx("div", { className: "grid gap-4 md:grid-cols-3", children: orderedBlocks(section.blocks).map((block, index) => /* @__PURE__ */ jsxs(
1670
+ "blockquote",
1671
+ {
1672
+ className: "rounded-lg border bg-white p-5",
1673
+ children: [
1674
+ /* @__PURE__ */ jsx("p", { className: "text-sm leading-6 text-neutral-700", children: settingString(block.settings, "quote") || settingContent(block.settings, "content") || settingContent(block.settings, "text") }),
1675
+ /* @__PURE__ */ jsx("footer", { className: "mt-4 text-sm font-medium text-neutral-950", children: settingContent(block.settings, "author") || settingContent(block.settings, "name") })
1676
+ ]
1677
+ },
1678
+ getCmsBlockKey(block, index)
1679
+ )) })
1680
+ ] });
1681
+ }
1682
+ function StatsSection({ section }) {
1683
+ return /* @__PURE__ */ jsxs("section", { className: "container mx-auto px-4 py-12", children: [
1684
+ /* @__PURE__ */ jsx(SectionHeading, { section }),
1685
+ /* @__PURE__ */ jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4", children: orderedBlocks(section.blocks).map((block, index) => /* @__PURE__ */ jsxs(
1686
+ "div",
1687
+ {
1688
+ className: "rounded-lg border bg-white p-5",
1689
+ children: [
1690
+ /* @__PURE__ */ jsx("p", { className: "text-3xl font-semibold text-neutral-950", children: settingContent(block.settings, "value") }),
1691
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-neutral-600", children: settingContent(block.settings, "label") || settingContent(block.settings, "title") })
1692
+ ]
1693
+ },
1694
+ getCmsBlockKey(block, index)
1695
+ )) })
1696
+ ] });
1697
+ }
1698
+ function TimelineSection({ section }) {
1699
+ return /* @__PURE__ */ jsxs("section", { className: "container mx-auto px-4 py-12", children: [
1700
+ /* @__PURE__ */ jsx(SectionHeading, { section }),
1701
+ /* @__PURE__ */ jsx("div", { className: "space-y-4 border-l pl-5", children: orderedBlocks(section.blocks).map((block, index) => /* @__PURE__ */ jsxs("article", { className: "relative", children: [
1702
+ /* @__PURE__ */ jsx("span", { className: "absolute -left-[27px] top-1 size-3 rounded-full bg-neutral-950" }),
1703
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-neutral-950", children: settingContent(block.settings, "title") }),
1704
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-neutral-600", children: settingContent(block.settings, "description") || settingContent(block.settings, "content") })
1705
+ ] }, getCmsBlockKey(block, index))) })
1706
+ ] });
1707
+ }
1708
+ function ContactFormSection({ section }) {
1709
+ const fields = orderedBlocks(section.blocks);
1710
+ const submitText = settingString(section.settings, "submitText") || "Enviar";
1711
+ return /* @__PURE__ */ jsxs("section", { className: "container mx-auto px-4 py-12", children: [
1712
+ /* @__PURE__ */ jsx(SectionHeading, { section }),
1713
+ /* @__PURE__ */ jsxs("form", { className: "mx-auto grid max-w-2xl gap-3", children: [
1714
+ (fields.length > 0 ? fields : defaultContactFields()).map(
1715
+ (field, index) => /* @__PURE__ */ jsx(ContactField, { field }, getCmsBlockKey(field, index))
1716
+ ),
1717
+ /* @__PURE__ */ jsx("button", { className: "h-10 rounded-md bg-neutral-950 px-4 text-sm font-medium text-white", children: submitText })
1718
+ ] })
1719
+ ] });
1720
+ }
1721
+ function ContactField({ field }) {
1722
+ const type = settingString(field.settings, "type") || "text";
1723
+ const label = settingString(field.settings, "label");
1724
+ const placeholder = settingString(field.settings, "placeholder");
1725
+ const name = settingString(field.settings, "name");
1726
+ const className = "h-10 rounded-md border px-3 text-sm";
1727
+ if (type === "textarea") {
1728
+ return /* @__PURE__ */ jsx(
1729
+ "textarea",
1730
+ {
1731
+ name,
1732
+ "aria-label": label || placeholder,
1733
+ className: "min-h-32 rounded-md border px-3 py-2 text-sm",
1734
+ placeholder: placeholder || label
1735
+ }
1736
+ );
1737
+ }
1738
+ if (type === "select") {
1739
+ const rawOptions = field.settings?.options;
1740
+ const options = Array.isArray(rawOptions) ? rawOptions.filter((item) => typeof item === "string") : settingString(field.settings, "options").split("\n").map((item) => item.trim()).filter(Boolean);
1741
+ return /* @__PURE__ */ jsxs("select", { name, "aria-label": label, className, children: [
1742
+ /* @__PURE__ */ jsx("option", { value: "", children: placeholder || label }),
1743
+ options.map((option) => /* @__PURE__ */ jsx("option", { value: option, children: option }, option))
1744
+ ] });
1745
+ }
1746
+ return /* @__PURE__ */ jsx(
1747
+ "input",
1748
+ {
1749
+ name,
1750
+ type,
1751
+ "aria-label": label || placeholder,
1752
+ className,
1753
+ placeholder: placeholder || label
1754
+ }
1755
+ );
1756
+ }
1757
+ function defaultContactFields() {
1758
+ return [
1759
+ {
1760
+ type: "field",
1761
+ order: 1,
1762
+ settings: { name: "fullName", type: "text", label: "Nombre" }
1763
+ },
1764
+ {
1765
+ type: "field",
1766
+ order: 2,
1767
+ settings: { name: "email", type: "email", label: "Email" }
1768
+ },
1769
+ {
1770
+ type: "field",
1771
+ order: 3,
1772
+ settings: { name: "message", type: "textarea", label: "Mensaje" }
1773
+ }
1774
+ ];
1775
+ }
1776
+ function UnknownSection({ section }) {
1777
+ return /* @__PURE__ */ jsx("section", { className: "container mx-auto px-4 py-8", children: /* @__PURE__ */ jsxs("div", { className: "rounded border border-dashed p-4 text-sm text-neutral-500", children: [
1778
+ 'Seccion tipo "',
1779
+ section.type,
1780
+ '" pendiente de renderer.'
1781
+ ] }) });
1782
+ }
1783
+
1784
+ // src/core/constants/currency.ts
1785
+ var countryCurrency = {
1786
+ "PEN": "es-PE",
1787
+ "USD": "en-US",
1788
+ "COP": "es-CO",
1789
+ "MXN": "es-MX"
1790
+ };
1791
+
1792
+ // src/core/utils/format-currency.ts
1793
+ function formatCurrency2(amount, currency = "USD") {
1794
+ const locale = countryCurrency[currency] ?? "en-US";
1795
+ return new Intl.NumberFormat(locale, {
1796
+ style: "currency",
1797
+ currency
1798
+ }).format(amount);
1799
+ }
1800
+ function AnnouncementBarSection({
1801
+ section,
1802
+ linkComponent: LinkComponent
1803
+ }) {
1804
+ const text = settingContent(section.settings, "text") || sectionTitleContent(section);
1805
+ const linkText = settingContent(section.settings, "linkText");
1806
+ const href = storefrontHref(settingString(section.settings, "linkUrl"));
1807
+ if (!text) {
1808
+ return null;
1809
+ }
1810
+ return /* @__PURE__ */ jsx("section", { className: "bg-neutral-950 px-4 py-2 text-white", children: /* @__PURE__ */ jsxs("div", { className: "container mx-auto flex flex-wrap items-center justify-center gap-2 text-center text-sm", children: [
1811
+ /* @__PURE__ */ jsx("span", { children: text }),
1812
+ linkText && href !== "#" ? /* @__PURE__ */ jsx(
1813
+ LinkComponent,
1814
+ {
1815
+ href,
1816
+ className: "font-medium underline underline-offset-4",
1817
+ children: linkText
1818
+ }
1819
+ ) : null
1820
+ ] }) });
1821
+ }
1822
+ function HeroSection({
1823
+ section,
1824
+ linkComponent: LinkComponent
1825
+ }) {
1826
+ const slides = orderedBlocks(section.blocks);
1827
+ const fallbackSlide = {
1828
+ id: `${section.id ?? "section"}-fallback`,
1829
+ type: "slide",
1830
+ order: 1,
1831
+ settings: section.settings ?? {}
1832
+ };
1833
+ const visibleSlides = slides.length > 0 ? slides : [fallbackSlide];
1834
+ return /* @__PURE__ */ jsx("section", { className: "bg-neutral-950 text-white", children: /* @__PURE__ */ jsx("div", { className: "flex snap-x overflow-x-auto", children: visibleSlides.map((slide, index) => {
1835
+ const settings = slide.settings ?? {};
1836
+ const imageUrl = settingString(settings, "imageUrl") || settingString(section.settings, "imageUrl") || DEFAULT_CMS_IMAGE_URL;
1837
+ const title = settingContent(settings, "title") || settingContent(section.settings, "title") || sectionTitleContent(section);
1838
+ const subtitle = settingContent(settings, "subtitle") || settingContent(section.settings, "subtitle");
1839
+ const ctaText = settingContent(settings, "ctaText") || settingContent(section.settings, "ctaText");
1840
+ const ctaUrl = settingString(settings, "ctaUrl") || settingString(section.settings, "ctaUrl");
1841
+ const href = storefrontHref(ctaUrl);
1842
+ return /* @__PURE__ */ jsxs(
1843
+ "article",
1844
+ {
1845
+ className: "relative min-h-[520px] w-full shrink-0 snap-start overflow-hidden",
1846
+ children: [
1847
+ /* @__PURE__ */ jsx(
1848
+ CmsImage,
1849
+ {
1850
+ src: imageUrl,
1851
+ alt: "",
1852
+ className: "absolute inset-0 h-full w-full object-cover opacity-75"
1853
+ }
1854
+ ),
1855
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gradient-to-r from-black/70 via-black/35 to-transparent" }),
1856
+ /* @__PURE__ */ jsx("div", { className: "container relative z-10 mx-auto flex min-h-[520px] items-center px-4", children: /* @__PURE__ */ jsxs("div", { className: "max-w-2xl py-16", children: [
1857
+ /* @__PURE__ */ jsx("h1", { className: "text-4xl font-semibold tracking-tight md:text-6xl", children: title }),
1858
+ subtitle ? /* @__PURE__ */ jsx("p", { className: "mt-4 max-w-xl text-base text-white/85 md:text-lg", children: subtitle }) : null,
1859
+ ctaText && href !== "#" ? /* @__PURE__ */ jsx(
1860
+ LinkComponent,
1861
+ {
1862
+ href,
1863
+ className: "mt-8 inline-flex h-10 items-center rounded-md bg-white px-5 text-sm font-medium text-neutral-950 transition hover:bg-white/90",
1864
+ children: ctaText
1865
+ }
1866
+ ) : null
1867
+ ] }) })
1868
+ ]
1869
+ },
1870
+ getCmsBlockKey(slide, index)
1871
+ );
1872
+ }) }) });
1873
+ }
1874
+ function ProductsSection({
1875
+ section,
1876
+ products,
1877
+ carousel,
1878
+ linkComponent,
1879
+ currency,
1880
+ preview
1881
+ }) {
1882
+ const placeholders = preview && products.length === 0;
1883
+ const count = Math.min(
1884
+ productPreviewCount(section, carousel),
1885
+ carousel ? 5 : 8
1886
+ );
1887
+ return /* @__PURE__ */ jsxs("section", { className: carousel ? "py-12" : "container mx-auto px-4 py-12", children: [
1888
+ carousel ? /* @__PURE__ */ jsx("div", { className: "container mx-auto px-4", children: /* @__PURE__ */ jsx(SectionHeading, { section }) }) : /* @__PURE__ */ jsx(SectionHeading, { section }),
1889
+ /* @__PURE__ */ jsx("div", { className: carousel ? "container mx-auto overflow-x-auto px-4" : "", children: /* @__PURE__ */ jsx(
1890
+ "div",
1891
+ {
1892
+ className: carousel ? "flex min-w-full gap-4 pb-2" : "grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4",
1893
+ children: placeholders ? Array.from({ length: count }).map((_, index) => /* @__PURE__ */ jsx(ProductPlaceholder, { carousel }, index)) : products.map((product) => /* @__PURE__ */ jsx(
1894
+ ProductCard,
1895
+ {
1896
+ product,
1897
+ carousel,
1898
+ linkComponent,
1899
+ currency
1900
+ },
1901
+ product.id
1902
+ ))
1903
+ }
1904
+ ) }),
1905
+ !placeholders && products.length === 0 ? /* @__PURE__ */ jsx(EmptyState, { children: "No hay productos para mostrar." }) : null
1906
+ ] });
1907
+ }
1908
+ function CategoryGridSection({
1909
+ section,
1910
+ collections,
1911
+ linkComponent: LinkComponent,
1912
+ preview
1913
+ }) {
1914
+ const blockItems = orderedBlocks(section.blocks).map((block) => {
1915
+ const settings = block.settings ?? {};
1916
+ const name = settingContent(settings, "name") || settingContent(settings, "title");
1917
+ const url = settingString(settings, "url") || settingString(settings, "linkUrl");
1918
+ const imageUrl = settingString(settings, "imageUrl");
1919
+ if (!name) {
1920
+ return null;
1921
+ }
1922
+ return {
1923
+ id: getCmsBlockKey(block, block.order),
1924
+ name,
1925
+ href: storefrontHref(url || "/collections"),
1926
+ imageUrl: imageUrl || DEFAULT_CMS_IMAGE_URL
1927
+ };
1928
+ }).filter(Boolean);
1929
+ const collectionItems = collections.map((collection) => ({
1930
+ id: collection.id,
1931
+ name: collection.name,
1932
+ href: `/store/collections/${collection.slug}`,
1933
+ imageUrl: collection.imageUrl || DEFAULT_CMS_IMAGE_URL
1934
+ }));
1935
+ const previewItems = preview ? createPreviewCollections(section) : [];
1936
+ const items = blockItems.length > 0 ? blockItems : collectionItems.length > 0 ? collectionItems : previewItems;
1937
+ return /* @__PURE__ */ jsxs("section", { className: "container mx-auto px-4 py-12", children: [
1938
+ /* @__PURE__ */ jsx(SectionHeading, { section }),
1939
+ items.length > 0 ? /* @__PURE__ */ jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", children: items.map((item) => /* @__PURE__ */ jsxs(
1940
+ LinkComponent,
1941
+ {
1942
+ href: item.href,
1943
+ className: "group overflow-hidden rounded-lg border bg-white",
1944
+ children: [
1945
+ /* @__PURE__ */ jsx("div", { className: "aspect-[4/3] overflow-hidden bg-neutral-100", children: /* @__PURE__ */ jsx(
1946
+ CmsImage,
1947
+ {
1948
+ src: item.imageUrl,
1949
+ alt: "",
1950
+ className: "h-full w-full object-cover transition duration-300 group-hover:scale-105"
1951
+ }
1952
+ ) }),
1953
+ /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx("h3", { className: "text-base font-medium text-neutral-950", children: item.name }) })
1954
+ ]
1955
+ },
1956
+ item.id
1957
+ )) }) : /* @__PURE__ */ jsx(EmptyState, { children: "No hay categorias para mostrar." })
1958
+ ] });
1959
+ }
1960
+ function PromoCardsSection({
1961
+ section,
1962
+ linkComponent: LinkComponent
1963
+ }) {
1964
+ const cards = orderedBlocks(section.blocks);
1965
+ return /* @__PURE__ */ jsxs("section", { className: "container mx-auto px-4 py-12", children: [
1966
+ /* @__PURE__ */ jsx(SectionHeading, { section }),
1967
+ /* @__PURE__ */ jsx("div", { className: "grid gap-4 md:grid-cols-2", children: cards.map((card, index) => {
1968
+ const settings = card.settings ?? {};
1969
+ const title = settingContent(settings, "title");
1970
+ const subtitle = settingContent(settings, "subtitle");
1971
+ const imageUrl = settingString(settings, "imageUrl") || DEFAULT_CMS_IMAGE_URL;
1972
+ const ctaText = settingContent(settings, "ctaText");
1973
+ const ctaUrl = storefrontHref(
1974
+ settingString(settings, "ctaUrl") || settingString(settings, "url")
1975
+ );
1976
+ return /* @__PURE__ */ jsxs(
1977
+ LinkComponent,
1978
+ {
1979
+ href: ctaUrl,
1980
+ className: "group relative min-h-[260px] overflow-hidden rounded-lg bg-neutral-900 text-white",
1981
+ children: [
1982
+ /* @__PURE__ */ jsx(
1983
+ CmsImage,
1984
+ {
1985
+ src: imageUrl,
1986
+ alt: "",
1987
+ className: "absolute inset-0 h-full w-full object-cover opacity-75 transition duration-300 group-hover:scale-105"
1988
+ }
1989
+ ),
1990
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/35" }),
1991
+ /* @__PURE__ */ jsxs("div", { className: "relative z-10 flex min-h-[260px] flex-col justify-end p-6", children: [
1992
+ /* @__PURE__ */ jsx("h3", { className: "text-2xl font-semibold", children: title }),
1993
+ subtitle ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-white/85", children: subtitle }) : null,
1994
+ ctaText ? /* @__PURE__ */ jsx("span", { className: "mt-4 text-sm font-medium", children: ctaText }) : null
1995
+ ] })
1996
+ ]
1997
+ },
1998
+ getCmsBlockKey(card, index)
1999
+ );
2000
+ }) })
2001
+ ] });
2002
+ }
2003
+ function LogoSliderSection({
2004
+ section,
2005
+ linkComponent: LinkComponent
2006
+ }) {
2007
+ return /* @__PURE__ */ jsxs("section", { className: "container mx-auto px-4 py-10", children: [
2008
+ /* @__PURE__ */ jsx(SectionHeading, { section }),
2009
+ /* @__PURE__ */ jsx("div", { className: "flex gap-4 overflow-x-auto", children: orderedBlocks(section.blocks).map((block, index) => {
2010
+ const settings = block.settings ?? {};
2011
+ const imageUrl = settingString(settings, "imageUrl") || DEFAULT_CMS_IMAGE_URL;
2012
+ const alt = settingString(settings, "alt") || "Logo";
2013
+ const href = storefrontHref(settingString(settings, "linkUrl"));
2014
+ const logo = /* @__PURE__ */ jsx("div", { className: "flex h-24 w-40 shrink-0 items-center justify-center rounded-lg border bg-white p-4", children: /* @__PURE__ */ jsx(
2015
+ CmsImage,
2016
+ {
2017
+ src: imageUrl,
2018
+ alt,
2019
+ className: "max-h-full max-w-full object-contain"
2020
+ }
2021
+ ) });
2022
+ return href !== "#" ? /* @__PURE__ */ jsx(LinkComponent, { href, children: logo }, getCmsBlockKey(block, index)) : /* @__PURE__ */ jsx("div", { children: logo }, getCmsBlockKey(block, index));
2023
+ }) })
2024
+ ] });
2025
+ }
2026
+ function ProductCard({
2027
+ product,
2028
+ carousel,
2029
+ linkComponent: LinkComponent,
2030
+ currency
2031
+ }) {
2032
+ const onSale = product.compareAtPrice != null && product.compareAtPrice > product.price;
2033
+ return /* @__PURE__ */ jsxs(
2034
+ LinkComponent,
2035
+ {
2036
+ href: `/product/${product.slug}`,
2037
+ className: carousel ? "group w-[220px] shrink-0 sm:w-[250px] md:w-[280px]" : "group",
2038
+ children: [
2039
+ /* @__PURE__ */ jsx("div", { className: "aspect-square overflow-hidden rounded-lg border border-neutral-200 bg-neutral-100 transition-all duration-300 group-hover:border-neutral-300", children: /* @__PURE__ */ jsx(
2040
+ CmsImage,
2041
+ {
2042
+ src: product.imageUrl || DEFAULT_CMS_IMAGE_URL,
2043
+ alt: product.name,
2044
+ className: "h-full w-full object-cover object-center transition-transform duration-500 group-hover:scale-105"
2045
+ }
2046
+ ) }),
2047
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex flex-col", children: [
2048
+ /* @__PURE__ */ jsx("h3", { className: "line-clamp-2 text-sm font-medium text-neutral-950", children: product.name }),
2049
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-2", children: [
2050
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-neutral-950", children: formatCurrency2(product.price, currency) }),
2051
+ onSale ? /* @__PURE__ */ jsx("span", { className: "text-sm text-neutral-500 line-through", children: formatCurrency2(product.compareAtPrice ?? 0, currency) }) : null
2052
+ ] })
2053
+ ] })
2054
+ ]
2055
+ }
2056
+ );
2057
+ }
2058
+ function ProductPlaceholder({ carousel }) {
2059
+ return /* @__PURE__ */ jsxs(
2060
+ "div",
2061
+ {
2062
+ className: carousel ? "w-[220px] shrink-0 sm:w-[250px] md:w-[280px]" : "",
2063
+ children: [
2064
+ /* @__PURE__ */ jsx("div", { className: "aspect-square rounded-lg bg-neutral-100" }),
2065
+ /* @__PURE__ */ jsx("div", { className: "mt-4 h-3 w-3/4 rounded bg-neutral-200" }),
2066
+ /* @__PURE__ */ jsx("div", { className: "mt-2 h-3 w-1/2 rounded bg-neutral-100" })
2067
+ ]
2068
+ }
2069
+ );
2070
+ }
2071
+ function productPreviewCount(section, carousel) {
2072
+ const configuredBlocks = orderedBlocks(section.blocks).length;
2073
+ if (configuredBlocks > 0) {
2074
+ return configuredBlocks;
2075
+ }
2076
+ return settingNumber(section.settings, "limit", carousel ? 5 : 8);
2077
+ }
2078
+ function createPreviewCollections(section) {
2079
+ const count = Math.min(settingNumber(section.settings, "limit", 6), 6);
2080
+ return Array.from({ length: count }, (_, index) => ({
2081
+ id: `preview-collection-${index + 1}`,
2082
+ name: `Categoria ${index + 1}`,
2083
+ href: "/store/collections/all",
2084
+ imageUrl: DEFAULT_CMS_IMAGE_URL
2085
+ }));
2086
+ }
2087
+ function LayoutSection({ section, renderSections }) {
2088
+ const settings = section.settings ?? {};
2089
+ const paddingY = settingString(settings, "paddingY") || "medium";
2090
+ const maxWidth = settingString(settings, "maxWidth") || "container";
2091
+ const background = settingString(settings, "background") || "default";
2092
+ return /* @__PURE__ */ jsx(
2093
+ "section",
2094
+ {
2095
+ className: `${layoutBackground(background)} ${layoutPadding(paddingY)}`,
2096
+ children: /* @__PURE__ */ jsx("div", { className: `mx-auto px-4 ${layoutMaxWidth(maxWidth)}`, children: renderSections(layoutChildren(settings, "content")) })
2097
+ }
2098
+ );
2099
+ }
2100
+ function LayoutGrid({ section, renderSections }) {
2101
+ const settings = section.settings ?? {};
2102
+ const columnCount = Math.min(
2103
+ Math.max(Math.round(settingNumber(settings, "columnCount", 2)), 1),
2104
+ 6
2105
+ );
2106
+ return /* @__PURE__ */ jsx(
2107
+ "div",
2108
+ {
2109
+ className: `grid ${layoutGap(settingString(settings, "gap"))}`,
2110
+ style: {
2111
+ gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))`
2112
+ },
2113
+ children: renderSections(layoutChildren(settings, "content"))
2114
+ }
2115
+ );
2116
+ }
2117
+ function LayoutFlex({ section, renderSections }) {
2118
+ const settings = section.settings ?? {};
2119
+ return /* @__PURE__ */ jsx(
2120
+ "div",
2121
+ {
2122
+ className: [
2123
+ "flex",
2124
+ settingString(settings, "direction") === "column" ? "flex-col" : "flex-col md:flex-row",
2125
+ settingString(settings, "wrap") === "nowrap" ? "flex-nowrap" : "flex-wrap",
2126
+ layoutGap(settingString(settings, "gap")),
2127
+ layoutAlign(settingString(settings, "align")),
2128
+ layoutJustify(settingString(settings, "justify"))
2129
+ ].join(" "),
2130
+ children: renderSections(layoutChildren(settings, "content"))
2131
+ }
2132
+ );
2133
+ }
2134
+ function LayoutSpace({ section }) {
2135
+ const settings = section.settings ?? {};
2136
+ const size = settingString(settings, "size") || "24px";
2137
+ const direction = settingString(settings, "direction") || "vertical";
2138
+ const isHorizontal = direction === "horizontal";
2139
+ const isBoth = direction === "both";
2140
+ return /* @__PURE__ */ jsx(
2141
+ "div",
2142
+ {
2143
+ "aria-hidden": "true",
2144
+ style: {
2145
+ display: "block",
2146
+ width: isHorizontal || isBoth ? size : "100%",
2147
+ height: isHorizontal ? "100%" : size
2148
+ }
2149
+ }
2150
+ );
2151
+ }
2152
+ function CmsSectionsRenderer({
2153
+ sections,
2154
+ sectionData,
2155
+ linkComponent,
2156
+ currency = "PEN",
2157
+ preview = false,
2158
+ wrap = true
2159
+ }) {
2160
+ const LinkComponent = linkComponent ?? DefaultLink;
2161
+ const sortedSections = [...sections].sort((a, b) => a.order - b.order);
2162
+ const content = /* @__PURE__ */ jsx(Fragment, { children: sortedSections.map((section, index) => {
2163
+ const sectionKey = getCmsSectionKey(section, index);
2164
+ return /* @__PURE__ */ jsx(
2165
+ CmsSection,
2166
+ {
2167
+ section,
2168
+ data: sectionData?.[sectionKey],
2169
+ linkComponent: LinkComponent,
2170
+ currency,
2171
+ preview
2172
+ },
2173
+ sectionKey
2174
+ );
2175
+ }) });
2176
+ return wrap ? /* @__PURE__ */ jsx("div", { className: "relative", children: content }) : content;
2177
+ }
2178
+ function CmsSection({
2179
+ section,
2180
+ data,
2181
+ linkComponent,
2182
+ currency,
2183
+ preview
2184
+ }) {
2185
+ const type = canonicalSectionType(section.type);
2186
+ const renderSections = (sections) => /* @__PURE__ */ jsx(
2187
+ CmsSectionsRenderer,
2188
+ {
2189
+ sections,
2190
+ linkComponent,
2191
+ currency,
2192
+ preview,
2193
+ wrap: false
2194
+ }
2195
+ );
2196
+ if (type === "layout-section") {
2197
+ return /* @__PURE__ */ jsx(
2198
+ LayoutSection,
2199
+ {
2200
+ section,
2201
+ linkComponent,
2202
+ currency,
2203
+ preview,
2204
+ renderSections
2205
+ }
2206
+ );
2207
+ }
2208
+ if (type === "layout-grid") {
2209
+ return /* @__PURE__ */ jsx(
2210
+ LayoutGrid,
2211
+ {
2212
+ section,
2213
+ linkComponent,
2214
+ currency,
2215
+ preview,
2216
+ renderSections
2217
+ }
2218
+ );
2219
+ }
2220
+ if (type === "layout-flex") {
2221
+ return /* @__PURE__ */ jsx(
2222
+ LayoutFlex,
2223
+ {
2224
+ section,
2225
+ linkComponent,
2226
+ currency,
2227
+ preview,
2228
+ renderSections
2229
+ }
2230
+ );
2231
+ }
2232
+ if (type === "layout-space") {
2233
+ return /* @__PURE__ */ jsx(
2234
+ LayoutSpace,
2235
+ {
2236
+ section,
2237
+ linkComponent,
2238
+ currency,
2239
+ preview,
2240
+ renderSections
2241
+ }
2242
+ );
2243
+ }
2244
+ if (type === "hero") {
2245
+ return /* @__PURE__ */ jsx(HeroSection, { section, linkComponent });
2246
+ }
2247
+ if (type === "announcement-bar") {
2248
+ return /* @__PURE__ */ jsx(AnnouncementBarSection, { section, linkComponent });
2249
+ }
2250
+ if (type === "product-list" || type === "product-carousel") {
2251
+ return /* @__PURE__ */ jsx(
2252
+ ProductsSection,
2253
+ {
2254
+ section,
2255
+ products: data?.products ?? [],
2256
+ carousel: type === "product-carousel",
2257
+ linkComponent,
2258
+ currency,
2259
+ preview
2260
+ }
2261
+ );
2262
+ }
2263
+ if (type === "category-grid" || type === "collection-list") {
2264
+ return /* @__PURE__ */ jsx(
2265
+ CategoryGridSection,
2266
+ {
2267
+ section,
2268
+ collections: data?.collections ?? [],
2269
+ linkComponent,
2270
+ preview
2271
+ }
2272
+ );
2273
+ }
2274
+ if (type === "promo-cards") {
2275
+ return /* @__PURE__ */ jsx(PromoCardsSection, { section, linkComponent });
2276
+ }
2277
+ if (type === "rich-text" || type === "seo-text") {
2278
+ return /* @__PURE__ */ jsx(RichTextSection, { section });
2279
+ }
2280
+ if (type === "custom-html") {
2281
+ return /* @__PURE__ */ jsx(CustomHtmlSection, { section });
2282
+ }
2283
+ if (type === "floating-block") {
2284
+ return /* @__PURE__ */ jsx(FloatingBlockSection, { section, linkComponent });
2285
+ }
2286
+ if (type === "faq") {
2287
+ return /* @__PURE__ */ jsx(FaqSection, { section });
2288
+ }
2289
+ if (type === "newsletter") {
2290
+ return /* @__PURE__ */ jsx(NewsletterSection, { section });
2291
+ }
2292
+ if (type === "trust-badges" || type === "widget-row") {
2293
+ return /* @__PURE__ */ jsx(IconRowSection, { section, linkComponent });
2294
+ }
2295
+ if (type === "logo-slider") {
2296
+ return /* @__PURE__ */ jsx(LogoSliderSection, { section, linkComponent });
2297
+ }
2298
+ if (type === "testimonials") {
2299
+ return /* @__PURE__ */ jsx(TestimonialsSection, { section });
2300
+ }
2301
+ if (type === "stats") {
2302
+ return /* @__PURE__ */ jsx(StatsSection, { section });
2303
+ }
2304
+ if (type === "timeline") {
2305
+ return /* @__PURE__ */ jsx(TimelineSection, { section });
2306
+ }
2307
+ if (type === "contact-form") {
2308
+ return /* @__PURE__ */ jsx(ContactFormSection, { section });
2309
+ }
2310
+ return /* @__PURE__ */ jsx(UnknownSection, { section });
2311
+ }
2312
+ async function DefaultCmsSectionsRenderer({
2313
+ sections
2314
+ }) {
2315
+ const sortedSections = [...sections].sort((a, b) => a.order - b.order);
2316
+ const dataEntries = await Promise.all(
2317
+ sortedSections.map(async (section, index) => {
2318
+ const data = await resolveSectionData(section);
2319
+ return [getCmsSectionKey(section, index), data];
2320
+ })
2321
+ );
2322
+ const sectionData = Object.fromEntries(dataEntries);
2323
+ return /* @__PURE__ */ jsx(
2324
+ CmsSectionsRenderer,
2325
+ {
2326
+ sections,
2327
+ sectionData,
2328
+ linkComponent: Link
2329
+ }
2330
+ );
2331
+ }
2332
+ async function resolveSectionData(section) {
2333
+ const type = canonicalSectionType(section.type);
2334
+ if (type === "product-list" || type === "product-carousel") {
2335
+ return { products: await resolveSectionProducts(section) };
2336
+ }
2337
+ if ((type === "category-grid" || type === "collection-list") && !hasConfiguredCollectionBlocks(section)) {
2338
+ return {
2339
+ collections: (await getCollectionsSafe()).slice(
2340
+ 0,
2341
+ settingNumber2(section.settings, "limit", 6)
2342
+ )
2343
+ };
2344
+ }
2345
+ return {};
2346
+ }
2347
+ async function resolveSectionProducts(section) {
2348
+ const slugs = orderedBlocks2(section.blocks).map((block) => settingString2(block.settings, "productSlug")).filter(Boolean).slice(0, settingNumber2(section.settings, "limit", 8));
2349
+ if (slugs.length > 0) {
2350
+ const products = await Promise.all(
2351
+ slugs.map((slug) => getStorefrontProductBySlug(slug).catch(() => null))
2352
+ );
2353
+ return products.filter(Boolean);
2354
+ }
2355
+ const settings = section.settings ?? {};
2356
+ const limit = settingNumber2(
2357
+ settings,
2358
+ "limit",
2359
+ settingNumber2(settings, "pageSize", 8)
2360
+ );
2361
+ const sort = productSortValue(
2362
+ settingString2(settings, "sort")
2363
+ );
2364
+ const collection = settingString2(settings, "collectionSlug") || settingString2(settings, "collection") || void 0;
2365
+ const category = settingString2(settings, "category") || void 0;
2366
+ const brand = settingString2(settings, "brand") || void 0;
2367
+ const search = settingString2(settings, "search") || void 0;
2368
+ const params = {
2369
+ page: 1,
2370
+ pageSize: limit,
2371
+ sort,
2372
+ collection,
2373
+ category,
2374
+ brand,
2375
+ search
2376
+ };
2377
+ const response = await getStorefrontProducts(params).catch(() => null);
2378
+ return response?.products ?? [];
2379
+ }
2380
+ async function getCollectionsSafe() {
2381
+ return getStorefrontCollections().catch(() => []);
2382
+ }
2383
+ function hasConfiguredCollectionBlocks(section) {
2384
+ return orderedBlocks2(section.blocks).some(
2385
+ (block) => settingString2(block.settings, "name") || settingString2(block.settings, "title")
2386
+ );
2387
+ }
2388
+ function orderedBlocks2(blocks) {
2389
+ return [...blocks ?? []].sort((a, b) => a.order - b.order);
2390
+ }
2391
+ function settingString2(settings, key) {
2392
+ const value = settings?.[key];
2393
+ return typeof value === "string" ? value : "";
2394
+ }
2395
+ function settingNumber2(settings, key, fallback) {
2396
+ const value = settings?.[key];
2397
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
2398
+ }
2399
+
2400
+ // src/npm/nextjs.ts
2401
+ var usePublicStore = () => useStore.getState().data;
2402
+
2403
+ export { CustomerAddressSchema, CustomerAddressUpsertSchema, CustomerMeSchema, CustomerOrderDetailSchema, CustomerOrderListItemSchema, CustomerProfilePatchSchema, CustomerRegisterRequestSchema, DefaultCmsSectionsRenderer, ECOMIQ_CLIENTS_STORE_KV_BINDING, ProductVariantSelector, SafeThemeInjector, StoreApiClient, addCartCoupon, buildStorefrontKvSnapshotKey, checkoutCart, createCart, createCustomerAddress, createStoreApiClient, customerMeToProfilePatch, deleteCustomerAddress, findStorefrontCmsPageBySlug, getCart, getCartCheckout, getCartPaymentMethods, getCustomerAddresses, getCustomerMe, getCustomerOrder, getCustomerOrders, getHostname, getPublicStore, getSharedOrderDraft, getShippingOptions, getStoreApiBaseUrl, getStoreApiClient, getStorefrontCollectionBySlug, getStorefrontCollections, getStorefrontPageBySlug, getStorefrontPagePreview, getStorefrontPages, getStorefrontProductBySlug, getStorefrontProducts, getStorefrontSettings, getStorefrontSettingsFromKv, isCartNotActiveApiError, normalizeStoreHost, patchCartAddresses, patchCartBuyer, patchCustomer, putCartShippingMethod, registerCustomer, removeCartCoupon, resolvePublicStoreSettingsSource, resolveStorefrontSettingsHost, sanitizeHtml, setClientStoreId, setServerRequestOrigin, setServerStoreId, storeApiClient, updateCustomerAddress, uploadPaymentReceipt, usePublicStore, useStore, useStoreCart, useStoreCollections, useStorePages, useStoreProducts };
2404
+ //# sourceMappingURL=nextjs.js.map
2405
+ //# sourceMappingURL=nextjs.js.map