@fayz-ai/storefront 0.10.0 → 0.11.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 (55) 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-SX3J3QIT.js} +1399 -585
  5. package/dist/chunk-SX3J3QIT.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/testids.d.ts +12 -0
  44. package/dist/lib/testids.d.ts.map +1 -1
  45. package/dist/render.js +1 -1
  46. package/dist/stores/cart.store.d.ts +28 -0
  47. package/dist/stores/cart.store.d.ts.map +1 -1
  48. package/dist/stores/delivery.store.d.ts +9 -0
  49. package/dist/stores/delivery.store.d.ts.map +1 -1
  50. package/dist/views/CheckoutPage.d.ts.map +1 -1
  51. package/dist/views/MyPurchasesPage.d.ts.map +1 -1
  52. package/dist/views/OrderConfirmationPage.d.ts.map +1 -1
  53. package/dist/workflows/checkout.d.ts.map +1 -1
  54. package/package.json +7 -7
  55. 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, 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
  }
@@ -6906,6 +6973,7 @@ function CartDrawer() {
6906
6973
  const freeAbove = config.shipping.freeAbove;
6907
6974
  const missingForFree = freeAbove != null && subtotal < freeAbove ? freeAbove - subtotal : 0;
6908
6975
  const progress = freeAbove != null ? Math.min(100, subtotal / freeAbove * 100) : 0;
6976
+ const wide = cart.lines.length >= 3;
6909
6977
  return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50", children: [
6910
6978
  /* @__PURE__ */ jsx(
6911
6979
  "div",
@@ -6921,11 +6989,14 @@ function CartDrawer() {
6921
6989
  "data-testid": TID.cartDrawer,
6922
6990
  role: "dialog",
6923
6991
  "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]" : ""}`,
6992
+ 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 it.
6993
+ // At one or two lines the left column was a 700px void with a card
6994
+ // floating at the top the panel read as broken, not as spacious.
6995
+ // Below the threshold the drawer stays the narrow single column every
6996
+ // store uses, which is also the shape a cart of one item deserves.
6997
+ wide ? "lg:max-w-3xl lg:grid lg:grid-cols-[55%_45%] lg:grid-rows-[auto_auto_1fr]" : ""}`,
6927
6998
  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: [
6999
+ /* @__PURE__ */ jsxs("div", { className: `flex items-center justify-between border-b px-5 py-4 ${wide ? "lg:col-start-2 lg:row-start-1" : ""}`, children: [
6929
7000
  /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: "Carrinho" }),
6930
7001
  /* @__PURE__ */ jsx(
6931
7002
  "button",
@@ -6933,13 +7004,13 @@ function CartDrawer() {
6933
7004
  type: "button",
6934
7005
  "aria-label": "Fechar carrinho",
6935
7006
  onClick: cart.closeDrawer,
6936
- className: "rounded-full p-1.5 hover:bg-muted",
7007
+ 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
7008
  children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
6938
7009
  }
6939
7010
  )
6940
7011
  ] }),
6941
7012
  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: [
7013
+ freeAbove != null && /* @__PURE__ */ jsxs("div", { "data-testid": TID.shippingProgress, className: `border-b bg-muted/40 px-5 py-3 ${wide ? "lg:col-start-2 lg:row-start-2" : ""}`, children: [
6943
7014
  /* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: missingForFree > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
6944
7015
  "Faltam ",
6945
7016
  /* @__PURE__ */ jsx("strong", { children: money(missingForFree) }),
@@ -6961,7 +7032,7 @@ function CartDrawer() {
6961
7032
  "div",
6962
7033
  {
6963
7034
  "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",
7035
+ className: `flex-1 space-y-4 overflow-y-auto py-4 pl-5 pr-6 ${wide ? "lg:col-start-1 lg:row-start-1 lg:row-span-3 lg:border-r" : ""}`,
6965
7036
  style: { scrollbarGutter: "stable" },
6966
7037
  children: [
6967
7038
  /* @__PURE__ */ jsx(Slot, { area: "cart", name: "beforeLines", context: cartSlotContext }),
@@ -6984,7 +7055,7 @@ function CartDrawer() {
6984
7055
  "data-testid": TID.lineRemove,
6985
7056
  "aria-label": `Remover ${line.name}`,
6986
7057
  onClick: () => cart.removeItem(line.lineId ?? line.productId),
6987
- className: "-m-1.5 p-1.5 text-muted-foreground hover:text-destructive",
7058
+ 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
7059
  children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
6989
7060
  }
6990
7061
  )
@@ -7025,7 +7096,7 @@ function CartDrawer() {
7025
7096
  "div",
7026
7097
  {
7027
7098
  "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",
7099
+ className: `space-y-3 border-t px-5 py-4 ${wide ? "lg:col-start-2 lg:row-start-3 lg:overflow-y-auto" : ""}`,
7029
7100
  children: [
7030
7101
  config.features.discounts && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
7031
7102
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
@@ -7055,7 +7126,7 @@ function CartDrawer() {
7055
7126
  discountError && /* @__PURE__ */ jsx("p", { "data-testid": TID.discountError, className: "text-xs text-destructive", children: discountError })
7056
7127
  ] }),
7057
7128
  /* @__PURE__ */ jsx(Slot, { area: "cart", name: "beforeSummary", context: cartSlotContext }),
7058
- /* @__PURE__ */ jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsx(DeliveryEstimator, { compact: true }) }),
7129
+ selectRequiresShipping(cart) && /* @__PURE__ */ jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsx(DeliveryEstimator, { compact: true }) }),
7059
7130
  /* @__PURE__ */ jsxs("dl", { className: "space-y-1.5 text-sm", children: [
7060
7131
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
7061
7132
  /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: "Subtotal" }),
@@ -7197,6 +7268,41 @@ async function storefrontPaths(input) {
7197
7268
  const unique = Array.from(new Set(paths));
7198
7269
  return input.includePrivate ? unique : unique.filter((p) => isPublicPath(resolved, p));
7199
7270
  }
7271
+
7272
+ // src/data/local-orders.ts
7273
+ var KEY = "fayz.storefront.orders.v1";
7274
+ var MAX = 25;
7275
+ function read() {
7276
+ try {
7277
+ const raw = globalThis.localStorage?.getItem(KEY);
7278
+ const parsed = raw ? JSON.parse(raw) : [];
7279
+ return Array.isArray(parsed) ? parsed.filter((id) => typeof id === "string") : [];
7280
+ } catch {
7281
+ return [];
7282
+ }
7283
+ }
7284
+ function rememberLocalOrder(orderId) {
7285
+ if (!orderId) return;
7286
+ try {
7287
+ const next = [orderId, ...read().filter((id) => id !== orderId)].slice(0, MAX);
7288
+ globalThis.localStorage?.setItem(KEY, JSON.stringify(next));
7289
+ } catch {
7290
+ }
7291
+ }
7292
+ function listLocalOrderIds() {
7293
+ return read();
7294
+ }
7295
+
7296
+ // src/lib/errors.ts
7297
+ function errorMessage(err) {
7298
+ if (err instanceof Error) return err.message;
7299
+ if (err && typeof err === "object" && typeof err.message === "string") {
7300
+ return err.message;
7301
+ }
7302
+ return "";
7303
+ }
7304
+
7305
+ // src/workflows/checkout.ts
7200
7306
  function formatDeliveryNotes(address, shippingAddress) {
7201
7307
  if (address?.notes?.trim()) return address.notes.trim();
7202
7308
  if (shippingAddress) return void 0;
@@ -7204,6 +7310,10 @@ function formatDeliveryNotes(address, shippingAddress) {
7204
7310
  const parts = [address.street, address.city, address.zip].map((part) => part?.trim()).filter(Boolean);
7205
7311
  return parts.length > 0 ? `Entrega: ${parts.join(", ")}` : void 0;
7206
7312
  }
7313
+ function isStaleCustomerError(err) {
7314
+ const message = errorMessage(err).toLowerCase();
7315
+ return message.includes("customer email mismatch") || message.includes("customer does not belong to this store") || message.includes("customer belongs to another account");
7316
+ }
7207
7317
  async function placeStorefrontOrder({
7208
7318
  config,
7209
7319
  cart,
@@ -7218,7 +7328,7 @@ async function placeStorefrontOrder({
7218
7328
  const name = customer.name.trim();
7219
7329
  const provider = getShopProvider();
7220
7330
  let customerId = session?.customerId ?? null;
7221
- if (!customerId || session?.email !== email) {
7331
+ if (!customerId || session?.email?.trim().toLowerCase() !== email) {
7222
7332
  const established = await establishCustomerSession(email, { name });
7223
7333
  customerId = established.customerId;
7224
7334
  }
@@ -7251,8 +7361,8 @@ async function placeStorefrontOrder({
7251
7361
  subtotal: cart.lines.reduce((sum, line) => sum + line.unitPrice * line.quantity, 0),
7252
7362
  customerEmail: email
7253
7363
  });
7254
- const order = await provider.placeOrder({
7255
- customerId: customerId ?? void 0,
7364
+ const place = (withCustomerId) => provider.placeOrder({
7365
+ customerId: withCustomerId,
7256
7366
  customer: { name, email },
7257
7367
  currency: config.currency,
7258
7368
  notes,
@@ -7266,10 +7376,19 @@ async function placeStorefrontOrder({
7266
7376
  metadata,
7267
7377
  items
7268
7378
  });
7379
+ let order;
7380
+ try {
7381
+ order = await place(customerId ?? void 0);
7382
+ } catch (err) {
7383
+ if (!customerId || !isStaleCustomerError(err)) throw err;
7384
+ customerId = null;
7385
+ order = await place(void 0);
7386
+ }
7269
7387
  if (markPaid) {
7270
7388
  if (provider.confirmPayment) await provider.confirmPayment(order.id);
7271
7389
  else await provider.updateOrder(order.id, { financialStatus: "paid" });
7272
7390
  }
7391
+ rememberLocalOrder(order.id);
7273
7392
  return { order, customerId: customerId ?? order.customerId ?? "" };
7274
7393
  }
7275
7394
  function SignInModal({ defaultEmail = "", onClose, onSignedIn }) {
@@ -7382,40 +7501,364 @@ function SignInModal({ defaultEmail = "", onClose, onSignedIn }) {
7382
7501
  }
7383
7502
  );
7384
7503
  }
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" }
7504
+ var LABELS2 = {
7505
+ 1: "Dados",
7506
+ 2: "Entrega",
7507
+ 3: "Pagamento",
7508
+ 4: "Pronto"
7392
7509
  };
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;
7510
+ function CheckoutSteps({ current, onGoBackTo }) {
7511
+ const steps = [1, 2, 3, 4];
7512
+ return /* @__PURE__ */ jsxs("nav", { "data-testid": TID.checkoutSteps, "aria-label": "Etapas do checkout", className: "mb-8", children: [
7513
+ /* @__PURE__ */ jsx("ol", { className: "flex items-center gap-1.5 sm:gap-2", children: steps.map((step, index) => {
7514
+ const done = step < current;
7515
+ const active2 = step === current;
7516
+ const canGoBack = done && !!onGoBackTo;
7517
+ const Tag = canGoBack ? "button" : "div";
7518
+ return /* @__PURE__ */ jsxs("li", { className: "flex min-w-0 flex-1 items-center gap-1.5 sm:gap-2", children: [
7519
+ /* @__PURE__ */ jsxs(
7520
+ Tag,
7521
+ {
7522
+ ...canGoBack ? { type: "button", onClick: () => onGoBackTo(step) } : {},
7523
+ "data-testid": TID.checkoutStep(step),
7524
+ "data-state": done ? "done" : active2 ? "current" : "pending",
7525
+ "aria-current": active2 ? "step" : void 0,
7526
+ className: [
7527
+ "flex min-w-0 items-center gap-2 rounded-lg px-2 py-1.5 text-left transition",
7528
+ canGoBack ? "hover:bg-muted" : "",
7529
+ active2 ? "text-foreground" : done ? "text-foreground" : "text-muted-foreground"
7530
+ ].join(" "),
7531
+ children: [
7532
+ /* @__PURE__ */ jsx(
7533
+ "span",
7534
+ {
7535
+ className: [
7536
+ "grid h-7 w-7 shrink-0 place-items-center rounded-full border text-xs font-semibold",
7537
+ done ? "border-primary bg-primary text-primary-foreground" : active2 ? "border-primary bg-background text-primary" : "border-muted-foreground/30 bg-background"
7538
+ ].join(" "),
7539
+ children: done ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : step
7540
+ }
7541
+ ),
7542
+ /* @__PURE__ */ jsx("span", { className: `hidden truncate text-sm sm:inline ${active2 ? "font-semibold" : ""}`, children: LABELS2[step] })
7543
+ ]
7544
+ }
7545
+ ),
7546
+ index < steps.length - 1 && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: `h-px flex-1 ${done ? "bg-primary" : "bg-border"}` })
7547
+ ] }, step);
7548
+ }) }),
7549
+ /* @__PURE__ */ jsxs("p", { className: "mt-2 text-sm font-semibold sm:hidden", children: [
7550
+ current,
7551
+ ". ",
7552
+ LABELS2[current]
7553
+ ] })
7554
+ ] });
7555
+ }
7556
+ function Skeleton({ className = "", style }) {
7557
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: `block rounded bg-muted ${className}`, style });
7558
+ }
7559
+ function SkeletonGroup({ label, className = "", children, ...rest }) {
7401
7560
  return /* @__PURE__ */ jsxs(
7402
7561
  "div",
7403
7562
  {
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
7563
  role: "status",
7407
- "aria-live": "polite",
7564
+ "aria-busy": "true",
7565
+ className: `animate-pulse motion-reduce:animate-none ${className}`,
7566
+ ...rest,
7408
7567
  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" })
7568
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: label }),
7569
+ children
7415
7570
  ]
7416
7571
  }
7417
7572
  );
7418
7573
  }
7574
+ function PulseDot({ className = "" }) {
7575
+ return /* @__PURE__ */ jsxs("span", { "aria-hidden": true, className: `relative flex h-2.5 w-2.5 shrink-0 ${className}`, children: [
7576
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-0 animate-ping rounded-full bg-current opacity-60 motion-reduce:animate-none" }),
7577
+ /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2.5 w-2.5 rounded-full bg-current" })
7578
+ ] });
7579
+ }
7580
+ var POLL_MS = 3e3;
7581
+ function CheckoutPayment({ input, onPaid, onBack, money, onUnavailable }) {
7582
+ const [phase, setPhase] = useState("opening");
7583
+ const [charge, setCharge] = useState(null);
7584
+ const [total, setTotal] = useState(null);
7585
+ const [sessionId, setSessionId] = useState(null);
7586
+ const [error, setError] = useState(null);
7587
+ const [copied, setCopied] = useState(false);
7588
+ const [checking, setChecking] = useState(false);
7589
+ const settled = useRef(false);
7590
+ const opened = useRef(false);
7591
+ const open = useCallback(async () => {
7592
+ const provider = getShopProvider();
7593
+ if (!provider.openCheckoutSession) {
7594
+ setError("Esta loja ainda n\xE3o recebe pagamento online.");
7595
+ setPhase("error");
7596
+ return;
7597
+ }
7598
+ setPhase("opening");
7599
+ setError(null);
7600
+ try {
7601
+ const session = await provider.openCheckoutSession(input);
7602
+ setSessionId(session.sessionId);
7603
+ setTotal(session.total);
7604
+ setCharge(session.charge);
7605
+ setPhase(session.charge ? "waiting" : "error");
7606
+ if (!session.charge) setError("N\xE3o conseguimos abrir a cobran\xE7a agora.");
7607
+ } catch (err) {
7608
+ if (err?.status === 404) {
7609
+ onUnavailable?.();
7610
+ return;
7611
+ }
7612
+ setError(err instanceof Error ? err.message : "N\xE3o conseguimos abrir a cobran\xE7a agora.");
7613
+ setPhase("error");
7614
+ }
7615
+ }, [input, onUnavailable]);
7616
+ useEffect(() => {
7617
+ if (opened.current) return;
7618
+ opened.current = true;
7619
+ void open();
7620
+ }, [open]);
7621
+ const check = useCallback(async () => {
7622
+ if (!sessionId || settled.current) return false;
7623
+ const provider = getShopProvider();
7624
+ if (!provider.settleCheckoutSession) return false;
7625
+ try {
7626
+ const result = await provider.settleCheckoutSession(sessionId);
7627
+ if (settled.current) return false;
7628
+ if (result.state === "paid" && result.orderId) {
7629
+ settled.current = true;
7630
+ setPhase("settling");
7631
+ onPaid(result.orderId);
7632
+ return true;
7633
+ }
7634
+ if (result.state === "failed" || result.state === "expired") {
7635
+ setError(
7636
+ result.reason ?? (result.state === "expired" ? "O c\xF3digo expirou. Gere um novo para continuar." : "O pagamento n\xE3o foi conclu\xEDdo.")
7637
+ );
7638
+ setPhase("error");
7639
+ }
7640
+ return false;
7641
+ } catch {
7642
+ return false;
7643
+ }
7644
+ }, [sessionId, onPaid]);
7645
+ useEffect(() => {
7646
+ if (phase !== "waiting" || !sessionId) return;
7647
+ let stopped = false;
7648
+ const timer = setInterval(() => {
7649
+ if (!stopped) void check();
7650
+ }, POLL_MS);
7651
+ return () => {
7652
+ stopped = true;
7653
+ clearInterval(timer);
7654
+ };
7655
+ }, [phase, sessionId, check]);
7656
+ async function confirmPaid() {
7657
+ setChecking(true);
7658
+ const paid = await check();
7659
+ if (!paid) setChecking(false);
7660
+ }
7661
+ async function copy() {
7662
+ if (!charge?.emv) return;
7663
+ try {
7664
+ await navigator.clipboard.writeText(charge.emv);
7665
+ setCopied(true);
7666
+ setTimeout(() => setCopied(false), 2400);
7667
+ } catch {
7668
+ toast.error("Copie o c\xF3digo manualmente");
7669
+ }
7670
+ }
7671
+ if (phase === "opening") {
7672
+ return (
7673
+ // Same layout as the charge below, so nothing jumps when it arrives.
7674
+ /* @__PURE__ */ jsxs(
7675
+ SkeletonGroup,
7676
+ {
7677
+ label: "Preparando seu pagamento",
7678
+ "data-testid": TID.checkoutPayment,
7679
+ className: "rounded-xl border bg-card p-6",
7680
+ children: [
7681
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7682
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-5 rounded-md" }),
7683
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-64 max-w-[70%]" })
7684
+ ] }),
7685
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-3.5 w-full max-w-sm" }),
7686
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 grid gap-5 sm:grid-cols-[auto_minmax(0,1fr)] sm:items-start", children: [
7687
+ /* @__PURE__ */ jsx(Skeleton, { className: "mx-auto h-44 w-44 rounded-lg" }),
7688
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
7689
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-32" }),
7690
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-4 h-3 w-28" }),
7691
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-1 h-16 w-full rounded-lg" }),
7692
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-3", children: [
7693
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-11 w-40 rounded-lg" }),
7694
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-11 w-32 rounded-lg" })
7695
+ ] })
7696
+ ] })
7697
+ ] })
7698
+ ]
7699
+ }
7700
+ )
7701
+ );
7702
+ }
7703
+ if (phase === "error") {
7704
+ return /* @__PURE__ */ jsxs("div", { "data-testid": TID.checkoutPayment, className: "space-y-3 rounded-xl border bg-card p-6", children: [
7705
+ /* @__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." }),
7706
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
7707
+ /* @__PURE__ */ jsx(
7708
+ "button",
7709
+ {
7710
+ type: "button",
7711
+ onClick: () => {
7712
+ opened.current = false;
7713
+ void open();
7714
+ },
7715
+ className: "rounded-lg bg-primary px-4 py-2.5 text-sm font-semibold text-primary-foreground transition hover:opacity-90",
7716
+ children: "Tentar de novo"
7717
+ }
7718
+ ),
7719
+ /* @__PURE__ */ jsx(
7720
+ "button",
7721
+ {
7722
+ type: "button",
7723
+ onClick: onBack,
7724
+ className: "rounded-lg border px-4 py-2.5 text-sm font-medium transition hover:bg-muted",
7725
+ children: "Voltar"
7726
+ }
7727
+ )
7728
+ ] })
7729
+ ] });
7730
+ }
7731
+ return /* @__PURE__ */ jsxs("div", { "data-testid": TID.checkoutPayment, className: "space-y-4", "aria-live": "polite", children: [
7732
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-card p-6", children: [
7733
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7734
+ /* @__PURE__ */ jsx(QrCode, { className: "h-5 w-5 text-primary" }),
7735
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: "Pague com Pix para fechar o pedido" })
7736
+ ] }),
7737
+ /* @__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." }),
7738
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 grid gap-5 sm:grid-cols-[auto_minmax(0,1fr)] sm:items-start", children: [
7739
+ charge?.qrcodeBase64 && /* @__PURE__ */ jsx(
7740
+ "img",
7741
+ {
7742
+ src: `data:image/png;base64,${charge.qrcodeBase64}`,
7743
+ alt: "QR code do Pix",
7744
+ className: "mx-auto h-44 w-44 rounded-lg border bg-white p-2"
7745
+ }
7746
+ ),
7747
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
7748
+ total != null && /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
7749
+ "Valor: ",
7750
+ /* @__PURE__ */ jsx("strong", { "data-testid": TID.checkoutPaymentTotal, children: money(total) })
7751
+ ] }),
7752
+ /* @__PURE__ */ jsx("p", { className: "mt-3 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Pix copia e cola" }),
7753
+ /* @__PURE__ */ jsx(
7754
+ "p",
7755
+ {
7756
+ "data-testid": TID.pixCode,
7757
+ className: "mt-1 max-h-24 overflow-y-auto break-all rounded-lg border bg-muted/40 p-3 font-mono text-xs",
7758
+ children: charge?.emv
7759
+ }
7760
+ ),
7761
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-3", children: [
7762
+ /* @__PURE__ */ jsxs(
7763
+ "button",
7764
+ {
7765
+ type: "button",
7766
+ onClick: copy,
7767
+ "data-testid": TID.pixCopy,
7768
+ 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",
7769
+ children: [
7770
+ copied ? /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Copy, { className: "h-4 w-4" }),
7771
+ copied ? "C\xF3digo copiado" : "Copiar c\xF3digo"
7772
+ ]
7773
+ }
7774
+ ),
7775
+ /* @__PURE__ */ jsxs(
7776
+ "button",
7777
+ {
7778
+ type: "button",
7779
+ onClick: () => {
7780
+ void confirmPaid();
7781
+ },
7782
+ disabled: checking || phase === "settling",
7783
+ "data-testid": TID.pixConfirm,
7784
+ 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",
7785
+ children: [
7786
+ checking || phase === "settling" ? /* @__PURE__ */ jsx(PulseDot, {}) : /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }),
7787
+ phase === "settling" ? "Confirmando\u2026" : checking ? "Verificando\u2026" : "J\xE1 paguei"
7788
+ ]
7789
+ }
7790
+ )
7791
+ ] })
7792
+ ] })
7793
+ ] })
7794
+ ] }),
7795
+ /* @__PURE__ */ jsx(
7796
+ "button",
7797
+ {
7798
+ type: "button",
7799
+ onClick: onBack,
7800
+ className: "text-sm text-muted-foreground underline hover:text-foreground",
7801
+ children: "Voltar e revisar o pedido"
7802
+ }
7803
+ )
7804
+ ] });
7805
+ }
7806
+ var CONFETTI_COLORS = ["hsl(var(--primary))", "#f59e0b", "#10b981", "#3b82f6", "#ec4899"];
7807
+ function SuccessMark() {
7808
+ const reduced = prefersReducedMotion();
7809
+ return /* @__PURE__ */ jsxs("div", { className: "relative mx-auto h-24 w-24", children: [
7810
+ !reduced && Array.from({ length: 14 }, (_, i) => /* @__PURE__ */ jsx(
7811
+ "span",
7812
+ {
7813
+ "aria-hidden": true,
7814
+ className: "absolute h-2 w-2 animate-confetti-fall rounded-sm",
7815
+ style: {
7816
+ left: `${i * 7.3 % 100}%`,
7817
+ top: "-4px",
7818
+ backgroundColor: CONFETTI_COLORS[i % CONFETTI_COLORS.length],
7819
+ animationDelay: `${200 + i % 7 * 90}ms`
7820
+ }
7821
+ },
7822
+ i
7823
+ )),
7824
+ /* @__PURE__ */ jsx("span", { className: `absolute inset-0 rounded-full bg-emerald-100 ${reduced ? "" : "animate-pop-in"}` }),
7825
+ /* @__PURE__ */ jsx("svg", { viewBox: "0 0 48 48", className: "absolute inset-0 h-full w-full p-6", "aria-hidden": true, children: /* @__PURE__ */ jsx(
7826
+ "path",
7827
+ {
7828
+ d: "M10 25 L20 35 L38 14",
7829
+ fill: "none",
7830
+ stroke: "rgb(5 150 105)",
7831
+ strokeWidth: "5",
7832
+ strokeLinecap: "round",
7833
+ strokeLinejoin: "round",
7834
+ strokeDasharray: "48",
7835
+ className: reduced ? "" : "animate-draw-check"
7836
+ }
7837
+ ) })
7838
+ ] });
7839
+ }
7840
+ var CHARGEABLE_PROVIDERS = ["abacatepay"];
7841
+ var PAYMENT_LABELS = {
7842
+ pix: { label: "Pix", hint: "Voc\xEA recebe a chave para pagar ap\xF3s confirmar o pedido" },
7843
+ credit_card: { label: "Cart\xE3o de cr\xE9dito", hint: "Maquininha na entrega" },
7844
+ debit_card: { label: "Cart\xE3o de d\xE9bito", hint: "Maquininha na entrega" },
7845
+ boleto: { label: "Boleto", hint: "Enviado por e-mail ap\xF3s a confirma\xE7\xE3o" },
7846
+ cash: { label: "Dinheiro", hint: "Pagamento na entrega" },
7847
+ other: { label: "Combinar com a loja", hint: "A loja entra em contato para acertar o pagamento" }
7848
+ };
7849
+ function placeOrderMessage(err) {
7850
+ const message = errorMessage(err);
7851
+ if (/customer (email mismatch|does not belong|belongs to another)/i.test(message)) {
7852
+ return "Sua sess\xE3o ficou presa a outro cadastro. Recarregue a p\xE1gina e tente de novo.";
7853
+ }
7854
+ if (/out of stock|insufficient|estoque/i.test(message)) {
7855
+ return "Um item do carrinho ficou sem estoque. Revise o carrinho e tente de novo.";
7856
+ }
7857
+ if (/delivery not available|postal code/i.test(message)) {
7858
+ return "Ainda n\xE3o entregamos nesse CEP. Tente outro endere\xE7o.";
7859
+ }
7860
+ return message.replace(/^shop_[a-z_]+:\s*/i, "") || "N\xE3o foi poss\xEDvel concluir o pedido.";
7861
+ }
7419
7862
  function field(label, testId, value, onChange, props) {
7420
7863
  return /* @__PURE__ */ jsxs("label", { className: "block", children: [
7421
7864
  /* @__PURE__ */ jsx("span", { className: "sr-only", children: label }),
@@ -7440,13 +7883,22 @@ function CheckoutPage() {
7440
7883
  const validateDiscount2 = useDiscountValidator();
7441
7884
  useStorefrontHead({ title: `Checkout \u2014 ${config.name}` });
7442
7885
  const paymentMethods = config.payments.methods;
7886
+ const policy = config.policy;
7887
+ const requiresAccount = policy?.guestCheckout === false;
7888
+ const fieldPolicy = policy?.checkoutFields ?? {};
7889
+ const gatewayCheckout = config.payments.mode === "gateway" || !!policy?.paymentProvider;
7890
+ const shows = (key) => key === "document" ? gatewayCheckout || fieldPolicy.document?.visible === true : fieldPolicy[key]?.visible !== false;
7891
+ const requires = (key) => key === "document" ? gatewayCheckout || fieldPolicy.document?.visible === true && fieldPolicy.document?.required === true : fieldPolicy[key]?.visible !== false && fieldPolicy[key]?.required === true;
7443
7892
  const [selectedAddressId, setSelectedAddressId] = useState("new");
7444
7893
  const [savedAddresses, setSavedAddresses] = useState([]);
7894
+ const [addressesLoading, setAddressesLoading] = useState(false);
7445
7895
  const [addressMode, setAddressMode] = useState("new");
7446
7896
  const [paymentMethod, setPaymentMethod] = useState(paymentMethods[0] ?? "pix");
7447
7897
  const [form, setForm] = useState({
7448
7898
  email: session.email ?? "",
7449
7899
  name: session.name ?? "",
7900
+ phone: "",
7901
+ document: "",
7450
7902
  street: "",
7451
7903
  city: "",
7452
7904
  zip: "",
@@ -7455,33 +7907,65 @@ function CheckoutPage() {
7455
7907
  district: "",
7456
7908
  state: ""
7457
7909
  });
7910
+ const [step, setStep] = useState(1);
7911
+ const [paidOrderId, setPaidOrderId] = useState(null);
7458
7912
  const [error, setError] = useState(null);
7459
7913
  const [discountCode, setDiscountCode] = useState("");
7460
7914
  const [discountError, setDiscountError] = useState(null);
7461
7915
  const [discountSuccess, setDiscountSuccess] = useState(null);
7462
7916
  const [applyingDiscount, setApplyingDiscount] = useState(false);
7463
- const [placing, setPlacing] = useState(false);
7464
- const [processingStep, setProcessingStep] = useState(0);
7465
7917
  const [showSignin, setShowSignin] = useState(false);
7918
+ const [placing, setPlacing] = useState(false);
7919
+ const leaving = React4.useRef(false);
7466
7920
  const [zipLookup, setZipLookup] = useState("idle");
7467
7921
  const subtotal = selectSubtotal(cart);
7468
7922
  const discountTotal = selectDiscountTotal(cart);
7469
7923
  const shipping = selectShipping(cart, config);
7470
7924
  const total = selectTotal(cart, config);
7471
7925
  const checkoutSlotContext = { subtotal, total };
7926
+ const checkoutSessionInput = React4.useMemo(() => ({
7927
+ items: cart.lines.map((line) => ({
7928
+ productId: line.productId,
7929
+ variantId: line.variantId ?? null,
7930
+ quantity: line.quantity,
7931
+ optionsLabel: line.optionsLabel ?? null
7932
+ })),
7933
+ customer: { name: form.name.trim(), email: form.email.trim() },
7934
+ shippingAddress: {
7935
+ postalCode: form.zip.trim(),
7936
+ street: form.street.trim(),
7937
+ number: form.number.trim() || void 0,
7938
+ complement: form.complement.trim() || void 0,
7939
+ district: form.district.trim() || void 0,
7940
+ city: form.city.trim(),
7941
+ state: form.state.trim().toUpperCase(),
7942
+ phone: form.phone.trim() || void 0,
7943
+ document: documentDigits(form.document) || void 0
7944
+ },
7945
+ discountCode: cart.discountCode ?? null,
7946
+ paymentMethod
7947
+ // eslint-disable-next-line react-hooks/exhaustive-deps
7948
+ }), [cart.lines, cart.discountCode, form, paymentMethod]);
7949
+ const [acquirerMissing, setAcquirerMissing] = useState(false);
7950
+ const chargesOnline = paymentMethod === "pix" && !acquirerMissing && (!policy?.paymentProvider || CHARGEABLE_PROVIDERS.includes(policy.paymentProvider));
7472
7951
  const money = (value) => formatMoney(value, config.currency, config.locale);
7473
7952
  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 }));
7953
+ if (cart.lines.length === 0 && !paidOrderId && !leaving.current) navigateTo(config.catalogPath);
7954
+ }, [cart.lines.length, config.catalogPath, paidOrderId]);
7955
+ const set = (key) => (value) => {
7956
+ setError(null);
7957
+ setForm((current) => ({ ...current, [key]: value }));
7958
+ };
7477
7959
  async function setZip(value) {
7478
7960
  const masked = formatPostalCode(value);
7961
+ setError(null);
7479
7962
  setForm((current) => ({ ...current, zip: masked }));
7480
7963
  if (normalizePostalCode(masked).length !== 8) {
7481
7964
  setZipLookup("idle");
7482
7965
  return;
7483
7966
  }
7484
7967
  setZipLookup("loading");
7968
+ void delivery.resolve(masked, subtotal);
7485
7969
  try {
7486
7970
  const found = await lookupPostalCode(masked);
7487
7971
  if (!found) {
@@ -7531,15 +8015,24 @@ function CheckoutPage() {
7531
8015
  if (!customerId) {
7532
8016
  setSavedAddresses([]);
7533
8017
  setAddressMode("new");
8018
+ setAddressesLoading(false);
7534
8019
  return;
7535
8020
  }
7536
8021
  let cancelled = false;
7537
8022
  const provider = getShopProvider();
7538
- void provider.listCustomerAddresses?.(customerId).then((addresses) => {
8023
+ const listAddresses = provider.listCustomerAddresses?.bind(provider);
8024
+ if (!listAddresses) {
8025
+ setAddressesLoading(false);
8026
+ return;
8027
+ }
8028
+ setAddressesLoading(true);
8029
+ void listAddresses(customerId).then((addresses) => {
7539
8030
  if (cancelled || addresses.length === 0) return;
7540
8031
  setSavedAddresses(addresses);
7541
8032
  applySavedAddress(addresses[0]);
7542
8033
  }).catch(() => {
8034
+ }).finally(() => {
8035
+ if (!cancelled) setAddressesLoading(false);
7543
8036
  });
7544
8037
  return () => {
7545
8038
  cancelled = true;
@@ -7572,7 +8065,11 @@ function CheckoutPage() {
7572
8065
  number: address.number ?? "",
7573
8066
  complement: address.complement ?? "",
7574
8067
  district: address.district ?? "",
7575
- state: address.state
8068
+ state: address.state,
8069
+ // Kept from the book when it has one, and NOT cleared when it does not:
8070
+ // a shopper who just typed their phone should not lose it by picking a
8071
+ // saved address that predates the field.
8072
+ phone: address.phone ?? current.phone
7576
8073
  }));
7577
8074
  }
7578
8075
  function selectAddress(addressId) {
@@ -7584,44 +8081,69 @@ function CheckoutPage() {
7584
8081
  setSelectedAddressId("new");
7585
8082
  setForm((current) => ({ ...current, street: "", city: "", zip: "", number: "", complement: "", district: "", state: "" }));
7586
8083
  }
7587
- function validate() {
8084
+ function fail(message, testId) {
8085
+ setError(message);
8086
+ if (testId) {
8087
+ const field2 = document.querySelector(`[data-testid="${testId}"]`);
8088
+ field2?.scrollIntoView({ block: "center", behavior: prefersReducedMotion() ? "auto" : "smooth" });
8089
+ field2?.focus({ preventScroll: true });
8090
+ }
8091
+ return false;
8092
+ }
8093
+ function validateContact() {
7588
8094
  setError(null);
8095
+ if (requiresAccount && !session.customerId) {
8096
+ setShowSignin(true);
8097
+ return fail("Esta loja pede uma conta para finalizar a compra. Entre ou crie a sua.");
8098
+ }
7589
8099
  if (!form.email.trim() || !/.+@.+\..+/.test(form.email)) {
7590
- setError("Informe um e-mail v\xE1lido.");
7591
- return false;
8100
+ return fail("Informe um e-mail v\xE1lido.", TID.checkoutEmail);
7592
8101
  }
7593
- if (!form.name.trim()) {
7594
- setError("Informe seu nome completo.");
7595
- return false;
8102
+ if (!form.name.trim()) return fail("Informe seu nome completo.", TID.checkoutName);
8103
+ if (requires("phone") && !form.phone.trim()) {
8104
+ return fail("Informe um telefone de contato.", "checkout-phone");
8105
+ }
8106
+ if (requires("document") && !isValidDocument(form.document)) {
8107
+ return fail(
8108
+ form.document.trim() ? "CPF ou CNPJ inv\xE1lido \u2014 confira os n\xFAmeros." : "Informe o CPF ou CNPJ do comprador.",
8109
+ TID.checkoutDocument
8110
+ );
7596
8111
  }
8112
+ return true;
8113
+ }
8114
+ function validateDelivery() {
8115
+ setError(null);
8116
+ if (!selectRequiresShipping(cart)) return true;
7597
8117
  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;
8118
+ return fail("Preencha ou selecione o endere\xE7o de entrega.", TID.checkoutStreet);
7600
8119
  }
7601
- if (!form.number.trim()) {
7602
- setError("Falta o n\xFAmero do endere\xE7o.");
7603
- return false;
8120
+ if (!form.number.trim()) return fail("Falta o n\xFAmero do endere\xE7o.", "checkout-number");
8121
+ if (requires("complement") && !form.complement.trim()) {
8122
+ return fail("Informe o complemento (apto, bloco).", "checkout-complement");
7604
8123
  }
7605
- if (form.state.trim().length !== 2) {
7606
- setError("Informe a UF com duas letras (ex.: RJ).");
7607
- return false;
8124
+ if (!/^[A-Za-z]{2}$/.test(form.state.trim())) {
8125
+ return fail("Informe a UF com duas letras (ex.: RJ).", "checkout-state");
7608
8126
  }
7609
8127
  if (delivery.status === "unserved" && delivery.postalCode === normalizePostalCode(form.zip)) {
7610
- setError("Ainda n\xE3o entregamos nesse CEP. Tente outro endere\xE7o.");
7611
- return false;
8128
+ return fail(
8129
+ delivery.refusedZone ? "Esta loja n\xE3o entrega nesse endere\xE7o. Tente outro." : "Ainda n\xE3o entregamos nesse CEP. Tente outro endere\xE7o.",
8130
+ TID.checkoutZip
8131
+ );
7612
8132
  }
7613
8133
  return true;
7614
8134
  }
7615
- async function placeOrder() {
8135
+ function goToStep(next) {
8136
+ setError(null);
8137
+ setStep(next);
8138
+ if (typeof window !== "undefined") window.scrollTo({ top: 0, behavior: "smooth" });
8139
+ }
8140
+ function advance() {
8141
+ if (step === 1 && validateContact()) goToStep(2);
8142
+ else if (step === 2 && validateDelivery()) goToStep(3);
8143
+ }
8144
+ async function confirmWithoutCharge() {
7616
8145
  if (!validate()) return;
7617
8146
  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
8147
  try {
7626
8148
  const { order, customerId } = await placeStorefrontOrder({
7627
8149
  config,
@@ -7636,31 +8158,67 @@ function CheckoutPage() {
7636
8158
  complement: form.complement.trim() || void 0,
7637
8159
  district: form.district.trim() || void 0,
7638
8160
  city: form.city.trim(),
7639
- state: form.state.trim().toUpperCase()
8161
+ state: form.state.trim().toUpperCase(),
8162
+ phone: form.phone.trim() || void 0,
8163
+ // Viaja no endereço porque a assinatura do shop_place_order não pode
8164
+ // ganhar parâmetro sem DROP (0050); uma trigger o levanta para a
8165
+ // coluna e o tira do jsonb, então nunca vira linha de endereço.
8166
+ document: documentDigits(form.document) || void 0
7640
8167
  },
7641
- // The method the buyer actually chose, which opens the ledger row in
7642
- // public.transactions with the right kind.
7643
8168
  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
8169
  });
7649
8170
  if (customerId) session.setSession({ customerId, email: form.email, name: form.name });
7650
- await minDuration;
8171
+ leaving.current = true;
7651
8172
  cart.clear();
7652
8173
  navigateTo(`/order/${order.id}`);
7653
- } catch (error2) {
7654
- setError(error2 instanceof Error ? error2.message : "N\xE3o foi poss\xEDvel concluir o pedido.");
8174
+ } catch (err) {
8175
+ setError(placeOrderMessage(err));
7655
8176
  setPlacing(false);
7656
- } finally {
7657
- stepTimers.forEach(clearTimeout);
7658
8177
  }
7659
8178
  }
7660
- const primaryCta = placing ? "Processando..." : `Comprar agora - ${money(total)}`;
8179
+ function validate() {
8180
+ setError(null);
8181
+ if (requiresAccount && !session.customerId) {
8182
+ setShowSignin(true);
8183
+ return fail("Esta loja pede uma conta para finalizar a compra. Entre ou crie a sua.");
8184
+ }
8185
+ if (!form.email.trim() || !/.+@.+\..+/.test(form.email)) {
8186
+ return fail("Informe um e-mail v\xE1lido.", TID.checkoutEmail);
8187
+ }
8188
+ if (requires("phone") && !form.phone.trim()) {
8189
+ return fail("Informe um telefone de contato.", "checkout-phone");
8190
+ }
8191
+ if (!form.name.trim()) {
8192
+ return fail("Informe seu nome completo.", TID.checkoutName);
8193
+ }
8194
+ if (requires("document") && !isValidDocument(form.document)) {
8195
+ return fail(
8196
+ form.document.trim() ? "CPF ou CNPJ inv\xE1lido \u2014 confira os n\xFAmeros." : "Informe o CPF ou CNPJ do comprador.",
8197
+ TID.checkoutDocument
8198
+ );
8199
+ }
8200
+ if (!form.street.trim() || !form.city.trim() || !form.zip.trim() || !form.district.trim()) {
8201
+ return fail("Preencha ou selecione o endere\xE7o de entrega.", TID.checkoutStreet);
8202
+ }
8203
+ if (!form.number.trim()) {
8204
+ return fail("Falta o n\xFAmero do endere\xE7o.", "checkout-number");
8205
+ }
8206
+ if (requires("complement") && !form.complement.trim()) {
8207
+ return fail("Informe o complemento (apto, bloco).", "checkout-complement");
8208
+ }
8209
+ if (form.state.trim().length !== 2) {
8210
+ return fail("Informe a UF com duas letras (ex.: RJ).", "checkout-state");
8211
+ }
8212
+ if (selectRequiresShipping(cart) && delivery.status === "unserved" && delivery.postalCode === normalizePostalCode(form.zip)) {
8213
+ return fail(
8214
+ delivery.refusedZone ? "Esta loja n\xE3o entrega nesse endere\xE7o. Tente outro." : "Ainda n\xE3o entregamos nesse CEP. Tente outro endere\xE7o.",
8215
+ TID.checkoutZip
8216
+ );
8217
+ }
8218
+ return true;
8219
+ }
7661
8220
  return /* @__PURE__ */ jsxs("main", { className: "min-h-screen bg-background", children: [
7662
8221
  /* @__PURE__ */ jsx(Slot, { area: "checkout", name: "beforeForm", context: checkoutSlotContext }),
7663
- placing && /* @__PURE__ */ jsx(ProcessingOverlay, { step: processingStep }),
7664
8222
  showSignin && !session.email && /* @__PURE__ */ jsx(
7665
8223
  SignInModal,
7666
8224
  {
@@ -7682,132 +8240,281 @@ function CheckoutPage() {
7682
8240
  ] }) }),
7683
8241
  /* @__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
8242
  /* @__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
8243
+ /* @__PURE__ */ jsx(
8244
+ CheckoutSteps,
8245
+ {
8246
+ current: step,
8247
+ onGoBackTo: step === 4 ? void 0 : (target) => goToStep(target)
8248
+ }
8249
+ ),
8250
+ /* @__PURE__ */ jsxs(
8251
+ "form",
8252
+ {
8253
+ className: "space-y-8",
8254
+ onSubmit: (event) => {
8255
+ event.preventDefault();
8256
+ advance();
8257
+ },
8258
+ children: [
8259
+ step === 1 && /* @__PURE__ */ jsxs("section", { children: [
8260
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between gap-4", children: [
8261
+ /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold tracking-tight", children: "Informa\xE7\xF5es de contato" }),
8262
+ session.email ? /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
8263
+ "Conectado como ",
8264
+ session.email
8265
+ ] }) : /* @__PURE__ */ jsx(
8266
+ "button",
8267
+ {
8268
+ type: "button",
8269
+ onClick: () => setShowSignin((value) => !value),
8270
+ className: "text-sm font-medium text-primary hover:underline",
8271
+ children: "J\xE1 tem conta? Entrar"
8272
+ }
8273
+ )
8274
+ ] }),
8275
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
8276
+ field("E-mail", TID.checkoutEmail, form.email, set("email"), {
8277
+ type: "email",
8278
+ placeholder: "E-mail"
8279
+ }),
8280
+ field("Nome completo", TID.checkoutName, form.name, set("name"), {
8281
+ placeholder: "Nome completo"
8282
+ }),
8283
+ shows("phone") && field("Telefone", "checkout-phone", form.phone, set("phone"), {
8284
+ type: "tel",
8285
+ autoComplete: "tel",
8286
+ placeholder: requires("phone") ? "Telefone" : "Telefone (opcional)"
8287
+ }),
8288
+ shows("document") && field(
8289
+ "CPF ou CNPJ",
8290
+ TID.checkoutDocument,
8291
+ form.document,
8292
+ (value) => {
8293
+ setError(null);
8294
+ setForm((current) => ({ ...current, document: formatDocument(value) }));
8295
+ },
8296
+ {
8297
+ // `inputMode` and not `type="number"`: the mask writes dots
8298
+ // and a dash, which a number input silently refuses to hold.
8299
+ inputMode: "numeric",
8300
+ autoComplete: "off",
8301
+ placeholder: requires("document") ? "CPF ou CNPJ" : "CPF ou CNPJ (opcional)"
8302
+ }
8303
+ )
8304
+ ] }),
8305
+ 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: [
8306
+ "Esta loja pede uma conta para finalizar a compra.",
8307
+ " ",
8308
+ /* @__PURE__ */ jsx(
8309
+ "button",
8310
+ {
8311
+ type: "button",
8312
+ onClick: () => setShowSignin(true),
8313
+ className: "font-semibold text-primary hover:underline",
8314
+ children: "Entrar ou criar conta"
8315
+ }
8316
+ )
8317
+ ] })
8318
+ ] }),
8319
+ step === 2 && /* @__PURE__ */ jsxs("section", { children: [
8320
+ /* @__PURE__ */ jsx("h2", { className: "mb-3 text-xl font-semibold tracking-tight", children: "Endere\xE7o de entrega" }),
8321
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 grid gap-3", children: [
8322
+ 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: [
8323
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-28" }),
8324
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-3 w-3/4" }),
8325
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-3 w-1/2" })
8326
+ ] }, row)) }),
8327
+ savedAddresses.map((address) => /* @__PURE__ */ jsx(
8328
+ "div",
8329
+ {
8330
+ 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"}`,
8331
+ children: /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => selectAddress(address.id), className: "block w-full text-left", children: [
8332
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-between gap-3 font-semibold", children: [
8333
+ address.label,
8334
+ 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" })
8335
+ ] }),
8336
+ /* @__PURE__ */ jsxs("span", { className: "mt-1 block text-muted-foreground", children: [
8337
+ [address.street, address.number].filter(Boolean).join(", "),
8338
+ address.complement ? ` \u2014 ${address.complement}` : ""
8339
+ ] }),
8340
+ /* @__PURE__ */ jsxs("span", { className: "block text-muted-foreground", children: [
8341
+ [address.district, address.city, address.state].filter(Boolean).join(" \xB7 "),
8342
+ " \u2014 ",
8343
+ address.postalCode
8344
+ ] })
8345
+ ] })
8346
+ },
8347
+ address.id
8348
+ )),
8349
+ /* @__PURE__ */ jsxs(
8350
+ "button",
8351
+ {
8352
+ type: "button",
8353
+ onClick: addNewAddress,
8354
+ 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"}`,
8355
+ children: [
8356
+ /* @__PURE__ */ jsx("span", { className: "block font-semibold text-primary", children: savedAddresses.length > 0 ? "+ Adicionar novo endere\xE7o" : "Informe o endere\xE7o de entrega" }),
8357
+ /* @__PURE__ */ jsx("span", { className: "mt-1 block text-muted-foreground", children: "Preencher outro endere\xE7o para esta compra" })
8358
+ ]
8359
+ }
8360
+ )
8361
+ ] }),
8362
+ addressMode === "new" && /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
8363
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-1 sm:grid-cols-[200px_1fr] sm:items-center", children: [
8364
+ field("CEP", TID.checkoutZip, form.zip, setZip, {
8365
+ placeholder: "CEP",
8366
+ inputMode: "numeric",
8367
+ autoComplete: "postal-code"
8368
+ }),
8369
+ zipLookup === "loading" ? /* @__PURE__ */ jsxs(SkeletonGroup, { label: "Buscando o endere\xE7o deste CEP", className: "grid gap-1.5 px-1", children: [
8370
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-2.5 w-40" }),
8371
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-2.5 w-24" })
8372
+ ] }) : /* @__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." })
8373
+ ] }),
8374
+ field("Endere\xE7o", TID.checkoutStreet, form.street, set("street"), { placeholder: "Rua, avenida\u2026" }),
8375
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
8376
+ field("N\xFAmero", "checkout-number", form.number, set("number"), { placeholder: "N\xFAmero" }),
8377
+ shows("complement") && field("Complemento", "checkout-complement", form.complement, set("complement"), {
8378
+ placeholder: requires("complement") ? "Apto, bloco" : "Apto, bloco (opcional)"
8379
+ })
8380
+ ] }),
8381
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
8382
+ field("Bairro", "checkout-district", form.district, set("district"), { placeholder: "Bairro" }),
8383
+ field("UF", "checkout-state", form.state, set("state"), { placeholder: "UF" })
8384
+ ] }),
8385
+ field("Cidade", TID.checkoutCity, form.city, set("city"), { placeholder: "Cidade" })
8386
+ ] })
8387
+ ] }),
8388
+ step === 3 && placing && /* @__PURE__ */ jsxs("section", { children: [
8389
+ /* @__PURE__ */ jsx("h2", { className: "mb-3 text-xl font-semibold tracking-tight", children: "Confirmando seu pedido" }),
8390
+ /* @__PURE__ */ jsxs(SkeletonGroup, { label: "Criando seu pedido", className: "rounded-xl border bg-card p-6 sm:p-8", children: [
8391
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-6 border-b pb-6 sm:grid-cols-[120px_minmax(0,1fr)]", children: [
8392
+ /* @__PURE__ */ jsx(Skeleton, { className: "mx-auto h-24 w-24 rounded-full" }),
8393
+ /* @__PURE__ */ jsxs("div", { children: [
8394
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-56 max-w-full" }),
8395
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-3.5 w-full max-w-sm" }),
8396
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-4 h-5 w-40" })
7739
8397
  ] })
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." })
8398
+ ] }),
8399
+ /* @__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: [
8400
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-16" }),
8401
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-4 w-24" })
8402
+ ] }, column)) }),
8403
+ /* @__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: [
8404
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-12 rounded-lg" }),
8405
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 flex-1" }),
8406
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-16" })
8407
+ ] }, line.lineId ?? line.productId)) })
8408
+ ] }),
8409
+ /* @__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
8410
  ] }),
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)" })
8411
+ step === 3 && !placing && /* @__PURE__ */ jsxs("section", { children: [
8412
+ /* @__PURE__ */ jsx("h2", { className: "mb-3 text-xl font-semibold tracking-tight", children: "Pagamento" }),
8413
+ /* @__PURE__ */ jsx("div", { className: "mb-3 grid gap-3", role: "radiogroup", "aria-label": "Forma de pagamento", children: paymentMethods.map((method) => {
8414
+ const copy = PAYMENT_LABELS[method];
8415
+ const selected = paymentMethod === method;
8416
+ return (
8417
+ // Cada forma de pagamento abre o que ELA pede, embaixo dela
8418
+ // mesma. Antes o painel do Pix ficava no fim da lista, longe
8419
+ // do cartão escolhido, e escolher parecia não fazer nada.
8420
+ /* @__PURE__ */ jsxs("div", { children: [
8421
+ /* @__PURE__ */ jsxs(
8422
+ "button",
8423
+ {
8424
+ type: "button",
8425
+ role: "radio",
8426
+ "aria-checked": selected,
8427
+ "data-testid": `checkout-payment-${method}`,
8428
+ onClick: () => setPaymentMethod(method),
8429
+ 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"}`,
8430
+ children: [
8431
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-between gap-3 font-semibold", children: [
8432
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
8433
+ /* @__PURE__ */ jsx(CreditCard, { className: "h-4 w-4" }),
8434
+ copy.label
8435
+ ] }),
8436
+ selected && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary", children: "Selecionado" })
8437
+ ] }),
8438
+ /* @__PURE__ */ jsx("span", { className: "mt-1 block text-muted-foreground", children: copy.hint })
8439
+ ]
8440
+ }
8441
+ ),
8442
+ selected && chargesOnline && /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
8443
+ CheckoutPayment,
8444
+ {
8445
+ input: checkoutSessionInput,
8446
+ money,
8447
+ onBack: () => goToStep(2),
8448
+ onUnavailable: () => setAcquirerMissing(true),
8449
+ onPaid: (orderId) => {
8450
+ leaving.current = true;
8451
+ setPaidOrderId(orderId);
8452
+ goToStep(4);
8453
+ cart.clear();
8454
+ }
8455
+ }
8456
+ ) })
8457
+ ] }, method)
8458
+ );
8459
+ }) }),
8460
+ /* @__PURE__ */ jsx(Slot, { area: "checkout", name: "afterPaymentMethods", context: checkoutSlotContext }),
8461
+ !chargesOnline && /* @__PURE__ */ jsxs("div", { className: "mt-5", children: [
8462
+ /* @__PURE__ */ jsx(
8463
+ "button",
8464
+ {
8465
+ type: "button",
8466
+ "data-testid": TID.placeOrder,
8467
+ disabled: placing,
8468
+ onClick: () => {
8469
+ void confirmWithoutCharge();
8470
+ },
8471
+ 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",
8472
+ children: `Fazer pedido - ${money(total)}`
8473
+ }
8474
+ ),
8475
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-center text-xs text-muted-foreground", children: "O pedido fica aguardando pagamento at\xE9 a loja confirmar." })
8476
+ ] })
7770
8477
  ] }),
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" })
8478
+ step === 4 && paidOrderId && /* @__PURE__ */ jsxs("section", { "data-testid": TID.checkoutDone, className: "rounded-xl border bg-card p-8 text-center", children: [
8479
+ /* @__PURE__ */ jsx(SuccessMark, {}),
8480
+ /* @__PURE__ */ jsx("h2", { className: "mt-4 text-2xl font-bold tracking-tight", children: "Pagamento confirmado!" }),
8481
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: "Seu pedido foi criado e a loja j\xE1 foi avisada." }),
8482
+ /* @__PURE__ */ jsx(
8483
+ "button",
8484
+ {
8485
+ type: "button",
8486
+ onClick: () => navigateTo(`/order/${paidOrderId}`),
8487
+ 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",
8488
+ children: "Ver meu pedido"
8489
+ }
8490
+ )
7774
8491
  ] }),
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
- ] }),
8492
+ 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 }),
8493
+ (step === 1 || step === 2) && /* @__PURE__ */ jsxs("div", { className: "grid gap-3 border-t pt-5", children: [
8494
+ /* @__PURE__ */ jsx(
8495
+ "button",
8496
+ {
8497
+ type: "submit",
8498
+ "data-testid": TID.checkoutContinue,
8499
+ className: "min-h-12 rounded-lg bg-primary px-6 py-4 text-sm font-bold text-primary-foreground transition hover:opacity-90",
8500
+ children: step === 1 ? "Continuar para a entrega" : `Ir para o pagamento - ${money(total)}`
8501
+ }
8502
+ ),
8503
+ step === 2 && /* @__PURE__ */ jsx(
8504
+ "button",
8505
+ {
8506
+ type: "button",
8507
+ "data-testid": TID.checkoutBack,
8508
+ onClick: () => goToStep(1),
8509
+ className: "min-h-11 rounded-lg border px-6 py-3 text-sm font-medium transition hover:bg-muted",
8510
+ children: "Voltar aos dados"
8511
+ }
8512
+ ),
8513
+ /* @__PURE__ */ jsx(Link, { to: config.catalogPath, className: "text-center text-sm text-muted-foreground hover:text-foreground", children: "Voltar ao carrinho" })
8514
+ ] })
8515
+ ]
8516
+ }
8517
+ ),
7811
8518
  /* @__PURE__ */ jsxs("footer", { className: "mt-10 flex flex-wrap gap-4 border-t pt-5 text-xs text-muted-foreground", children: [
7812
8519
  /* @__PURE__ */ jsx(Link, { to: "/privacy", className: "hover:text-foreground", children: "Privacidade" }),
7813
8520
  /* @__PURE__ */ jsx(Link, { to: "/terms", className: "hover:text-foreground", children: "Termos" }),
@@ -7901,21 +8608,7 @@ function CheckoutPage() {
7901
8608
  /* @__PURE__ */ jsx(ShieldCheck, { className: "h-4 w-4 text-primary" }),
7902
8609
  "Compra protegida e dados criptografados."
7903
8610
  ] }),
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
- ] })
8611
+ /* @__PURE__ */ jsx("div", { className: "mt-6 border-t pt-5", children: /* @__PURE__ */ jsx(Slot, { area: "checkout", name: "afterForm", context: checkoutSlotContext }) })
7919
8612
  ] })
7920
8613
  ] })
7921
8614
  ] });
@@ -7980,9 +8673,10 @@ function orderTrackingSteps(order) {
7980
8673
  {
7981
8674
  id: "payment",
7982
8675
  label: paid ? "Pagamento aprovado" : "Aguardando pagamento",
7983
- description: paid ? "Compra confirmada" : "Processando",
8676
+ // "Processando" descrevia um pagamento que ninguém iniciou.
8677
+ description: paid ? "Compra confirmada" : "Ainda n\xE3o recebemos",
7984
8678
  state: paid ? "complete" : "current",
7985
- icon: Check
8679
+ icon: paid ? Check : Clock
7986
8680
  },
7987
8681
  {
7988
8682
  id: "preparing",
@@ -8016,7 +8710,9 @@ function OrderTrackingTimeline({ order, compact: compact2 = false }) {
8016
8710
  children: steps.map((step) => {
8017
8711
  const Icon2 = step.icon;
8018
8712
  const cancelled = step.state === "cancelled";
8019
- const active2 = step.state !== "pending";
8713
+ const done = step.state === "complete";
8714
+ const current = step.state === "current";
8715
+ const active2 = done || current;
8020
8716
  return /* @__PURE__ */ jsx(
8021
8717
  "li",
8022
8718
  {
@@ -8033,15 +8729,15 @@ function OrderTrackingTimeline({ order, compact: compact2 = false }) {
8033
8729
  {
8034
8730
  className: [
8035
8731
  "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"
8732
+ 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
8733
  ].join(" "),
8038
8734
  "aria-hidden": true,
8039
8735
  children: /* @__PURE__ */ jsx(Icon2, { className: "h-3.5 w-3.5" })
8040
8736
  }
8041
8737
  ),
8042
8738
  /* @__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 })
8739
+ /* @__PURE__ */ jsx("span", { className: "block font-semibold leading-snug text-foreground", children: step.label }),
8740
+ /* @__PURE__ */ jsx("span", { className: "mt-0.5 block leading-snug text-muted-foreground", children: step.description })
8045
8741
  ] })
8046
8742
  ] })
8047
8743
  },
@@ -8051,6 +8747,32 @@ function OrderTrackingTimeline({ order, compact: compact2 = false }) {
8051
8747
  }
8052
8748
  );
8053
8749
  }
8750
+
8751
+ // src/components/account/section.ts
8752
+ var ACCOUNT_SECTIONS = [
8753
+ "profile",
8754
+ "orders",
8755
+ "addresses",
8756
+ "payments"
8757
+ ];
8758
+ var DEFAULT_ACCOUNT_SECTION = "orders";
8759
+ function isAccountSection(value) {
8760
+ return ACCOUNT_SECTIONS.includes(value);
8761
+ }
8762
+ function parseAccountSection(path) {
8763
+ const query = path.split("?")[1];
8764
+ if (!query) return DEFAULT_ACCOUNT_SECTION;
8765
+ for (const pair of query.split("&")) {
8766
+ const [key, raw = ""] = pair.split("=");
8767
+ if (key !== "section") continue;
8768
+ const value = decodeURIComponent(raw);
8769
+ return isAccountSection(value) ? value : DEFAULT_ACCOUNT_SECTION;
8770
+ }
8771
+ return DEFAULT_ACCOUNT_SECTION;
8772
+ }
8773
+ function accountSectionPath(section) {
8774
+ return `/account?section=${section}`;
8775
+ }
8054
8776
  var menuItems = [
8055
8777
  { id: "profile", label: "Perfil", icon: User },
8056
8778
  { id: "orders", label: "Meus pedidos", icon: Package },
@@ -8084,11 +8806,16 @@ function CustomerAccountShell({ title, subtitle, active: active2, onSelect, chil
8084
8806
  const Icon2 = item.icon;
8085
8807
  const selected = item.id === active2;
8086
8808
  return /* @__PURE__ */ jsxs(
8087
- "button",
8809
+ Link,
8088
8810
  {
8089
- type: "button",
8811
+ to: accountSectionPath(item.id),
8812
+ "data-section": item.id,
8090
8813
  "aria-current": selected ? "page" : void 0,
8091
- onClick: () => onSelect?.(item.id),
8814
+ onClick: (event) => {
8815
+ if (!onSelect) return;
8816
+ event.preventDefault();
8817
+ onSelect(item.id);
8818
+ },
8092
8819
  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
8820
  children: [
8094
8821
  /* @__PURE__ */ jsx(Icon2, { className: "h-4 w-4" }),
@@ -8123,38 +8850,23 @@ function CustomerAccountShell({ title, subtitle, active: active2, onSelect, chil
8123
8850
  ] })
8124
8851
  ] });
8125
8852
  }
8126
- var CONFETTI_COLORS = ["hsl(var(--primary))", "#f59e0b", "#10b981", "#3b82f6", "#ec4899"];
8127
- function SuccessMark() {
8128
- const reduced = prefersReducedMotion();
8853
+ function PendingMark() {
8129
8854
  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`
8855
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-0 rounded-full bg-amber-100" }),
8856
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 48 48", className: "absolute inset-0 h-full w-full p-6", "aria-hidden": true, children: [
8857
+ /* @__PURE__ */ jsx("circle", { cx: "24", cy: "24", r: "17", fill: "none", stroke: "rgb(180 83 9)", strokeWidth: "4" }),
8858
+ /* @__PURE__ */ jsx(
8859
+ "path",
8860
+ {
8861
+ d: "M24 14 L24 25 L31 29",
8862
+ fill: "none",
8863
+ stroke: "rgb(180 83 9)",
8864
+ strokeWidth: "4",
8865
+ strokeLinecap: "round",
8866
+ strokeLinejoin: "round"
8140
8867
  }
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
- ) })
8868
+ )
8869
+ ] })
8158
8870
  ] });
8159
8871
  }
8160
8872
  function OrderConfirmationPage({ orderId }) {
@@ -8163,7 +8875,7 @@ function OrderConfirmationPage({ orderId }) {
8163
8875
  const [loading, setLoading] = useState(true);
8164
8876
  const money = (v) => formatMoney(v, config.currency, config.locale);
8165
8877
  useStorefrontHead({ title: order ? `Pedido #${order.orderNumber} \u2014 ${config.name}` : config.name });
8166
- useEffect(() => {
8878
+ const reload = React4.useCallback(() => {
8167
8879
  let cancelled = false;
8168
8880
  getShopProvider().getOrder(orderId).then((o) => {
8169
8881
  if (!cancelled) setOrder(o);
@@ -8174,8 +8886,40 @@ function OrderConfirmationPage({ orderId }) {
8174
8886
  cancelled = true;
8175
8887
  };
8176
8888
  }, [orderId]);
8889
+ useEffect(() => reload(), [reload]);
8177
8890
  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" });
8891
+ 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: [
8892
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-6 border-b pb-6 sm:grid-cols-[120px_minmax(0,1fr)]", children: [
8893
+ /* @__PURE__ */ jsx(Skeleton, { className: "mx-auto h-24 w-24 rounded-full" }),
8894
+ /* @__PURE__ */ jsxs("div", { children: [
8895
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-64 max-w-full" }),
8896
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-3.5 w-full max-w-md" }),
8897
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-4 h-6 w-48" })
8898
+ ] })
8899
+ ] }),
8900
+ /* @__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: [
8901
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-16" }),
8902
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-4 w-28" }),
8903
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-3 w-20" })
8904
+ ] }, column)) }),
8905
+ /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
8906
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-32" }),
8907
+ /* @__PURE__ */ jsx("div", { className: "mt-3 flex items-center gap-2", children: [0, 1, 2, 3].map((node) => /* @__PURE__ */ jsxs(React4.Fragment, { children: [
8908
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-7 shrink-0 rounded-full" }),
8909
+ node < 3 && /* @__PURE__ */ jsx(Skeleton, { className: "h-px flex-1" })
8910
+ ] }, node)) })
8911
+ ] }),
8912
+ /* @__PURE__ */ jsx("div", { className: "mt-8 space-y-3", children: [0, 1].map((row) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
8913
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-12 rounded-lg" }),
8914
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 flex-1" }),
8915
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-16" })
8916
+ ] }, row)) }),
8917
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 space-y-2 border-t pt-4", children: [
8918
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-full max-w-xs" }),
8919
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-full max-w-[14rem]" }),
8920
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-40" })
8921
+ ] })
8922
+ ] }) });
8179
8923
  }
8180
8924
  if (!order) {
8181
8925
  return /* @__PURE__ */ jsxs("main", { className: "mx-auto max-w-2xl px-4 py-20 text-center", children: [
@@ -8183,24 +8927,20 @@ function OrderConfirmationPage({ orderId }) {
8183
8927
  /* @__PURE__ */ jsx(Link, { to: "/", className: "mt-4 inline-block text-primary underline", children: "Voltar \xE0 loja" })
8184
8928
  ] });
8185
8929
  }
8930
+ const paid = isPaid(order.financialStatus);
8186
8931
  return /* @__PURE__ */ jsx(
8187
8932
  CustomerAccountShell,
8188
8933
  {
8189
8934
  title: "Detalhe do pedido",
8190
- subtitle: "Compra confirmada e vinculada \xE0 sua conta.",
8935
+ subtitle: paid ? "Compra confirmada e vinculada \xE0 sua conta." : "Pedido guardado, aguardando o pagamento.",
8191
8936
  active: "orders",
8937
+ onSelect: (section) => navigateTo(`/account?section=${section}`),
8192
8938
  children: /* @__PURE__ */ jsxs("article", { "data-testid": TID.orderDetail, className: "animate-fade-up rounded-xl border bg-card p-6 sm:p-8", children: [
8193
8939
  /* @__PURE__ */ jsxs("div", { className: "grid gap-6 border-b pb-6 sm:grid-cols-[120px_minmax(0,1fr)]", children: [
8194
- /* @__PURE__ */ jsx(SuccessMark, {}),
8940
+ paid ? /* @__PURE__ */ jsx(SuccessMark, {}) : /* @__PURE__ */ jsx(PendingMark, {}),
8195
8941
  /* @__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
- ] }),
8942
+ /* @__PURE__ */ jsx("h2", { className: "sf-heading text-3xl font-bold tracking-tight", children: paid ? "Pedido confirmado!" : "Falta o pagamento" }),
8943
+ /* @__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
8944
  /* @__PURE__ */ jsxs("p", { className: "mt-4 text-lg", children: [
8205
8945
  "Pedido ",
8206
8946
  /* @__PURE__ */ jsxs("span", { "data-testid": TID.orderNumber, className: "font-semibold", children: [
@@ -8227,18 +8967,39 @@ function OrderConfirmationPage({ orderId }) {
8227
8967
  ] }),
8228
8968
  /* @__PURE__ */ jsxs("div", { children: [
8229
8969
  /* @__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" }),
8970
+ /* @__PURE__ */ jsx("p", { className: "mt-1 font-semibold", children: order.fulfillmentStatus === "fulfilled" ? "Enviado" : paid ? "Preparando envio" : "Aguardando pagamento" }),
8231
8971
  /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: order.notes ?? "Endere\xE7o salvo no pedido" })
8232
8972
  ] }),
8233
8973
  /* @__PURE__ */ jsxs("div", { children: [
8234
8974
  /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: "Pagamento" }),
8235
8975
  /* @__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" })
8976
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: paid ? "Recebido pela loja" : "Ainda n\xE3o recebido" })
8237
8977
  ] })
8238
8978
  ] }),
8239
8979
  /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
8240
8980
  /* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-semibold uppercase text-muted-foreground", children: "Acompanhamento" }),
8241
- /* @__PURE__ */ jsx(OrderTrackingTimeline, { order })
8981
+ /* @__PURE__ */ jsx(OrderTrackingTimeline, { order }),
8982
+ (order.shipments ?? []).filter((s2) => s2.trackingCode).map((shipment) => /* @__PURE__ */ jsxs(
8983
+ "div",
8984
+ {
8985
+ "data-testid": TID.orderTracking + "-code",
8986
+ 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",
8987
+ children: [
8988
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: shipment.carrier ? `Enviado por ${shipment.carrier}` : "Enviado" }),
8989
+ shipment.trackingUrl ? /* @__PURE__ */ jsx(
8990
+ "a",
8991
+ {
8992
+ href: shipment.trackingUrl,
8993
+ target: "_blank",
8994
+ rel: "noreferrer",
8995
+ className: "font-mono font-medium text-primary hover:underline",
8996
+ children: shipment.trackingCode
8997
+ }
8998
+ ) : /* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: shipment.trackingCode })
8999
+ ]
9000
+ },
9001
+ shipment.id
9002
+ ))
8242
9003
  ] }),
8243
9004
  /* @__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
9005
  item.imageUrl && /* @__PURE__ */ jsx(SmoothImage, { src: item.imageUrl, alt: item.name, className: "h-12 w-12 rounded-lg border object-cover" }),
@@ -8301,16 +9062,23 @@ function useMyOrders() {
8301
9062
  const refresh = useCallback(() => setTick((t) => t + 1), []);
8302
9063
  useEffect(() => {
8303
9064
  let cancelled = false;
9065
+ const local = listLocalOrderIds();
8304
9066
  if (!customerId && !email) {
8305
- setOrders([]);
8306
- setLoading(false);
8307
- return;
9067
+ if (local.length === 0) {
9068
+ setOrders([]);
9069
+ setLoading(false);
9070
+ return;
9071
+ }
8308
9072
  }
8309
9073
  setLoading(true);
8310
9074
  const query = customerId ? { customerId, limit: 50 } : { customerEmail: email, limit: 50 };
8311
9075
  getShopProvider().listOrders(query).then((data) => {
8312
9076
  if (!cancelled) setOrders(data);
8313
- }).catch(() => {
9077
+ }).catch(async () => {
9078
+ const mine = await Promise.all(
9079
+ listLocalOrderIds().map((id) => getShopProvider().getOrder(id).catch(() => null))
9080
+ );
9081
+ if (!cancelled) setOrders(mine.filter((o) => !!o));
8314
9082
  }).finally(() => {
8315
9083
  if (!cancelled) setLoading(false);
8316
9084
  });
@@ -8432,7 +9200,27 @@ function OrdersPanel() {
8432
9200
  const config = useStorefrontConfig();
8433
9201
  const { orders, loading } = useMyOrders();
8434
9202
  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" });
9203
+ if (loading) {
9204
+ 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: [
9205
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
9206
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-32" }),
9207
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-24 rounded-full" })
9208
+ ] }),
9209
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-3 w-20" }),
9210
+ /* @__PURE__ */ jsx("div", { className: "mt-4 flex items-center gap-2", children: [0, 1, 2, 3].map((node) => /* @__PURE__ */ jsxs(React4.Fragment, { children: [
9211
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-6 shrink-0 rounded-full" }),
9212
+ node < 3 && /* @__PURE__ */ jsx(Skeleton, { className: "h-px flex-1" })
9213
+ ] }, node)) }),
9214
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex flex-wrap gap-2", children: [
9215
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-32 rounded-lg" }),
9216
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-24 rounded-lg" })
9217
+ ] }),
9218
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center justify-between border-t pt-3", children: [
9219
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24" }),
9220
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-20" })
9221
+ ] })
9222
+ ] }, row)) });
9223
+ }
8436
9224
  if (orders.length === 0) {
8437
9225
  return /* @__PURE__ */ jsxs("div", { "data-testid": TID.purchasesEmpty, className: "rounded-2xl border bg-card py-16 text-center", children: [
8438
9226
  /* @__PURE__ */ jsx(Package, { className: "mx-auto h-10 w-10 text-muted-foreground" }),
@@ -8479,12 +9267,14 @@ function ProfilePanel() {
8479
9267
  const [name, setName] = useState(session.name ?? "");
8480
9268
  const [saving, setSaving] = useState(false);
8481
9269
  const [saved, setSaved] = useState(false);
9270
+ const [error, setError] = useState(null);
8482
9271
  async function save(e) {
8483
9272
  e.preventDefault();
8484
9273
  const trimmed = name.trim();
8485
9274
  if (!trimmed) return;
8486
9275
  setSaving(true);
8487
9276
  setSaved(false);
9277
+ setError(null);
8488
9278
  try {
8489
9279
  if (session.customerId) {
8490
9280
  const parts = trimmed.split(/\s+/).filter(Boolean);
@@ -8495,6 +9285,8 @@ function ProfilePanel() {
8495
9285
  }
8496
9286
  useSessionStore.setState({ name: trimmed });
8497
9287
  setSaved(true);
9288
+ } catch {
9289
+ setError("N\xE3o foi poss\xEDvel salvar seu nome agora. Tente novamente em instantes.");
8498
9290
  } finally {
8499
9291
  setSaving(false);
8500
9292
  }
@@ -8539,7 +9331,8 @@ function ProfilePanel() {
8539
9331
  }
8540
9332
  ),
8541
9333
  saved && /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-emerald-700", children: "Salvo" })
8542
- ] })
9334
+ ] }),
9335
+ error && /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm text-destructive", children: error })
8543
9336
  ] });
8544
9337
  }
8545
9338
  function AddressesPanel() {
@@ -8574,7 +9367,7 @@ var SECTION_SUBTITLE = {
8574
9367
  function MyPurchasesPage() {
8575
9368
  const config = useStorefrontConfig();
8576
9369
  const session = useSessionStore();
8577
- const [section, setSection] = useState("orders");
9370
+ const section = parseAccountSection(useRoutePath());
8578
9371
  useStorefrontHead({ title: `Minha conta \u2014 ${config.name}` });
8579
9372
  if (!session.email) {
8580
9373
  return /* @__PURE__ */ jsx("main", { className: "mx-auto max-w-3xl px-4 py-12 sm:px-6", children: /* @__PURE__ */ jsx(AuthForm, {}) });
@@ -8585,7 +9378,6 @@ function MyPurchasesPage() {
8585
9378
  title: "Minha conta",
8586
9379
  subtitle: SECTION_SUBTITLE[section],
8587
9380
  active: section,
8588
- onSelect: setSection,
8589
9381
  children: [
8590
9382
  section === "profile" && /* @__PURE__ */ jsx(ProfilePanel, {}),
8591
9383
  section === "orders" && /* @__PURE__ */ jsx(OrdersPanel, {}),
@@ -8595,6 +9387,27 @@ function MyPurchasesPage() {
8595
9387
  }
8596
9388
  );
8597
9389
  }
9390
+ function initShippingItems() {
9391
+ setShopShippingItemsResolver(
9392
+ () => useCartStore.getState().lines.map((line) => ({
9393
+ quantity: line.quantity,
9394
+ weight: line.weight ?? null,
9395
+ weightUnit: line.weightUnit ?? null,
9396
+ height: line.heightCm ?? null,
9397
+ width: line.widthCm ?? null,
9398
+ length: line.lengthCm ?? null,
9399
+ packageId: line.packageId ?? null,
9400
+ // Undefined is a line added before this existed. Treated as physical:
9401
+ // freight wrongly charged is a complaint, freight wrongly skipped is a
9402
+ // parcel the merchant pays for.
9403
+ isPhysical: line.requiresShipping !== false,
9404
+ productId: line.productId,
9405
+ variantId: line.variantId,
9406
+ name: line.name,
9407
+ sku: line.sku
9408
+ }))
9409
+ );
9410
+ }
8598
9411
  function NotFoundPage() {
8599
9412
  const config = useStorefrontConfig();
8600
9413
  useStorefrontHead({ title: `P\xE1gina n\xE3o encontrada \u2014 ${config.name}` });
@@ -8827,6 +9640,7 @@ function initStorefrontRuntime(config) {
8827
9640
  }));
8828
9641
  }
8829
9642
  setShopProvider(resolveStorefrontProvider(config));
9643
+ initShippingItems();
8830
9644
  const reconcileCart = () => {
8831
9645
  const provider = getShopProvider();
8832
9646
  void useCartStore.getState().reconcile(async (line) => {
@@ -8879,7 +9693,7 @@ function createStorefrontApp(config) {
8879
9693
  initStorefrontRuntime(config);
8880
9694
  const router = defaultRouterAdapter(resolved.routing ?? "hash");
8881
9695
  function StorefrontApp() {
8882
- return /* @__PURE__ */ jsx(StorefrontConfigProvider, { value: resolved, children: /* @__PURE__ */ jsx(StorefrontShell, { router }) });
9696
+ return /* @__PURE__ */ jsx(StorefrontConfigHost, { config: resolved, children: /* @__PURE__ */ jsx(StorefrontShell, { router }) });
8883
9697
  }
8884
9698
  StorefrontApp.displayName = "StorefrontApp";
8885
9699
  return StorefrontApp;
@@ -8902,7 +9716,7 @@ function createStorefront(options) {
8902
9716
  // src/document/sections.ts
8903
9717
  var STORE_DOCUMENT_KIND = "fayz.store";
8904
9718
  var STORE_COMMERCE_MODES = ["checkout", "catalog", "enquiry"];
8905
- var STORE_PAYMENT_MODES = ["mock", "pix-mercadopago"];
9719
+ var STORE_PAYMENT_MODES = ["mock", "pix-mercadopago", "gateway"];
8906
9720
  var STORE_PAYMENT_METHOD_KINDS = [
8907
9721
  "pix",
8908
9722
  "credit_card",
@@ -10227,6 +11041,6 @@ ${formatErrors(report.errors)}`;
10227
11041
  return config;
10228
11042
  }
10229
11043
 
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
11044
+ 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 };
11045
+ //# sourceMappingURL=chunk-SX3J3QIT.js.map
11046
+ //# sourceMappingURL=chunk-SX3J3QIT.js.map