@fayz-ai/storefront 0.10.0 → 0.12.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.
Files changed (56) hide show
  1. package/dist/app/createStorefrontApp.d.ts.map +1 -1
  2. package/dist/app/router.d.ts +1 -1
  3. package/dist/app/scaffold.d.ts.map +1 -1
  4. package/dist/{chunk-6B7H3IZ6.js → chunk-SDQHSUFC.js} +1398 -585
  5. package/dist/chunk-SDQHSUFC.js.map +1 -0
  6. package/dist/components/Skeleton.d.ts +17 -0
  7. package/dist/components/Skeleton.d.ts.map +1 -0
  8. package/dist/components/account/CustomerAccountShell.d.ts +7 -3
  9. package/dist/components/account/CustomerAccountShell.d.ts.map +1 -1
  10. package/dist/components/account/section.d.ts +15 -0
  11. package/dist/components/account/section.d.ts.map +1 -0
  12. package/dist/components/cart/CartDrawer.d.ts.map +1 -1
  13. package/dist/components/cart/QuantityInput.d.ts.map +1 -1
  14. package/dist/components/checkout/CheckoutPayment.d.ts +17 -0
  15. package/dist/components/checkout/CheckoutPayment.d.ts.map +1 -0
  16. package/dist/components/checkout/CheckoutSteps.d.ts +9 -0
  17. package/dist/components/checkout/CheckoutSteps.d.ts.map +1 -0
  18. package/dist/components/layout/StorefrontHeader.d.ts.map +1 -1
  19. package/dist/components/order/OrderTrackingTimeline.d.ts.map +1 -1
  20. package/dist/components/order/SuccessMark.d.ts +10 -0
  21. package/dist/components/order/SuccessMark.d.ts.map +1 -0
  22. package/dist/data/loaders.d.ts +0 -4
  23. package/dist/data/loaders.d.ts.map +1 -1
  24. package/dist/data/local-orders.d.ts +5 -0
  25. package/dist/data/local-orders.d.ts.map +1 -0
  26. package/dist/data/shipping-items.d.ts +20 -0
  27. package/dist/data/shipping-items.d.ts.map +1 -0
  28. package/dist/document/config.d.ts +21 -3
  29. package/dist/document/config.d.ts.map +1 -1
  30. package/dist/document/sections.d.ts +1 -1
  31. package/dist/document/sections.d.ts.map +1 -1
  32. package/dist/document/store-policy.d.ts +29 -0
  33. package/dist/document/store-policy.d.ts.map +1 -0
  34. package/dist/hooks/use-my-orders.d.ts.map +1 -1
  35. package/dist/hooks/use-shipping.d.ts +3 -0
  36. package/dist/hooks/use-shipping.d.ts.map +1 -1
  37. package/dist/index.d.ts +2 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +4 -3
  40. package/dist/index.js.map +1 -1
  41. package/dist/lib/errors.d.ts +9 -0
  42. package/dist/lib/errors.d.ts.map +1 -0
  43. package/dist/lib/motion.d.ts.map +1 -1
  44. package/dist/lib/testids.d.ts +12 -0
  45. package/dist/lib/testids.d.ts.map +1 -1
  46. package/dist/render.js +1 -1
  47. package/dist/stores/cart.store.d.ts +28 -0
  48. package/dist/stores/cart.store.d.ts.map +1 -1
  49. package/dist/stores/delivery.store.d.ts +9 -0
  50. package/dist/stores/delivery.store.d.ts.map +1 -1
  51. package/dist/views/CheckoutPage.d.ts.map +1 -1
  52. package/dist/views/MyPurchasesPage.d.ts.map +1 -1
  53. package/dist/views/OrderConfirmationPage.d.ts.map +1 -1
  54. package/dist/workflows/checkout.d.ts.map +1 -1
  55. package/package.json +7 -7
  56. package/dist/chunk-6B7H3IZ6.js.map +0 -1
@@ -2,13 +2,13 @@ import { create } from 'zustand';
2
2
  import { persist } from 'zustand/middleware';
3
3
  import { createAuthRuntime, createMockAuthAdapter } from '@fayz-ai/plugin-auth';
4
4
  import { getShopProvider, setShopProvider } from '@fayz-ai/shop/runtime';
5
- import { setShopAccessTokenResolver } from '@fayz-ai/sdk/shop';
6
- import { normalizePostalCode, lookupPostalCode, defaultRouterAdapter, defineLoader, SIDECAR_REFERENCES, staticRouterAdapter, isLoaderParamRef, isLoaderPropRef, resolveLoader, dataNeedKey, getBlockMeta, defaultPropsFromSettings, getBlockErrorVisibility, isBlockEnabledOn, checkBlockConstraints, checkBlockContext, repairBlockTree, renderBlocks, clearExtensionPoints, defineExtensionPoint, runExtensionPoint, runEscapeValve, breadcrumbJsonLd, hasEntityDeclaration, resolveEntityDeclaration, defineBlock, formatPostalCode, defineEntity, listEntityDeclarations, resolveHandle, resolveSidecarDeclaration, seoOrigin, RESERVED_ENTITY_FIELDS, ENTITY_FIELD_TYPES, handleNameFor, registerHandle, hasHandle, listHandles, blockRegistry, loaderRegistry, listLoaders } from '@fayz-ai/core';
5
+ import { setShopCarrierQuotesResolver, setShopAccessTokenResolver, setShopShippingItemsResolver } from '@fayz-ai/sdk/shop';
6
+ import { normalizePostalCode, lookupPostalCode, defaultRouterAdapter, defineLoader, staticRouterAdapter, isLoaderParamRef, isLoaderPropRef, resolveLoader, dataNeedKey, getBlockMeta, defaultPropsFromSettings, getBlockErrorVisibility, isBlockEnabledOn, checkBlockConstraints, checkBlockContext, repairBlockTree, renderBlocks, clearExtensionPoints, defineExtensionPoint, runExtensionPoint, runEscapeValve, breadcrumbJsonLd, hasEntityDeclaration, resolveEntityDeclaration, defineBlock, formatPostalCode, defineEntity, listEntityDeclarations, resolveHandle, seoOrigin, RESERVED_ENTITY_FIELDS, ENTITY_FIELD_TYPES, handleNameFor, registerHandle, hasHandle, listHandles, blockRegistry, loaderRegistry, listLoaders } from '@fayz-ai/core';
7
7
  import React4, { createContext, useContext, useEffect, useRef, useState, useSyncExternalStore, useMemo, useCallback } from 'react';
8
8
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
- import { ChevronLeft, ChevronRight, ShoppingBag, X, Minus, Plus, Send, MessageCircle, Mail, Truck, RefreshCcw, ShieldCheck, ChevronDown, Check, Search, Trash2, Lock, CreditCard, Star, MapPin, Phone, User, UserCircle, Package, LogOut, Menu, PackageCheck, XCircle, RotateCcw, Clock, QrCode, Info, AlertCircle } from 'lucide-react';
9
+ import { ChevronLeft, ChevronRight, ShoppingBag, X, Minus, Plus, Send, MessageCircle, Mail, Truck, RefreshCcw, ShieldCheck, ChevronDown, Check, Search, Trash2, Lock, CreditCard, Star, MapPin, Phone, User, UserCircle, Package, LogOut, Menu, QrCode, Copy, XCircle, RotateCcw, Clock, PackageCheck, Info, AlertCircle } from 'lucide-react';
10
10
  import { getLucideIcon } from '@fayz-ai/ui/icons';
11
- import { validateDiscount } from '@fayz-ai/shop';
11
+ import { validateDiscount, documentDigits, isChargeableProvider, formatDocument, isValidDocument } from '@fayz-ai/shop';
12
12
  import { storefrontDiscountToPromotion, previewCouponPercent, discountToPromotion, PROMOTION_TYPES, PROMOTION_STATUSES, APPLICATION_METHOD_TYPES, APPLICATION_METHOD_TARGET_TYPES, APPLICATION_METHOD_ALLOCATIONS, hasRuleAttribute, listRuleAttributeKeys, RULE_OPERATORS, resolveRuleAttribute } from '@fayz-ai/shop/rules';
13
13
  import { createMockShopProvider } from '@fayz-ai/shop/mock';
14
14
 
@@ -156,6 +156,274 @@ async function signOutCustomer() {
156
156
  }
157
157
  }
158
158
 
159
+ // src/lib/format.ts
160
+ function formatMoney(value, currency = "BRL", locale = "pt-BR") {
161
+ return new Intl.NumberFormat(locale, { style: "currency", currency }).format(value);
162
+ }
163
+ function roundCents(value) {
164
+ return Math.round(value * 100) / 100;
165
+ }
166
+
167
+ // src/data/product-options.ts
168
+ function cleanValues(value) {
169
+ if (!Array.isArray(value)) return [];
170
+ return Array.from(new Set(value.filter((item) => typeof item === "string" && item.trim().length > 0)));
171
+ }
172
+ function cleanOptionGroup(value) {
173
+ if (!value || typeof value !== "object") return null;
174
+ const record = value;
175
+ const id = typeof record.id === "string" ? record.id : typeof record.name === "string" ? record.name : null;
176
+ const label = typeof record.label === "string" ? record.label : typeof record.name === "string" ? record.name : id;
177
+ const values = cleanValues(record.values);
178
+ if (!id || !label || values.length === 0) return null;
179
+ return { id, label, values };
180
+ }
181
+ var warnedOverlayProducts = /* @__PURE__ */ new Set();
182
+ function resetProductOptionsDeprecationWarnings() {
183
+ warnedOverlayProducts.clear();
184
+ }
185
+ function getProductOptionGroups(product) {
186
+ if (product.options && product.options.length > 0) {
187
+ return [...product.options].sort((a, b) => a.position - b.position).map((option) => ({ id: option.name, label: option.name, values: [...option.values] }));
188
+ }
189
+ const options = product.metadata.options;
190
+ const explicit = Array.isArray(options) ? options.map(cleanOptionGroup).filter((group) => Boolean(group)) : [];
191
+ const groups = [...explicit];
192
+ if (groups.length === 0) {
193
+ const sizes = cleanValues(product.metadata.sizes);
194
+ const colors = cleanValues(product.metadata.colors);
195
+ if (sizes.length > 0) groups.push({ id: "Tamanho", label: "Tamanho", values: sizes });
196
+ if (colors.length > 0) groups.push({ id: "Cor", label: "Cor", values: colors });
197
+ }
198
+ if (groups.length > 0 && !warnedOverlayProducts.has(product.id)) {
199
+ warnedOverlayProducts.add(product.id);
200
+ console.warn(
201
+ `[storefront] product "${product.slug}" renders options from the DEPRECATED metadata overlay (metadata.options/sizes/colors). These labels are display-only \u2014 checkout cannot price or reserve them. Define real variants (0028) instead; the overlay path is removed at M9.`
202
+ );
203
+ }
204
+ return groups;
205
+ }
206
+ function resolveVariantForSelection(product, selection) {
207
+ const variants = product.variants;
208
+ if (!variants || variants.length === 0) return null;
209
+ const normalized = normalizeProductOptionSelection(selection);
210
+ const wanted = Object.entries(normalized);
211
+ return variants.find((variant) => {
212
+ const options = variant.options;
213
+ return Object.keys(options).length === wanted.length && wanted.every(([key, value]) => options[key] === value);
214
+ }) ?? null;
215
+ }
216
+ function selectionPrice(product, selection) {
217
+ const variant = resolveVariantForSelection(product, selection);
218
+ return variant?.price ?? product.price;
219
+ }
220
+ function normalizeProductOptionSelection(selection) {
221
+ if (!selection) return {};
222
+ return Object.fromEntries(
223
+ Object.entries(selection).filter((entry) => entry[0].trim().length > 0 && entry[1].trim().length > 0).sort(([a], [b]) => a.localeCompare(b))
224
+ );
225
+ }
226
+ function formatProductOptionSelection(selection) {
227
+ return Object.entries(normalizeProductOptionSelection(selection)).map(([label, value]) => `${label}: ${value}`).join(" \xB7 ");
228
+ }
229
+ function productOptionSelectionKey(selection) {
230
+ const normalized = normalizeProductOptionSelection(selection);
231
+ const entries = Object.entries(normalized);
232
+ return entries.length > 0 ? JSON.stringify(entries) : "";
233
+ }
234
+ var EMPTY = {
235
+ postalCode: "",
236
+ address: null,
237
+ options: [],
238
+ selectedZoneId: null,
239
+ status: "idle",
240
+ error: null,
241
+ refusedZone: null
242
+ };
243
+ var useDeliveryStore = create()(
244
+ persist(
245
+ (set, get) => ({
246
+ ...EMPTY,
247
+ async resolve(postalCode, subtotal) {
248
+ const code = normalizePostalCode(postalCode);
249
+ if (code.length !== 8) {
250
+ set({ ...EMPTY, postalCode, status: "error", error: "Digite os 8 d\xEDgitos do CEP." });
251
+ return;
252
+ }
253
+ set({ postalCode: code, status: "loading", error: null });
254
+ const shop = getShopProvider();
255
+ const refusalPromise = shop.checkDelivery ? shop.checkDelivery(code).then((check) => check.refused ? check.refusedZone || "Regi\xE3o n\xE3o atendida" : null).catch(() => null) : Promise.resolve(null);
256
+ let options = [];
257
+ try {
258
+ options = await shop.quoteShipping?.(code, subtotal) ?? [];
259
+ } catch {
260
+ set({
261
+ ...EMPTY,
262
+ postalCode: code,
263
+ status: "error",
264
+ error: "N\xE3o foi poss\xEDvel calcular o frete agora. Tente de novo."
265
+ });
266
+ return;
267
+ }
268
+ const refusedZone = await refusalPromise;
269
+ if (refusedZone) options = [];
270
+ let address = null;
271
+ let notFound = false;
272
+ try {
273
+ address = await lookupPostalCode(code);
274
+ notFound = address === null;
275
+ } catch {
276
+ address = null;
277
+ }
278
+ set({
279
+ postalCode: code,
280
+ address,
281
+ options,
282
+ selectedZoneId: options[0]?.zoneId ?? null,
283
+ refusedZone,
284
+ // A store with no zones configured quotes nothing, and that must NOT
285
+ // read as "we don't deliver here" — it falls back to the store-wide
286
+ // flat rate, which the cart already knows how to show.
287
+ status: options.length > 0 ? "served" : "unserved",
288
+ // Reported alongside the price, not instead of it: the CEP may be
289
+ // mistyped even though its range is covered.
290
+ error: notFound ? "CEP n\xE3o encontrado. Confira os n\xFAmeros." : null
291
+ });
292
+ },
293
+ selectZone(zoneId) {
294
+ if (!get().options.some((option) => option.zoneId === zoneId)) return;
295
+ set({ selectedZoneId: zoneId });
296
+ void getShopProvider().selectShippingOption?.(zoneId)?.catch(() => {
297
+ });
298
+ },
299
+ clear() {
300
+ set({ ...EMPTY });
301
+ }
302
+ }),
303
+ {
304
+ name: "fayz.storefront.delivery.v1",
305
+ // Deliberately NOT persisting `options`: a rehydrated
306
+ // price would be shown as current without anything having quoted it. The
307
+ // address survives, the money is asked for again.
308
+ partialize: (state) => ({ postalCode: state.postalCode, address: state.address }),
309
+ onRehydrateStorage: () => (state) => {
310
+ if (state?.address) state.status = "idle";
311
+ }
312
+ }
313
+ )
314
+ );
315
+ function resolveOptionRate(option, subtotal) {
316
+ if (option.freeAbove != null && subtotal >= option.freeAbove) return 0;
317
+ return option.baseRate;
318
+ }
319
+ var selectQuotedShipping = (state, subtotal) => {
320
+ if (state.status !== "served") return null;
321
+ const option = state.options.find((o) => o.zoneId === state.selectedZoneId) ?? state.options[0];
322
+ return option ? resolveOptionRate(option, subtotal) : null;
323
+ };
324
+
325
+ // src/stores/cart.store.ts
326
+ var useCartStore = create()(
327
+ persist(
328
+ (set, get) => ({
329
+ lines: [],
330
+ discountCode: null,
331
+ discountPercent: 0,
332
+ isOpen: false,
333
+ justAddedLineId: null,
334
+ addItem: (product, qty = 1, options) => set((state) => {
335
+ const normalizedOptions = normalizeProductOptionSelection(options);
336
+ const optionKey = productOptionSelectionKey(normalizedOptions);
337
+ const lineId = optionKey ? `${product.id}:${optionKey}` : product.id;
338
+ const variant = resolveVariantForSelection(product, normalizedOptions);
339
+ const unitPrice = variant?.price ?? product.price;
340
+ const stock = variant ? variant.inventoryCount : product.inventoryCount;
341
+ const existing = state.lines.find((l) => (l.lineId ?? l.productId) === lineId);
342
+ if (existing) {
343
+ const nextQty = Math.min(existing.quantity + qty, existing.maxQuantity || Infinity);
344
+ return {
345
+ // Auto-open the cart on add — the standard premium "slide-in cart"
346
+ // signal — and flag the line so the drawer can highlight it.
347
+ isOpen: true,
348
+ justAddedLineId: lineId,
349
+ lines: state.lines.map(
350
+ (l) => (l.lineId ?? l.productId) === lineId ? { ...l, lineId, quantity: nextQty } : l
351
+ )
352
+ };
353
+ }
354
+ const image = product.images.find((i) => i.isPrimary) ?? product.images[0];
355
+ const line = {
356
+ lineId,
357
+ productId: product.id,
358
+ variantId: variant?.id,
359
+ slug: product.slug,
360
+ name: product.name,
361
+ sku: variant?.sku ?? product.sku,
362
+ options: normalizedOptions,
363
+ optionsLabel: formatProductOptionSelection(normalizedOptions),
364
+ unitPrice,
365
+ quantity: Math.min(qty, stock || Infinity),
366
+ imageUrl: image?.url ?? null,
367
+ maxQuantity: stock,
368
+ requiresShipping: product.isPhysical !== false,
369
+ weight: variant?.weight ?? product.weight ?? null,
370
+ weightUnit: variant?.weightUnit ?? product.weightUnit ?? null,
371
+ heightCm: variant?.heightCm ?? product.heightCm ?? null,
372
+ widthCm: variant?.widthCm ?? product.widthCm ?? null,
373
+ lengthCm: variant?.lengthCm ?? product.lengthCm ?? null,
374
+ packageId: product.packageId ?? null
375
+ };
376
+ return { isOpen: true, justAddedLineId: lineId, lines: [...state.lines, line] };
377
+ }),
378
+ setQuantity: (lineId, qty) => set((state) => {
379
+ if (qty <= 0) return { lines: state.lines.filter((l) => (l.lineId ?? l.productId) !== lineId) };
380
+ return {
381
+ lines: state.lines.map(
382
+ (l) => (l.lineId ?? l.productId) === lineId ? { ...l, quantity: Math.min(qty, l.maxQuantity || Infinity) } : l
383
+ )
384
+ };
385
+ }),
386
+ removeItem: (lineId) => set((state) => ({ lines: state.lines.filter((l) => (l.lineId ?? l.productId) !== lineId) })),
387
+ applyDiscount: (code, percent) => set({ discountCode: code, discountPercent: percent }),
388
+ clearDiscount: () => set({ discountCode: null, discountPercent: 0 }),
389
+ clear: () => set({ lines: [], discountCode: null, discountPercent: 0 }),
390
+ openDrawer: () => set({ isOpen: true }),
391
+ closeDrawer: () => set({ isOpen: false, justAddedLineId: null }),
392
+ reconcile: async (resolve) => {
393
+ const lines = get().lines;
394
+ if (lines.length === 0) return;
395
+ const keep = await Promise.all(
396
+ lines.map((line) => resolve(line).catch(() => true))
397
+ );
398
+ const kept = lines.filter((_, i) => keep[i]);
399
+ if (kept.length !== lines.length) set({ lines: kept });
400
+ }
401
+ }),
402
+ {
403
+ name: "fayz.storefront.cart.v1",
404
+ partialize: (state) => ({
405
+ lines: state.lines,
406
+ discountCode: state.discountCode,
407
+ discountPercent: state.discountPercent
408
+ })
409
+ }
410
+ )
411
+ );
412
+ var selectCount = (s2) => s2.lines.reduce((n, l) => n + l.quantity, 0);
413
+ var selectSubtotal = (s2) => roundCents(s2.lines.reduce((sum, l) => sum + l.unitPrice * l.quantity, 0));
414
+ var selectDiscountTotal = (s2) => roundCents(selectSubtotal(s2) * (s2.discountPercent / 100));
415
+ var selectRequiresShipping = (s2) => s2.lines.some((l) => l.requiresShipping !== false);
416
+ var selectShipping = (s2, cfg) => {
417
+ if (s2.lines.length === 0) return 0;
418
+ if (!selectRequiresShipping(s2)) return 0;
419
+ const subtotal = selectSubtotal(s2);
420
+ const quoted = selectQuotedShipping(useDeliveryStore.getState(), subtotal);
421
+ if (quoted != null) return quoted;
422
+ if (cfg.shipping.freeAbove != null && subtotal >= cfg.shipping.freeAbove) return 0;
423
+ return cfg.shipping.flatRate;
424
+ };
425
+ var selectTotal = (s2, cfg) => roundCents(selectSubtotal(s2) - selectDiscountTotal(s2) + selectShipping(s2, cfg));
426
+
159
427
  // src/data/facets.ts
160
428
  function matchesFacets(metadata, facets) {
161
429
  for (const [key, wanted] of Object.entries(facets)) {
@@ -249,7 +517,7 @@ async function categoriesList(_params, ctx) {
249
517
  return ctx.shop.listCategories();
250
518
  }
251
519
  function entityCapableShop(shop) {
252
- if (!shop.listEntityRecords || !shop.getEntityRecordBySlug || !shop.getEntityFields) {
520
+ if (!shop.listEntityRecords || !shop.getEntityRecordBySlug) {
253
521
  throw new Error(
254
522
  "This backend provider does not support custom entities (describeStore capabilities.customEntities). Both the Fayz Supabase provider and the mock provider do \u2014 a custom provider must implement the entity methods."
255
523
  );
@@ -272,12 +540,6 @@ async function entityBySlug(params, ctx) {
272
540
  const shop = entityCapableShop(ctx.shop);
273
541
  return shop.getEntityRecordBySlug(params.entity, params.slug);
274
542
  }
275
- async function entityFields(params, ctx) {
276
- resolveSidecarDeclaration(String(params.reference ?? ""));
277
- if (!params.id) return null;
278
- const shop = entityCapableShop(ctx.shop);
279
- return shop.getEntityFields(params.reference, params.id);
280
- }
281
543
  async function collectionsList(_params, ctx) {
282
544
  const products = await ctx.shop.listProducts({ status: "active" });
283
545
  const tags = /* @__PURE__ */ new Set();
@@ -422,28 +684,6 @@ function registerStorefrontLoaders() {
422
684
  }
423
685
  ]
424
686
  });
425
- defineLoader("entity.fields", entityFields, {
426
- source: "sdk",
427
- title: "Custom fields of a core record",
428
- description: "The declared sidecar fields (defineSidecarFields) of one product or category \u2014 the entity loader path for core-record extensions.",
429
- output: "The validated custom-field object, or null when the record has no sidecar row.",
430
- settings: [
431
- {
432
- id: "reference",
433
- type: "select",
434
- label: "Record kind",
435
- options: SIDECAR_REFERENCES.map((reference) => ({ value: reference, label: reference })),
436
- required: true
437
- },
438
- {
439
- id: "id",
440
- type: "text",
441
- label: "Record id",
442
- help: "The core record's id. On a product page, bind through the resolved product.",
443
- required: true
444
- }
445
- ]
446
- });
447
687
  }
448
688
 
449
689
  // src/blocks/sections.ts
@@ -818,6 +1058,7 @@ var TID = {
818
1058
  // checkout
819
1059
  checkoutEmail: "checkout-email",
820
1060
  checkoutName: "checkout-name",
1061
+ checkoutDocument: "checkout-document",
821
1062
  checkoutStreet: "checkout-street",
822
1063
  checkoutCity: "checkout-city",
823
1064
  checkoutZip: "checkout-zip",
@@ -825,6 +1066,19 @@ var TID = {
825
1066
  checkoutExpiry: "checkout-expiry",
826
1067
  checkoutCvc: "checkout-cvc",
827
1068
  placeOrder: "place-order",
1069
+ // checkout em passos
1070
+ checkoutSteps: "checkout-steps",
1071
+ checkoutStep: (n) => `checkout-step-${n}`,
1072
+ checkoutPayment: "checkout-payment",
1073
+ checkoutPaymentTotal: "checkout-payment-total",
1074
+ checkoutContinue: "checkout-continue",
1075
+ checkoutBack: "checkout-back",
1076
+ checkoutDone: "checkout-done",
1077
+ // payment step
1078
+ pixPanel: "pix-panel",
1079
+ pixCode: "pix-code",
1080
+ pixCopy: "pix-copy",
1081
+ pixConfirm: "pix-confirm",
828
1082
  checkoutError: "checkout-error",
829
1083
  checkoutProcessing: "checkout-processing",
830
1084
  // confirmation
@@ -1435,267 +1689,17 @@ var categoriesBlockMeta = {
1435
1689
  label: "Heading",
1436
1690
  help: "Optional heading above the categories. Leave empty to render the grid with no heading."
1437
1691
  }
1438
- ],
1439
- presets: [
1440
- { name: "Category tiles", props: { style: "tiles", title: "Nossas categorias" } },
1441
- { name: "Category bubbles", props: { style: "bubbles", title: "Compre por categoria" } }
1442
- ],
1443
- // Not on 'product': a full category index on a product page competes with the
1444
- // buying decision instead of supporting it.
1445
- enabledOn: ["home", "catalog", "content"],
1446
- // The declared read (M3): the whole category list, no params.
1447
- data: { categories: { loader: "categories.list" } }
1448
- };
1449
-
1450
- // src/lib/format.ts
1451
- function formatMoney(value, currency = "BRL", locale = "pt-BR") {
1452
- return new Intl.NumberFormat(locale, { style: "currency", currency }).format(value);
1453
- }
1454
- function roundCents(value) {
1455
- return Math.round(value * 100) / 100;
1456
- }
1457
-
1458
- // src/data/product-options.ts
1459
- function cleanValues(value) {
1460
- if (!Array.isArray(value)) return [];
1461
- return Array.from(new Set(value.filter((item) => typeof item === "string" && item.trim().length > 0)));
1462
- }
1463
- function cleanOptionGroup(value) {
1464
- if (!value || typeof value !== "object") return null;
1465
- const record = value;
1466
- const id = typeof record.id === "string" ? record.id : typeof record.name === "string" ? record.name : null;
1467
- const label = typeof record.label === "string" ? record.label : typeof record.name === "string" ? record.name : id;
1468
- const values = cleanValues(record.values);
1469
- if (!id || !label || values.length === 0) return null;
1470
- return { id, label, values };
1471
- }
1472
- var warnedOverlayProducts = /* @__PURE__ */ new Set();
1473
- function resetProductOptionsDeprecationWarnings() {
1474
- warnedOverlayProducts.clear();
1475
- }
1476
- function getProductOptionGroups(product) {
1477
- if (product.options && product.options.length > 0) {
1478
- return [...product.options].sort((a, b) => a.position - b.position).map((option) => ({ id: option.name, label: option.name, values: [...option.values] }));
1479
- }
1480
- const options = product.metadata.options;
1481
- const explicit = Array.isArray(options) ? options.map(cleanOptionGroup).filter((group) => Boolean(group)) : [];
1482
- const groups = [...explicit];
1483
- if (groups.length === 0) {
1484
- const sizes = cleanValues(product.metadata.sizes);
1485
- const colors = cleanValues(product.metadata.colors);
1486
- if (sizes.length > 0) groups.push({ id: "Tamanho", label: "Tamanho", values: sizes });
1487
- if (colors.length > 0) groups.push({ id: "Cor", label: "Cor", values: colors });
1488
- }
1489
- if (groups.length > 0 && !warnedOverlayProducts.has(product.id)) {
1490
- warnedOverlayProducts.add(product.id);
1491
- console.warn(
1492
- `[storefront] product "${product.slug}" renders options from the DEPRECATED metadata overlay (metadata.options/sizes/colors). These labels are display-only \u2014 checkout cannot price or reserve them. Define real variants (0028) instead; the overlay path is removed at M9.`
1493
- );
1494
- }
1495
- return groups;
1496
- }
1497
- function resolveVariantForSelection(product, selection) {
1498
- const variants = product.variants;
1499
- if (!variants || variants.length === 0) return null;
1500
- const normalized = normalizeProductOptionSelection(selection);
1501
- const wanted = Object.entries(normalized);
1502
- return variants.find((variant) => {
1503
- const options = variant.options;
1504
- return Object.keys(options).length === wanted.length && wanted.every(([key, value]) => options[key] === value);
1505
- }) ?? null;
1506
- }
1507
- function selectionPrice(product, selection) {
1508
- const variant = resolveVariantForSelection(product, selection);
1509
- return variant?.price ?? product.price;
1510
- }
1511
- function normalizeProductOptionSelection(selection) {
1512
- if (!selection) return {};
1513
- return Object.fromEntries(
1514
- Object.entries(selection).filter((entry) => entry[0].trim().length > 0 && entry[1].trim().length > 0).sort(([a], [b]) => a.localeCompare(b))
1515
- );
1516
- }
1517
- function formatProductOptionSelection(selection) {
1518
- return Object.entries(normalizeProductOptionSelection(selection)).map(([label, value]) => `${label}: ${value}`).join(" \xB7 ");
1519
- }
1520
- function productOptionSelectionKey(selection) {
1521
- const normalized = normalizeProductOptionSelection(selection);
1522
- const entries = Object.entries(normalized);
1523
- return entries.length > 0 ? JSON.stringify(entries) : "";
1524
- }
1525
- var EMPTY = {
1526
- postalCode: "",
1527
- address: null,
1528
- options: [],
1529
- selectedZoneId: null,
1530
- status: "idle",
1531
- error: null
1532
- };
1533
- var useDeliveryStore = create()(
1534
- persist(
1535
- (set, get) => ({
1536
- ...EMPTY,
1537
- async resolve(postalCode, subtotal) {
1538
- const code = normalizePostalCode(postalCode);
1539
- if (code.length !== 8) {
1540
- set({ ...EMPTY, postalCode, status: "error", error: "Digite os 8 d\xEDgitos do CEP." });
1541
- return;
1542
- }
1543
- set({ postalCode: code, status: "loading", error: null });
1544
- let options = [];
1545
- try {
1546
- options = await getShopProvider().quoteShipping?.(code, subtotal) ?? [];
1547
- } catch {
1548
- set({
1549
- ...EMPTY,
1550
- postalCode: code,
1551
- status: "error",
1552
- error: "N\xE3o foi poss\xEDvel calcular o frete agora. Tente de novo."
1553
- });
1554
- return;
1555
- }
1556
- let address = null;
1557
- let notFound = false;
1558
- try {
1559
- address = await lookupPostalCode(code);
1560
- notFound = address === null;
1561
- } catch {
1562
- address = null;
1563
- }
1564
- set({
1565
- postalCode: code,
1566
- address,
1567
- options,
1568
- selectedZoneId: options[0]?.zoneId ?? null,
1569
- // A store with no zones configured quotes nothing, and that must NOT
1570
- // read as "we don't deliver here" — it falls back to the store-wide
1571
- // flat rate, which the cart already knows how to show.
1572
- status: options.length > 0 ? "served" : "unserved",
1573
- // Reported alongside the price, not instead of it: the CEP may be
1574
- // mistyped even though its range is covered.
1575
- error: notFound ? "CEP n\xE3o encontrado. Confira os n\xFAmeros." : null
1576
- });
1577
- },
1578
- selectZone(zoneId) {
1579
- if (get().options.some((option) => option.zoneId === zoneId)) set({ selectedZoneId: zoneId });
1580
- },
1581
- clear() {
1582
- set({ ...EMPTY });
1583
- }
1584
- }),
1585
- {
1586
- name: "fayz.storefront.delivery.v1",
1587
- // Deliberately NOT persisting `options`: a rehydrated
1588
- // price would be shown as current without anything having quoted it. The
1589
- // address survives, the money is asked for again.
1590
- partialize: (state) => ({ postalCode: state.postalCode, address: state.address }),
1591
- onRehydrateStorage: () => (state) => {
1592
- if (state?.address) state.status = "idle";
1593
- }
1594
- }
1595
- )
1596
- );
1597
- function resolveOptionRate(option, subtotal) {
1598
- if (option.freeAbove != null && subtotal >= option.freeAbove) return 0;
1599
- return option.baseRate;
1600
- }
1601
- var selectQuotedShipping = (state, subtotal) => {
1602
- if (state.status !== "served") return null;
1603
- const option = state.options.find((o) => o.zoneId === state.selectedZoneId) ?? state.options[0];
1604
- return option ? resolveOptionRate(option, subtotal) : null;
1605
- };
1606
-
1607
- // src/stores/cart.store.ts
1608
- var useCartStore = create()(
1609
- persist(
1610
- (set, get) => ({
1611
- lines: [],
1612
- discountCode: null,
1613
- discountPercent: 0,
1614
- isOpen: false,
1615
- justAddedLineId: null,
1616
- addItem: (product, qty = 1, options) => set((state) => {
1617
- const normalizedOptions = normalizeProductOptionSelection(options);
1618
- const optionKey = productOptionSelectionKey(normalizedOptions);
1619
- const lineId = optionKey ? `${product.id}:${optionKey}` : product.id;
1620
- const variant = resolveVariantForSelection(product, normalizedOptions);
1621
- const unitPrice = variant?.price ?? product.price;
1622
- const stock = variant ? variant.inventoryCount : product.inventoryCount;
1623
- const existing = state.lines.find((l) => (l.lineId ?? l.productId) === lineId);
1624
- if (existing) {
1625
- const nextQty = Math.min(existing.quantity + qty, existing.maxQuantity || Infinity);
1626
- return {
1627
- // Auto-open the cart on add — the standard premium "slide-in cart"
1628
- // signal — and flag the line so the drawer can highlight it.
1629
- isOpen: true,
1630
- justAddedLineId: lineId,
1631
- lines: state.lines.map(
1632
- (l) => (l.lineId ?? l.productId) === lineId ? { ...l, lineId, quantity: nextQty } : l
1633
- )
1634
- };
1635
- }
1636
- const image = product.images.find((i) => i.isPrimary) ?? product.images[0];
1637
- const line = {
1638
- lineId,
1639
- productId: product.id,
1640
- variantId: variant?.id,
1641
- slug: product.slug,
1642
- name: product.name,
1643
- sku: variant?.sku ?? product.sku,
1644
- options: normalizedOptions,
1645
- optionsLabel: formatProductOptionSelection(normalizedOptions),
1646
- unitPrice,
1647
- quantity: Math.min(qty, stock || Infinity),
1648
- imageUrl: image?.url ?? null,
1649
- maxQuantity: stock
1650
- };
1651
- return { isOpen: true, justAddedLineId: lineId, lines: [...state.lines, line] };
1652
- }),
1653
- setQuantity: (lineId, qty) => set((state) => {
1654
- if (qty <= 0) return { lines: state.lines.filter((l) => (l.lineId ?? l.productId) !== lineId) };
1655
- return {
1656
- lines: state.lines.map(
1657
- (l) => (l.lineId ?? l.productId) === lineId ? { ...l, quantity: Math.min(qty, l.maxQuantity || Infinity) } : l
1658
- )
1659
- };
1660
- }),
1661
- removeItem: (lineId) => set((state) => ({ lines: state.lines.filter((l) => (l.lineId ?? l.productId) !== lineId) })),
1662
- applyDiscount: (code, percent) => set({ discountCode: code, discountPercent: percent }),
1663
- clearDiscount: () => set({ discountCode: null, discountPercent: 0 }),
1664
- clear: () => set({ lines: [], discountCode: null, discountPercent: 0 }),
1665
- openDrawer: () => set({ isOpen: true }),
1666
- closeDrawer: () => set({ isOpen: false, justAddedLineId: null }),
1667
- reconcile: async (resolve) => {
1668
- const lines = get().lines;
1669
- if (lines.length === 0) return;
1670
- const keep = await Promise.all(
1671
- lines.map((line) => resolve(line).catch(() => true))
1672
- );
1673
- const kept = lines.filter((_, i) => keep[i]);
1674
- if (kept.length !== lines.length) set({ lines: kept });
1675
- }
1676
- }),
1677
- {
1678
- name: "fayz.storefront.cart.v1",
1679
- partialize: (state) => ({
1680
- lines: state.lines,
1681
- discountCode: state.discountCode,
1682
- discountPercent: state.discountPercent
1683
- })
1684
- }
1685
- )
1686
- );
1687
- var selectCount = (s2) => s2.lines.reduce((n, l) => n + l.quantity, 0);
1688
- var selectSubtotal = (s2) => roundCents(s2.lines.reduce((sum, l) => sum + l.unitPrice * l.quantity, 0));
1689
- var selectDiscountTotal = (s2) => roundCents(selectSubtotal(s2) * (s2.discountPercent / 100));
1690
- var selectShipping = (s2, cfg) => {
1691
- if (s2.lines.length === 0) return 0;
1692
- const subtotal = selectSubtotal(s2);
1693
- const quoted = selectQuotedShipping(useDeliveryStore.getState(), subtotal);
1694
- if (quoted != null) return quoted;
1695
- if (cfg.shipping.freeAbove != null && subtotal >= cfg.shipping.freeAbove) return 0;
1696
- return cfg.shipping.flatRate;
1692
+ ],
1693
+ presets: [
1694
+ { name: "Category tiles", props: { style: "tiles", title: "Nossas categorias" } },
1695
+ { name: "Category bubbles", props: { style: "bubbles", title: "Compre por categoria" } }
1696
+ ],
1697
+ // Not on 'product': a full category index on a product page competes with the
1698
+ // buying decision instead of supporting it.
1699
+ enabledOn: ["home", "catalog", "content"],
1700
+ // The declared read (M3): the whole category list, no params.
1701
+ data: { categories: { loader: "categories.list" } }
1697
1702
  };
1698
- var selectTotal = (s2, cfg) => roundCents(selectSubtotal(s2) - selectDiscountTotal(s2) + selectShipping(s2, cfg));
1699
1703
  function useEnquiry() {
1700
1704
  const config = useStorefrontConfig();
1701
1705
  const createProductEnquiry = async (input) => {
@@ -3458,7 +3462,7 @@ function QuantityInput({ value, onChange, min = 1, max, testId, incTestId, decTe
3458
3462
  "data-testid": decTestId,
3459
3463
  "aria-label": "Diminuir quantidade",
3460
3464
  onClick: () => onChange(clamp(value - 1)),
3461
- className: "px-2.5 py-1.5 text-muted-foreground hover:text-foreground disabled:opacity-40",
3465
+ className: "min-h-11 min-w-11 px-2.5 py-1.5 text-muted-foreground hover:text-foreground disabled:opacity-40 sm:min-h-0 sm:min-w-0 grid place-items-center",
3462
3466
  disabled: value <= min,
3463
3467
  children: /* @__PURE__ */ jsx(Minus, { className: "h-3.5 w-3.5" })
3464
3468
  }
@@ -3475,7 +3479,7 @@ function QuantityInput({ value, onChange, min = 1, max, testId, incTestId, decTe
3475
3479
  const n = parseInt(e.target.value, 10);
3476
3480
  if (!isNaN(n)) onChange(clamp(n));
3477
3481
  },
3478
- className: "w-12 border-x bg-transparent py-1.5 text-center text-sm [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
3482
+ className: "h-11 w-12 border-x bg-transparent py-1.5 text-center text-sm [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none sm:h-auto"
3479
3483
  }
3480
3484
  ),
3481
3485
  /* @__PURE__ */ jsx(
@@ -3485,7 +3489,7 @@ function QuantityInput({ value, onChange, min = 1, max, testId, incTestId, decTe
3485
3489
  "data-testid": incTestId,
3486
3490
  "aria-label": "Aumentar quantidade",
3487
3491
  onClick: () => onChange(clamp(value + 1)),
3488
- className: "px-2.5 py-1.5 text-muted-foreground hover:text-foreground disabled:opacity-40",
3492
+ className: "min-h-11 min-w-11 px-2.5 py-1.5 text-muted-foreground hover:text-foreground disabled:opacity-40 sm:min-h-0 sm:min-w-0 grid place-items-center",
3489
3493
  disabled: max != null && value >= max,
3490
3494
  children: /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" })
3491
3495
  }
@@ -4333,7 +4337,7 @@ function ProductDetailPage({ slug }) {
4333
4337
  }
4334
4338
  ) }),
4335
4339
  /* @__PURE__ */ jsx(Slot, { area: "productDetail", name: "afterAddToCart", context: slotContext }),
4336
- config.commerceMode === "checkout" && /* @__PURE__ */ jsx(DeliveryEstimator, {}),
4340
+ config.commerceMode === "checkout" && product.isPhysical !== false && /* @__PURE__ */ jsx(DeliveryEstimator, {}),
4337
4341
  config.commerceMode === "checkout" && /* @__PURE__ */ jsxs("div", { className: "mt-6 grid grid-cols-3 gap-3 border-t pt-6 text-center", children: [
4338
4342
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1.5 text-[11px] font-medium text-muted-foreground", children: [
4339
4343
  /* @__PURE__ */ jsx(Truck, { className: "h-5 w-5 text-primary" }),
@@ -5959,6 +5963,49 @@ function sectionsToBlocks(sections) {
5959
5963
  return { type, id: `${type}-${i}`, props };
5960
5964
  });
5961
5965
  }
5966
+ function applyStoreSettings(config, settings) {
5967
+ if (!settings) return config;
5968
+ const next = { ...config, policy: settings };
5969
+ if (settings.currency) next.currency = settings.currency;
5970
+ if (settings.locale) next.locale = settings.locale;
5971
+ if (settings.paymentMethods?.length) {
5972
+ next.payments = { ...config.payments, methods: settings.paymentMethods };
5973
+ }
5974
+ if (settings.discountsEnabled !== void 0) {
5975
+ next.features = { ...config.features, discounts: settings.discountsEnabled };
5976
+ }
5977
+ if (settings.guestCheckout === false) {
5978
+ next.features = { ...next.features, accounts: true };
5979
+ }
5980
+ return next;
5981
+ }
5982
+ function StorefrontConfigHost({
5983
+ config,
5984
+ children
5985
+ }) {
5986
+ const [policy, setPolicy] = React4.useState(null);
5987
+ React4.useEffect(() => {
5988
+ let cancelled = false;
5989
+ let provider;
5990
+ try {
5991
+ provider = config.provider ?? getShopProvider();
5992
+ } catch {
5993
+ return;
5994
+ }
5995
+ if (typeof provider.getPublicStoreSettings !== "function") return;
5996
+ void provider.getPublicStoreSettings().then((settings) => {
5997
+ if (cancelled) return;
5998
+ setPolicy(settings);
5999
+ setShopCarrierQuotesResolver(() => settings?.carrierQuotes);
6000
+ }).catch(() => {
6001
+ });
6002
+ return () => {
6003
+ cancelled = true;
6004
+ };
6005
+ }, [config]);
6006
+ const resolved = React4.useMemo(() => applyStoreSettings(config, policy), [config, policy]);
6007
+ return /* @__PURE__ */ jsx(StorefrontConfigProvider, { value: resolved, children });
6008
+ }
5962
6009
  var RADIUS_MAP = {
5963
6010
  none: { button: "0px", card: "0px", input: "0px" },
5964
6011
  soft: { button: "6px", card: "10px", input: "6px" },
@@ -6381,12 +6428,28 @@ function HeaderActions({ onMenuClick }) {
6381
6428
  const openDrawer = useCartStore((s2) => s2.openDrawer);
6382
6429
  const session = useSessionStore();
6383
6430
  const [accountOpen, setAccountOpen] = useState(false);
6431
+ const accountRef = React4.useRef(null);
6384
6432
  const pop = usePopOnChange(count);
6385
6433
  const firstName = session.name?.split(/\s+/)[0];
6386
6434
  const initial2 = (firstName ?? session.email ?? "C").slice(0, 1).toUpperCase();
6435
+ React4.useEffect(() => {
6436
+ if (!accountOpen) return;
6437
+ const onPointerDown = (event) => {
6438
+ if (!accountRef.current?.contains(event.target)) setAccountOpen(false);
6439
+ };
6440
+ const onKey = (event) => {
6441
+ if (event.key === "Escape") setAccountOpen(false);
6442
+ };
6443
+ document.addEventListener("pointerdown", onPointerDown);
6444
+ document.addEventListener("keydown", onKey);
6445
+ return () => {
6446
+ document.removeEventListener("pointerdown", onPointerDown);
6447
+ document.removeEventListener("keydown", onKey);
6448
+ };
6449
+ }, [accountOpen]);
6387
6450
  return /* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-1", children: [
6388
6451
  /* @__PURE__ */ jsx(Slot, { area: "header", name: "beforeActions" }),
6389
- config.features.accounts && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
6452
+ config.features.accounts && /* @__PURE__ */ jsxs("div", { ref: accountRef, className: "relative", children: [
6390
6453
  session.email ? /* @__PURE__ */ jsxs(
6391
6454
  "button",
6392
6455
  {
@@ -6421,7 +6484,7 @@ function HeaderActions({ onMenuClick }) {
6421
6484
  /* @__PURE__ */ jsx("p", { className: "font-semibold", children: session.name || firstName || "Cliente" }),
6422
6485
  /* @__PURE__ */ jsx("p", { className: "truncate text-sm text-muted-foreground", children: session.email })
6423
6486
  ] }),
6424
- /* @__PURE__ */ jsxs("div", { className: "p-2", children: [
6487
+ /* @__PURE__ */ jsxs("div", { className: "p-2", onClick: () => setAccountOpen(false), children: [
6425
6488
  /* @__PURE__ */ jsxs(Link, { to: "/account", className: "flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-semibold hover:bg-muted", children: [
6426
6489
  /* @__PURE__ */ jsx(UserCircle, { className: "h-4 w-4" }),
6427
6490
  "Perfil"
@@ -6898,6 +6961,10 @@ function CartDrawer() {
6898
6961
  setDiscountError(result.message ?? "Cupom inv\xE1lido.");
6899
6962
  toast.error("Cupom inv\xE1lido", result.message ?? "Verifique o c\xF3digo e tente novamente.");
6900
6963
  }
6964
+ } catch (err) {
6965
+ setDiscountError("N\xE3o conseguimos validar esse cupom agora.");
6966
+ toast.error("N\xE3o conseguimos validar o cupom", "Tente de novo em alguns instantes.");
6967
+ console.warn("[cart] falha ao validar cupom", err);
6901
6968
  } finally {
6902
6969
  setApplying(false);
6903
6970
  }
@@ -6921,11 +6988,9 @@ function CartDrawer() {
6921
6988
  "data-testid": TID.cartDrawer,
6922
6989
  role: "dialog",
6923
6990
  "aria-label": "Carrinho",
6924
- className: `absolute right-0 top-0 flex h-full w-full max-w-md animate-slide-in-from-right flex-col bg-background shadow-2xl ${// The 2-column desktop layout only makes sense with items to fill
6925
- // it — an empty cart stays the normal narrow single-column panel.
6926
- cart.lines.length > 0 ? "lg:max-w-3xl lg:grid lg:grid-cols-[55%_45%] lg:grid-rows-[auto_auto_1fr]" : ""}`,
6991
+ className: "absolute right-0 top-0 flex h-full w-full max-w-md animate-slide-in-from-right flex-col bg-background shadow-2xl",
6927
6992
  children: [
6928
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-5 py-4 lg:col-start-2 lg:row-start-1", children: [
6993
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-5 py-4", children: [
6929
6994
  /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: "Carrinho" }),
6930
6995
  /* @__PURE__ */ jsx(
6931
6996
  "button",
@@ -6933,13 +6998,13 @@ function CartDrawer() {
6933
6998
  type: "button",
6934
6999
  "aria-label": "Fechar carrinho",
6935
7000
  onClick: cart.closeDrawer,
6936
- className: "rounded-full p-1.5 hover:bg-muted",
7001
+ className: "grid min-h-11 min-w-11 place-items-center rounded-full p-1.5 hover:bg-muted sm:min-h-0 sm:min-w-0",
6937
7002
  children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
6938
7003
  }
6939
7004
  )
6940
7005
  ] }),
6941
7006
  cart.lines.length === 0 ? /* @__PURE__ */ jsx("div", { "data-testid": TID.cartEmpty, className: "flex flex-1 items-center justify-center p-8 text-center text-muted-foreground", children: "Seu carrinho est\xE1 vazio." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
6942
- freeAbove != null && /* @__PURE__ */ jsxs("div", { "data-testid": TID.shippingProgress, className: "border-b bg-muted/40 px-5 py-3 lg:col-start-2 lg:row-start-2", children: [
7007
+ freeAbove != null && /* @__PURE__ */ jsxs("div", { "data-testid": TID.shippingProgress, className: "border-b bg-muted/40 px-5 py-3", children: [
6943
7008
  /* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: missingForFree > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
6944
7009
  "Faltam ",
6945
7010
  /* @__PURE__ */ jsx("strong", { children: money(missingForFree) }),
@@ -6961,7 +7026,7 @@ function CartDrawer() {
6961
7026
  "div",
6962
7027
  {
6963
7028
  "data-testid": TID.cartLinesList,
6964
- className: "flex-1 space-y-4 overflow-y-auto py-4 pl-5 pr-6 lg:col-start-1 lg:row-start-1 lg:row-span-3 lg:border-r",
7029
+ className: "flex-1 space-y-4 overflow-y-auto py-4 pl-5 pr-6",
6965
7030
  style: { scrollbarGutter: "stable" },
6966
7031
  children: [
6967
7032
  /* @__PURE__ */ jsx(Slot, { area: "cart", name: "beforeLines", context: cartSlotContext }),
@@ -6984,7 +7049,7 @@ function CartDrawer() {
6984
7049
  "data-testid": TID.lineRemove,
6985
7050
  "aria-label": `Remover ${line.name}`,
6986
7051
  onClick: () => cart.removeItem(line.lineId ?? line.productId),
6987
- className: "-m-1.5 p-1.5 text-muted-foreground hover:text-destructive",
7052
+ className: "-m-1.5 grid min-h-11 min-w-11 place-items-center p-1.5 text-muted-foreground hover:text-destructive sm:min-h-0 sm:min-w-0",
6988
7053
  children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
6989
7054
  }
6990
7055
  )
@@ -7025,7 +7090,7 @@ function CartDrawer() {
7025
7090
  "div",
7026
7091
  {
7027
7092
  "data-testid": TID.cartFooter,
7028
- className: "space-y-3 border-t px-5 py-4 lg:col-start-2 lg:row-start-3 lg:overflow-y-auto",
7093
+ className: "space-y-3 border-t px-5 py-4",
7029
7094
  children: [
7030
7095
  config.features.discounts && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
7031
7096
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
@@ -7055,7 +7120,7 @@ function CartDrawer() {
7055
7120
  discountError && /* @__PURE__ */ jsx("p", { "data-testid": TID.discountError, className: "text-xs text-destructive", children: discountError })
7056
7121
  ] }),
7057
7122
  /* @__PURE__ */ jsx(Slot, { area: "cart", name: "beforeSummary", context: cartSlotContext }),
7058
- /* @__PURE__ */ jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsx(DeliveryEstimator, { compact: true }) }),
7123
+ selectRequiresShipping(cart) && /* @__PURE__ */ jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsx(DeliveryEstimator, { compact: true }) }),
7059
7124
  /* @__PURE__ */ jsxs("dl", { className: "space-y-1.5 text-sm", children: [
7060
7125
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
7061
7126
  /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: "Subtotal" }),
@@ -7197,6 +7262,41 @@ async function storefrontPaths(input) {
7197
7262
  const unique = Array.from(new Set(paths));
7198
7263
  return input.includePrivate ? unique : unique.filter((p) => isPublicPath(resolved, p));
7199
7264
  }
7265
+
7266
+ // src/data/local-orders.ts
7267
+ var KEY = "fayz.storefront.orders.v1";
7268
+ var MAX = 25;
7269
+ function read() {
7270
+ try {
7271
+ const raw = globalThis.localStorage?.getItem(KEY);
7272
+ const parsed = raw ? JSON.parse(raw) : [];
7273
+ return Array.isArray(parsed) ? parsed.filter((id) => typeof id === "string") : [];
7274
+ } catch {
7275
+ return [];
7276
+ }
7277
+ }
7278
+ function rememberLocalOrder(orderId) {
7279
+ if (!orderId) return;
7280
+ try {
7281
+ const next = [orderId, ...read().filter((id) => id !== orderId)].slice(0, MAX);
7282
+ globalThis.localStorage?.setItem(KEY, JSON.stringify(next));
7283
+ } catch {
7284
+ }
7285
+ }
7286
+ function listLocalOrderIds() {
7287
+ return read();
7288
+ }
7289
+
7290
+ // src/lib/errors.ts
7291
+ function errorMessage(err) {
7292
+ if (err instanceof Error) return err.message;
7293
+ if (err && typeof err === "object" && typeof err.message === "string") {
7294
+ return err.message;
7295
+ }
7296
+ return "";
7297
+ }
7298
+
7299
+ // src/workflows/checkout.ts
7200
7300
  function formatDeliveryNotes(address, shippingAddress) {
7201
7301
  if (address?.notes?.trim()) return address.notes.trim();
7202
7302
  if (shippingAddress) return void 0;
@@ -7204,6 +7304,10 @@ function formatDeliveryNotes(address, shippingAddress) {
7204
7304
  const parts = [address.street, address.city, address.zip].map((part) => part?.trim()).filter(Boolean);
7205
7305
  return parts.length > 0 ? `Entrega: ${parts.join(", ")}` : void 0;
7206
7306
  }
7307
+ function isStaleCustomerError(err) {
7308
+ const message = errorMessage(err).toLowerCase();
7309
+ return message.includes("customer email mismatch") || message.includes("customer does not belong to this store") || message.includes("customer belongs to another account");
7310
+ }
7207
7311
  async function placeStorefrontOrder({
7208
7312
  config,
7209
7313
  cart,
@@ -7218,7 +7322,7 @@ async function placeStorefrontOrder({
7218
7322
  const name = customer.name.trim();
7219
7323
  const provider = getShopProvider();
7220
7324
  let customerId = session?.customerId ?? null;
7221
- if (!customerId || session?.email !== email) {
7325
+ if (!customerId || session?.email?.trim().toLowerCase() !== email) {
7222
7326
  const established = await establishCustomerSession(email, { name });
7223
7327
  customerId = established.customerId;
7224
7328
  }
@@ -7251,8 +7355,8 @@ async function placeStorefrontOrder({
7251
7355
  subtotal: cart.lines.reduce((sum, line) => sum + line.unitPrice * line.quantity, 0),
7252
7356
  customerEmail: email
7253
7357
  });
7254
- const order = await provider.placeOrder({
7255
- customerId: customerId ?? void 0,
7358
+ const place = (withCustomerId) => provider.placeOrder({
7359
+ customerId: withCustomerId,
7256
7360
  customer: { name, email },
7257
7361
  currency: config.currency,
7258
7362
  notes,
@@ -7266,10 +7370,19 @@ async function placeStorefrontOrder({
7266
7370
  metadata,
7267
7371
  items
7268
7372
  });
7373
+ let order;
7374
+ try {
7375
+ order = await place(customerId ?? void 0);
7376
+ } catch (err) {
7377
+ if (!customerId || !isStaleCustomerError(err)) throw err;
7378
+ customerId = null;
7379
+ order = await place(void 0);
7380
+ }
7269
7381
  if (markPaid) {
7270
7382
  if (provider.confirmPayment) await provider.confirmPayment(order.id);
7271
7383
  else await provider.updateOrder(order.id, { financialStatus: "paid" });
7272
7384
  }
7385
+ rememberLocalOrder(order.id);
7273
7386
  return { order, customerId: customerId ?? order.customerId ?? "" };
7274
7387
  }
7275
7388
  function SignInModal({ defaultEmail = "", onClose, onSignedIn }) {
@@ -7382,40 +7495,365 @@ function SignInModal({ defaultEmail = "", onClose, onSignedIn }) {
7382
7495
  }
7383
7496
  );
7384
7497
  }
7385
- var PAYMENT_LABELS = {
7386
- pix: { label: "Pix", hint: "Voc\xEA recebe a chave para pagar ap\xF3s confirmar o pedido" },
7387
- credit_card: { label: "Cart\xE3o de cr\xE9dito", hint: "Maquininha na entrega" },
7388
- debit_card: { label: "Cart\xE3o de d\xE9bito", hint: "Maquininha na entrega" },
7389
- boleto: { label: "Boleto", hint: "Enviado por e-mail ap\xF3s a confirma\xE7\xE3o" },
7390
- cash: { label: "Dinheiro", hint: "Pagamento na entrega" },
7391
- other: { label: "Combinar com a loja", hint: "A loja entra em contato para acertar o pagamento" }
7498
+ var LABELS2 = {
7499
+ 1: "Dados",
7500
+ 2: "Entrega",
7501
+ 3: "Pagamento",
7502
+ 4: "Pronto"
7392
7503
  };
7393
- var PROCESSING_STEPS = [
7394
- { icon: Lock, label: "Validando seus dados..." },
7395
- { icon: PackageCheck, label: "Registrando seu pedido..." }
7396
- ];
7397
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
7398
- function ProcessingOverlay({ step }) {
7399
- const current = PROCESSING_STEPS[Math.min(step, PROCESSING_STEPS.length - 1)];
7400
- const Icon2 = current.icon;
7504
+ function CheckoutSteps({ current, onGoBackTo }) {
7505
+ const steps = [1, 2, 3, 4];
7506
+ return /* @__PURE__ */ jsxs("nav", { "data-testid": TID.checkoutSteps, "aria-label": "Etapas do checkout", className: "mb-8", children: [
7507
+ /* @__PURE__ */ jsx("ol", { className: "flex items-center gap-1.5 sm:gap-2", children: steps.map((step, index) => {
7508
+ const done = step < current;
7509
+ const active2 = step === current;
7510
+ const canGoBack = done && !!onGoBackTo;
7511
+ const Tag = canGoBack ? "button" : "div";
7512
+ return /* @__PURE__ */ jsxs("li", { className: "flex min-w-0 flex-1 items-center gap-1.5 sm:gap-2", children: [
7513
+ /* @__PURE__ */ jsxs(
7514
+ Tag,
7515
+ {
7516
+ ...canGoBack ? { type: "button", onClick: () => onGoBackTo(step) } : {},
7517
+ "data-testid": TID.checkoutStep(step),
7518
+ "data-state": done ? "done" : active2 ? "current" : "pending",
7519
+ "aria-current": active2 ? "step" : void 0,
7520
+ className: [
7521
+ "flex min-w-0 items-center gap-2 rounded-lg px-2 py-1.5 text-left transition",
7522
+ canGoBack ? "hover:bg-muted" : "",
7523
+ active2 ? "text-foreground" : done ? "text-foreground" : "text-muted-foreground"
7524
+ ].join(" "),
7525
+ children: [
7526
+ /* @__PURE__ */ jsx(
7527
+ "span",
7528
+ {
7529
+ className: [
7530
+ "grid h-7 w-7 shrink-0 place-items-center rounded-full border text-xs font-semibold",
7531
+ done ? "border-primary bg-primary text-primary-foreground" : active2 ? "border-primary bg-background text-primary" : "border-muted-foreground/30 bg-background"
7532
+ ].join(" "),
7533
+ children: done ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : step
7534
+ }
7535
+ ),
7536
+ /* @__PURE__ */ jsx("span", { className: `hidden truncate text-sm sm:inline ${active2 ? "font-semibold" : ""}`, children: LABELS2[step] })
7537
+ ]
7538
+ }
7539
+ ),
7540
+ index < steps.length - 1 && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: `h-px flex-1 ${done ? "bg-primary" : "bg-border"}` })
7541
+ ] }, step);
7542
+ }) }),
7543
+ /* @__PURE__ */ jsxs("p", { className: "mt-2 text-sm font-semibold sm:hidden", children: [
7544
+ current,
7545
+ ". ",
7546
+ LABELS2[current]
7547
+ ] })
7548
+ ] });
7549
+ }
7550
+ function Skeleton({ className = "", style }) {
7551
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: `block rounded bg-muted ${className}`, style });
7552
+ }
7553
+ function SkeletonGroup({ label, className = "", children, ...rest }) {
7401
7554
  return /* @__PURE__ */ jsxs(
7402
7555
  "div",
7403
7556
  {
7404
- "data-testid": TID.checkoutProcessing,
7405
- className: "fixed inset-0 z-50 flex animate-fade-in flex-col items-center justify-center bg-background/95 backdrop-blur-sm",
7406
7557
  role: "status",
7407
- "aria-live": "polite",
7558
+ "aria-busy": "true",
7559
+ className: `animate-pulse motion-reduce:animate-none ${className}`,
7560
+ ...rest,
7408
7561
  children: [
7409
- /* @__PURE__ */ jsxs("div", { className: "relative flex h-24 w-24 items-center justify-center", children: [
7410
- /* @__PURE__ */ jsx("span", { className: "absolute inset-0 animate-spin rounded-full border-[3px] border-muted border-t-primary", style: { animationDuration: "1.1s" } }),
7411
- /* @__PURE__ */ jsx(Icon2, { className: "h-9 w-9 animate-pop-in text-primary" }, step)
7412
- ] }),
7413
- /* @__PURE__ */ jsx("p", { className: "mt-6 animate-fade-up font-medium", children: current.label }, `label-${step}`),
7414
- /* @__PURE__ */ jsx("p", { className: "mt-8 text-xs text-muted-foreground", children: "Pagamento seguro - n\xE3o feche esta janela" })
7562
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: label }),
7563
+ children
7415
7564
  ]
7416
7565
  }
7417
7566
  );
7418
7567
  }
7568
+ function PulseDot({ className = "" }) {
7569
+ return /* @__PURE__ */ jsxs("span", { "aria-hidden": true, className: `relative flex h-2.5 w-2.5 shrink-0 ${className}`, children: [
7570
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-0 animate-ping rounded-full bg-current opacity-60 motion-reduce:animate-none" }),
7571
+ /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2.5 w-2.5 rounded-full bg-current" })
7572
+ ] });
7573
+ }
7574
+ var POLL_MS = 3e3;
7575
+ function CheckoutPayment({ input, onPaid, onBack, money, onUnavailable }) {
7576
+ const [phase, setPhase] = useState("opening");
7577
+ const [charge, setCharge] = useState(null);
7578
+ const [total, setTotal] = useState(null);
7579
+ const [sessionId, setSessionId] = useState(null);
7580
+ const [provider, setProvider] = useState(null);
7581
+ const [error, setError] = useState(null);
7582
+ const [copied, setCopied] = useState(false);
7583
+ const [checking, setChecking] = useState(false);
7584
+ const settled = useRef(false);
7585
+ const opened = useRef(false);
7586
+ const open = useCallback(async () => {
7587
+ const shop = getShopProvider();
7588
+ if (!shop.openCheckoutSession) {
7589
+ setError("Esta loja ainda n\xE3o recebe pagamento online.");
7590
+ setPhase("error");
7591
+ return;
7592
+ }
7593
+ setPhase("opening");
7594
+ setError(null);
7595
+ try {
7596
+ const session = await shop.openCheckoutSession(input);
7597
+ setSessionId(session.sessionId);
7598
+ setProvider(session.provider ?? input.provider ?? null);
7599
+ setTotal(session.total);
7600
+ setCharge(session.charge);
7601
+ setPhase(session.charge ? "waiting" : "error");
7602
+ if (!session.charge) setError("N\xE3o conseguimos abrir a cobran\xE7a agora.");
7603
+ } catch (err) {
7604
+ if (err?.status === 404) {
7605
+ onUnavailable?.();
7606
+ return;
7607
+ }
7608
+ setError(err instanceof Error ? err.message : "N\xE3o conseguimos abrir a cobran\xE7a agora.");
7609
+ setPhase("error");
7610
+ }
7611
+ }, [input, onUnavailable]);
7612
+ useEffect(() => {
7613
+ if (opened.current) return;
7614
+ opened.current = true;
7615
+ void open();
7616
+ }, [open]);
7617
+ const check = useCallback(async () => {
7618
+ if (!sessionId || settled.current) return false;
7619
+ const shop = getShopProvider();
7620
+ if (!shop.settleCheckoutSession) return false;
7621
+ try {
7622
+ const result = await shop.settleCheckoutSession(sessionId, provider);
7623
+ if (settled.current) return false;
7624
+ if (result.state === "paid" && result.orderId) {
7625
+ settled.current = true;
7626
+ setPhase("settling");
7627
+ onPaid(result.orderId);
7628
+ return true;
7629
+ }
7630
+ if (result.state === "failed" || result.state === "expired") {
7631
+ setError(
7632
+ result.reason ?? (result.state === "expired" ? "O c\xF3digo expirou. Gere um novo para continuar." : "O pagamento n\xE3o foi conclu\xEDdo.")
7633
+ );
7634
+ setPhase("error");
7635
+ }
7636
+ return false;
7637
+ } catch {
7638
+ return false;
7639
+ }
7640
+ }, [sessionId, provider, onPaid]);
7641
+ useEffect(() => {
7642
+ if (phase !== "waiting" || !sessionId) return;
7643
+ let stopped = false;
7644
+ const timer = setInterval(() => {
7645
+ if (!stopped) void check();
7646
+ }, POLL_MS);
7647
+ return () => {
7648
+ stopped = true;
7649
+ clearInterval(timer);
7650
+ };
7651
+ }, [phase, sessionId, check]);
7652
+ async function confirmPaid() {
7653
+ setChecking(true);
7654
+ const paid = await check();
7655
+ if (!paid) setChecking(false);
7656
+ }
7657
+ async function copy() {
7658
+ if (!charge?.emv) return;
7659
+ try {
7660
+ await navigator.clipboard.writeText(charge.emv);
7661
+ setCopied(true);
7662
+ setTimeout(() => setCopied(false), 2400);
7663
+ } catch {
7664
+ toast.error("Copie o c\xF3digo manualmente");
7665
+ }
7666
+ }
7667
+ if (phase === "opening") {
7668
+ return (
7669
+ // Same layout as the charge below, so nothing jumps when it arrives.
7670
+ /* @__PURE__ */ jsxs(
7671
+ SkeletonGroup,
7672
+ {
7673
+ label: "Preparando seu pagamento",
7674
+ "data-testid": TID.checkoutPayment,
7675
+ className: "rounded-xl border bg-card p-6",
7676
+ children: [
7677
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7678
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-5 rounded-md" }),
7679
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-64 max-w-[70%]" })
7680
+ ] }),
7681
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-3.5 w-full max-w-sm" }),
7682
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 grid gap-5 sm:grid-cols-[auto_minmax(0,1fr)] sm:items-start", children: [
7683
+ /* @__PURE__ */ jsx(Skeleton, { className: "mx-auto h-44 w-44 rounded-lg" }),
7684
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
7685
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-32" }),
7686
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-4 h-3 w-28" }),
7687
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-1 h-16 w-full rounded-lg" }),
7688
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-3", children: [
7689
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-11 w-40 rounded-lg" }),
7690
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-11 w-32 rounded-lg" })
7691
+ ] })
7692
+ ] })
7693
+ ] })
7694
+ ]
7695
+ }
7696
+ )
7697
+ );
7698
+ }
7699
+ if (phase === "error") {
7700
+ return /* @__PURE__ */ jsxs("div", { "data-testid": TID.checkoutPayment, className: "space-y-3 rounded-xl border bg-card p-6", children: [
7701
+ /* @__PURE__ */ jsx("p", { "data-testid": TID.checkoutError, className: "rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2 text-sm text-destructive", children: error ?? "N\xE3o conseguimos abrir a cobran\xE7a agora." }),
7702
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
7703
+ /* @__PURE__ */ jsx(
7704
+ "button",
7705
+ {
7706
+ type: "button",
7707
+ onClick: () => {
7708
+ opened.current = false;
7709
+ void open();
7710
+ },
7711
+ className: "rounded-lg bg-primary px-4 py-2.5 text-sm font-semibold text-primary-foreground transition hover:opacity-90",
7712
+ children: "Tentar de novo"
7713
+ }
7714
+ ),
7715
+ /* @__PURE__ */ jsx(
7716
+ "button",
7717
+ {
7718
+ type: "button",
7719
+ onClick: onBack,
7720
+ className: "rounded-lg border px-4 py-2.5 text-sm font-medium transition hover:bg-muted",
7721
+ children: "Voltar"
7722
+ }
7723
+ )
7724
+ ] })
7725
+ ] });
7726
+ }
7727
+ return /* @__PURE__ */ jsxs("div", { "data-testid": TID.checkoutPayment, className: "space-y-4", "aria-live": "polite", children: [
7728
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-card p-6", children: [
7729
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7730
+ /* @__PURE__ */ jsx(QrCode, { className: "h-5 w-5 text-primary" }),
7731
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: "Pague com Pix para fechar o pedido" })
7732
+ ] }),
7733
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Seu pedido \xE9 criado assim que o pagamento entrar. Pode deixar esta tela aberta." }),
7734
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 grid gap-5 sm:grid-cols-[auto_minmax(0,1fr)] sm:items-start", children: [
7735
+ charge?.qrcodeBase64 && /* @__PURE__ */ jsx(
7736
+ "img",
7737
+ {
7738
+ src: `data:image/png;base64,${charge.qrcodeBase64}`,
7739
+ alt: "QR code do Pix",
7740
+ className: "mx-auto h-44 w-44 rounded-lg border bg-white p-2"
7741
+ }
7742
+ ),
7743
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
7744
+ total != null && /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
7745
+ "Valor: ",
7746
+ /* @__PURE__ */ jsx("strong", { "data-testid": TID.checkoutPaymentTotal, children: money(total) })
7747
+ ] }),
7748
+ /* @__PURE__ */ jsx("p", { className: "mt-3 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Pix copia e cola" }),
7749
+ /* @__PURE__ */ jsx(
7750
+ "p",
7751
+ {
7752
+ "data-testid": TID.pixCode,
7753
+ className: "mt-1 max-h-24 overflow-y-auto break-all rounded-lg border bg-muted/40 p-3 font-mono text-xs",
7754
+ children: charge?.emv
7755
+ }
7756
+ ),
7757
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-3", children: [
7758
+ /* @__PURE__ */ jsxs(
7759
+ "button",
7760
+ {
7761
+ type: "button",
7762
+ onClick: copy,
7763
+ "data-testid": TID.pixCopy,
7764
+ className: "inline-flex min-h-11 items-center gap-2 rounded-lg bg-primary px-4 py-2.5 text-sm font-medium text-primary-foreground transition hover:opacity-90",
7765
+ children: [
7766
+ copied ? /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Copy, { className: "h-4 w-4" }),
7767
+ copied ? "C\xF3digo copiado" : "Copiar c\xF3digo"
7768
+ ]
7769
+ }
7770
+ ),
7771
+ /* @__PURE__ */ jsxs(
7772
+ "button",
7773
+ {
7774
+ type: "button",
7775
+ onClick: () => {
7776
+ void confirmPaid();
7777
+ },
7778
+ disabled: checking || phase === "settling",
7779
+ "data-testid": TID.pixConfirm,
7780
+ className: "inline-flex min-h-11 items-center gap-2 rounded-lg border border-primary px-4 py-2.5 text-sm font-semibold text-primary transition hover:bg-primary/5 disabled:opacity-60",
7781
+ children: [
7782
+ checking || phase === "settling" ? /* @__PURE__ */ jsx(PulseDot, {}) : /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }),
7783
+ phase === "settling" ? "Confirmando\u2026" : checking ? "Verificando\u2026" : "J\xE1 paguei"
7784
+ ]
7785
+ }
7786
+ )
7787
+ ] })
7788
+ ] })
7789
+ ] })
7790
+ ] }),
7791
+ /* @__PURE__ */ jsx(
7792
+ "button",
7793
+ {
7794
+ type: "button",
7795
+ onClick: onBack,
7796
+ className: "text-sm text-muted-foreground underline hover:text-foreground",
7797
+ children: "Voltar e revisar o pedido"
7798
+ }
7799
+ )
7800
+ ] });
7801
+ }
7802
+ var CONFETTI_COLORS = ["hsl(var(--primary))", "#f59e0b", "#10b981", "#3b82f6", "#ec4899"];
7803
+ function SuccessMark() {
7804
+ const reduced = prefersReducedMotion();
7805
+ return /* @__PURE__ */ jsxs("div", { className: "relative mx-auto h-24 w-24", children: [
7806
+ !reduced && Array.from({ length: 14 }, (_, i) => /* @__PURE__ */ jsx(
7807
+ "span",
7808
+ {
7809
+ "aria-hidden": true,
7810
+ className: "absolute h-2 w-2 animate-confetti-fall rounded-sm",
7811
+ style: {
7812
+ left: `${i * 7.3 % 100}%`,
7813
+ top: "-4px",
7814
+ backgroundColor: CONFETTI_COLORS[i % CONFETTI_COLORS.length],
7815
+ animationDelay: `${200 + i % 7 * 90}ms`
7816
+ }
7817
+ },
7818
+ i
7819
+ )),
7820
+ /* @__PURE__ */ jsx("span", { className: `absolute inset-0 rounded-full bg-emerald-100 ${reduced ? "" : "animate-pop-in"}` }),
7821
+ /* @__PURE__ */ jsx("svg", { viewBox: "0 0 48 48", className: "absolute inset-0 h-full w-full p-6", "aria-hidden": true, children: /* @__PURE__ */ jsx(
7822
+ "path",
7823
+ {
7824
+ d: "M10 25 L20 35 L38 14",
7825
+ fill: "none",
7826
+ stroke: "rgb(5 150 105)",
7827
+ strokeWidth: "5",
7828
+ strokeLinecap: "round",
7829
+ strokeLinejoin: "round",
7830
+ strokeDasharray: "48",
7831
+ className: reduced ? "" : "animate-draw-check"
7832
+ }
7833
+ ) })
7834
+ ] });
7835
+ }
7836
+ var PAYMENT_LABELS = {
7837
+ pix: { label: "Pix", hint: "Voc\xEA recebe a chave para pagar ap\xF3s confirmar o pedido" },
7838
+ credit_card: { label: "Cart\xE3o de cr\xE9dito", hint: "Maquininha na entrega" },
7839
+ debit_card: { label: "Cart\xE3o de d\xE9bito", hint: "Maquininha na entrega" },
7840
+ boleto: { label: "Boleto", hint: "Enviado por e-mail ap\xF3s a confirma\xE7\xE3o" },
7841
+ cash: { label: "Dinheiro", hint: "Pagamento na entrega" },
7842
+ other: { label: "Combinar com a loja", hint: "A loja entra em contato para acertar o pagamento" }
7843
+ };
7844
+ function placeOrderMessage(err) {
7845
+ const message = errorMessage(err);
7846
+ if (/customer (email mismatch|does not belong|belongs to another)/i.test(message)) {
7847
+ return "Sua sess\xE3o ficou presa a outro cadastro. Recarregue a p\xE1gina e tente de novo.";
7848
+ }
7849
+ if (/out of stock|insufficient|estoque/i.test(message)) {
7850
+ return "Um item do carrinho ficou sem estoque. Revise o carrinho e tente de novo.";
7851
+ }
7852
+ if (/delivery not available|postal code/i.test(message)) {
7853
+ return "Ainda n\xE3o entregamos nesse CEP. Tente outro endere\xE7o.";
7854
+ }
7855
+ return message.replace(/^shop_[a-z_]+:\s*/i, "") || "N\xE3o foi poss\xEDvel concluir o pedido.";
7856
+ }
7419
7857
  function field(label, testId, value, onChange, props) {
7420
7858
  return /* @__PURE__ */ jsxs("label", { className: "block", children: [
7421
7859
  /* @__PURE__ */ jsx("span", { className: "sr-only", children: label }),
@@ -7440,13 +7878,22 @@ function CheckoutPage() {
7440
7878
  const validateDiscount2 = useDiscountValidator();
7441
7879
  useStorefrontHead({ title: `Checkout \u2014 ${config.name}` });
7442
7880
  const paymentMethods = config.payments.methods;
7881
+ const policy = config.policy;
7882
+ const requiresAccount = policy?.guestCheckout === false;
7883
+ const fieldPolicy = policy?.checkoutFields ?? {};
7884
+ const gatewayCheckout = config.payments.mode === "gateway" || !!policy?.paymentProvider;
7885
+ const shows = (key) => key === "document" ? gatewayCheckout || fieldPolicy.document?.visible === true : fieldPolicy[key]?.visible !== false;
7886
+ const requires = (key) => key === "document" ? gatewayCheckout || fieldPolicy.document?.visible === true && fieldPolicy.document?.required === true : fieldPolicy[key]?.visible !== false && fieldPolicy[key]?.required === true;
7443
7887
  const [selectedAddressId, setSelectedAddressId] = useState("new");
7444
7888
  const [savedAddresses, setSavedAddresses] = useState([]);
7889
+ const [addressesLoading, setAddressesLoading] = useState(false);
7445
7890
  const [addressMode, setAddressMode] = useState("new");
7446
7891
  const [paymentMethod, setPaymentMethod] = useState(paymentMethods[0] ?? "pix");
7447
7892
  const [form, setForm] = useState({
7448
7893
  email: session.email ?? "",
7449
7894
  name: session.name ?? "",
7895
+ phone: "",
7896
+ document: "",
7450
7897
  street: "",
7451
7898
  city: "",
7452
7899
  zip: "",
@@ -7455,33 +7902,69 @@ function CheckoutPage() {
7455
7902
  district: "",
7456
7903
  state: ""
7457
7904
  });
7905
+ const [step, setStep] = useState(1);
7906
+ const [paidOrderId, setPaidOrderId] = useState(null);
7458
7907
  const [error, setError] = useState(null);
7459
7908
  const [discountCode, setDiscountCode] = useState("");
7460
7909
  const [discountError, setDiscountError] = useState(null);
7461
7910
  const [discountSuccess, setDiscountSuccess] = useState(null);
7462
7911
  const [applyingDiscount, setApplyingDiscount] = useState(false);
7463
- const [placing, setPlacing] = useState(false);
7464
- const [processingStep, setProcessingStep] = useState(0);
7465
7912
  const [showSignin, setShowSignin] = useState(false);
7913
+ const [placing, setPlacing] = useState(false);
7914
+ const leaving = React4.useRef(false);
7466
7915
  const [zipLookup, setZipLookup] = useState("idle");
7467
7916
  const subtotal = selectSubtotal(cart);
7468
7917
  const discountTotal = selectDiscountTotal(cart);
7469
7918
  const shipping = selectShipping(cart, config);
7470
7919
  const total = selectTotal(cart, config);
7471
7920
  const checkoutSlotContext = { subtotal, total };
7921
+ const checkoutSessionInput = React4.useMemo(() => ({
7922
+ items: cart.lines.map((line) => ({
7923
+ productId: line.productId,
7924
+ variantId: line.variantId ?? null,
7925
+ quantity: line.quantity,
7926
+ optionsLabel: line.optionsLabel ?? null
7927
+ })),
7928
+ customer: { name: form.name.trim(), email: form.email.trim() },
7929
+ shippingAddress: {
7930
+ postalCode: form.zip.trim(),
7931
+ street: form.street.trim(),
7932
+ number: form.number.trim() || void 0,
7933
+ complement: form.complement.trim() || void 0,
7934
+ district: form.district.trim() || void 0,
7935
+ city: form.city.trim(),
7936
+ state: form.state.trim().toUpperCase(),
7937
+ phone: form.phone.trim() || void 0,
7938
+ document: documentDigits(form.document) || void 0
7939
+ },
7940
+ discountCode: cart.discountCode ?? null,
7941
+ paymentMethod,
7942
+ // Qual adquirente cobra. Sai dos ajustes da loja e não de uma escolha do
7943
+ // comprador — a função reconfere de qualquer forma, mas mandar daqui é o
7944
+ // que faz o clique bater na função do gateway que a loja realmente conectou.
7945
+ provider: policy?.paymentProvider ?? null
7946
+ // eslint-disable-next-line react-hooks/exhaustive-deps
7947
+ }), [cart.lines, cart.discountCode, form, paymentMethod, policy?.paymentProvider]);
7948
+ const [acquirerMissing, setAcquirerMissing] = useState(false);
7949
+ const chargesOnline = paymentMethod === "pix" && !acquirerMissing && (!policy?.paymentProvider || isChargeableProvider(policy.paymentProvider));
7472
7950
  const money = (value) => formatMoney(value, config.currency, config.locale);
7473
7951
  useEffect(() => {
7474
- if (cart.lines.length === 0 && !placing) navigateTo(config.catalogPath);
7475
- }, [cart.lines.length, config.catalogPath, placing]);
7476
- const set = (key) => (value) => setForm((current) => ({ ...current, [key]: value }));
7952
+ if (cart.lines.length === 0 && !paidOrderId && !leaving.current) navigateTo(config.catalogPath);
7953
+ }, [cart.lines.length, config.catalogPath, paidOrderId]);
7954
+ const set = (key) => (value) => {
7955
+ setError(null);
7956
+ setForm((current) => ({ ...current, [key]: value }));
7957
+ };
7477
7958
  async function setZip(value) {
7478
7959
  const masked = formatPostalCode(value);
7960
+ setError(null);
7479
7961
  setForm((current) => ({ ...current, zip: masked }));
7480
7962
  if (normalizePostalCode(masked).length !== 8) {
7481
7963
  setZipLookup("idle");
7482
7964
  return;
7483
7965
  }
7484
7966
  setZipLookup("loading");
7967
+ void delivery.resolve(masked, subtotal);
7485
7968
  try {
7486
7969
  const found = await lookupPostalCode(masked);
7487
7970
  if (!found) {
@@ -7531,15 +8014,24 @@ function CheckoutPage() {
7531
8014
  if (!customerId) {
7532
8015
  setSavedAddresses([]);
7533
8016
  setAddressMode("new");
8017
+ setAddressesLoading(false);
7534
8018
  return;
7535
8019
  }
7536
8020
  let cancelled = false;
7537
8021
  const provider = getShopProvider();
7538
- void provider.listCustomerAddresses?.(customerId).then((addresses) => {
8022
+ const listAddresses = provider.listCustomerAddresses?.bind(provider);
8023
+ if (!listAddresses) {
8024
+ setAddressesLoading(false);
8025
+ return;
8026
+ }
8027
+ setAddressesLoading(true);
8028
+ void listAddresses(customerId).then((addresses) => {
7539
8029
  if (cancelled || addresses.length === 0) return;
7540
8030
  setSavedAddresses(addresses);
7541
8031
  applySavedAddress(addresses[0]);
7542
8032
  }).catch(() => {
8033
+ }).finally(() => {
8034
+ if (!cancelled) setAddressesLoading(false);
7543
8035
  });
7544
8036
  return () => {
7545
8037
  cancelled = true;
@@ -7572,7 +8064,11 @@ function CheckoutPage() {
7572
8064
  number: address.number ?? "",
7573
8065
  complement: address.complement ?? "",
7574
8066
  district: address.district ?? "",
7575
- state: address.state
8067
+ state: address.state,
8068
+ // Kept from the book when it has one, and NOT cleared when it does not:
8069
+ // a shopper who just typed their phone should not lose it by picking a
8070
+ // saved address that predates the field.
8071
+ phone: address.phone ?? current.phone
7576
8072
  }));
7577
8073
  }
7578
8074
  function selectAddress(addressId) {
@@ -7584,44 +8080,69 @@ function CheckoutPage() {
7584
8080
  setSelectedAddressId("new");
7585
8081
  setForm((current) => ({ ...current, street: "", city: "", zip: "", number: "", complement: "", district: "", state: "" }));
7586
8082
  }
7587
- function validate() {
8083
+ function fail(message, testId) {
8084
+ setError(message);
8085
+ if (testId) {
8086
+ const field2 = document.querySelector(`[data-testid="${testId}"]`);
8087
+ field2?.scrollIntoView({ block: "center", behavior: prefersReducedMotion() ? "auto" : "smooth" });
8088
+ field2?.focus({ preventScroll: true });
8089
+ }
8090
+ return false;
8091
+ }
8092
+ function validateContact() {
7588
8093
  setError(null);
8094
+ if (requiresAccount && !session.customerId) {
8095
+ setShowSignin(true);
8096
+ return fail("Esta loja pede uma conta para finalizar a compra. Entre ou crie a sua.");
8097
+ }
7589
8098
  if (!form.email.trim() || !/.+@.+\..+/.test(form.email)) {
7590
- setError("Informe um e-mail v\xE1lido.");
7591
- return false;
8099
+ return fail("Informe um e-mail v\xE1lido.", TID.checkoutEmail);
7592
8100
  }
7593
- if (!form.name.trim()) {
7594
- setError("Informe seu nome completo.");
7595
- return false;
8101
+ if (!form.name.trim()) return fail("Informe seu nome completo.", TID.checkoutName);
8102
+ if (requires("phone") && !form.phone.trim()) {
8103
+ return fail("Informe um telefone de contato.", "checkout-phone");
8104
+ }
8105
+ if (requires("document") && !isValidDocument(form.document)) {
8106
+ return fail(
8107
+ form.document.trim() ? "CPF ou CNPJ inv\xE1lido \u2014 confira os n\xFAmeros." : "Informe o CPF ou CNPJ do comprador.",
8108
+ TID.checkoutDocument
8109
+ );
7596
8110
  }
8111
+ return true;
8112
+ }
8113
+ function validateDelivery() {
8114
+ setError(null);
8115
+ if (!selectRequiresShipping(cart)) return true;
7597
8116
  if (!form.street.trim() || !form.city.trim() || !form.zip.trim() || !form.district.trim()) {
7598
- setError("Preencha ou selecione o endere\xE7o de entrega.");
7599
- return false;
8117
+ return fail("Preencha ou selecione o endere\xE7o de entrega.", TID.checkoutStreet);
7600
8118
  }
7601
- if (!form.number.trim()) {
7602
- setError("Falta o n\xFAmero do endere\xE7o.");
7603
- return false;
8119
+ if (!form.number.trim()) return fail("Falta o n\xFAmero do endere\xE7o.", "checkout-number");
8120
+ if (requires("complement") && !form.complement.trim()) {
8121
+ return fail("Informe o complemento (apto, bloco).", "checkout-complement");
7604
8122
  }
7605
- if (form.state.trim().length !== 2) {
7606
- setError("Informe a UF com duas letras (ex.: RJ).");
7607
- return false;
8123
+ if (!/^[A-Za-z]{2}$/.test(form.state.trim())) {
8124
+ return fail("Informe a UF com duas letras (ex.: RJ).", "checkout-state");
7608
8125
  }
7609
8126
  if (delivery.status === "unserved" && delivery.postalCode === normalizePostalCode(form.zip)) {
7610
- setError("Ainda n\xE3o entregamos nesse CEP. Tente outro endere\xE7o.");
7611
- return false;
8127
+ return fail(
8128
+ delivery.refusedZone ? "Esta loja n\xE3o entrega nesse endere\xE7o. Tente outro." : "Ainda n\xE3o entregamos nesse CEP. Tente outro endere\xE7o.",
8129
+ TID.checkoutZip
8130
+ );
7612
8131
  }
7613
8132
  return true;
7614
8133
  }
7615
- async function placeOrder() {
8134
+ function goToStep(next) {
8135
+ setError(null);
8136
+ setStep(next);
8137
+ if (typeof window !== "undefined") window.scrollTo({ top: 0, behavior: "smooth" });
8138
+ }
8139
+ function advance() {
8140
+ if (step === 1 && validateContact()) goToStep(2);
8141
+ else if (step === 2 && validateDelivery()) goToStep(3);
8142
+ }
8143
+ async function confirmWithoutCharge() {
7616
8144
  if (!validate()) return;
7617
8145
  setPlacing(true);
7618
- setProcessingStep(0);
7619
- const stepMs = prefersReducedMotion() ? 120 : 600;
7620
- const stepTimers = [
7621
- setTimeout(() => setProcessingStep(1), stepMs),
7622
- setTimeout(() => setProcessingStep(2), stepMs * 2)
7623
- ];
7624
- const minDuration = sleep(stepMs * 2);
7625
8146
  try {
7626
8147
  const { order, customerId } = await placeStorefrontOrder({
7627
8148
  config,
@@ -7636,31 +8157,67 @@ function CheckoutPage() {
7636
8157
  complement: form.complement.trim() || void 0,
7637
8158
  district: form.district.trim() || void 0,
7638
8159
  city: form.city.trim(),
7639
- state: form.state.trim().toUpperCase()
8160
+ state: form.state.trim().toUpperCase(),
8161
+ phone: form.phone.trim() || void 0,
8162
+ // Viaja no endereço porque a assinatura do shop_place_order não pode
8163
+ // ganhar parâmetro sem DROP (0050); uma trigger o levanta para a
8164
+ // coluna e o tira do jsonb, então nunca vira linha de endereço.
8165
+ document: documentDigits(form.document) || void 0
7640
8166
  },
7641
- // The method the buyer actually chose, which opens the ledger row in
7642
- // public.transactions with the right kind.
7643
8167
  paymentMethod
7644
- // Never marked paid from the browser. It used to be, through an RPC that
7645
- // was granted to anon — so any buyer holding their own order id could
7646
- // declare it settled. The order is now born `pending` and only the
7647
- // merchant (or a PSP webhook) can confirm the money arrived.
7648
8168
  });
7649
8169
  if (customerId) session.setSession({ customerId, email: form.email, name: form.name });
7650
- await minDuration;
8170
+ leaving.current = true;
7651
8171
  cart.clear();
7652
8172
  navigateTo(`/order/${order.id}`);
7653
- } catch (error2) {
7654
- setError(error2 instanceof Error ? error2.message : "N\xE3o foi poss\xEDvel concluir o pedido.");
8173
+ } catch (err) {
8174
+ setError(placeOrderMessage(err));
7655
8175
  setPlacing(false);
7656
- } finally {
7657
- stepTimers.forEach(clearTimeout);
7658
8176
  }
7659
8177
  }
7660
- const primaryCta = placing ? "Processando..." : `Comprar agora - ${money(total)}`;
8178
+ function validate() {
8179
+ setError(null);
8180
+ if (requiresAccount && !session.customerId) {
8181
+ setShowSignin(true);
8182
+ return fail("Esta loja pede uma conta para finalizar a compra. Entre ou crie a sua.");
8183
+ }
8184
+ if (!form.email.trim() || !/.+@.+\..+/.test(form.email)) {
8185
+ return fail("Informe um e-mail v\xE1lido.", TID.checkoutEmail);
8186
+ }
8187
+ if (requires("phone") && !form.phone.trim()) {
8188
+ return fail("Informe um telefone de contato.", "checkout-phone");
8189
+ }
8190
+ if (!form.name.trim()) {
8191
+ return fail("Informe seu nome completo.", TID.checkoutName);
8192
+ }
8193
+ if (requires("document") && !isValidDocument(form.document)) {
8194
+ return fail(
8195
+ form.document.trim() ? "CPF ou CNPJ inv\xE1lido \u2014 confira os n\xFAmeros." : "Informe o CPF ou CNPJ do comprador.",
8196
+ TID.checkoutDocument
8197
+ );
8198
+ }
8199
+ if (!form.street.trim() || !form.city.trim() || !form.zip.trim() || !form.district.trim()) {
8200
+ return fail("Preencha ou selecione o endere\xE7o de entrega.", TID.checkoutStreet);
8201
+ }
8202
+ if (!form.number.trim()) {
8203
+ return fail("Falta o n\xFAmero do endere\xE7o.", "checkout-number");
8204
+ }
8205
+ if (requires("complement") && !form.complement.trim()) {
8206
+ return fail("Informe o complemento (apto, bloco).", "checkout-complement");
8207
+ }
8208
+ if (form.state.trim().length !== 2) {
8209
+ return fail("Informe a UF com duas letras (ex.: RJ).", "checkout-state");
8210
+ }
8211
+ if (selectRequiresShipping(cart) && delivery.status === "unserved" && delivery.postalCode === normalizePostalCode(form.zip)) {
8212
+ return fail(
8213
+ delivery.refusedZone ? "Esta loja n\xE3o entrega nesse endere\xE7o. Tente outro." : "Ainda n\xE3o entregamos nesse CEP. Tente outro endere\xE7o.",
8214
+ TID.checkoutZip
8215
+ );
8216
+ }
8217
+ return true;
8218
+ }
7661
8219
  return /* @__PURE__ */ jsxs("main", { className: "min-h-screen bg-background", children: [
7662
8220
  /* @__PURE__ */ jsx(Slot, { area: "checkout", name: "beforeForm", context: checkoutSlotContext }),
7663
- placing && /* @__PURE__ */ jsx(ProcessingOverlay, { step: processingStep }),
7664
8221
  showSignin && !session.email && /* @__PURE__ */ jsx(
7665
8222
  SignInModal,
7666
8223
  {
@@ -7682,132 +8239,281 @@ function CheckoutPage() {
7682
8239
  ] }) }),
7683
8240
  /* @__PURE__ */ jsxs("div", { className: "mx-auto grid max-w-6xl grid-cols-1 gap-8 px-4 py-8 sm:px-6 lg:grid-cols-5 lg:px-6", children: [
7684
8241
  /* @__PURE__ */ jsxs("section", { className: "lg:col-span-3 lg:pr-10", children: [
7685
- /* @__PURE__ */ jsxs("nav", { className: "mb-8 flex flex-wrap items-center gap-2 text-sm text-muted-foreground", "aria-label": "Checkout steps", children: [
7686
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: "Informa\xE7\xF5es" }),
7687
- /* @__PURE__ */ jsx("span", { children: "/" }),
7688
- /* @__PURE__ */ jsx("span", { children: "Entrega" }),
7689
- /* @__PURE__ */ jsx("span", { children: "/" }),
7690
- /* @__PURE__ */ jsx("span", { children: "Pagamento" })
7691
- ] }),
7692
- /* @__PURE__ */ jsxs("div", { className: "space-y-8", children: [
7693
- /* @__PURE__ */ jsxs("section", { children: [
7694
- /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between gap-4", children: [
7695
- /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold tracking-tight", children: "Informa\xE7\xF5es de contato" }),
7696
- session.email ? /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
7697
- "Conectado como ",
7698
- session.email
7699
- ] }) : /* @__PURE__ */ jsx(
7700
- "button",
7701
- {
7702
- type: "button",
7703
- onClick: () => setShowSignin((value) => !value),
7704
- className: "text-sm font-medium text-primary hover:underline",
7705
- children: "J\xE1 tem conta? Entrar"
7706
- }
7707
- )
7708
- ] }),
7709
- /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
7710
- field("E-mail", TID.checkoutEmail, form.email, set("email"), {
7711
- type: "email",
7712
- placeholder: "E-mail ou telefone"
7713
- }),
7714
- field("Nome completo", TID.checkoutName, form.name, set("name"), {
7715
- placeholder: "Nome completo"
7716
- })
7717
- ] })
7718
- ] }),
7719
- /* @__PURE__ */ jsxs("section", { children: [
7720
- /* @__PURE__ */ jsx("h2", { className: "mb-3 text-xl font-semibold tracking-tight", children: "Endere\xE7o de entrega" }),
7721
- /* @__PURE__ */ jsxs("div", { className: "mb-3 grid gap-3", children: [
7722
- savedAddresses.map((address) => /* @__PURE__ */ jsx(
7723
- "div",
7724
- {
7725
- className: `group relative rounded-lg border p-3 pr-11 text-left text-sm transition hover:bg-muted/40 ${selectedAddressId === address.id ? "border-primary bg-primary/5" : "border-border bg-background"}`,
7726
- children: /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => selectAddress(address.id), className: "block w-full text-left", children: [
7727
- /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-between gap-3 font-semibold", children: [
7728
- address.label,
7729
- addressMode === "saved" && selectedAddressId === address.id && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary", children: "Selecionado" })
7730
- ] }),
7731
- /* @__PURE__ */ jsxs("span", { className: "mt-1 block text-muted-foreground", children: [
7732
- [address.street, address.number].filter(Boolean).join(", "),
7733
- address.complement ? ` \u2014 ${address.complement}` : ""
7734
- ] }),
7735
- /* @__PURE__ */ jsxs("span", { className: "block text-muted-foreground", children: [
7736
- [address.district, address.city, address.state].filter(Boolean).join(" \xB7 "),
7737
- " \u2014 ",
7738
- address.postalCode
8242
+ /* @__PURE__ */ jsx(
8243
+ CheckoutSteps,
8244
+ {
8245
+ current: step,
8246
+ onGoBackTo: step === 4 ? void 0 : (target) => goToStep(target)
8247
+ }
8248
+ ),
8249
+ /* @__PURE__ */ jsxs(
8250
+ "form",
8251
+ {
8252
+ className: "space-y-8",
8253
+ onSubmit: (event) => {
8254
+ event.preventDefault();
8255
+ advance();
8256
+ },
8257
+ children: [
8258
+ step === 1 && /* @__PURE__ */ jsxs("section", { children: [
8259
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between gap-4", children: [
8260
+ /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold tracking-tight", children: "Informa\xE7\xF5es de contato" }),
8261
+ session.email ? /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
8262
+ "Conectado como ",
8263
+ session.email
8264
+ ] }) : /* @__PURE__ */ jsx(
8265
+ "button",
8266
+ {
8267
+ type: "button",
8268
+ onClick: () => setShowSignin((value) => !value),
8269
+ className: "text-sm font-medium text-primary hover:underline",
8270
+ children: "J\xE1 tem conta? Entrar"
8271
+ }
8272
+ )
8273
+ ] }),
8274
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
8275
+ field("E-mail", TID.checkoutEmail, form.email, set("email"), {
8276
+ type: "email",
8277
+ placeholder: "E-mail"
8278
+ }),
8279
+ field("Nome completo", TID.checkoutName, form.name, set("name"), {
8280
+ placeholder: "Nome completo"
8281
+ }),
8282
+ shows("phone") && field("Telefone", "checkout-phone", form.phone, set("phone"), {
8283
+ type: "tel",
8284
+ autoComplete: "tel",
8285
+ placeholder: requires("phone") ? "Telefone" : "Telefone (opcional)"
8286
+ }),
8287
+ shows("document") && field(
8288
+ "CPF ou CNPJ",
8289
+ TID.checkoutDocument,
8290
+ form.document,
8291
+ (value) => {
8292
+ setError(null);
8293
+ setForm((current) => ({ ...current, document: formatDocument(value) }));
8294
+ },
8295
+ {
8296
+ // `inputMode` and not `type="number"`: the mask writes dots
8297
+ // and a dash, which a number input silently refuses to hold.
8298
+ inputMode: "numeric",
8299
+ autoComplete: "off",
8300
+ placeholder: requires("document") ? "CPF ou CNPJ" : "CPF ou CNPJ (opcional)"
8301
+ }
8302
+ )
8303
+ ] }),
8304
+ requiresAccount && !session.customerId && /* @__PURE__ */ jsxs("p", { className: "mt-3 rounded-lg border border-primary/30 bg-primary/5 px-3 py-2 text-sm", children: [
8305
+ "Esta loja pede uma conta para finalizar a compra.",
8306
+ " ",
8307
+ /* @__PURE__ */ jsx(
8308
+ "button",
8309
+ {
8310
+ type: "button",
8311
+ onClick: () => setShowSignin(true),
8312
+ className: "font-semibold text-primary hover:underline",
8313
+ children: "Entrar ou criar conta"
8314
+ }
8315
+ )
8316
+ ] })
8317
+ ] }),
8318
+ step === 2 && /* @__PURE__ */ jsxs("section", { children: [
8319
+ /* @__PURE__ */ jsx("h2", { className: "mb-3 text-xl font-semibold tracking-tight", children: "Endere\xE7o de entrega" }),
8320
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 grid gap-3", children: [
8321
+ addressesLoading && savedAddresses.length === 0 && /* @__PURE__ */ jsx(SkeletonGroup, { label: "Buscando seus endere\xE7os salvos", className: "grid gap-3", children: [0, 1].map((row) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg border p-3", children: [
8322
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-28" }),
8323
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-3 w-3/4" }),
8324
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-3 w-1/2" })
8325
+ ] }, row)) }),
8326
+ savedAddresses.map((address) => /* @__PURE__ */ jsx(
8327
+ "div",
8328
+ {
8329
+ className: `group relative rounded-lg border p-3 pr-11 text-left text-sm transition hover:bg-muted/40 ${selectedAddressId === address.id ? "border-primary bg-primary/5" : "border-border bg-background"}`,
8330
+ children: /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => selectAddress(address.id), className: "block w-full text-left", children: [
8331
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-between gap-3 font-semibold", children: [
8332
+ address.label,
8333
+ addressMode === "saved" && selectedAddressId === address.id && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary", children: "Selecionado" })
8334
+ ] }),
8335
+ /* @__PURE__ */ jsxs("span", { className: "mt-1 block text-muted-foreground", children: [
8336
+ [address.street, address.number].filter(Boolean).join(", "),
8337
+ address.complement ? ` \u2014 ${address.complement}` : ""
8338
+ ] }),
8339
+ /* @__PURE__ */ jsxs("span", { className: "block text-muted-foreground", children: [
8340
+ [address.district, address.city, address.state].filter(Boolean).join(" \xB7 "),
8341
+ " \u2014 ",
8342
+ address.postalCode
8343
+ ] })
8344
+ ] })
8345
+ },
8346
+ address.id
8347
+ )),
8348
+ /* @__PURE__ */ jsxs(
8349
+ "button",
8350
+ {
8351
+ type: "button",
8352
+ onClick: addNewAddress,
8353
+ className: `rounded-lg border border-dashed p-3 text-left text-sm transition hover:bg-muted/40 ${addressMode === "new" ? "border-primary bg-primary/5" : "border-border bg-muted/20"}`,
8354
+ children: [
8355
+ /* @__PURE__ */ jsx("span", { className: "block font-semibold text-primary", children: savedAddresses.length > 0 ? "+ Adicionar novo endere\xE7o" : "Informe o endere\xE7o de entrega" }),
8356
+ /* @__PURE__ */ jsx("span", { className: "mt-1 block text-muted-foreground", children: "Preencher outro endere\xE7o para esta compra" })
8357
+ ]
8358
+ }
8359
+ )
8360
+ ] }),
8361
+ addressMode === "new" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
8362
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-1 sm:grid-cols-[200px_1fr] sm:items-center", children: [
8363
+ field("CEP", TID.checkoutZip, form.zip, setZip, {
8364
+ placeholder: "CEP",
8365
+ inputMode: "numeric",
8366
+ autoComplete: "postal-code"
8367
+ }),
8368
+ zipLookup === "loading" ? /* @__PURE__ */ jsxs(SkeletonGroup, { label: "Buscando o endere\xE7o deste CEP", className: "grid gap-1.5 px-1", children: [
8369
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-2.5 w-40" }),
8370
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-2.5 w-24" })
8371
+ ] }) : /* @__PURE__ */ jsx("p", { className: "px-1 text-xs text-muted-foreground", children: zipLookup === "not-found" ? "CEP n\xE3o encontrado \u2014 preencha o endere\xE7o \xE0 m\xE3o." : "Preenchemos o endere\xE7o para voc\xEA." })
8372
+ ] }),
8373
+ field("Endere\xE7o", TID.checkoutStreet, form.street, set("street"), { placeholder: "Rua, avenida\u2026" }),
8374
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
8375
+ field("N\xFAmero", "checkout-number", form.number, set("number"), { placeholder: "N\xFAmero" }),
8376
+ shows("complement") && field("Complemento", "checkout-complement", form.complement, set("complement"), {
8377
+ placeholder: requires("complement") ? "Apto, bloco" : "Apto, bloco (opcional)"
8378
+ })
8379
+ ] }),
8380
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
8381
+ field("Bairro", "checkout-district", form.district, set("district"), { placeholder: "Bairro" }),
8382
+ field("UF", "checkout-state", form.state, set("state"), { placeholder: "UF" })
8383
+ ] }),
8384
+ field("Cidade", TID.checkoutCity, form.city, set("city"), { placeholder: "Cidade" })
8385
+ ] })
8386
+ ] }),
8387
+ step === 3 && placing && /* @__PURE__ */ jsxs("section", { children: [
8388
+ /* @__PURE__ */ jsx("h2", { className: "mb-3 text-xl font-semibold tracking-tight", children: "Confirmando seu pedido" }),
8389
+ /* @__PURE__ */ jsxs(SkeletonGroup, { label: "Criando seu pedido", className: "rounded-xl border bg-card p-6 sm:p-8", children: [
8390
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-6 border-b pb-6 sm:grid-cols-[120px_minmax(0,1fr)]", children: [
8391
+ /* @__PURE__ */ jsx(Skeleton, { className: "mx-auto h-24 w-24 rounded-full" }),
8392
+ /* @__PURE__ */ jsxs("div", { children: [
8393
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-56 max-w-full" }),
8394
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-3.5 w-full max-w-sm" }),
8395
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-4 h-5 w-40" })
7739
8396
  ] })
7740
- ] })
7741
- },
7742
- address.id
7743
- )),
7744
- /* @__PURE__ */ jsxs(
7745
- "button",
7746
- {
7747
- type: "button",
7748
- onClick: addNewAddress,
7749
- className: `rounded-lg border border-dashed p-3 text-left text-sm transition hover:bg-muted/40 ${addressMode === "new" ? "border-primary bg-primary/5" : "border-border bg-muted/20"}`,
7750
- children: [
7751
- /* @__PURE__ */ jsx("span", { className: "block font-semibold text-primary", children: savedAddresses.length > 0 ? "+ Adicionar novo endere\xE7o" : "Informe o endere\xE7o de entrega" }),
7752
- /* @__PURE__ */ jsx("span", { className: "mt-1 block text-muted-foreground", children: "Preencher outro endere\xE7o para esta compra" })
7753
- ]
7754
- }
7755
- )
7756
- ] }),
7757
- addressMode === "new" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
7758
- /* @__PURE__ */ jsxs("div", { className: "grid gap-1 sm:grid-cols-[200px_1fr] sm:items-center", children: [
7759
- field("CEP", TID.checkoutZip, form.zip, setZip, {
7760
- placeholder: "CEP",
7761
- inputMode: "numeric",
7762
- autoComplete: "postal-code"
7763
- }),
7764
- /* @__PURE__ */ jsx("p", { className: "px-1 text-xs text-muted-foreground", children: zipLookup === "loading" ? "Buscando endere\xE7o\u2026" : zipLookup === "not-found" ? "CEP n\xE3o encontrado \u2014 preencha o endere\xE7o \xE0 m\xE3o." : "Preenchemos o endere\xE7o para voc\xEA." })
8397
+ ] }),
8398
+ /* @__PURE__ */ jsx("div", { className: "mt-6 grid gap-4 rounded-xl border bg-muted/20 p-4 sm:grid-cols-3", children: [0, 1, 2].map((column) => /* @__PURE__ */ jsxs("div", { children: [
8399
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-16" }),
8400
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-4 w-24" })
8401
+ ] }, column)) }),
8402
+ /* @__PURE__ */ jsx("div", { className: "mt-8 space-y-3", children: cart.lines.slice(0, 3).map((line) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
8403
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-12 rounded-lg" }),
8404
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 flex-1" }),
8405
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-16" })
8406
+ ] }, line.lineId ?? line.productId)) })
8407
+ ] }),
8408
+ /* @__PURE__ */ jsx("p", { className: "mt-3 text-center text-sm text-muted-foreground", children: "N\xE3o feche esta p\xE1gina \u2014 estamos registrando seu pedido na loja." })
7765
8409
  ] }),
7766
- field("Endere\xE7o", TID.checkoutStreet, form.street, set("street"), { placeholder: "Rua, avenida\u2026" }),
7767
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
7768
- field("N\xFAmero", "checkout-number", form.number, set("number"), { placeholder: "N\xFAmero" }),
7769
- field("Complemento", "checkout-complement", form.complement, set("complement"), { placeholder: "Apto, bloco (opcional)" })
8410
+ step === 3 && !placing && /* @__PURE__ */ jsxs("section", { children: [
8411
+ /* @__PURE__ */ jsx("h2", { className: "mb-3 text-xl font-semibold tracking-tight", children: "Pagamento" }),
8412
+ /* @__PURE__ */ jsx("div", { className: "mb-3 grid gap-3", role: "radiogroup", "aria-label": "Forma de pagamento", children: paymentMethods.map((method) => {
8413
+ const copy = PAYMENT_LABELS[method];
8414
+ const selected = paymentMethod === method;
8415
+ return (
8416
+ // Cada forma de pagamento abre o que ELA pede, embaixo dela
8417
+ // mesma. Antes o painel do Pix ficava no fim da lista, longe
8418
+ // do cartão escolhido, e escolher parecia não fazer nada.
8419
+ /* @__PURE__ */ jsxs("div", { children: [
8420
+ /* @__PURE__ */ jsxs(
8421
+ "button",
8422
+ {
8423
+ type: "button",
8424
+ role: "radio",
8425
+ "aria-checked": selected,
8426
+ "data-testid": `checkout-payment-${method}`,
8427
+ onClick: () => setPaymentMethod(method),
8428
+ className: `w-full rounded-lg border p-3 text-left text-sm transition hover:bg-muted/40 ${selected ? "border-primary bg-primary/5" : "border-border bg-background"}`,
8429
+ children: [
8430
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-between gap-3 font-semibold", children: [
8431
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
8432
+ /* @__PURE__ */ jsx(CreditCard, { className: "h-4 w-4" }),
8433
+ copy.label
8434
+ ] }),
8435
+ selected && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary", children: "Selecionado" })
8436
+ ] }),
8437
+ /* @__PURE__ */ jsx("span", { className: "mt-1 block text-muted-foreground", children: copy.hint })
8438
+ ]
8439
+ }
8440
+ ),
8441
+ selected && chargesOnline && /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
8442
+ CheckoutPayment,
8443
+ {
8444
+ input: checkoutSessionInput,
8445
+ money,
8446
+ onBack: () => goToStep(2),
8447
+ onUnavailable: () => setAcquirerMissing(true),
8448
+ onPaid: (orderId) => {
8449
+ leaving.current = true;
8450
+ setPaidOrderId(orderId);
8451
+ goToStep(4);
8452
+ cart.clear();
8453
+ }
8454
+ }
8455
+ ) })
8456
+ ] }, method)
8457
+ );
8458
+ }) }),
8459
+ /* @__PURE__ */ jsx(Slot, { area: "checkout", name: "afterPaymentMethods", context: checkoutSlotContext }),
8460
+ !chargesOnline && /* @__PURE__ */ jsxs("div", { className: "mt-5", children: [
8461
+ /* @__PURE__ */ jsx(
8462
+ "button",
8463
+ {
8464
+ type: "button",
8465
+ "data-testid": TID.placeOrder,
8466
+ disabled: placing,
8467
+ onClick: () => {
8468
+ void confirmWithoutCharge();
8469
+ },
8470
+ className: "min-h-12 w-full rounded-xl bg-primary px-6 py-3 font-semibold text-primary-foreground transition hover:opacity-90 disabled:opacity-60",
8471
+ children: `Fazer pedido - ${money(total)}`
8472
+ }
8473
+ ),
8474
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-center text-xs text-muted-foreground", children: "O pedido fica aguardando pagamento at\xE9 a loja confirmar." })
8475
+ ] })
7770
8476
  ] }),
7771
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
7772
- field("Bairro", "checkout-district", form.district, set("district"), { placeholder: "Bairro" }),
7773
- field("UF", "checkout-state", form.state, set("state"), { placeholder: "UF" })
8477
+ step === 4 && paidOrderId && /* @__PURE__ */ jsxs("section", { "data-testid": TID.checkoutDone, className: "rounded-xl border bg-card p-8 text-center", children: [
8478
+ /* @__PURE__ */ jsx(SuccessMark, {}),
8479
+ /* @__PURE__ */ jsx("h2", { className: "mt-4 text-2xl font-bold tracking-tight", children: "Pagamento confirmado!" }),
8480
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: "Seu pedido foi criado e a loja j\xE1 foi avisada." }),
8481
+ /* @__PURE__ */ jsx(
8482
+ "button",
8483
+ {
8484
+ type: "button",
8485
+ onClick: () => navigateTo(`/order/${paidOrderId}`),
8486
+ className: "mt-6 inline-flex min-h-11 items-center rounded-lg bg-primary px-6 py-3 text-sm font-semibold text-primary-foreground transition hover:opacity-90",
8487
+ children: "Ver meu pedido"
8488
+ }
8489
+ )
7774
8490
  ] }),
7775
- field("Cidade", TID.checkoutCity, form.city, set("city"), { placeholder: "Cidade" })
7776
- ] })
7777
- ] }),
7778
- /* @__PURE__ */ jsxs("section", { children: [
7779
- /* @__PURE__ */ jsx("h2", { className: "mb-3 text-xl font-semibold tracking-tight", children: "Pagamento" }),
7780
- /* @__PURE__ */ jsx("div", { className: "mb-3 grid gap-3", role: "radiogroup", "aria-label": "Forma de pagamento", children: paymentMethods.map((method) => {
7781
- const copy = PAYMENT_LABELS[method];
7782
- const selected = paymentMethod === method;
7783
- return /* @__PURE__ */ jsxs(
7784
- "button",
7785
- {
7786
- type: "button",
7787
- role: "radio",
7788
- "aria-checked": selected,
7789
- "data-testid": `checkout-payment-${method}`,
7790
- onClick: () => setPaymentMethod(method),
7791
- className: `rounded-lg border p-3 text-left text-sm transition hover:bg-muted/40 ${selected ? "border-primary bg-primary/5" : "border-border bg-background"}`,
7792
- children: [
7793
- /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-between gap-3 font-semibold", children: [
7794
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
7795
- /* @__PURE__ */ jsx(CreditCard, { className: "h-4 w-4" }),
7796
- copy.label
7797
- ] }),
7798
- selected && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary", children: "Selecionado" })
7799
- ] }),
7800
- /* @__PURE__ */ jsx("span", { className: "mt-1 block text-muted-foreground", children: copy.hint })
7801
- ]
7802
- },
7803
- method
7804
- );
7805
- }) }),
7806
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: "Nenhuma cobran\xE7a \xE9 feita agora. Seu pedido \xE9 registrado e a loja confirma o pagamento." }),
7807
- /* @__PURE__ */ jsx(Slot, { area: "checkout", name: "afterPaymentMethods", context: checkoutSlotContext })
7808
- ] }),
7809
- error && /* @__PURE__ */ jsx("p", { "data-testid": TID.checkoutError, className: "rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2 text-sm text-destructive", children: error })
7810
- ] }),
8491
+ error && /* @__PURE__ */ jsx("p", { "data-testid": TID.checkoutError, className: "rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2 text-sm text-destructive", children: error }),
8492
+ (step === 1 || step === 2) && /* @__PURE__ */ jsxs("div", { className: "grid gap-3 border-t pt-5", children: [
8493
+ /* @__PURE__ */ jsx(
8494
+ "button",
8495
+ {
8496
+ type: "submit",
8497
+ "data-testid": TID.checkoutContinue,
8498
+ className: "min-h-12 rounded-lg bg-primary px-6 py-4 text-sm font-bold text-primary-foreground transition hover:opacity-90",
8499
+ children: step === 1 ? "Continuar para a entrega" : `Ir para o pagamento - ${money(total)}`
8500
+ }
8501
+ ),
8502
+ step === 2 && /* @__PURE__ */ jsx(
8503
+ "button",
8504
+ {
8505
+ type: "button",
8506
+ "data-testid": TID.checkoutBack,
8507
+ onClick: () => goToStep(1),
8508
+ className: "min-h-11 rounded-lg border px-6 py-3 text-sm font-medium transition hover:bg-muted",
8509
+ children: "Voltar aos dados"
8510
+ }
8511
+ ),
8512
+ /* @__PURE__ */ jsx(Link, { to: config.catalogPath, className: "text-center text-sm text-muted-foreground hover:text-foreground", children: "Voltar ao carrinho" })
8513
+ ] })
8514
+ ]
8515
+ }
8516
+ ),
7811
8517
  /* @__PURE__ */ jsxs("footer", { className: "mt-10 flex flex-wrap gap-4 border-t pt-5 text-xs text-muted-foreground", children: [
7812
8518
  /* @__PURE__ */ jsx(Link, { to: "/privacy", className: "hover:text-foreground", children: "Privacidade" }),
7813
8519
  /* @__PURE__ */ jsx(Link, { to: "/terms", className: "hover:text-foreground", children: "Termos" }),
@@ -7901,21 +8607,7 @@ function CheckoutPage() {
7901
8607
  /* @__PURE__ */ jsx(ShieldCheck, { className: "h-4 w-4 text-primary" }),
7902
8608
  "Compra protegida e dados criptografados."
7903
8609
  ] }),
7904
- /* @__PURE__ */ jsxs("div", { className: "mt-6 grid gap-3 border-t pt-5", children: [
7905
- /* @__PURE__ */ jsx(
7906
- "button",
7907
- {
7908
- type: "button",
7909
- "data-testid": TID.placeOrder,
7910
- onClick: placeOrder,
7911
- disabled: placing,
7912
- className: "rounded-lg bg-primary px-6 py-4 text-sm font-bold text-primary-foreground transition hover:opacity-90 disabled:opacity-60",
7913
- children: primaryCta
7914
- }
7915
- ),
7916
- /* @__PURE__ */ jsx(Link, { to: config.catalogPath, className: "text-center text-sm text-muted-foreground hover:text-foreground", children: "Voltar ao carrinho" }),
7917
- /* @__PURE__ */ jsx(Slot, { area: "checkout", name: "afterForm", context: checkoutSlotContext })
7918
- ] })
8610
+ /* @__PURE__ */ jsx("div", { className: "mt-6 border-t pt-5", children: /* @__PURE__ */ jsx(Slot, { area: "checkout", name: "afterForm", context: checkoutSlotContext }) })
7919
8611
  ] })
7920
8612
  ] })
7921
8613
  ] });
@@ -7980,9 +8672,10 @@ function orderTrackingSteps(order) {
7980
8672
  {
7981
8673
  id: "payment",
7982
8674
  label: paid ? "Pagamento aprovado" : "Aguardando pagamento",
7983
- description: paid ? "Compra confirmada" : "Processando",
8675
+ // "Processando" descrevia um pagamento que ninguém iniciou.
8676
+ description: paid ? "Compra confirmada" : "Ainda n\xE3o recebemos",
7984
8677
  state: paid ? "complete" : "current",
7985
- icon: Check
8678
+ icon: paid ? Check : Clock
7986
8679
  },
7987
8680
  {
7988
8681
  id: "preparing",
@@ -8016,7 +8709,9 @@ function OrderTrackingTimeline({ order, compact: compact2 = false }) {
8016
8709
  children: steps.map((step) => {
8017
8710
  const Icon2 = step.icon;
8018
8711
  const cancelled = step.state === "cancelled";
8019
- const active2 = step.state !== "pending";
8712
+ const done = step.state === "complete";
8713
+ const current = step.state === "current";
8714
+ const active2 = done || current;
8020
8715
  return /* @__PURE__ */ jsx(
8021
8716
  "li",
8022
8717
  {
@@ -8033,15 +8728,15 @@ function OrderTrackingTimeline({ order, compact: compact2 = false }) {
8033
8728
  {
8034
8729
  className: [
8035
8730
  "grid h-7 w-7 shrink-0 place-items-center rounded-full border",
8036
- cancelled ? "border-rose-300 bg-rose-500 text-white" : active2 ? "border-primary bg-primary text-primary-foreground" : "border-muted-foreground/30 bg-background"
8731
+ cancelled ? "border-rose-300 bg-rose-500 text-white" : done ? "border-primary bg-primary text-primary-foreground" : current ? "border-primary bg-background text-primary" : "border-muted-foreground/30 bg-background"
8037
8732
  ].join(" "),
8038
8733
  "aria-hidden": true,
8039
8734
  children: /* @__PURE__ */ jsx(Icon2, { className: "h-3.5 w-3.5" })
8040
8735
  }
8041
8736
  ),
8042
8737
  /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
8043
- /* @__PURE__ */ jsx("span", { className: "block truncate font-semibold text-foreground", children: step.label }),
8044
- /* @__PURE__ */ jsx("span", { className: "mt-0.5 block truncate text-muted-foreground", children: step.description })
8738
+ /* @__PURE__ */ jsx("span", { className: "block font-semibold leading-snug text-foreground", children: step.label }),
8739
+ /* @__PURE__ */ jsx("span", { className: "mt-0.5 block leading-snug text-muted-foreground", children: step.description })
8045
8740
  ] })
8046
8741
  ] })
8047
8742
  },
@@ -8051,6 +8746,32 @@ function OrderTrackingTimeline({ order, compact: compact2 = false }) {
8051
8746
  }
8052
8747
  );
8053
8748
  }
8749
+
8750
+ // src/components/account/section.ts
8751
+ var ACCOUNT_SECTIONS = [
8752
+ "profile",
8753
+ "orders",
8754
+ "addresses",
8755
+ "payments"
8756
+ ];
8757
+ var DEFAULT_ACCOUNT_SECTION = "orders";
8758
+ function isAccountSection(value) {
8759
+ return ACCOUNT_SECTIONS.includes(value);
8760
+ }
8761
+ function parseAccountSection(path) {
8762
+ const query = path.split("?")[1];
8763
+ if (!query) return DEFAULT_ACCOUNT_SECTION;
8764
+ for (const pair of query.split("&")) {
8765
+ const [key, raw = ""] = pair.split("=");
8766
+ if (key !== "section") continue;
8767
+ const value = decodeURIComponent(raw);
8768
+ return isAccountSection(value) ? value : DEFAULT_ACCOUNT_SECTION;
8769
+ }
8770
+ return DEFAULT_ACCOUNT_SECTION;
8771
+ }
8772
+ function accountSectionPath(section) {
8773
+ return `/account?section=${section}`;
8774
+ }
8054
8775
  var menuItems = [
8055
8776
  { id: "profile", label: "Perfil", icon: User },
8056
8777
  { id: "orders", label: "Meus pedidos", icon: Package },
@@ -8084,11 +8805,16 @@ function CustomerAccountShell({ title, subtitle, active: active2, onSelect, chil
8084
8805
  const Icon2 = item.icon;
8085
8806
  const selected = item.id === active2;
8086
8807
  return /* @__PURE__ */ jsxs(
8087
- "button",
8808
+ Link,
8088
8809
  {
8089
- type: "button",
8810
+ to: accountSectionPath(item.id),
8811
+ "data-section": item.id,
8090
8812
  "aria-current": selected ? "page" : void 0,
8091
- onClick: () => onSelect?.(item.id),
8813
+ onClick: (event) => {
8814
+ if (!onSelect) return;
8815
+ event.preventDefault();
8816
+ onSelect(item.id);
8817
+ },
8092
8818
  className: `flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left text-sm font-semibold transition ${selected ? "bg-primary/10 text-primary" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`,
8093
8819
  children: [
8094
8820
  /* @__PURE__ */ jsx(Icon2, { className: "h-4 w-4" }),
@@ -8123,38 +8849,23 @@ function CustomerAccountShell({ title, subtitle, active: active2, onSelect, chil
8123
8849
  ] })
8124
8850
  ] });
8125
8851
  }
8126
- var CONFETTI_COLORS = ["hsl(var(--primary))", "#f59e0b", "#10b981", "#3b82f6", "#ec4899"];
8127
- function SuccessMark() {
8128
- const reduced = prefersReducedMotion();
8852
+ function PendingMark() {
8129
8853
  return /* @__PURE__ */ jsxs("div", { className: "relative mx-auto h-24 w-24", children: [
8130
- !reduced && Array.from({ length: 14 }, (_, i) => /* @__PURE__ */ jsx(
8131
- "span",
8132
- {
8133
- "aria-hidden": true,
8134
- className: "absolute h-2 w-2 animate-confetti-fall rounded-sm",
8135
- style: {
8136
- left: `${i * 7.3 % 100}%`,
8137
- top: "-4px",
8138
- backgroundColor: CONFETTI_COLORS[i % CONFETTI_COLORS.length],
8139
- animationDelay: `${200 + i % 7 * 90}ms`
8854
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-0 rounded-full bg-amber-100" }),
8855
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 48 48", className: "absolute inset-0 h-full w-full p-6", "aria-hidden": true, children: [
8856
+ /* @__PURE__ */ jsx("circle", { cx: "24", cy: "24", r: "17", fill: "none", stroke: "rgb(180 83 9)", strokeWidth: "4" }),
8857
+ /* @__PURE__ */ jsx(
8858
+ "path",
8859
+ {
8860
+ d: "M24 14 L24 25 L31 29",
8861
+ fill: "none",
8862
+ stroke: "rgb(180 83 9)",
8863
+ strokeWidth: "4",
8864
+ strokeLinecap: "round",
8865
+ strokeLinejoin: "round"
8140
8866
  }
8141
- },
8142
- i
8143
- )),
8144
- /* @__PURE__ */ jsx("span", { className: `absolute inset-0 rounded-full bg-emerald-100 ${reduced ? "" : "animate-pop-in"}` }),
8145
- /* @__PURE__ */ jsx("svg", { viewBox: "0 0 48 48", className: "absolute inset-0 h-full w-full p-6", "aria-hidden": true, children: /* @__PURE__ */ jsx(
8146
- "path",
8147
- {
8148
- d: "M10 25 L20 35 L38 14",
8149
- fill: "none",
8150
- stroke: "rgb(5 150 105)",
8151
- strokeWidth: "5",
8152
- strokeLinecap: "round",
8153
- strokeLinejoin: "round",
8154
- strokeDasharray: "48",
8155
- className: reduced ? "" : "animate-draw-check"
8156
- }
8157
- ) })
8867
+ )
8868
+ ] })
8158
8869
  ] });
8159
8870
  }
8160
8871
  function OrderConfirmationPage({ orderId }) {
@@ -8163,7 +8874,7 @@ function OrderConfirmationPage({ orderId }) {
8163
8874
  const [loading, setLoading] = useState(true);
8164
8875
  const money = (v) => formatMoney(v, config.currency, config.locale);
8165
8876
  useStorefrontHead({ title: order ? `Pedido #${order.orderNumber} \u2014 ${config.name}` : config.name });
8166
- useEffect(() => {
8877
+ const reload = React4.useCallback(() => {
8167
8878
  let cancelled = false;
8168
8879
  getShopProvider().getOrder(orderId).then((o) => {
8169
8880
  if (!cancelled) setOrder(o);
@@ -8174,8 +8885,40 @@ function OrderConfirmationPage({ orderId }) {
8174
8885
  cancelled = true;
8175
8886
  };
8176
8887
  }, [orderId]);
8888
+ useEffect(() => reload(), [reload]);
8177
8889
  if (loading) {
8178
- return /* @__PURE__ */ jsx("main", { className: "mx-auto max-w-2xl px-4 py-20 text-center text-muted-foreground", children: "Carregando\u2026" });
8890
+ return /* @__PURE__ */ jsx(CustomerAccountShell, { title: "Detalhe do pedido", active: "orders", children: /* @__PURE__ */ jsxs(SkeletonGroup, { label: "Carregando seu pedido", className: "rounded-xl border bg-card p-6 sm:p-8", children: [
8891
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-6 border-b pb-6 sm:grid-cols-[120px_minmax(0,1fr)]", children: [
8892
+ /* @__PURE__ */ jsx(Skeleton, { className: "mx-auto h-24 w-24 rounded-full" }),
8893
+ /* @__PURE__ */ jsxs("div", { children: [
8894
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-64 max-w-full" }),
8895
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-3.5 w-full max-w-md" }),
8896
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-4 h-6 w-48" })
8897
+ ] })
8898
+ ] }),
8899
+ /* @__PURE__ */ jsx("div", { className: "mt-6 grid gap-4 rounded-xl border bg-muted/20 p-4 sm:grid-cols-3", children: [0, 1, 2].map((column) => /* @__PURE__ */ jsxs("div", { children: [
8900
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-16" }),
8901
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-4 w-28" }),
8902
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-3 w-20" })
8903
+ ] }, column)) }),
8904
+ /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
8905
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-32" }),
8906
+ /* @__PURE__ */ jsx("div", { className: "mt-3 flex items-center gap-2", children: [0, 1, 2, 3].map((node) => /* @__PURE__ */ jsxs(React4.Fragment, { children: [
8907
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-7 shrink-0 rounded-full" }),
8908
+ node < 3 && /* @__PURE__ */ jsx(Skeleton, { className: "h-px flex-1" })
8909
+ ] }, node)) })
8910
+ ] }),
8911
+ /* @__PURE__ */ jsx("div", { className: "mt-8 space-y-3", children: [0, 1].map((row) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
8912
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-12 rounded-lg" }),
8913
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 flex-1" }),
8914
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-16" })
8915
+ ] }, row)) }),
8916
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 space-y-2 border-t pt-4", children: [
8917
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-full max-w-xs" }),
8918
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-full max-w-[14rem]" }),
8919
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-40" })
8920
+ ] })
8921
+ ] }) });
8179
8922
  }
8180
8923
  if (!order) {
8181
8924
  return /* @__PURE__ */ jsxs("main", { className: "mx-auto max-w-2xl px-4 py-20 text-center", children: [
@@ -8183,24 +8926,20 @@ function OrderConfirmationPage({ orderId }) {
8183
8926
  /* @__PURE__ */ jsx(Link, { to: "/", className: "mt-4 inline-block text-primary underline", children: "Voltar \xE0 loja" })
8184
8927
  ] });
8185
8928
  }
8929
+ const paid = isPaid(order.financialStatus);
8186
8930
  return /* @__PURE__ */ jsx(
8187
8931
  CustomerAccountShell,
8188
8932
  {
8189
8933
  title: "Detalhe do pedido",
8190
- subtitle: "Compra confirmada e vinculada \xE0 sua conta.",
8934
+ subtitle: paid ? "Compra confirmada e vinculada \xE0 sua conta." : "Pedido guardado, aguardando o pagamento.",
8191
8935
  active: "orders",
8936
+ onSelect: (section) => navigateTo(`/account?section=${section}`),
8192
8937
  children: /* @__PURE__ */ jsxs("article", { "data-testid": TID.orderDetail, className: "animate-fade-up rounded-xl border bg-card p-6 sm:p-8", children: [
8193
8938
  /* @__PURE__ */ jsxs("div", { className: "grid gap-6 border-b pb-6 sm:grid-cols-[120px_minmax(0,1fr)]", children: [
8194
- /* @__PURE__ */ jsx(SuccessMark, {}),
8939
+ paid ? /* @__PURE__ */ jsx(SuccessMark, {}) : /* @__PURE__ */ jsx(PendingMark, {}),
8195
8940
  /* @__PURE__ */ jsxs("div", { className: "text-center sm:text-left", children: [
8196
- /* @__PURE__ */ jsx("h2", { className: "sf-heading text-3xl font-bold tracking-tight", children: "Pedido confirmado!" }),
8197
- /* @__PURE__ */ jsxs("p", { className: "mt-1 text-muted-foreground", children: [
8198
- "Obrigado pela compra, ",
8199
- order.customerName ?? "cliente",
8200
- ". Enviamos a confirma\xE7\xE3o para ",
8201
- order.customerEmail,
8202
- "."
8203
- ] }),
8941
+ /* @__PURE__ */ jsx("h2", { className: "sf-heading text-3xl font-bold tracking-tight", children: paid ? "Pedido confirmado!" : "Falta o pagamento" }),
8942
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-muted-foreground", children: paid ? `Obrigado pela compra, ${order.customerName ?? "cliente"}. A loja j\xE1 est\xE1 preparando seu pedido.` : `Guardamos seu pedido, ${order.customerName ?? "cliente"}. Ele s\xF3 \xE9 preparado depois que o pagamento entrar.` }),
8204
8943
  /* @__PURE__ */ jsxs("p", { className: "mt-4 text-lg", children: [
8205
8944
  "Pedido ",
8206
8945
  /* @__PURE__ */ jsxs("span", { "data-testid": TID.orderNumber, className: "font-semibold", children: [
@@ -8227,18 +8966,39 @@ function OrderConfirmationPage({ orderId }) {
8227
8966
  ] }),
8228
8967
  /* @__PURE__ */ jsxs("div", { children: [
8229
8968
  /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "Entrega" }),
8230
- /* @__PURE__ */ jsx("p", { className: "mt-1 font-semibold", children: order.fulfillmentStatus === "fulfilled" ? "Enviado" : "Preparando envio" }),
8969
+ /* @__PURE__ */ jsx("p", { className: "mt-1 font-semibold", children: order.fulfillmentStatus === "fulfilled" ? "Enviado" : paid ? "Preparando envio" : "Aguardando pagamento" }),
8231
8970
  /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: order.notes ?? "Endere\xE7o salvo no pedido" })
8232
8971
  ] }),
8233
8972
  /* @__PURE__ */ jsxs("div", { children: [
8234
8973
  /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "Pagamento" }),
8235
8974
  /* @__PURE__ */ jsx("p", { className: "mt-1 font-semibold", children: financialStatusBadge(order.financialStatus).label }),
8236
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "Processado no checkout" })
8975
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: paid ? "Recebido pela loja" : "Ainda n\xE3o recebido" })
8237
8976
  ] })
8238
8977
  ] }),
8239
8978
  /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
8240
8979
  /* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold uppercase text-muted-foreground", children: "Acompanhamento" }),
8241
- /* @__PURE__ */ jsx(OrderTrackingTimeline, { order })
8980
+ /* @__PURE__ */ jsx(OrderTrackingTimeline, { order }),
8981
+ (order.shipments ?? []).filter((s2) => s2.trackingCode).map((shipment) => /* @__PURE__ */ jsxs(
8982
+ "div",
8983
+ {
8984
+ "data-testid": TID.orderTracking + "-code",
8985
+ className: "mt-4 flex flex-wrap items-center gap-x-3 gap-y-1 rounded-lg border bg-muted/30 px-4 py-3 text-sm",
8986
+ children: [
8987
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: shipment.carrier ? `Enviado por ${shipment.carrier}` : "Enviado" }),
8988
+ shipment.trackingUrl ? /* @__PURE__ */ jsx(
8989
+ "a",
8990
+ {
8991
+ href: shipment.trackingUrl,
8992
+ target: "_blank",
8993
+ rel: "noreferrer",
8994
+ className: "font-mono font-medium text-primary hover:underline",
8995
+ children: shipment.trackingCode
8996
+ }
8997
+ ) : /* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: shipment.trackingCode })
8998
+ ]
8999
+ },
9000
+ shipment.id
9001
+ ))
8242
9002
  ] }),
8243
9003
  /* @__PURE__ */ jsx("ul", { className: "mt-8 space-y-3 text-left", children: order.items.map((item) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 text-sm", children: [
8244
9004
  item.imageUrl && /* @__PURE__ */ jsx(SmoothImage, { src: item.imageUrl, alt: item.name, className: "h-12 w-12 rounded-lg border object-cover" }),
@@ -8301,16 +9061,23 @@ function useMyOrders() {
8301
9061
  const refresh = useCallback(() => setTick((t) => t + 1), []);
8302
9062
  useEffect(() => {
8303
9063
  let cancelled = false;
9064
+ const local = listLocalOrderIds();
8304
9065
  if (!customerId && !email) {
8305
- setOrders([]);
8306
- setLoading(false);
8307
- return;
9066
+ if (local.length === 0) {
9067
+ setOrders([]);
9068
+ setLoading(false);
9069
+ return;
9070
+ }
8308
9071
  }
8309
9072
  setLoading(true);
8310
9073
  const query = customerId ? { customerId, limit: 50 } : { customerEmail: email, limit: 50 };
8311
9074
  getShopProvider().listOrders(query).then((data) => {
8312
9075
  if (!cancelled) setOrders(data);
8313
- }).catch(() => {
9076
+ }).catch(async () => {
9077
+ const mine = await Promise.all(
9078
+ listLocalOrderIds().map((id) => getShopProvider().getOrder(id).catch(() => null))
9079
+ );
9080
+ if (!cancelled) setOrders(mine.filter((o) => !!o));
8314
9081
  }).finally(() => {
8315
9082
  if (!cancelled) setLoading(false);
8316
9083
  });
@@ -8432,7 +9199,27 @@ function OrdersPanel() {
8432
9199
  const config = useStorefrontConfig();
8433
9200
  const { orders, loading } = useMyOrders();
8434
9201
  const money = (v) => formatMoney(v, config.currency, config.locale);
8435
- if (loading) return /* @__PURE__ */ jsx("div", { className: "py-16 text-center text-muted-foreground", children: "Carregando\u2026" });
9202
+ if (loading) {
9203
+ return /* @__PURE__ */ jsx(SkeletonGroup, { label: "Carregando seus pedidos", className: "space-y-4", children: [0, 1].map((row) => /* @__PURE__ */ jsxs("div", { className: "rounded-2xl border bg-card p-5", children: [
9204
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
9205
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-32" }),
9206
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-24 rounded-full" })
9207
+ ] }),
9208
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-3 w-20" }),
9209
+ /* @__PURE__ */ jsx("div", { className: "mt-4 flex items-center gap-2", children: [0, 1, 2, 3].map((node) => /* @__PURE__ */ jsxs(React4.Fragment, { children: [
9210
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-6 shrink-0 rounded-full" }),
9211
+ node < 3 && /* @__PURE__ */ jsx(Skeleton, { className: "h-px flex-1" })
9212
+ ] }, node)) }),
9213
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex flex-wrap gap-2", children: [
9214
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-32 rounded-lg" }),
9215
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-24 rounded-lg" })
9216
+ ] }),
9217
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center justify-between border-t pt-3", children: [
9218
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24" }),
9219
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-20" })
9220
+ ] })
9221
+ ] }, row)) });
9222
+ }
8436
9223
  if (orders.length === 0) {
8437
9224
  return /* @__PURE__ */ jsxs("div", { "data-testid": TID.purchasesEmpty, className: "rounded-2xl border bg-card py-16 text-center", children: [
8438
9225
  /* @__PURE__ */ jsx(Package, { className: "mx-auto h-10 w-10 text-muted-foreground" }),
@@ -8479,12 +9266,14 @@ function ProfilePanel() {
8479
9266
  const [name, setName] = useState(session.name ?? "");
8480
9267
  const [saving, setSaving] = useState(false);
8481
9268
  const [saved, setSaved] = useState(false);
9269
+ const [error, setError] = useState(null);
8482
9270
  async function save(e) {
8483
9271
  e.preventDefault();
8484
9272
  const trimmed = name.trim();
8485
9273
  if (!trimmed) return;
8486
9274
  setSaving(true);
8487
9275
  setSaved(false);
9276
+ setError(null);
8488
9277
  try {
8489
9278
  if (session.customerId) {
8490
9279
  const parts = trimmed.split(/\s+/).filter(Boolean);
@@ -8495,6 +9284,8 @@ function ProfilePanel() {
8495
9284
  }
8496
9285
  useSessionStore.setState({ name: trimmed });
8497
9286
  setSaved(true);
9287
+ } catch {
9288
+ setError("N\xE3o foi poss\xEDvel salvar seu nome agora. Tente novamente em instantes.");
8498
9289
  } finally {
8499
9290
  setSaving(false);
8500
9291
  }
@@ -8539,7 +9330,8 @@ function ProfilePanel() {
8539
9330
  }
8540
9331
  ),
8541
9332
  saved && /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-emerald-700", children: "Salvo" })
8542
- ] })
9333
+ ] }),
9334
+ error && /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm text-destructive", children: error })
8543
9335
  ] });
8544
9336
  }
8545
9337
  function AddressesPanel() {
@@ -8574,7 +9366,7 @@ var SECTION_SUBTITLE = {
8574
9366
  function MyPurchasesPage() {
8575
9367
  const config = useStorefrontConfig();
8576
9368
  const session = useSessionStore();
8577
- const [section, setSection] = useState("orders");
9369
+ const section = parseAccountSection(useRoutePath());
8578
9370
  useStorefrontHead({ title: `Minha conta \u2014 ${config.name}` });
8579
9371
  if (!session.email) {
8580
9372
  return /* @__PURE__ */ jsx("main", { className: "mx-auto max-w-3xl px-4 py-12 sm:px-6", children: /* @__PURE__ */ jsx(AuthForm, {}) });
@@ -8585,7 +9377,6 @@ function MyPurchasesPage() {
8585
9377
  title: "Minha conta",
8586
9378
  subtitle: SECTION_SUBTITLE[section],
8587
9379
  active: section,
8588
- onSelect: setSection,
8589
9380
  children: [
8590
9381
  section === "profile" && /* @__PURE__ */ jsx(ProfilePanel, {}),
8591
9382
  section === "orders" && /* @__PURE__ */ jsx(OrdersPanel, {}),
@@ -8595,6 +9386,27 @@ function MyPurchasesPage() {
8595
9386
  }
8596
9387
  );
8597
9388
  }
9389
+ function initShippingItems() {
9390
+ setShopShippingItemsResolver(
9391
+ () => useCartStore.getState().lines.map((line) => ({
9392
+ quantity: line.quantity,
9393
+ weight: line.weight ?? null,
9394
+ weightUnit: line.weightUnit ?? null,
9395
+ height: line.heightCm ?? null,
9396
+ width: line.widthCm ?? null,
9397
+ length: line.lengthCm ?? null,
9398
+ packageId: line.packageId ?? null,
9399
+ // Undefined is a line added before this existed. Treated as physical:
9400
+ // freight wrongly charged is a complaint, freight wrongly skipped is a
9401
+ // parcel the merchant pays for.
9402
+ isPhysical: line.requiresShipping !== false,
9403
+ productId: line.productId,
9404
+ variantId: line.variantId,
9405
+ name: line.name,
9406
+ sku: line.sku
9407
+ }))
9408
+ );
9409
+ }
8598
9410
  function NotFoundPage() {
8599
9411
  const config = useStorefrontConfig();
8600
9412
  useStorefrontHead({ title: `P\xE1gina n\xE3o encontrada \u2014 ${config.name}` });
@@ -8827,6 +9639,7 @@ function initStorefrontRuntime(config) {
8827
9639
  }));
8828
9640
  }
8829
9641
  setShopProvider(resolveStorefrontProvider(config));
9642
+ initShippingItems();
8830
9643
  const reconcileCart = () => {
8831
9644
  const provider = getShopProvider();
8832
9645
  void useCartStore.getState().reconcile(async (line) => {
@@ -8879,7 +9692,7 @@ function createStorefrontApp(config) {
8879
9692
  initStorefrontRuntime(config);
8880
9693
  const router = defaultRouterAdapter(resolved.routing ?? "hash");
8881
9694
  function StorefrontApp() {
8882
- return /* @__PURE__ */ jsx(StorefrontConfigProvider, { value: resolved, children: /* @__PURE__ */ jsx(StorefrontShell, { router }) });
9695
+ return /* @__PURE__ */ jsx(StorefrontConfigHost, { config: resolved, children: /* @__PURE__ */ jsx(StorefrontShell, { router }) });
8883
9696
  }
8884
9697
  StorefrontApp.displayName = "StorefrontApp";
8885
9698
  return StorefrontApp;
@@ -8902,7 +9715,7 @@ function createStorefront(options) {
8902
9715
  // src/document/sections.ts
8903
9716
  var STORE_DOCUMENT_KIND = "fayz.store";
8904
9717
  var STORE_COMMERCE_MODES = ["checkout", "catalog", "enquiry"];
8905
- var STORE_PAYMENT_MODES = ["mock", "pix-mercadopago"];
9718
+ var STORE_PAYMENT_MODES = ["mock", "pix-mercadopago", "gateway"];
8906
9719
  var STORE_PAYMENT_METHOD_KINDS = [
8907
9720
  "pix",
8908
9721
  "credit_card",
@@ -10227,6 +11040,6 @@ ${formatErrors(report.errors)}`;
10227
11040
  return config;
10228
11041
  }
10229
11042
 
10230
- export { BEFORE_PLACE_ORDER_VALVE, BLOCK_STRUCTURAL_KEYS, BenefitsRow, BlockDataError, CART_LINE_ANNOTATION_POINT, CATALOG_BODY_BLOCK, CHECKOUT_FIELD_POINT, CartDrawer, CatalogPage, CategoryShowcase, CheckoutPage, CollectionCta, CollectionHero, ContentPage, CountdownBand, EmailConfirmationRequiredError, EntityListBlock, FaqSection, FiltersPanel, FormBlock, HeroSection, ImageTiles, Link, ManifestoBlock, MediaCarousel, MotifStrip, MyPurchasesPage, NewsletterBand, ORDER_METADATA_POINT, OrderConfirmationPage, OrderTrackingTimeline, PAGE_DATA_SCRIPT_ID, PLACEHOLDER_ASPECTS, PRODUCT_BODY_BLOCK, PRODUCT_CARD_BADGE_POINT, PageDataScope, PaymentTerms, Price, ProductCard, ProductDetailPage, ProductEnquiryForm, ProductGallery, ProductGrid, ProductOptionSelector, ProductRail, ProductReviews, ProductSlider, ProductSpecs, ProductSpotlight, PromoBanner, QuantityInput, Reveal, STOREFRONT_COMPONENT_KEYS, STOREFRONT_PAGE_KINDS, STOREFRONT_SLOTS, STORE_BACKEND_PROVIDERS, STORE_COMMERCE_MODES, STORE_DOCUMENT_KIND, STORE_PAYMENT_METHOD_KINDS, STORE_PAYMENT_MODES, STORE_ROUTE_CHROMES, STORE_ROUTE_KINDS, STORE_SECTIONS, STORE_SECTION_NAMES, SealsBand, SearchOverlay, Slot, SmoothImage, StepsSection, StorefrontConfigProvider, StorefrontFooter, StorefrontHeader, StorefrontPage, StorefrontRouterProvider, StorefrontShell, StorefrontThemeStyle, StoryQuote, THEME_SCALE_FIELDS, THEME_SCALE_VAR_PREFIX, TID, Testimonials, TierCards, bannerPlaceholder, blockInstanceId, buildEnquiryFromForm, checkPageBlocks, collectDataNeeds, collectOrderMetadata, contentPagePath, createStorefront, createStorefrontApp, currentStaticRenderPass, defaultPageBlocks, entityListBlockMeta, establishCustomerSession, exportStore, formatMoney, formatProductOptionSelection, getCartLineAnnotations, getCheckoutFieldContributions, getCustomerAuthAdapter, getProductCardBadges, getProductOptionGroups, hydrateStoreDocument, hydratedEntriesFor, initStorefrontRuntime, isJsonValue, isKnownPath, isNestedBlockRef, isPublicPath, isRecord, listSlots, matchContentPage, matchPath, matchesFacets, mergeCollectionFacet, mergePageTheme, navigateTo, nextMilestone, normalizeProductOptionSelection, orderGalleryImages, overrideComponent, overrideProps, pageSeo, pageThemeSelector, pickProductCardImage, placeStorefrontOrder, prefersReducedMotion, primaryImageUrl, productCardComponentContract, productJsonLd, productOptionSelectionKey, productPlaceholder, productSeo, readHydratedPageData, registerStorefrontBlocks, registerStorefrontEntities, registerStorefrontExtensionPoints, registerStorefrontLoaders, resetPageDiagnostics, resetProductOptionsDeprecationWarnings, resetStorefrontExtensionPoints, resolveAuthAdapter, resolveConfig, resolveDataParams, resolveOverride, resolvePageData, resolvePageDataNeed, resolvePaymentTerms, resolveStorefrontProvider, resolveStorefrontRoute, resolveVariantForSelection, roundCents, routePathParams, runBeforePlaceOrderValve, sectionToJsonSchema, sectionsToBlocks, selectCount, selectDiscountTotal, selectShipping, selectSubtotal, selectTotal, selectionPrice, serializePageData, shellRouterAdapter, signInByEmail, signOutCustomer, signUpCustomer, slugifyStoreName, storefrontComponentContracts, storefrontPaths, themeToCss, toBlockNodes, toFacetMap, useBlockData, useCartStore, useCatalogStore, useCategories, useDeliveryStore, useDiscountValidator, useEnquiry, useHashPath, useInView, useLoader, useMyOrders, useNavigate, usePopOnChange, useProduct, useProducts, useRoutePath, useRouterAdapter, useScrollToTopOnNavigate, useScrolled, useSessionStore, useSlotContext, useStorefrontActions, useStorefrontConfig, useStorefrontConfigOptional, useStorefrontHead, useStorefrontPage, useStorefrontPageOptional, usedSlots, validateStore, withFacet, withStaticRenderPass };
10231
- //# sourceMappingURL=chunk-6B7H3IZ6.js.map
10232
- //# sourceMappingURL=chunk-6B7H3IZ6.js.map
11043
+ export { BEFORE_PLACE_ORDER_VALVE, BLOCK_STRUCTURAL_KEYS, BenefitsRow, BlockDataError, CART_LINE_ANNOTATION_POINT, CATALOG_BODY_BLOCK, CHECKOUT_FIELD_POINT, CartDrawer, CatalogPage, CategoryShowcase, CheckoutPage, CollectionCta, CollectionHero, ContentPage, CountdownBand, EmailConfirmationRequiredError, EntityListBlock, FaqSection, FiltersPanel, FormBlock, HeroSection, ImageTiles, Link, ManifestoBlock, MediaCarousel, MotifStrip, MyPurchasesPage, NewsletterBand, ORDER_METADATA_POINT, OrderConfirmationPage, OrderTrackingTimeline, PAGE_DATA_SCRIPT_ID, PLACEHOLDER_ASPECTS, PRODUCT_BODY_BLOCK, PRODUCT_CARD_BADGE_POINT, PageDataScope, PaymentTerms, Price, ProductCard, ProductDetailPage, ProductEnquiryForm, ProductGallery, ProductGrid, ProductOptionSelector, ProductRail, ProductReviews, ProductSlider, ProductSpecs, ProductSpotlight, PromoBanner, QuantityInput, Reveal, STOREFRONT_COMPONENT_KEYS, STOREFRONT_PAGE_KINDS, STOREFRONT_SLOTS, STORE_BACKEND_PROVIDERS, STORE_COMMERCE_MODES, STORE_DOCUMENT_KIND, STORE_PAYMENT_METHOD_KINDS, STORE_PAYMENT_MODES, STORE_ROUTE_CHROMES, STORE_ROUTE_KINDS, STORE_SECTIONS, STORE_SECTION_NAMES, SealsBand, SearchOverlay, Slot, SmoothImage, StepsSection, StorefrontConfigHost, StorefrontConfigProvider, StorefrontFooter, StorefrontHeader, StorefrontPage, StorefrontRouterProvider, StorefrontShell, StorefrontThemeStyle, StoryQuote, THEME_SCALE_FIELDS, THEME_SCALE_VAR_PREFIX, TID, Testimonials, TierCards, applyStoreSettings, bannerPlaceholder, blockInstanceId, buildEnquiryFromForm, checkPageBlocks, collectDataNeeds, collectOrderMetadata, contentPagePath, createStorefront, createStorefrontApp, currentStaticRenderPass, defaultPageBlocks, entityListBlockMeta, establishCustomerSession, exportStore, formatMoney, formatProductOptionSelection, getCartLineAnnotations, getCheckoutFieldContributions, getCustomerAuthAdapter, getProductCardBadges, getProductOptionGroups, hydrateStoreDocument, hydratedEntriesFor, initStorefrontRuntime, isJsonValue, isKnownPath, isNestedBlockRef, isPublicPath, isRecord, listSlots, matchContentPage, matchPath, matchesFacets, mergeCollectionFacet, mergePageTheme, navigateTo, nextMilestone, normalizeProductOptionSelection, orderGalleryImages, overrideComponent, overrideProps, pageSeo, pageThemeSelector, pickProductCardImage, placeStorefrontOrder, prefersReducedMotion, primaryImageUrl, productCardComponentContract, productJsonLd, productOptionSelectionKey, productPlaceholder, productSeo, readHydratedPageData, registerStorefrontBlocks, registerStorefrontEntities, registerStorefrontExtensionPoints, registerStorefrontLoaders, resetPageDiagnostics, resetProductOptionsDeprecationWarnings, resetStorefrontExtensionPoints, resolveAuthAdapter, resolveConfig, resolveDataParams, resolveOverride, resolvePageData, resolvePageDataNeed, resolvePaymentTerms, resolveStorefrontProvider, resolveStorefrontRoute, resolveVariantForSelection, roundCents, routePathParams, runBeforePlaceOrderValve, sectionToJsonSchema, sectionsToBlocks, selectCount, selectDiscountTotal, selectRequiresShipping, selectShipping, selectSubtotal, selectTotal, selectionPrice, serializePageData, shellRouterAdapter, signInByEmail, signOutCustomer, signUpCustomer, slugifyStoreName, storefrontComponentContracts, storefrontPaths, themeToCss, toBlockNodes, toFacetMap, useBlockData, useCartStore, useCatalogStore, useCategories, useDeliveryStore, useDiscountValidator, useEnquiry, useHashPath, useInView, useLoader, useMyOrders, useNavigate, usePopOnChange, useProduct, useProducts, useRoutePath, useRouterAdapter, useScrollToTopOnNavigate, useScrolled, useSessionStore, useSlotContext, useStorefrontActions, useStorefrontConfig, useStorefrontConfigOptional, useStorefrontHead, useStorefrontPage, useStorefrontPageOptional, usedSlots, validateStore, withFacet, withStaticRenderPass };
11044
+ //# sourceMappingURL=chunk-SDQHSUFC.js.map
11045
+ //# sourceMappingURL=chunk-SDQHSUFC.js.map