@fayz-ai/storefront 0.17.0 → 0.19.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 (69) hide show
  1. package/dist/app/createStorefrontApp.d.ts.map +1 -1
  2. package/dist/app/prerender.d.ts.map +1 -1
  3. package/dist/app/serving.d.ts +2 -0
  4. package/dist/app/serving.d.ts.map +1 -1
  5. package/dist/blocks/page-data.d.ts.map +1 -1
  6. package/dist/{chunk-STL6MATQ.js → chunk-77V5ZAF5.js} +1861 -273
  7. package/dist/chunk-77V5ZAF5.js.map +1 -0
  8. package/dist/components/cart/CartDrawer.d.ts.map +1 -1
  9. package/dist/components/checkout/CheckoutSteps.d.ts +10 -1
  10. package/dist/components/checkout/CheckoutSteps.d.ts.map +1 -1
  11. package/dist/components/checkout/DeliveryEstimator.d.ts.map +1 -1
  12. package/dist/components/delivery/AddressSheet.d.ts +22 -0
  13. package/dist/components/delivery/AddressSheet.d.ts.map +1 -0
  14. package/dist/components/delivery/AddressStrip.d.ts +18 -0
  15. package/dist/components/delivery/AddressStrip.d.ts.map +1 -0
  16. package/dist/components/delivery/ItemSheet.d.ts +42 -0
  17. package/dist/components/delivery/ItemSheet.d.ts.map +1 -0
  18. package/dist/components/delivery/MenuItemRow.d.ts +29 -0
  19. package/dist/components/delivery/MenuItemRow.d.ts.map +1 -0
  20. package/dist/components/delivery/MenuToolbar.d.ts +29 -0
  21. package/dist/components/delivery/MenuToolbar.d.ts.map +1 -0
  22. package/dist/components/delivery/ScheduleSheet.d.ts +14 -0
  23. package/dist/components/delivery/ScheduleSheet.d.ts.map +1 -0
  24. package/dist/components/delivery/ServiceStatus.d.ts +15 -0
  25. package/dist/components/delivery/ServiceStatus.d.ts.map +1 -0
  26. package/dist/components/delivery/StoreCover.d.ts +22 -0
  27. package/dist/components/delivery/StoreCover.d.ts.map +1 -0
  28. package/dist/components/delivery/store-time.d.ts +15 -0
  29. package/dist/components/delivery/store-time.d.ts.map +1 -0
  30. package/dist/components/layout/StorefrontHeader.d.ts +4 -1
  31. package/dist/components/layout/StorefrontHeader.d.ts.map +1 -1
  32. package/dist/components/layout/Toaster.d.ts.map +1 -1
  33. package/dist/components/marketing/FormBlock.d.ts.map +1 -1
  34. package/dist/components/product/ProductEnquiryForm.d.ts.map +1 -1
  35. package/dist/document/config.d.ts +101 -2
  36. package/dist/document/config.d.ts.map +1 -1
  37. package/dist/document/round-trip.d.ts.map +1 -1
  38. package/dist/document/sections.d.ts +2 -2
  39. package/dist/document/sections.d.ts.map +1 -1
  40. package/dist/document/upgrade.d.ts.map +1 -1
  41. package/dist/extend/component-contracts.d.ts +7 -1
  42. package/dist/extend/component-contracts.d.ts.map +1 -1
  43. package/dist/hooks/use-address.d.ts.map +1 -1
  44. package/dist/hooks/use-checkout.d.ts.map +1 -1
  45. package/dist/hooks/use-order.d.ts.map +1 -1
  46. package/dist/hooks/use-products.d.ts.map +1 -1
  47. package/dist/index.d.ts +11 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +11 -17
  50. package/dist/index.js.map +1 -1
  51. package/dist/lib/testids.d.ts +36 -0
  52. package/dist/lib/testids.d.ts.map +1 -1
  53. package/dist/render.js +3 -3
  54. package/dist/render.js.map +1 -1
  55. package/dist/stores/cart.store.d.ts +22 -1
  56. package/dist/stores/cart.store.d.ts.map +1 -1
  57. package/dist/stores/delivery.store.d.ts +64 -1
  58. package/dist/stores/delivery.store.d.ts.map +1 -1
  59. package/dist/stores/service.store.d.ts +35 -0
  60. package/dist/stores/service.store.d.ts.map +1 -0
  61. package/dist/stores/toast.store.d.ts +14 -0
  62. package/dist/stores/toast.store.d.ts.map +1 -1
  63. package/dist/views/CheckoutPage.d.ts.map +1 -1
  64. package/dist/views/MenuPage.d.ts +23 -0
  65. package/dist/views/MenuPage.d.ts.map +1 -0
  66. package/dist/workflows/checkout.d.ts +15 -2
  67. package/dist/workflows/checkout.d.ts.map +1 -1
  68. package/package.json +6 -6
  69. package/dist/chunk-STL6MATQ.js.map +0 -1
@@ -3,11 +3,11 @@ 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
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
- import React4, { createContext, useContext, useEffect, useRef, useState, useSyncExternalStore, useMemo, useCallback } from 'react';
6
+ import { normalizePostalCode, lookupPostalCode, defaultRouterAdapter, defineLoader, staticRouterAdapter, isLoaderParamRef, isLoaderPropRef, resolveLoader, errorMessage, 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
+ import React37, { createContext, useContext, useEffect, useRef, useState, useSyncExternalStore, useMemo, useCallback } from 'react';
8
8
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
9
  import { isStorageImageUrl, imageVariantUrl, imageSrcSet, IMAGE_SRCSET_WIDTHS, validateDiscount, documentDigits, isChargeableProvider, supportsCard, formatDocument, isValidDocument, gatewayLabel } from '@fayz-ai/shop';
10
- 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
+ import { ChevronLeft, ChevronRight, ShoppingBag, X, Minus, Plus, Send, MessageCircle, Mail, Truck, RefreshCcw, ShieldCheck, ChevronDown, Check, Search, Menu, Trash2, Lock, CreditCard, Store, Bike, Clock, MapPin, Star, Phone, User, UserCircle, Package, LogOut, QrCode, Copy, XCircle, RotateCcw, PackageCheck, Info, AlertCircle } from 'lucide-react';
11
11
  import { getLucideIcon } from '@fayz-ai/ui/icons';
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';
@@ -231,6 +231,13 @@ function productOptionSelectionKey(selection) {
231
231
  const entries = Object.entries(normalized);
232
232
  return entries.length > 0 ? JSON.stringify(entries) : "";
233
233
  }
234
+ var HINT_TIMEOUT_MS = 1200;
235
+ var MAX_SAVED_ADDRESSES = 8;
236
+ function newAddressId() {
237
+ const c = globalThis.crypto;
238
+ if (c && typeof c.randomUUID === "function") return c.randomUUID();
239
+ return `addr-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
240
+ }
234
241
  var EMPTY = {
235
242
  postalCode: "",
236
243
  address: null,
@@ -238,12 +245,24 @@ var EMPTY = {
238
245
  selectedZoneId: null,
239
246
  status: "idle",
240
247
  error: null,
241
- refusedZone: null
248
+ refusedZone: null,
249
+ reviewZone: null
242
250
  };
243
251
  var useDeliveryStore = create()(
244
252
  persist(
245
253
  (set, get) => ({
246
254
  ...EMPTY,
255
+ // 'delivery' é o default porque é o que toda loja anterior à 0094 é, e o
256
+ // que uma loja sem retirada continua sendo.
257
+ fulfillmentType: "delivery",
258
+ scheduledFor: null,
259
+ setFulfillmentType(type) {
260
+ if (get().fulfillmentType === type) return;
261
+ set({ fulfillmentType: type, scheduledFor: null });
262
+ },
263
+ setScheduledFor(startsAt) {
264
+ set({ scheduledFor: startsAt });
265
+ },
247
266
  async resolve(postalCode, subtotal) {
248
267
  const code = normalizePostalCode(postalCode);
249
268
  if (code.length !== 8) {
@@ -252,10 +271,17 @@ var useDeliveryStore = create()(
252
271
  }
253
272
  set({ postalCode: code, status: "loading", error: null });
254
273
  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);
274
+ const addressPromise = lookupPostalCode(code).catch(() => null);
275
+ const hint = await Promise.race([
276
+ addressPromise.then((a) => a ? { district: a.district, city: a.city, state: a.state, cityCode: a.cityCode } : void 0),
277
+ new Promise((resolve) => {
278
+ setTimeout(() => resolve(void 0), HINT_TIMEOUT_MS);
279
+ })
280
+ ]);
281
+ const checkPromise = shop.checkDelivery ? shop.checkDelivery(code, hint).catch(() => null) : Promise.resolve(null);
256
282
  let options = [];
257
283
  try {
258
- options = await shop.quoteShipping?.(code, subtotal) ?? [];
284
+ options = await shop.quoteShipping?.(code, subtotal, hint) ?? [];
259
285
  } catch {
260
286
  set({
261
287
  ...EMPTY,
@@ -265,22 +291,19 @@ var useDeliveryStore = create()(
265
291
  });
266
292
  return;
267
293
  }
268
- const refusedZone = await refusalPromise;
294
+ const check = await checkPromise;
295
+ const refusedZone = check?.refused ? check.refusedZone || "Regi\xE3o n\xE3o atendida" : null;
296
+ const reviewZone = !refusedZone && check?.outcome === "review" ? check.zoneName || "Regi\xE3o a confirmar" : null;
269
297
  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
- }
298
+ const address = await addressPromise;
299
+ const notFound = address === null;
278
300
  set({
279
301
  postalCode: code,
280
302
  address,
281
303
  options,
282
304
  selectedZoneId: options[0]?.zoneId ?? null,
283
305
  refusedZone,
306
+ reviewZone,
284
307
  // A store with no zones configured quotes nothing, and that must NOT
285
308
  // read as "we don't deliver here" — it falls back to the store-wide
286
309
  // flat rate, which the cart already knows how to show.
@@ -298,6 +321,37 @@ var useDeliveryStore = create()(
298
321
  },
299
322
  clear() {
300
323
  set({ ...EMPTY });
324
+ },
325
+ savedAddresses: [],
326
+ selectedAddressId: null,
327
+ saveAddress(input) {
328
+ const id = newAddressId();
329
+ const entry = { ...input, id, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
330
+ set((state) => ({
331
+ // Mesmo CEP + mesmo número é o MESMO endereço com o complemento
332
+ // corrigido, não um segundo. Sem isto, três tentativas de acertar o
333
+ // apartamento viram três linhas idênticas na lista.
334
+ savedAddresses: [
335
+ entry,
336
+ ...state.savedAddresses.filter(
337
+ (a) => normalizePostalCode(a.postalCode) !== normalizePostalCode(entry.postalCode) || a.number.trim().toLowerCase() !== entry.number.trim().toLowerCase()
338
+ )
339
+ ].slice(0, MAX_SAVED_ADDRESSES),
340
+ selectedAddressId: id
341
+ }));
342
+ return id;
343
+ },
344
+ async selectAddress(id, subtotal) {
345
+ const entry = get().savedAddresses.find((a) => a.id === id);
346
+ if (!entry) return;
347
+ set({ selectedAddressId: id });
348
+ await get().resolve(entry.postalCode, subtotal);
349
+ },
350
+ removeAddress(id) {
351
+ set((state) => ({
352
+ savedAddresses: state.savedAddresses.filter((a) => a.id !== id),
353
+ selectedAddressId: state.selectedAddressId === id ? null : state.selectedAddressId
354
+ }));
301
355
  }
302
356
  }),
303
357
  {
@@ -305,7 +359,21 @@ var useDeliveryStore = create()(
305
359
  // Deliberately NOT persisting `options`: a rehydrated
306
360
  // price would be shown as current without anything having quoted it. The
307
361
  // address survives, the money is asked for again.
308
- partialize: (state) => ({ postalCode: state.postalCode, address: state.address }),
362
+ // `fulfillmentType` acompanha o endereço e pela mesma razão: é uma
363
+ // preferência ("eu sempre busco na loja"), e ela vale amanhã.
364
+ // `scheduledFor` NÃO acompanha, pela mesma razão de `options`: um horário
365
+ // escolhido ontem é um horário que já passou, e restaurá-lo mandaria o
366
+ // comprador ao checkout com uma janela que o servidor vai recusar.
367
+ partialize: (state) => ({
368
+ postalCode: state.postalCode,
369
+ address: state.address,
370
+ fulfillmentType: state.fulfillmentType,
371
+ // O caderno é o ponto: número e complemento são o que o comprador
372
+ // digita de novo a cada visita, e o que nenhuma consulta de CEP
373
+ // devolve. Sem persistir, salvar endereço não salva nada.
374
+ savedAddresses: state.savedAddresses,
375
+ selectedAddressId: state.selectedAddressId
376
+ }),
309
377
  onRehydrateStorage: () => (state) => {
310
378
  if (state?.address) state.status = "idle";
311
379
  }
@@ -332,10 +400,12 @@ var useCartStore = create()(
332
400
  discountFreeShipping: false,
333
401
  isOpen: false,
334
402
  justAddedLineId: null,
335
- addItem: (product, qty = 1, options) => set((state) => {
403
+ addItem: (product, qty = 1, options, notes, opts) => set((state) => {
336
404
  const normalizedOptions = normalizeProductOptionSelection(options);
337
405
  const optionKey = productOptionSelectionKey(normalizedOptions);
338
- const lineId = optionKey ? `${product.id}:${optionKey}` : product.id;
406
+ const cleanNotes = notes?.trim().slice(0, 250) || void 0;
407
+ const baseId = optionKey ? `${product.id}:${optionKey}` : product.id;
408
+ const lineId = cleanNotes ? `${baseId}#${cleanNotes}` : baseId;
339
409
  const variant = resolveVariantForSelection(product, normalizedOptions);
340
410
  const unitPrice = variant?.price ?? product.price;
341
411
  const stock = variant ? variant.inventoryCount : product.inventoryCount;
@@ -345,7 +415,7 @@ var useCartStore = create()(
345
415
  return {
346
416
  // Auto-open the cart on add — the standard premium "slide-in cart"
347
417
  // signal — and flag the line so the drawer can highlight it.
348
- isOpen: true,
418
+ isOpen: opts?.openDrawer ?? true,
349
419
  justAddedLineId: lineId,
350
420
  lines: state.lines.map(
351
421
  (l) => (l.lineId ?? l.productId) === lineId ? { ...l, lineId, quantity: nextQty } : l
@@ -372,9 +442,14 @@ var useCartStore = create()(
372
442
  heightCm: variant?.heightCm ?? product.heightCm ?? null,
373
443
  widthCm: variant?.widthCm ?? product.widthCm ?? null,
374
444
  lengthCm: variant?.lengthCm ?? product.lengthCm ?? null,
375
- packageId: product.packageId ?? null
445
+ packageId: product.packageId ?? null,
446
+ notes: cleanNotes
447
+ };
448
+ return {
449
+ isOpen: opts?.openDrawer ?? true,
450
+ justAddedLineId: lineId,
451
+ lines: [...state.lines, line]
376
452
  };
377
- return { isOpen: true, justAddedLineId: lineId, lines: [...state.lines, line] };
378
453
  }),
379
454
  setQuantity: (lineId, qty) => set((state) => {
380
455
  if (qty <= 0) return { lines: state.lines.filter((l) => (l.lineId ?? l.productId) !== lineId) };
@@ -737,7 +812,7 @@ function resolveConfig(config) {
737
812
  const homePage = config.pages?.home ?? (config.home ? { blocks: config.home.sections } : void 0);
738
813
  const catalogPath = homePage ? "/catalog" : "/";
739
814
  const commerceMode = config.commerceMode ?? "checkout";
740
- const checkoutEnabled = commerceMode === "checkout";
815
+ const checkoutEnabled = commerceMode === "checkout" || commerceMode === "delivery";
741
816
  return {
742
817
  ...config,
743
818
  pages: { ...config.pages, ...homePage ? { home: homePage } : {} },
@@ -753,6 +828,22 @@ function resolveConfig(config) {
753
828
  terms: config.payments?.terms
754
829
  },
755
830
  commerceMode,
831
+ // Só existe no modo delivery. Fora dele o objeto inteiro é undefined, e é
832
+ // isso que a rota /menu consulta para decidir se existe.
833
+ delivery: commerceMode === "delivery" ? {
834
+ menuPath: config.delivery?.menuPath ?? "/menu",
835
+ coverImage: config.delivery?.coverImage,
836
+ logoImage: config.delivery?.logoImage,
837
+ locality: config.delivery?.locality,
838
+ highlightSlugs: config.delivery?.highlightSlugs,
839
+ // A ORDEM é a do seletor na tela. Um Set aqui perderia isso.
840
+ fulfillmentModes: config.delivery?.fulfillmentModes?.length ? config.delivery.fulfillmentModes : ["delivery"].slice(),
841
+ // Agendamento é opt-in duas vezes: aqui e na loja (0094 devolve zero
842
+ // slots quando scheduling_enabled é falso). A tela só oferece quando
843
+ // as duas concordam.
844
+ scheduling: config.delivery?.scheduling ?? false,
845
+ pickupAddress: config.delivery?.pickupAddress
846
+ } : void 0,
756
847
  enquiry: {
757
848
  label: config.enquiry?.label ?? "Contact me",
758
849
  successMessage: config.enquiry?.successMessage ?? "Thanks. We received your enquiry.",
@@ -974,7 +1065,7 @@ function Link({ to, children, onClick, target, ...rest }) {
974
1065
  event.preventDefault();
975
1066
  adapter.navigate(to);
976
1067
  };
977
- return React4.createElement(
1068
+ return React37.createElement(
978
1069
  "a",
979
1070
  { href: adapter.href(to), onClick: handleClick, target, ...rest },
980
1071
  children
@@ -1093,6 +1184,43 @@ var TID = {
1093
1184
  cartTotal: "cart-total",
1094
1185
  goCheckout: "go-checkout",
1095
1186
  cartEmpty: "cart-empty",
1187
+ // menu / delivery mode (0094)
1188
+ menuPage: "menu-page",
1189
+ menuSection: (slug) => `menu-section-${slug}`,
1190
+ menuItem: (slug) => `menu-item-${slug}`,
1191
+ menuQuickAdd: (slug) => `menu-quick-add-${slug}`,
1192
+ devPlaceOrderWithoutCharge: "dev-place-order-without-charge",
1193
+ menuCartBar: "menu-cart-bar",
1194
+ menuItemSoldOut: "menu-sold-out",
1195
+ itemSheet: "item-sheet",
1196
+ itemSheetNotes: "item-sheet-notes",
1197
+ itemSheetNotesCount: "item-sheet-notes-count",
1198
+ itemSheetQty: "item-sheet-qty",
1199
+ itemSheetAdd: "item-sheet-add",
1200
+ serviceStatus: "service-status",
1201
+ serviceHoursModal: "service-hours-modal",
1202
+ serviceClosedGate: "service-closed-gate",
1203
+ fulfillmentMode: (mode) => `fulfillment-mode-${mode}`,
1204
+ scheduleSheet: "schedule-sheet",
1205
+ scheduleSlot: (iso) => `schedule-slot-${iso}`,
1206
+ storeCover: "store-cover",
1207
+ menuSearch: "menu-search",
1208
+ menuCategoryNav: "menu-category-nav",
1209
+ menuCategoryLink: (slug) => `menu-category-link-${slug}`,
1210
+ menuHighlights: "menu-highlights",
1211
+ menuItemBadge: (slug) => `menu-item-badge-${slug}`,
1212
+ deliveryTabBar: "delivery-tab-bar",
1213
+ deliveryTab: (key) => `delivery-tab-${key}`,
1214
+ addressStrip: "address-strip",
1215
+ addressSheet: "address-sheet",
1216
+ addressCep: "address-cep",
1217
+ addressNumber: "address-number",
1218
+ addressComplement: "address-complement",
1219
+ addressReference: "address-reference",
1220
+ addressSave: "address-save",
1221
+ addressAddNew: "address-add-new",
1222
+ savedAddress: (id) => `saved-address-${id}`,
1223
+ itemSheetGroup: (label) => `item-sheet-group-${label}`,
1096
1224
  // delivery estimate (product page + cart)
1097
1225
  deliveryEstimator: "delivery-estimator",
1098
1226
  deliveryCepInput: "delivery-cep-input",
@@ -1177,9 +1305,9 @@ function SmoothImage({
1177
1305
  const easing = resolved.easing ?? "cubic-bezier(0.22, 1, 0.36, 1)";
1178
1306
  const blur = resolved.blur ?? true;
1179
1307
  const transform = resolved.transform ?? "none";
1180
- const ref = React4.useRef(null);
1181
- const [loaded, setLoaded] = React4.useState(() => mode === "none" || !clientMounted);
1182
- const delivered = React4.useMemo(() => {
1308
+ const ref = React37.useRef(null);
1309
+ const [loaded, setLoaded] = React37.useState(() => mode === "none" || !clientMounted);
1310
+ const delivered = React37.useMemo(() => {
1183
1311
  if (transform !== "supabase" || !src || srcSet || !isStorageImageUrl(src)) return { src, srcSet, sizes };
1184
1312
  if (variant?.width) {
1185
1313
  const w = variant.width;
@@ -1197,10 +1325,10 @@ function SmoothImage({
1197
1325
  sizes: variant?.sizes ?? sizes ?? "100vw"
1198
1326
  };
1199
1327
  }, [transform, src, srcSet, sizes, variant?.width, variant?.sizes, variant?.widths, variant?.quality]);
1200
- React4.useEffect(() => {
1328
+ React37.useEffect(() => {
1201
1329
  clientMounted = true;
1202
1330
  }, []);
1203
- React4.useEffect(() => {
1331
+ React37.useEffect(() => {
1204
1332
  if (mode === "none") {
1205
1333
  setLoaded(true);
1206
1334
  return;
@@ -1556,7 +1684,7 @@ async function resolvePageDataNeed(need) {
1556
1684
  const data = await impl(need.params, { shop: getShopProvider() });
1557
1685
  return { status: "ready", data };
1558
1686
  } catch (err) {
1559
- const message = err instanceof Error ? err.message : String(err);
1687
+ const message = errorMessage(err);
1560
1688
  console.error(`[storefront] loader "${need.loader}" failed:`, err);
1561
1689
  return { status: "error", error: message };
1562
1690
  }
@@ -2468,8 +2596,8 @@ var BADGE_TONE_CLASSES = {
2468
2596
  warning: "bg-amber-500 text-white"
2469
2597
  };
2470
2598
  function useExtensionBadges(product) {
2471
- const [badges, setBadges] = React4.useState([]);
2472
- React4.useEffect(() => {
2599
+ const [badges, setBadges] = React37.useState([]);
2600
+ React37.useEffect(() => {
2473
2601
  let cancelled = false;
2474
2602
  getProductCardBadges(product).then((next) => {
2475
2603
  if (!cancelled && next.length > 0) setBadges(next);
@@ -3665,7 +3793,7 @@ function scalarToString(value) {
3665
3793
  }
3666
3794
  function ProductSpecs({ product }) {
3667
3795
  const config = useStorefrontConfig();
3668
- const extraExcluded = React4.useMemo(
3796
+ const extraExcluded = React37.useMemo(
3669
3797
  () => new Set(config.productSpecs?.exclude ?? []),
3670
3798
  [config.productSpecs?.exclude]
3671
3799
  );
@@ -3772,7 +3900,7 @@ function useProducts(opts) {
3772
3900
  setProducts(data);
3773
3901
  setError(null);
3774
3902
  }).catch((e) => {
3775
- if (!cancelled) setError(e instanceof Error ? e.message : String(e));
3903
+ if (!cancelled) setError(errorMessage(e));
3776
3904
  }).finally(() => {
3777
3905
  if (!cancelled) setLoading(false);
3778
3906
  });
@@ -3825,7 +3953,7 @@ function ProductEnquiryForm({ product, onSuccess }) {
3825
3953
  onSuccess?.();
3826
3954
  } catch (e) {
3827
3955
  setStatus("error");
3828
- setError(e instanceof Error ? e.message : String(e));
3956
+ setError(errorMessage(e));
3829
3957
  }
3830
3958
  };
3831
3959
  return /* @__PURE__ */ jsxs(
@@ -4201,7 +4329,8 @@ function DeliveryEstimator({ compact: compact2 = false }) {
4201
4329
  }
4202
4330
  ) }, option.zoneId);
4203
4331
  }) }),
4204
- delivery.status === "unserved" && /* @__PURE__ */ jsx("p", { "data-testid": TID.deliveryUnserved, className: "mt-3 text-sm text-destructive", children: "Ainda n\xE3o entregamos nesse CEP." }),
4332
+ delivery.status === "unserved" && /* @__PURE__ */ jsx("p", { "data-testid": TID.deliveryUnserved, className: "mt-3 text-sm text-destructive", children: delivery.refusedZone ? "Esta loja n\xE3o entrega nesse endere\xE7o." : "Ainda n\xE3o entregamos nesse CEP." }),
4333
+ delivery.reviewZone && delivery.status === "served" && /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm text-amber-600 dark:text-amber-500", children: "Entregamos nessa regi\xE3o, mas confirmamos com voc\xEA antes de sair." }),
4205
4334
  delivery.postalCode && /* @__PURE__ */ jsx(
4206
4335
  "button",
4207
4336
  {
@@ -4536,7 +4665,7 @@ function FormBlock({
4536
4665
  setValues({});
4537
4666
  } catch (e) {
4538
4667
  setStatus("error");
4539
- setError(e instanceof Error ? e.message : String(e));
4668
+ setError(errorMessage(e));
4540
4669
  }
4541
4670
  };
4542
4671
  return /* @__PURE__ */ jsxs("section", { "data-testid": TID.formBlock, className: "mx-auto max-w-2xl px-4 py-14 sm:px-6", children: [
@@ -5316,7 +5445,7 @@ function CollectionHero({
5316
5445
  {
5317
5446
  className: `absolute inset-0 flex flex-col justify-center p-6 text-white sm:p-12 ${OVERLAY_CLASS[overlay]} ${centered ? "items-center text-center" : "items-start text-left"}`,
5318
5447
  children: /* @__PURE__ */ jsxs(Reveal, { className: centered ? "max-w-3xl" : "max-w-2xl", children: [
5319
- logos && logos.length > 0 && /* @__PURE__ */ jsx("div", { className: `mb-6 flex items-center gap-5 ${centered ? "justify-center" : ""}`, children: logos.map((logo, i) => /* @__PURE__ */ jsxs(React4.Fragment, { children: [
5448
+ logos && logos.length > 0 && /* @__PURE__ */ jsx("div", { className: `mb-6 flex items-center gap-5 ${centered ? "justify-center" : ""}`, children: logos.map((logo, i) => /* @__PURE__ */ jsxs(React37.Fragment, { children: [
5320
5449
  i > 0 && /* @__PURE__ */ jsx("span", { className: "text-2xl font-light opacity-60", "aria-hidden": true, children: "\xD7" }),
5321
5450
  /* @__PURE__ */ jsx("img", { src: logo.src, alt: logo.alt, className: "h-10 w-auto object-contain sm:h-14" })
5322
5451
  ] }, logo.src)) }),
@@ -6073,8 +6202,8 @@ function StorefrontConfigHost({
6073
6202
  config,
6074
6203
  children
6075
6204
  }) {
6076
- const [policy, setPolicy] = React4.useState(null);
6077
- React4.useEffect(() => {
6205
+ const [policy, setPolicy] = React37.useState(null);
6206
+ React37.useEffect(() => {
6078
6207
  let cancelled = false;
6079
6208
  let provider;
6080
6209
  try {
@@ -6093,7 +6222,7 @@ function StorefrontConfigHost({
6093
6222
  cancelled = true;
6094
6223
  };
6095
6224
  }, [config]);
6096
- const resolved = React4.useMemo(() => applyStoreSettings(config, policy), [config, policy]);
6225
+ const resolved = React37.useMemo(() => applyStoreSettings(config, policy), [config, policy]);
6097
6226
  return /* @__PURE__ */ jsx(StorefrontConfigProvider, { value: resolved, children });
6098
6227
  }
6099
6228
  var RADIUS_MAP = {
@@ -6213,7 +6342,7 @@ function StorefrontThemeStyle({
6213
6342
  document.getElementById(linkId)?.remove();
6214
6343
  };
6215
6344
  }, [fontsHref, linkId, selector]);
6216
- return React4.createElement(
6345
+ return React37.createElement(
6217
6346
  "style",
6218
6347
  { "data-sf-theme": theme.name },
6219
6348
  themeToCss(theme, { selector })
@@ -6312,7 +6441,7 @@ function SearchOverlay({ onClose }) {
6312
6441
  "\u201D."
6313
6442
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
6314
6443
  /* @__PURE__ */ jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4", children: suggestions.map(
6315
- (product) => searchConfig.renderSuggestion ? /* @__PURE__ */ jsx(React4.Fragment, { children: searchConfig.renderSuggestion(product) }, product.id) : /* @__PURE__ */ jsx(SuggestionCard, { product, onNavigate: onClose }, product.id)
6444
+ (product) => searchConfig.renderSuggestion ? /* @__PURE__ */ jsx(React37.Fragment, { children: searchConfig.renderSuggestion(product) }, product.id) : /* @__PURE__ */ jsx(SuggestionCard, { product, onNavigate: onClose }, product.id)
6316
6445
  ) }),
6317
6446
  /* @__PURE__ */ jsx(
6318
6447
  "button",
@@ -6518,11 +6647,11 @@ function HeaderActions({ onMenuClick }) {
6518
6647
  const openDrawer = useCartStore((s2) => s2.openDrawer);
6519
6648
  const session = useSessionStore();
6520
6649
  const [accountOpen, setAccountOpen] = useState(false);
6521
- const accountRef = React4.useRef(null);
6650
+ const accountRef = React37.useRef(null);
6522
6651
  const pop = usePopOnChange(count);
6523
6652
  const firstName = session.name?.split(/\s+/)[0];
6524
6653
  const initial2 = (firstName ?? session.email ?? "C").slice(0, 1).toUpperCase();
6525
- React4.useEffect(() => {
6654
+ React37.useEffect(() => {
6526
6655
  if (!accountOpen) return;
6527
6656
  const onPointerDown = (event) => {
6528
6657
  if (!accountRef.current?.contains(event.target)) setAccountOpen(false);
@@ -6689,7 +6818,7 @@ function MobileMenuDrawer({ open, onClose }) {
6689
6818
  navigateTo(config.catalogPath);
6690
6819
  onClose();
6691
6820
  };
6692
- React4.useEffect(() => {
6821
+ React37.useEffect(() => {
6693
6822
  if (!open) return;
6694
6823
  const onKey = (e) => {
6695
6824
  if (e.key === "Escape") onClose();
@@ -6759,8 +6888,9 @@ function MobileMenuDrawer({ open, onClose }) {
6759
6888
  )
6760
6889
  ] });
6761
6890
  }
6762
- function StorefrontHeader() {
6891
+ function StorefrontHeader({ chrome } = {}) {
6763
6892
  const config = useStorefrontConfig();
6893
+ const compact2 = chrome === "minimal";
6764
6894
  const variant = config.theme?.header?.variant ?? "classic";
6765
6895
  const scrolled = useScrolled();
6766
6896
  const showSearch = config.theme?.header?.showSearch !== false;
@@ -6774,8 +6904,27 @@ function StorefrontHeader() {
6774
6904
  style: headerStyle,
6775
6905
  children: [
6776
6906
  /* @__PURE__ */ jsx(AnnouncementBar, {}),
6777
- /* @__PURE__ */ jsx(UtilityBar, {}),
6778
- variant === "centered" ? (
6907
+ !compact2 && /* @__PURE__ */ jsx(UtilityBar, {}),
6908
+ compact2 ? (
6909
+ // O cabeçalho de um app de pedido: marca, busca, carrinho — e o resto da
6910
+ // navegação atrás do menu, alcançável e fora do caminho.
6911
+ /* @__PURE__ */ jsxs("div", { className: "mx-auto flex h-14 max-w-7xl items-center gap-3 px-4 sm:px-6", children: [
6912
+ /* @__PURE__ */ jsx(
6913
+ "button",
6914
+ {
6915
+ type: "button",
6916
+ "aria-label": "Abrir menu",
6917
+ "aria-expanded": mobileMenuOpen,
6918
+ onClick: () => setMobileMenuOpen((v) => !v),
6919
+ className: "-ml-2 grid h-11 w-11 shrink-0 place-items-center rounded-full",
6920
+ children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
6921
+ }
6922
+ ),
6923
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 truncate", children: logo }),
6924
+ showSearch && /* @__PURE__ */ jsx(SearchInput, { iconOnly: true, className: "shrink-0" }),
6925
+ /* @__PURE__ */ jsx(HeaderActions, {})
6926
+ ] })
6927
+ ) : variant === "centered" ? (
6779
6928
  // Rio/Flex pattern: centered logo row, nav row below
6780
6929
  /* @__PURE__ */ jsxs(Fragment, { children: [
6781
6930
  /* @__PURE__ */ jsxs("div", { className: "mx-auto grid h-16 max-w-7xl grid-cols-[1fr_auto_1fr] items-center px-4 sm:px-6", children: [
@@ -6995,7 +7144,8 @@ var useToastStore = create((set) => ({
6995
7144
  title: input.title,
6996
7145
  description: input.description,
6997
7146
  variant: input.variant ?? "info",
6998
- durationMs: input.durationMs ?? 3400
7147
+ durationMs: input.durationMs ?? 3400,
7148
+ action: input.action
6999
7149
  };
7000
7150
  set((s2) => ({ toasts: [...s2.toasts, toast2] }));
7001
7151
  return id;
@@ -7126,7 +7276,7 @@ function CartDrawer() {
7126
7276
  "div",
7127
7277
  {
7128
7278
  "data-testid": TID.cartLinesList,
7129
- className: "flex-1 space-y-4 overflow-y-auto py-4 pl-5 pr-6",
7279
+ className: "min-h-0 flex-1 space-y-4 overflow-y-auto py-4 pl-5 pr-6",
7130
7280
  style: { scrollbarGutter: "stable" },
7131
7281
  children: [
7132
7282
  /* @__PURE__ */ jsx(Slot, { area: "cart", name: "beforeLines", context: cartSlotContext }),
@@ -7156,6 +7306,11 @@ function CartDrawer() {
7156
7306
  ] }),
7157
7307
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: line.sku }),
7158
7308
  line.optionsLabel && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: line.optionsLabel }),
7309
+ line.notes && /* @__PURE__ */ jsxs("span", { className: "text-xs italic text-muted-foreground", children: [
7310
+ "\u201C",
7311
+ line.notes,
7312
+ "\u201D"
7313
+ ] }),
7159
7314
  /* @__PURE__ */ jsx(CartLineAnnotations, { line }),
7160
7315
  /* @__PURE__ */ jsxs("div", { className: "mt-auto flex items-center justify-between pt-2", children: [
7161
7316
  /* @__PURE__ */ jsx(
@@ -7190,7 +7345,7 @@ function CartDrawer() {
7190
7345
  "div",
7191
7346
  {
7192
7347
  "data-testid": TID.cartFooter,
7193
- className: "space-y-3 border-t px-5 py-4",
7348
+ className: "max-h-[55%] shrink-0 space-y-3 overflow-y-auto border-t px-5 py-4",
7194
7349
  children: [
7195
7350
  config.features.discounts && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
7196
7351
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
@@ -7333,6 +7488,7 @@ function resolveStorefrontRoute(config, rawPath) {
7333
7488
  if (matchPath("/checkout", path)) return { kind: "checkout" };
7334
7489
  if (matchPath("/account", path)) return { kind: "account" };
7335
7490
  if (matchPath("/catalog", path)) return { kind: "catalog" };
7491
+ if (resolved.delivery && matchPath(resolved.delivery.menuPath, path)) return { kind: "menu" };
7336
7492
  if (resolved.search.mode === "overlay" && matchPath(resolved.search.resultsPath, path)) {
7337
7493
  return { kind: "searchResults" };
7338
7494
  }
@@ -7347,7 +7503,7 @@ function resolveStorefrontRoute(config, rawPath) {
7347
7503
  function isKnownPath(config, path) {
7348
7504
  return resolveStorefrontRoute(config, path).kind !== "notFound";
7349
7505
  }
7350
- var STATIC_PATTERNS = ["/", "/catalog", "/privacy", "/terms", "/refunds"];
7506
+ var STATIC_PATTERNS = ["/", "/catalog", "/menu", "/privacy", "/terms", "/refunds"];
7351
7507
  function ensureResolved(config) {
7352
7508
  return "features" in config && "pages" in config ? config : resolveConfig(config);
7353
7509
  }
@@ -7401,7 +7557,7 @@ function listLocalOrderIds() {
7401
7557
  }
7402
7558
 
7403
7559
  // src/lib/errors.ts
7404
- function errorMessage(err) {
7560
+ function errorMessage5(err) {
7405
7561
  if (err instanceof Error) return err.message;
7406
7562
  if (err && typeof err === "object" && typeof err.message === "string") {
7407
7563
  return err.message;
@@ -7418,7 +7574,7 @@ function formatDeliveryNotes(address, shippingAddress) {
7418
7574
  return parts.length > 0 ? `Entrega: ${parts.join(", ")}` : void 0;
7419
7575
  }
7420
7576
  function isStaleCustomerError(err) {
7421
- const message = errorMessage(err).toLowerCase();
7577
+ const message = errorMessage5(err).toLowerCase();
7422
7578
  return message.includes("customer email mismatch") || message.includes("customer does not belong to this store") || message.includes("customer belongs to another account");
7423
7579
  }
7424
7580
  async function placeStorefrontOrder({
@@ -7428,6 +7584,8 @@ async function placeStorefrontOrder({
7428
7584
  customer,
7429
7585
  address,
7430
7586
  shippingAddress,
7587
+ fulfillmentType,
7588
+ scheduledFor,
7431
7589
  paymentMethod,
7432
7590
  markPaid = false
7433
7591
  }) {
@@ -7446,9 +7604,12 @@ async function placeStorefrontOrder({
7446
7604
  // from it and decrements ITS stock, so two sizes never share a counter.
7447
7605
  variantId: line.variantId || void 0,
7448
7606
  quantity: line.quantity,
7449
- optionsLabel: line.optionsLabel || void 0
7607
+ optionsLabel: line.optionsLabel || void 0,
7608
+ // A observação da linha (0094). Vai por item porque é por item: duas
7609
+ // unidades do mesmo prato com pedidos diferentes são duas linhas.
7610
+ notes: line.notes || void 0
7450
7611
  }));
7451
- const shippingTotal = selectShipping(cart, config);
7612
+ const shippingTotal = fulfillmentType === "pickup" ? 0 : selectShipping(cart, config);
7452
7613
  const replaced = await runBeforePlaceOrderValve({
7453
7614
  items,
7454
7615
  customer: { name, email },
@@ -7481,6 +7642,8 @@ async function placeStorefrontOrder({
7481
7642
  shippingAddress,
7482
7643
  paymentMethod,
7483
7644
  metadata,
7645
+ fulfillmentType,
7646
+ scheduledFor,
7484
7647
  items
7485
7648
  });
7486
7649
  let order;
@@ -7649,47 +7812,61 @@ var LABELS2 = {
7649
7812
  3: "Pagamento",
7650
7813
  4: "Pronto"
7651
7814
  };
7652
- function CheckoutSteps({ current, onGoBackTo }) {
7653
- const steps = [1, 2, 3, 4];
7815
+ function CheckoutSteps({ current, onGoBackTo, omit }) {
7816
+ const steps = [1, 2, 3, 4].filter((s2) => !omit?.includes(s2));
7654
7817
  return /* @__PURE__ */ jsxs("nav", { "data-testid": TID.checkoutSteps, "aria-label": "Etapas do checkout", className: "mb-8", children: [
7655
7818
  /* @__PURE__ */ jsx("ol", { className: "flex items-center gap-1.5 sm:gap-2", children: steps.map((step, index) => {
7656
7819
  const done = step < current;
7657
7820
  const active2 = step === current;
7658
7821
  const canGoBack = done && !!onGoBackTo;
7659
7822
  const Tag = canGoBack ? "button" : "div";
7660
- return /* @__PURE__ */ jsxs("li", { className: "flex min-w-0 flex-1 items-center gap-1.5 sm:gap-2", children: [
7823
+ return (
7824
+ // `flex-1` só em quem TEM traço. O último passo não tem, e com
7825
+ // flex-1 ele reservava uma fatia inteira para desenhar apenas a
7826
+ // bolinha — que então parava no começo da fatia em vez do fim, e a
7827
+ // régua saía torta. Sem flex-1, o traço absorve a sobra e as
7828
+ // bolinhas encostam nas pontas.
7661
7829
  /* @__PURE__ */ jsxs(
7662
- Tag,
7830
+ "li",
7663
7831
  {
7664
- ...canGoBack ? { type: "button", onClick: () => onGoBackTo(step) } : {},
7665
- "data-testid": TID.checkoutStep(step),
7666
- "data-state": done ? "done" : active2 ? "current" : "pending",
7667
- "aria-current": active2 ? "step" : void 0,
7668
- className: [
7669
- "flex min-w-0 items-center gap-2 rounded-lg px-2 py-1.5 text-left transition",
7670
- canGoBack ? "hover:bg-muted" : "",
7671
- active2 ? "text-foreground" : done ? "text-foreground" : "text-muted-foreground"
7672
- ].join(" "),
7832
+ className: `flex min-w-0 items-center gap-1.5 sm:gap-2 ${index < steps.length - 1 ? "flex-1" : ""}`,
7673
7833
  children: [
7674
- /* @__PURE__ */ jsx(
7675
- "span",
7834
+ /* @__PURE__ */ jsxs(
7835
+ Tag,
7676
7836
  {
7837
+ ...canGoBack ? { type: "button", onClick: () => onGoBackTo(step) } : {},
7838
+ "data-testid": TID.checkoutStep(step),
7839
+ "data-state": done ? "done" : active2 ? "current" : "pending",
7840
+ "aria-current": active2 ? "step" : void 0,
7677
7841
  className: [
7678
- "grid h-7 w-7 shrink-0 place-items-center rounded-full border text-xs font-semibold",
7679
- done ? "border-primary bg-primary text-primary-foreground" : active2 ? "border-primary bg-background text-primary" : "border-muted-foreground/30 bg-background"
7842
+ "flex min-w-0 items-center gap-2 rounded-lg px-2 py-1.5 text-left transition",
7843
+ canGoBack ? "hover:bg-muted" : "",
7844
+ active2 ? "text-foreground" : done ? "text-foreground" : "text-muted-foreground"
7680
7845
  ].join(" "),
7681
- children: done ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : step
7846
+ children: [
7847
+ /* @__PURE__ */ jsx(
7848
+ "span",
7849
+ {
7850
+ className: [
7851
+ "grid h-7 w-7 shrink-0 place-items-center rounded-full border text-xs font-semibold",
7852
+ done ? "border-primary bg-primary text-primary-foreground" : active2 ? "border-primary bg-background text-primary" : "border-muted-foreground/30 bg-background"
7853
+ ].join(" "),
7854
+ children: done ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : index + 1
7855
+ }
7856
+ ),
7857
+ /* @__PURE__ */ jsx("span", { className: `hidden truncate text-sm sm:inline ${active2 ? "font-semibold" : ""}`, children: LABELS2[step] })
7858
+ ]
7682
7859
  }
7683
7860
  ),
7684
- /* @__PURE__ */ jsx("span", { className: `hidden truncate text-sm sm:inline ${active2 ? "font-semibold" : ""}`, children: LABELS2[step] })
7861
+ index < steps.length - 1 && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: `h-px flex-1 ${done ? "bg-primary" : "bg-border"}` })
7685
7862
  ]
7686
- }
7687
- ),
7688
- index < steps.length - 1 && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: `h-px flex-1 ${done ? "bg-primary" : "bg-border"}` })
7689
- ] }, step);
7863
+ },
7864
+ step
7865
+ )
7866
+ );
7690
7867
  }) }),
7691
7868
  /* @__PURE__ */ jsxs("p", { className: "mt-2 text-sm font-semibold sm:hidden", children: [
7692
- current,
7869
+ steps.indexOf(current) + 1,
7693
7870
  ". ",
7694
7871
  LABELS2[current]
7695
7872
  ] })
@@ -7844,13 +8021,13 @@ function CardBrick({ publicKey, amount, email, onSubmit, onError }) {
7844
8021
  var POLL_MS = 3e3;
7845
8022
  var RESUME_KEY = "fz.checkout.resume";
7846
8023
  function useExpiry(expiresAt) {
7847
- const target = React4.useMemo(() => {
8024
+ const target = React37.useMemo(() => {
7848
8025
  if (!expiresAt) return null;
7849
8026
  const t = new Date(expiresAt).getTime();
7850
8027
  return Number.isFinite(t) ? t : null;
7851
8028
  }, [expiresAt]);
7852
- const [now, setNow] = React4.useState(() => Date.now());
7853
- React4.useEffect(() => {
8029
+ const [now, setNow] = React37.useState(() => Date.now());
8030
+ React37.useEffect(() => {
7854
8031
  if (target === null) return;
7855
8032
  const id = setInterval(() => setNow(Date.now()), 1e3);
7856
8033
  return () => clearInterval(id);
@@ -8396,7 +8573,7 @@ var PAYMENT_LABELS = {
8396
8573
  other: { label: "Combinar com a loja", hint: "A loja entra em contato para acertar o pagamento" }
8397
8574
  };
8398
8575
  function placeOrderMessage(err) {
8399
- const message = errorMessage(err);
8576
+ const message = errorMessage5(err);
8400
8577
  if (/customer (email mismatch|does not belong|belongs to another)/i.test(message)) {
8401
8578
  return "Sua sess\xE3o ficou presa a outro cadastro. Recarregue a p\xE1gina e tente de novo.";
8402
8579
  }
@@ -8408,9 +8585,16 @@ function placeOrderMessage(err) {
8408
8585
  }
8409
8586
  return message.replace(/^shop_[a-z_]+:\s*/i, "") || "N\xE3o foi poss\xEDvel concluir o pedido.";
8410
8587
  }
8588
+ var DEV_BUILD = (() => {
8589
+ try {
8590
+ return import.meta.env?.DEV === true;
8591
+ } catch {
8592
+ return false;
8593
+ }
8594
+ })();
8411
8595
  function field(label, testId, value, onChange, props) {
8412
8596
  return /* @__PURE__ */ jsxs("label", { className: "block", children: [
8413
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: label }),
8597
+ /* @__PURE__ */ jsx("span", { className: "mb-1.5 block text-xs font-medium text-muted-foreground", children: label }),
8414
8598
  /* @__PURE__ */ jsx(
8415
8599
  "input",
8416
8600
  {
@@ -8470,7 +8654,7 @@ function CheckoutPage() {
8470
8654
  district: "",
8471
8655
  state: ""
8472
8656
  });
8473
- React4.useEffect(() => {
8657
+ React37.useEffect(() => {
8474
8658
  if (!known.ready) return;
8475
8659
  setForm((current) => ({
8476
8660
  ...current,
@@ -8486,6 +8670,26 @@ function CheckoutPage() {
8486
8670
  }));
8487
8671
  }, [known.ready, known.phone, known.document, known.address]);
8488
8672
  const [step, setStep] = useState(resume ? 3 : 1);
8673
+ const fulfillmentType = useDeliveryStore((s2) => s2.fulfillmentType);
8674
+ const scheduledFor = useDeliveryStore((s2) => s2.scheduledFor);
8675
+ const deliveryReference = useDeliveryStore((s2) => {
8676
+ const kept = s2.savedAddresses.find((a) => a.id === s2.selectedAddressId);
8677
+ return kept?.reference?.trim() || "";
8678
+ });
8679
+ const isPickup = fulfillmentType === "pickup";
8680
+ const [dbPickup, setDbPickup] = useState(null);
8681
+ useEffect(() => {
8682
+ if (!isPickup) return;
8683
+ let alive = true;
8684
+ void (async () => {
8685
+ const settings = await getShopProvider().getPublicStoreSettings?.().catch(() => null);
8686
+ if (alive && settings?.pickupAddress) setDbPickup(settings.pickupAddress);
8687
+ })();
8688
+ return () => {
8689
+ alive = false;
8690
+ };
8691
+ }, [isPickup]);
8692
+ const pickupAddress = dbPickup ?? config.delivery?.pickupAddress ?? null;
8489
8693
  const [paidOrderId, setPaidOrderId] = useState(null);
8490
8694
  const [error, setError] = useState(null);
8491
8695
  const [discountCode, setDiscountCode] = useState("");
@@ -8494,14 +8698,15 @@ function CheckoutPage() {
8494
8698
  const [applyingDiscount, setApplyingDiscount] = useState(false);
8495
8699
  const [showSignin, setShowSignin] = useState(false);
8496
8700
  const [placing, setPlacing] = useState(false);
8497
- const leaving = React4.useRef(false);
8701
+ const leaving = React37.useRef(false);
8498
8702
  const [zipLookup, setZipLookup] = useState("idle");
8499
8703
  const subtotal = selectSubtotal(cart);
8500
8704
  const discountTotal = selectDiscountTotal(cart);
8501
- const shipping = selectShipping(cart, config);
8502
- const total = selectTotal(cart, config);
8705
+ const shippingRaw = selectShipping(cart, config);
8706
+ const shipping = isPickup ? 0 : shippingRaw;
8707
+ const total = selectTotal(cart, config) - (isPickup ? shippingRaw : 0);
8503
8708
  const checkoutSlotContext = { subtotal, total };
8504
- const checkoutSessionInput = React4.useMemo(() => ({
8709
+ const checkoutSessionInput = React37.useMemo(() => ({
8505
8710
  items: cart.lines.map((line) => ({
8506
8711
  productId: line.productId,
8507
8712
  variantId: line.variantId ?? null,
@@ -8651,10 +8856,23 @@ function CheckoutPage() {
8651
8856
  }, [session.customerId]);
8652
8857
  useEffect(() => {
8653
8858
  if (addressMode !== "new") return;
8859
+ const kept = delivery.savedAddresses.find((a) => a.id === delivery.selectedAddressId);
8654
8860
  const looked = delivery.address;
8655
- if (!looked) return;
8861
+ if (!kept && !looked) return;
8656
8862
  setForm((current) => {
8657
8863
  if (current.zip.trim() || current.street.trim()) return current;
8864
+ if (kept) {
8865
+ return {
8866
+ ...current,
8867
+ zip: formatPostalCode(kept.postalCode),
8868
+ street: kept.street,
8869
+ number: kept.number,
8870
+ complement: kept.complement ?? "",
8871
+ district: kept.district,
8872
+ city: kept.city,
8873
+ state: kept.state
8874
+ };
8875
+ }
8658
8876
  return {
8659
8877
  ...current,
8660
8878
  zip: formatPostalCode(looked.postalCode),
@@ -8664,7 +8882,7 @@ function CheckoutPage() {
8664
8882
  state: looked.state
8665
8883
  };
8666
8884
  });
8667
- }, [delivery.address, addressMode]);
8885
+ }, [delivery.address, delivery.savedAddresses, delivery.selectedAddressId, addressMode]);
8668
8886
  function applySavedAddress(address) {
8669
8887
  setAddressMode("saved");
8670
8888
  setSelectedAddressId(address.id);
@@ -8724,6 +8942,7 @@ function CheckoutPage() {
8724
8942
  }
8725
8943
  function validateDelivery() {
8726
8944
  setError(null);
8945
+ if (isPickup) return true;
8727
8946
  if (!selectRequiresShipping(cart)) return true;
8728
8947
  if (!form.street.trim() || !form.city.trim() || !form.zip.trim() || !form.district.trim()) {
8729
8948
  return fail("Preencha ou selecione o endere\xE7o de entrega.", TID.checkoutStreet);
@@ -8748,13 +8967,33 @@ function CheckoutPage() {
8748
8967
  setStep(next);
8749
8968
  if (typeof window !== "undefined") window.scrollTo({ top: 0, behavior: "smooth" });
8750
8969
  }
8970
+ function orderAddress() {
8971
+ if (isPickup && pickupAddress) {
8972
+ return {
8973
+ ...pickupAddress,
8974
+ phone: form.phone.trim() || void 0,
8975
+ document: documentDigits(form.document) || void 0
8976
+ };
8977
+ }
8978
+ return {
8979
+ postalCode: form.zip.trim(),
8980
+ street: form.street.trim(),
8981
+ number: form.number.trim() || void 0,
8982
+ complement: form.complement.trim() || void 0,
8983
+ district: form.district.trim() || void 0,
8984
+ city: form.city.trim(),
8985
+ state: form.state.trim().toUpperCase(),
8986
+ phone: form.phone.trim() || void 0,
8987
+ document: documentDigits(form.document) || void 0
8988
+ };
8989
+ }
8751
8990
  function advance() {
8752
8991
  if (step === 1 && validateContact()) {
8753
8992
  void getShopProvider().saveCustomerContact?.({
8754
8993
  phone: form.phone.trim() || null,
8755
8994
  document: documentDigits(form.document) || null
8756
8995
  });
8757
- goToStep(2);
8996
+ goToStep(isPickup ? 3 : 2);
8758
8997
  } else if (step === 2 && validateDelivery()) goToStep(3);
8759
8998
  }
8760
8999
  async function confirmWithoutCharge() {
@@ -8766,21 +9005,26 @@ function CheckoutPage() {
8766
9005
  cart,
8767
9006
  session,
8768
9007
  customer: { email: form.email, name: form.name },
8769
- address: { street: form.street, city: form.city, zip: form.zip },
8770
- shippingAddress: {
8771
- postalCode: form.zip.trim(),
8772
- street: form.street.trim(),
8773
- number: form.number.trim() || void 0,
8774
- complement: form.complement.trim() || void 0,
8775
- district: form.district.trim() || void 0,
8776
- city: form.city.trim(),
8777
- state: form.state.trim().toUpperCase(),
8778
- phone: form.phone.trim() || void 0,
8779
- // Viaja no endereço porque a assinatura do shop_place_order não pode
8780
- // ganhar parâmetro sem DROP (0050); uma trigger o levanta para a
8781
- // coluna e o tira do jsonb, então nunca vira linha de endereço.
8782
- document: documentDigits(form.document) || void 0
9008
+ // `notes` é o recado do comprador para a loja, e o ponto de referência
9009
+ // é exatamente isso: "portão azul, ao lado da padaria" não cabe em
9010
+ // nenhum campo estruturado do endereço e é o que o entregador lê.
9011
+ address: {
9012
+ street: form.street,
9013
+ city: form.city,
9014
+ zip: form.zip,
9015
+ notes: deliveryReference || void 0
8783
9016
  },
9017
+ // Viaja com o `document` dentro: a assinatura do shop_place_order não
9018
+ // pode ganhar parâmetro sem DROP (0050), e uma trigger o levanta para a
9019
+ // coluna. Na retirada este endereço é o balcão — ver orderAddress().
9020
+ shippingAddress: orderAddress(),
9021
+ // Entrega/retirada e agendamento (0094) vão AQUI porque esta tela chama
9022
+ // placeStorefrontOrder direto, sem passar pelo useCheckout — e sem esta
9023
+ // linha o pedido nascia sem `fulfillment_type`, com a retirada
9024
+ // indistinguível de uma entrega e o frete cobrado assim mesmo. Foi um
9025
+ // pedido de teste real (#1379) que mostrou isso.
9026
+ fulfillmentType,
9027
+ scheduledFor: scheduledFor ?? void 0,
8784
9028
  paymentMethod
8785
9029
  });
8786
9030
  if (customerId) session.setSession({ customerId, email: form.email, name: form.name });
@@ -8813,6 +9057,7 @@ function CheckoutPage() {
8813
9057
  TID.checkoutDocument
8814
9058
  );
8815
9059
  }
9060
+ if (isPickup) return true;
8816
9061
  if (!form.street.trim() || !form.city.trim() || !form.zip.trim() || !form.district.trim()) {
8817
9062
  return fail("Preencha ou selecione o endere\xE7o de entrega.", TID.checkoutStreet);
8818
9063
  }
@@ -8860,7 +9105,8 @@ function CheckoutPage() {
8860
9105
  CheckoutSteps,
8861
9106
  {
8862
9107
  current: step,
8863
- onGoBackTo: step === 4 ? void 0 : (target) => goToStep(target)
9108
+ omit: isPickup ? [2] : void 0,
9109
+ onGoBackTo: step === 4 ? void 0 : (target) => goToStep(isPickup && target === 2 ? 1 : target)
8864
9110
  }
8865
9111
  ),
8866
9112
  /* @__PURE__ */ jsxs(
@@ -9092,6 +9338,27 @@ function CheckoutPage() {
9092
9338
  }
9093
9339
  ),
9094
9340
  /* @__PURE__ */ jsx("p", { className: "mt-2 text-center text-xs text-muted-foreground", children: "O pedido fica aguardando pagamento at\xE9 a loja confirmar." })
9341
+ ] }),
9342
+ DEV_BUILD && chargesOnline && /* @__PURE__ */ jsxs("div", { className: "mt-6 rounded-xl border border-dashed border-amber-500/60 bg-amber-500/5 p-4", children: [
9343
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-amber-600", children: "Somente em desenvolvimento" }),
9344
+ /* @__PURE__ */ jsx(
9345
+ "button",
9346
+ {
9347
+ type: "button",
9348
+ "data-testid": TID.devPlaceOrderWithoutCharge,
9349
+ disabled: placing,
9350
+ onClick: () => {
9351
+ void confirmWithoutCharge();
9352
+ },
9353
+ className: "mt-2 min-h-11 w-full rounded-lg border border-amber-500/60 px-4 py-2.5 text-sm font-medium text-amber-700 transition hover:bg-amber-500/10 disabled:opacity-60",
9354
+ children: `Criar pedido sem cobrar \u2014 ${money(total)}`
9355
+ }
9356
+ ),
9357
+ /* @__PURE__ */ jsxs("p", { className: "mt-2 text-xs text-muted-foreground", children: [
9358
+ "Pula o adquirente e grava o pedido como ",
9359
+ /* @__PURE__ */ jsx("code", { children: "pending" }),
9360
+ ", igual a uma loja que confirma o pagamento \xE0 m\xE3o. Este bloco n\xE3o vai para produ\xE7\xE3o."
9361
+ ] })
9095
9362
  ] })
9096
9363
  ] }),
9097
9364
  step === 4 && paidOrderId && /* @__PURE__ */ jsxs("section", { "data-testid": TID.checkoutDone, className: "rounded-xl border bg-card p-8 text-center", children: [
@@ -9116,7 +9383,7 @@ function CheckoutPage() {
9116
9383
  type: "submit",
9117
9384
  "data-testid": TID.checkoutContinue,
9118
9385
  className: "min-h-12 rounded-lg bg-primary px-6 py-4 text-sm font-bold text-primary-foreground transition hover:opacity-90",
9119
- children: step === 1 ? "Continuar para a entrega" : `Ir para o pagamento - ${money(total)}`
9386
+ children: step === 1 ? isPickup ? `Ir para o pagamento - ${money(total)}` : "Continuar para a entrega" : `Ir para o pagamento - ${money(total)}`
9120
9387
  }
9121
9388
  ),
9122
9389
  step === 2 && /* @__PURE__ */ jsx(
@@ -9133,12 +9400,7 @@ function CheckoutPage() {
9133
9400
  ] })
9134
9401
  ]
9135
9402
  }
9136
- ),
9137
- /* @__PURE__ */ jsxs("footer", { className: "mt-10 flex flex-wrap gap-4 border-t pt-5 text-xs text-muted-foreground", children: [
9138
- /* @__PURE__ */ jsx(Link, { to: "/privacy", className: "hover:text-foreground", children: "Privacidade" }),
9139
- /* @__PURE__ */ jsx(Link, { to: "/terms", className: "hover:text-foreground", children: "Termos" }),
9140
- /* @__PURE__ */ jsx(Link, { to: "/refunds", className: "hover:text-foreground", children: "Trocas e devolu\xE7\xF5es" })
9141
- ] })
9403
+ )
9142
9404
  ] }),
9143
9405
  /* @__PURE__ */ jsxs("aside", { className: "rounded-xl border bg-muted/20 p-5 lg:sticky lg:top-6 lg:col-span-2 lg:self-start", children: [
9144
9406
  /* @__PURE__ */ jsx("h2", { className: "sr-only", children: "Resumo do pedido" }),
@@ -9150,7 +9412,12 @@ function CheckoutPage() {
9150
9412
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
9151
9413
  /* @__PURE__ */ jsx("p", { className: "truncate font-medium", children: line.name }),
9152
9414
  line.sku && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: line.sku }),
9153
- line.optionsLabel && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: line.optionsLabel })
9415
+ line.optionsLabel && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: line.optionsLabel }),
9416
+ line.notes && /* @__PURE__ */ jsxs("p", { className: "text-xs italic text-muted-foreground", children: [
9417
+ "\u201C",
9418
+ line.notes,
9419
+ "\u201D"
9420
+ ] })
9154
9421
  ] }),
9155
9422
  /* @__PURE__ */ jsx("span", { "data-price": (line.unitPrice * line.quantity).toFixed(2), children: money(line.unitPrice * line.quantity) })
9156
9423
  ] }, line.lineId ?? line.productId)) }),
@@ -9216,7 +9483,7 @@ function CheckoutPage() {
9216
9483
  ] }),
9217
9484
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
9218
9485
  /* @__PURE__ */ jsxs("dt", { className: cart.discountFreeShipping ? "text-emerald-700" : "text-muted-foreground", children: [
9219
- "Entrega",
9486
+ isPickup ? "Retirada na loja" : "Entrega",
9220
9487
  cart.discountFreeShipping && /* @__PURE__ */ jsxs(
9221
9488
  "button",
9222
9489
  {
@@ -9231,7 +9498,7 @@ function CheckoutPage() {
9231
9498
  }
9232
9499
  )
9233
9500
  ] }),
9234
- /* @__PURE__ */ jsx("dd", { "data-price": shipping.toFixed(2), children: shipping === 0 ? "Gr\xE1tis" : money(shipping) })
9501
+ /* @__PURE__ */ jsx("dd", { "data-price": shipping.toFixed(2), children: isPickup ? "Sem taxa" : shipping === 0 ? "Gr\xE1tis" : money(shipping) })
9235
9502
  ] }),
9236
9503
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between border-t pt-5 text-lg font-bold", children: [
9237
9504
  /* @__PURE__ */ jsx("dt", { children: "Total" }),
@@ -9243,159 +9510,1447 @@ function CheckoutPage() {
9243
9510
  "Compra protegida e dados criptografados."
9244
9511
  ] }),
9245
9512
  /* @__PURE__ */ jsx("div", { className: "mt-6 border-t pt-5", children: /* @__PURE__ */ jsx(Slot, { area: "checkout", name: "afterForm", context: checkoutSlotContext }) })
9513
+ ] }),
9514
+ /* @__PURE__ */ jsxs("footer", { className: "flex flex-wrap gap-4 border-t pt-5 text-xs text-muted-foreground lg:col-span-5", children: [
9515
+ /* @__PURE__ */ jsx(Link, { to: "/privacy", className: "hover:text-foreground", children: "Privacidade" }),
9516
+ /* @__PURE__ */ jsx(Link, { to: "/terms", className: "hover:text-foreground", children: "Termos" }),
9517
+ /* @__PURE__ */ jsx(Link, { to: "/refunds", className: "hover:text-foreground", children: "Trocas e devolu\xE7\xF5es" })
9246
9518
  ] })
9247
9519
  ] })
9248
9520
  ] });
9249
9521
  }
9250
-
9251
- // src/data/order-status.ts
9252
- var FINANCIAL = {
9253
- pending: { label: "Pendente", tone: "pending" },
9254
- paid: { label: "Pago", tone: "positive" },
9255
- partially_paid: { label: "Parcialmente pago", tone: "pending" },
9256
- partially_refunded: { label: "Parcialmente reembolsado", tone: "negative" },
9257
- refunded: { label: "Reembolsado", tone: "negative" },
9258
- voided: { label: "Cancelado", tone: "negative" }
9259
- };
9260
- var TONE_CLASS = {
9261
- positive: "bg-emerald-100 text-emerald-800",
9262
- pending: "bg-amber-100 text-amber-800",
9263
- negative: "bg-rose-100 text-rose-800"
9264
- };
9265
- function financialStatusBadge(status) {
9266
- const entry = FINANCIAL[status] ?? { label: status, tone: "pending" };
9267
- return { label: entry.label, className: TONE_CLASS[entry.tone] };
9268
- }
9269
- function isPaid(status) {
9270
- return status === "paid" || status === "partially_paid";
9522
+ function selectIsOpen(state) {
9523
+ if (!state.service) return true;
9524
+ if (!state.service.configured) return true;
9525
+ return state.service.open;
9271
9526
  }
9272
- function isRefunded(status) {
9273
- return status === "refunded" || status === "partially_refunded";
9527
+ function selectShowsHours(state) {
9528
+ return Boolean(state.service?.configured);
9274
9529
  }
9275
- function orderTrackingSteps(order) {
9276
- const received = {
9277
- id: "received",
9278
- label: "Pedido recebido",
9279
- description: `#${order.orderNumber}`,
9280
- state: "complete",
9281
- icon: Check
9282
- };
9283
- if (order.status === "cancelled" || order.financialStatus === "voided") {
9284
- return [
9285
- received,
9286
- { id: "payment", label: "Pedido cancelado", description: "Este pedido foi cancelado", state: "cancelled", icon: XCircle }
9287
- ];
9530
+ var useServiceStore = create()((set, get) => ({
9531
+ status: "idle",
9532
+ service: null,
9533
+ fulfillmentType: "delivery",
9534
+ async refresh(fulfillmentType) {
9535
+ const type = fulfillmentType ?? get().fulfillmentType;
9536
+ set({ status: "loading", fulfillmentType: type });
9537
+ try {
9538
+ const shop = getShopProvider();
9539
+ const service = await shop?.getServiceStatus?.(type) ?? null;
9540
+ set({ service, status: "ready" });
9541
+ } catch {
9542
+ set({ service: null, status: "ready" });
9543
+ }
9288
9544
  }
9289
- if (isRefunded(order.financialStatus)) {
9290
- return [
9291
- received,
9292
- { id: "payment", label: "Pagamento aprovado", description: "Compra confirmada", state: "complete", icon: Check },
9545
+ }));
9546
+
9547
+ // src/components/delivery/store-time.ts
9548
+ function storeTime(iso, timeZone, options = {}) {
9549
+ return new Intl.DateTimeFormat("pt-BR", {
9550
+ ...options.weekday ? { weekday: "short" } : {},
9551
+ hour: "2-digit",
9552
+ minute: "2-digit",
9553
+ timeZone
9554
+ }).format(new Date(iso));
9555
+ }
9556
+ function StoreCover({ onShowHours }) {
9557
+ const config = useStorefrontConfig();
9558
+ const cover = config.delivery?.coverImage;
9559
+ const logo = config.delivery?.logoImage;
9560
+ const locality = config.delivery?.locality;
9561
+ const modes = config.delivery?.fulfillmentModes ?? ["delivery"];
9562
+ const type = useDeliveryStore((s2) => s2.fulfillmentType);
9563
+ const setFulfillmentType = useDeliveryStore((s2) => s2.setFulfillmentType);
9564
+ const refreshService = useServiceStore((s2) => s2.refresh);
9565
+ const service = useServiceStore();
9566
+ const showsHours = selectShowsHours(service);
9567
+ const open = selectIsOpen(service);
9568
+ function choose(next) {
9569
+ setFulfillmentType(next);
9570
+ void refreshService(next);
9571
+ }
9572
+ return /* @__PURE__ */ jsxs("header", { "data-testid": TID.storeCover, className: "relative isolate bg-neutral-900 text-white", children: [
9573
+ cover && /* @__PURE__ */ jsx(
9574
+ "img",
9293
9575
  {
9294
- id: "refund",
9295
- label: order.financialStatus === "refunded" ? "Reembolsado" : "Reembolso parcial",
9296
- description: "Valor devolvido ao cliente",
9297
- state: "cancelled",
9298
- icon: RotateCcw
9576
+ src: cover,
9577
+ alt: "",
9578
+ "aria-hidden": true,
9579
+ className: "absolute inset-0 -z-10 h-full w-full object-cover"
9299
9580
  }
9300
- ];
9301
- }
9302
- const paid = isPaid(order.financialStatus);
9303
- const partiallyFulfilled = order.fulfillmentStatus === "partially_fulfilled";
9304
- const fulfilled = order.fulfillmentStatus === "fulfilled";
9305
- return [
9306
- received,
9307
- {
9308
- id: "payment",
9309
- label: paid ? "Pagamento aprovado" : "Aguardando pagamento",
9310
- // "Processando" descrevia um pagamento que ninguém iniciou.
9311
- description: paid ? "Compra confirmada" : "Ainda n\xE3o recebemos",
9312
- state: paid ? "complete" : "current",
9313
- icon: paid ? Check : Clock
9314
- },
9315
- {
9316
- id: "preparing",
9317
- label: "Preparando envio",
9318
- description: paid ? "Separando produtos" : "Come\xE7a ap\xF3s o pagamento",
9319
- state: fulfilled || partiallyFulfilled ? "complete" : paid ? "current" : "pending",
9320
- icon: PackageCheck
9321
- },
9322
- {
9323
- id: "delivery",
9324
- label: fulfilled ? "Pedido enviado" : partiallyFulfilled ? "Envio parcial" : "Entrega",
9325
- description: fulfilled ? "A caminho do cliente" : partiallyFulfilled ? "Parte do pedido saiu" : "Pr\xF3xima etapa",
9326
- state: fulfilled ? "complete" : partiallyFulfilled ? "current" : "pending",
9327
- icon: fulfilled || partiallyFulfilled ? Truck : Clock
9328
- }
9329
- ];
9330
- }
9331
- var GRID_COLS = {
9332
- 2: "sm:grid-cols-2",
9333
- 3: "sm:grid-cols-3",
9334
- 4: "sm:grid-cols-4"
9335
- };
9336
- function OrderTrackingTimeline({ order, compact: compact2 = false }) {
9337
- const steps = orderTrackingSteps(order);
9338
- return /* @__PURE__ */ jsx(
9339
- "ol",
9340
- {
9341
- "data-testid": TID.orderTracking,
9342
- className: `grid ${compact2 ? "gap-2" : "gap-3"} ${GRID_COLS[steps.length] ?? "sm:grid-cols-4"}`,
9343
- "aria-label": "Acompanhamento do pedido",
9344
- children: steps.map((step) => {
9345
- const Icon2 = step.icon;
9346
- const cancelled = step.state === "cancelled";
9347
- const done = step.state === "complete";
9348
- const current = step.state === "current";
9349
- const active2 = done || current;
9581
+ ),
9582
+ /* @__PURE__ */ jsx(
9583
+ "div",
9584
+ {
9585
+ "aria-hidden": true,
9586
+ className: "absolute inset-0 -z-10 bg-gradient-to-b from-black/30 via-black/45 to-black/75"
9587
+ }
9588
+ ),
9589
+ /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl px-4 pb-4 pt-8", children: [
9590
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 [text-shadow:0_1px_3px_rgb(0_0_0_/_0.7)]", children: [
9591
+ /* @__PURE__ */ jsx("div", { className: "grid h-14 w-14 shrink-0 place-items-center overflow-hidden rounded-xl bg-white/10 ring-1 ring-white/20", children: logo ? /* @__PURE__ */ jsx("img", { src: logo, alt: "", "aria-hidden": true, className: "h-full w-full object-cover" }) : /* @__PURE__ */ jsx("span", { className: "text-xl font-bold", children: config.name.slice(0, 1) }) }),
9592
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
9593
+ /* @__PURE__ */ jsx("h1", { className: "truncate text-xl font-bold leading-tight", children: config.name }),
9594
+ locality && /* @__PURE__ */ jsx("p", { className: "truncate text-sm text-white/70", children: locality }),
9595
+ showsHours && service.service && // O estado é o botão que abre a placa da porta. Um selo que diz
9596
+ // "Fechado" e não leva a lugar nenhum deixa a pessoa sem a única
9597
+ // pergunta seguinte que ela tem: então quando abre?
9598
+ /* @__PURE__ */ jsxs(
9599
+ "button",
9600
+ {
9601
+ type: "button",
9602
+ "data-testid": TID.serviceStatus,
9603
+ "data-open": open ? "true" : "false",
9604
+ onClick: onShowHours,
9605
+ className: "mt-0.5 flex items-center gap-1.5 text-sm font-semibold underline-offset-2 hover:underline",
9606
+ children: [
9607
+ /* @__PURE__ */ jsx(
9608
+ "span",
9609
+ {
9610
+ "aria-hidden": true,
9611
+ className: `h-2 w-2 rounded-full ${open ? "bg-emerald-400" : "bg-red-400"}`
9612
+ }
9613
+ ),
9614
+ open ? /* @__PURE__ */ jsx(OpenUntil, {}) : /* @__PURE__ */ jsx(ClosedUntil, {})
9615
+ ]
9616
+ }
9617
+ )
9618
+ ] })
9619
+ ] }),
9620
+ modes.length > 1 && /* @__PURE__ */ jsx("div", { className: "mt-4 flex gap-1", children: modes.map((mode) => {
9621
+ const active2 = type === mode;
9350
9622
  return /* @__PURE__ */ jsx(
9351
- "li",
9623
+ "button",
9352
9624
  {
9353
- "data-testid": TID.orderTrackingStep(step.id),
9354
- "data-state": step.state,
9355
- className: [
9356
- "relative min-w-0 rounded-xl border p-3",
9357
- cancelled ? "border-rose-200 bg-rose-50 text-rose-700" : active2 ? "border-primary/35 bg-primary/5" : "bg-muted/20 text-muted-foreground",
9358
- compact2 ? "text-xs" : "text-sm"
9359
- ].join(" "),
9360
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
9361
- /* @__PURE__ */ jsx(
9362
- "span",
9363
- {
9364
- className: [
9365
- "grid h-7 w-7 shrink-0 place-items-center rounded-full border",
9366
- 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"
9367
- ].join(" "),
9368
- "aria-hidden": true,
9369
- children: /* @__PURE__ */ jsx(Icon2, { className: "h-3.5 w-3.5" })
9370
- }
9371
- ),
9372
- /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
9373
- /* @__PURE__ */ jsx("span", { className: "block font-semibold leading-snug text-foreground", children: step.label }),
9374
- /* @__PURE__ */ jsx("span", { className: "mt-0.5 block leading-snug text-muted-foreground", children: step.description })
9375
- ] })
9376
- ] })
9625
+ type: "button",
9626
+ "data-testid": TID.fulfillmentMode(mode),
9627
+ "aria-pressed": active2,
9628
+ onClick: () => choose(mode),
9629
+ className: `rounded-full px-5 py-2 text-sm font-semibold transition ${active2 ? "bg-white text-neutral-900" : "text-white/80"}`,
9630
+ children: mode === "pickup" ? "Retirada" : "Entrega"
9377
9631
  },
9378
- step.id
9632
+ mode
9379
9633
  );
9380
- })
9381
- }
9382
- );
9634
+ }) })
9635
+ ] })
9636
+ ] });
9383
9637
  }
9384
-
9385
- // src/components/account/section.ts
9386
- var ACCOUNT_SECTIONS = [
9387
- "profile",
9388
- "orders",
9389
- "addresses",
9390
- "payments"
9391
- ];
9392
- var DEFAULT_ACCOUNT_SECTION = "orders";
9393
- function isAccountSection(value) {
9394
- return ACCOUNT_SECTIONS.includes(value);
9638
+ function OpenUntil() {
9639
+ const service = useServiceStore((s2) => s2.service);
9640
+ if (!service) return null;
9641
+ return /* @__PURE__ */ jsxs("span", { children: [
9642
+ "Aberto",
9643
+ service.closesAt ? ` at\xE9 ${storeTime(service.closesAt, service.timezone)}` : ""
9644
+ ] });
9395
9645
  }
9396
- function parseAccountSection(path) {
9397
- const query = path.split("?")[1];
9398
- if (!query) return DEFAULT_ACCOUNT_SECTION;
9646
+ function ClosedUntil() {
9647
+ const service = useServiceStore((s2) => s2.service);
9648
+ if (!service) return null;
9649
+ return /* @__PURE__ */ jsxs("span", { children: [
9650
+ "Fechado",
9651
+ service.nextOpenAt ? ` \u2022 abre ${storeTime(service.nextOpenAt, service.timezone, { weekday: true })}` : ""
9652
+ ] });
9653
+ }
9654
+ function AddressStrip({
9655
+ onChangeAddress,
9656
+ onSchedule
9657
+ }) {
9658
+ const config = useStorefrontConfig();
9659
+ const scheduling = config.delivery?.scheduling ?? false;
9660
+ const timezone = useServiceStore((s2) => s2.service?.timezone) ?? "America/Sao_Paulo";
9661
+ const delivery = useDeliveryStore();
9662
+ const type = delivery.fulfillmentType;
9663
+ const isPickup = type === "pickup";
9664
+ const money = (v) => formatMoney(v, config.currency, config.locale);
9665
+ const option = delivery.options.find((o) => o.zoneId === delivery.selectedZoneId) ?? delivery.options[0];
9666
+ const chosen = delivery.savedAddresses.find((a) => a.id === delivery.selectedAddressId);
9667
+ const pickup = config.delivery?.pickupAddress;
9668
+ const feeLine = isPickup ? "Sem taxa de entrega" : option ? option.free ? "Entrega gr\xE1tis" : money(option.rate) : `A partir de ${money(config.shipping.flatRate)}`;
9669
+ const etaLine = option?.etaMaxDays != null ? ` \u2022 at\xE9 ${option.etaMaxDays} dia(s)` : "";
9670
+ return /* @__PURE__ */ jsxs("div", { "data-testid": TID.addressStrip, className: "border-b border-border bg-background", children: [
9671
+ /* @__PURE__ */ jsxs(
9672
+ "button",
9673
+ {
9674
+ type: "button",
9675
+ onClick: isPickup ? void 0 : onChangeAddress,
9676
+ disabled: isPickup,
9677
+ className: "mx-auto flex w-full max-w-3xl items-center gap-3 px-4 py-3 text-left disabled:cursor-default",
9678
+ children: [
9679
+ isPickup ? /* @__PURE__ */ jsx(Store, { className: "h-6 w-6 shrink-0 text-muted-foreground" }) : /* @__PURE__ */ jsx(Bike, { className: "h-6 w-6 shrink-0 text-muted-foreground" }),
9680
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
9681
+ /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: isPickup ? "Retirar na loja" : `Entregar em${etaLine} \u2022 ${feeLine}` }),
9682
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold", children: isPickup ? pickup ? `${pickup.street}${pickup.number ? `, ${pickup.number}` : ""}` : "Sem taxa de entrega" : chosen ? `${chosen.street}, ${chosen.number}${chosen.label ? ` \xB7 ${chosen.label}` : ""}` : delivery.address ? `${delivery.address.street}${delivery.address.district ? `, ${delivery.address.district}` : ""}` : "Selecionar endere\xE7o" })
9683
+ ] }),
9684
+ !isPickup && /* @__PURE__ */ jsx(ChevronRight, { className: "h-5 w-5 shrink-0 text-muted-foreground" })
9685
+ ]
9686
+ }
9687
+ ),
9688
+ scheduling && onSchedule && /* @__PURE__ */ jsxs(
9689
+ "button",
9690
+ {
9691
+ type: "button",
9692
+ onClick: onSchedule,
9693
+ className: "mx-auto flex w-full max-w-3xl items-center gap-3 border-t border-border px-4 py-2.5 text-left",
9694
+ children: [
9695
+ /* @__PURE__ */ jsx(Clock, { className: "h-5 w-5 shrink-0 text-muted-foreground" }),
9696
+ delivery.scheduledFor ? /* @__PURE__ */ jsxs("span", { className: "text-sm font-semibold", children: [
9697
+ "Agendado \u2022",
9698
+ " ",
9699
+ new Intl.DateTimeFormat("pt-BR", {
9700
+ weekday: "short",
9701
+ day: "2-digit",
9702
+ month: "2-digit",
9703
+ hour: "2-digit",
9704
+ minute: "2-digit",
9705
+ timeZone: timezone
9706
+ }).format(new Date(delivery.scheduledFor))
9707
+ ] }) : /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Agendar para depois" }),
9708
+ /* @__PURE__ */ jsx(ChevronRight, { className: "ml-auto h-5 w-5 shrink-0 text-muted-foreground" })
9709
+ ]
9710
+ }
9711
+ )
9712
+ ] });
9713
+ }
9714
+ function MenuToolbar({
9715
+ sections,
9716
+ query,
9717
+ onQuery,
9718
+ autoFocusSearch
9719
+ }) {
9720
+ const inputRef = React37.useRef(null);
9721
+ const [active2, setActive] = React37.useState(null);
9722
+ React37.useEffect(() => {
9723
+ setActive(
9724
+ (current) => current && sections.some((s2) => s2.slug === current) ? current : sections[0]?.slug ?? null
9725
+ );
9726
+ }, [sections]);
9727
+ React37.useEffect(() => {
9728
+ if (autoFocusSearch) inputRef.current?.focus();
9729
+ }, [autoFocusSearch]);
9730
+ React37.useEffect(() => {
9731
+ if (typeof IntersectionObserver === "undefined") return;
9732
+ const seen = /* @__PURE__ */ new Map();
9733
+ const observer = new IntersectionObserver(
9734
+ (entries) => {
9735
+ for (const entry of entries) {
9736
+ const slug = entry.target.getAttribute("data-section-slug");
9737
+ if (slug) seen.set(slug, entry.isIntersecting);
9738
+ }
9739
+ const first = sections.find((s2) => seen.get(s2.slug));
9740
+ if (first) setActive(first.slug);
9741
+ },
9742
+ // A faixa é a parte da tela logo abaixo da barra: uma seção "ativa" é a
9743
+ // que está sob os olhos, não a que encostou no rodapé.
9744
+ { rootMargin: "-140px 0px -65% 0px", threshold: 0 }
9745
+ );
9746
+ for (const section of sections) {
9747
+ const el = document.getElementById(`menu-${section.slug}`);
9748
+ if (el) observer.observe(el);
9749
+ }
9750
+ return () => observer.disconnect();
9751
+ }, [sections]);
9752
+ function jumpTo(slug) {
9753
+ setActive(slug);
9754
+ document.getElementById(`menu-${slug}`)?.scrollIntoView({ behavior: "smooth", block: "start" });
9755
+ }
9756
+ return /* @__PURE__ */ jsxs("div", { className: "sticky top-0 z-30 border-b border-border bg-background/95 backdrop-blur", children: [
9757
+ /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-3xl px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
9758
+ /* @__PURE__ */ jsx(
9759
+ Search,
9760
+ {
9761
+ "aria-hidden": true,
9762
+ className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"
9763
+ }
9764
+ ),
9765
+ /* @__PURE__ */ jsx(
9766
+ "input",
9767
+ {
9768
+ ref: inputRef,
9769
+ type: "text",
9770
+ inputMode: "search",
9771
+ autoComplete: "off",
9772
+ "data-testid": TID.menuSearch,
9773
+ value: query,
9774
+ onChange: (e) => onQuery(e.target.value),
9775
+ placeholder: "Buscar no card\xE1pio",
9776
+ "aria-label": "Buscar no card\xE1pio",
9777
+ className: "h-11 w-full rounded-full border border-border bg-muted/40 pl-9 pr-9 text-base"
9778
+ }
9779
+ ),
9780
+ query && /* @__PURE__ */ jsx(
9781
+ "button",
9782
+ {
9783
+ type: "button",
9784
+ onClick: () => onQuery(""),
9785
+ "aria-label": "Limpar busca",
9786
+ className: "absolute right-2 top-1/2 grid h-8 w-8 -translate-y-1/2 place-items-center rounded-full",
9787
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
9788
+ }
9789
+ )
9790
+ ] }) }),
9791
+ !query && sections.length > 1 && /* @__PURE__ */ jsx(
9792
+ "nav",
9793
+ {
9794
+ "data-testid": TID.menuCategoryNav,
9795
+ "aria-label": "Categorias",
9796
+ className: "mx-auto w-full max-w-3xl overflow-x-auto pb-3 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
9797
+ children: /* @__PURE__ */ jsx("ul", { className: "flex w-max gap-2 px-4", children: sections.map((section) => {
9798
+ const on = active2 === section.slug;
9799
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
9800
+ "button",
9801
+ {
9802
+ type: "button",
9803
+ "data-testid": TID.menuCategoryLink(section.slug),
9804
+ "aria-current": on ? "true" : void 0,
9805
+ onClick: () => jumpTo(section.slug),
9806
+ className: `whitespace-nowrap rounded-full px-4 py-2 text-sm font-medium transition ${on ? "bg-foreground text-background" : "border border-border text-muted-foreground"}`,
9807
+ children: section.name
9808
+ }
9809
+ ) }, section.id);
9810
+ }) })
9811
+ }
9812
+ )
9813
+ ] });
9814
+ }
9815
+ function MenuItemRow({
9816
+ product,
9817
+ onOpen,
9818
+ onQuickAdd,
9819
+ disabled,
9820
+ badge,
9821
+ accent,
9822
+ fromPrice
9823
+ }) {
9824
+ const config = useStorefrontConfig();
9825
+ const money = (v) => formatMoney(v, config.currency, config.locale);
9826
+ const image = product.images?.find((i) => i.isPrimary) ?? product.images?.[0];
9827
+ const soldOut = product.inventoryCount <= 0;
9828
+ const onSale = product.compareAtPrice != null && product.compareAtPrice > product.price;
9829
+ const off = onSale ? Math.floor((product.compareAtPrice - product.price) / product.compareAtPrice * 100) : 0;
9830
+ const dim = soldOut ? "opacity-45" : "";
9831
+ return /* @__PURE__ */ jsxs(
9832
+ "li",
9833
+ {
9834
+ "data-testid": TID.menuItem(product.slug),
9835
+ className: `relative border-b border-border last:border-b-0 ${accent ? "border-l-[3px] border-l-primary pl-3" : ""}`,
9836
+ children: [
9837
+ /* @__PURE__ */ jsx(
9838
+ "button",
9839
+ {
9840
+ type: "button",
9841
+ onClick: () => onOpen(product),
9842
+ "aria-label": product.name,
9843
+ className: "absolute inset-0 z-10"
9844
+ }
9845
+ ),
9846
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full items-start gap-3 py-4 text-left", children: [
9847
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
9848
+ /* @__PURE__ */ jsx("h3", { className: `text-base font-semibold leading-snug ${dim}`, children: product.name }),
9849
+ product.description && // Três linhas: o suficiente para a frase que vende e curto o
9850
+ // bastante para a próxima linha caber na tela.
9851
+ /* @__PURE__ */ jsx("p", { className: `mt-1 line-clamp-3 text-sm text-muted-foreground ${dim}`, children: product.description }),
9852
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-2 gap-y-1", children: [
9853
+ /* @__PURE__ */ jsxs("span", { className: `flex flex-wrap items-baseline gap-x-2 gap-y-1 ${dim}`, children: [
9854
+ fromPrice && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "A partir de" }),
9855
+ /* @__PURE__ */ jsx(
9856
+ "span",
9857
+ {
9858
+ className: onSale ? "font-bold text-emerald-600 dark:text-emerald-400" : "font-semibold",
9859
+ children: money(product.price)
9860
+ }
9861
+ ),
9862
+ onSale && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground line-through", children: money(product.compareAtPrice) }),
9863
+ onSale && off >= 5 && // Cor sozinha não conta a promoção — o texto "-12%" conta, e é
9864
+ // o que um leitor de tela e um olho daltônico recebem.
9865
+ /* @__PURE__ */ jsxs("span", { className: "rounded bg-emerald-600 px-1.5 py-0.5 text-[11px] font-bold text-white", children: [
9866
+ "-",
9867
+ off,
9868
+ "%"
9869
+ ] })
9870
+ ] }),
9871
+ onQuickAdd && !soldOut && /* @__PURE__ */ jsx(
9872
+ "button",
9873
+ {
9874
+ type: "button",
9875
+ "data-testid": TID.menuQuickAdd(product.slug),
9876
+ onClick: () => onQuickAdd(product),
9877
+ disabled,
9878
+ "aria-label": `Adicionar ${product.name}`,
9879
+ className: "relative z-20 grid h-8 w-8 shrink-0 place-items-center rounded-full bg-primary text-primary-foreground disabled:opacity-40",
9880
+ children: /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" })
9881
+ }
9882
+ )
9883
+ ] }),
9884
+ soldOut && /* @__PURE__ */ jsx("p", { className: "mt-1.5", children: /* @__PURE__ */ jsx(
9885
+ "span",
9886
+ {
9887
+ "data-testid": TID.menuItemSoldOut,
9888
+ className: "rounded bg-muted px-2 py-0.5 text-xs uppercase tracking-wide text-muted-foreground",
9889
+ children: "Indispon\xEDvel"
9890
+ }
9891
+ ) })
9892
+ ] }),
9893
+ image && /* @__PURE__ */ jsx(
9894
+ SmoothImage,
9895
+ {
9896
+ src: image.url,
9897
+ alt: "",
9898
+ variant: { width: 96 },
9899
+ className: `h-24 w-24 shrink-0 rounded-lg object-cover ${soldOut ? "opacity-40 grayscale" : ""}`
9900
+ }
9901
+ )
9902
+ ] })
9903
+ ]
9904
+ }
9905
+ );
9906
+ }
9907
+ var NOTES_MAX = 250;
9908
+ function ItemSheet({
9909
+ product,
9910
+ onClose,
9911
+ onAdd,
9912
+ disabled,
9913
+ disabledReason
9914
+ }) {
9915
+ const config = useStorefrontConfig();
9916
+ const money = (v) => formatMoney(v, config.currency, config.locale);
9917
+ const groups = React37.useMemo(() => getProductOptionGroups(product), [product]);
9918
+ const [options, setOptions] = React37.useState({});
9919
+ const [notes, setNotes] = React37.useState("");
9920
+ const [quantity, setQuantity] = React37.useState(1);
9921
+ const image = product.images?.find((i) => i.isPrimary) ?? product.images?.[0];
9922
+ const stock = product.inventoryCount;
9923
+ const missing = groups.filter((g) => !options[g.label]);
9924
+ const ready = missing.length === 0;
9925
+ const unit = selectionPrice(product, options);
9926
+ const soldOut = stock <= 0;
9927
+ const blocked = disabled || soldOut;
9928
+ const blockedReason = soldOut ? "Este item est\xE1 indispon\xEDvel no momento." : disabledReason;
9929
+ return /* @__PURE__ */ jsx(
9930
+ "div",
9931
+ {
9932
+ "data-testid": TID.itemSheet,
9933
+ role: "dialog",
9934
+ "aria-modal": "true",
9935
+ "aria-label": product.name,
9936
+ className: "fixed inset-0 z-50 flex flex-col bg-background sm:items-center sm:justify-center sm:bg-black/50 sm:p-6",
9937
+ children: /* @__PURE__ */ jsxs("div", { className: "flex h-full w-full flex-col overflow-hidden bg-background sm:h-auto sm:max-h-[85vh] sm:max-w-lg sm:rounded-2xl", children: [
9938
+ /* @__PURE__ */ jsxs("div", { className: "relative shrink-0", children: [
9939
+ image && /* @__PURE__ */ jsx(
9940
+ SmoothImage,
9941
+ {
9942
+ src: image.url,
9943
+ alt: "",
9944
+ variant: { width: 640 },
9945
+ className: "h-56 w-full object-cover"
9946
+ }
9947
+ ),
9948
+ /* @__PURE__ */ jsx(
9949
+ "button",
9950
+ {
9951
+ type: "button",
9952
+ onClick: onClose,
9953
+ "aria-label": "Fechar",
9954
+ className: "absolute left-3 top-3 grid h-9 w-9 place-items-center rounded-full bg-background/90 shadow",
9955
+ children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
9956
+ }
9957
+ )
9958
+ ] }),
9959
+ /* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto pb-4", children: [
9960
+ /* @__PURE__ */ jsxs("div", { className: "px-4 pt-4", children: [
9961
+ /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold", children: product.name }),
9962
+ stock > 0 && stock <= 20 && /* @__PURE__ */ jsxs("p", { className: "mt-1 text-sm font-medium", children: [
9963
+ stock,
9964
+ " unidades dispon\xEDveis"
9965
+ ] }),
9966
+ product.description && /* @__PURE__ */ jsx("p", { className: "mt-3 whitespace-pre-line text-sm text-muted-foreground", children: product.description })
9967
+ ] }),
9968
+ groups.map((group) => /* @__PURE__ */ jsx(
9969
+ OptionGroup,
9970
+ {
9971
+ product,
9972
+ group,
9973
+ selection: options,
9974
+ onSelect: (value) => setOptions((prev) => ({ ...prev, [group.label]: value }))
9975
+ },
9976
+ group.id
9977
+ )),
9978
+ /* @__PURE__ */ jsxs("div", { className: "mt-6 px-4", children: [
9979
+ /* @__PURE__ */ jsx(
9980
+ "label",
9981
+ {
9982
+ htmlFor: "item-sheet-notes",
9983
+ className: "text-xs font-medium uppercase tracking-wide text-muted-foreground",
9984
+ children: "Observa\xE7\xF5es do item"
9985
+ }
9986
+ ),
9987
+ /* @__PURE__ */ jsx(
9988
+ "textarea",
9989
+ {
9990
+ id: "item-sheet-notes",
9991
+ "data-testid": TID.itemSheetNotes,
9992
+ value: notes,
9993
+ onChange: (e) => setNotes(e.target.value.slice(0, NOTES_MAX)),
9994
+ rows: 3,
9995
+ placeholder: "Ex.: sem cebola, ponto da carne\u2026",
9996
+ className: "mt-2 w-full resize-none rounded-lg border border-border bg-transparent p-3 text-base"
9997
+ }
9998
+ ),
9999
+ /* @__PURE__ */ jsxs(
10000
+ "p",
10001
+ {
10002
+ "data-testid": TID.itemSheetNotesCount,
10003
+ className: "mt-1 text-right text-xs text-muted-foreground",
10004
+ children: [
10005
+ notes.length,
10006
+ "/",
10007
+ NOTES_MAX
10008
+ ]
10009
+ }
10010
+ )
10011
+ ] })
10012
+ ] }),
10013
+ /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-t border-border p-4 pb-[max(1rem,env(safe-area-inset-bottom))]", children: [
10014
+ blocked && blockedReason && /* @__PURE__ */ jsx("p", { className: "mb-3 text-center text-sm text-muted-foreground", children: blockedReason }),
10015
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
10016
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-full border border-border px-2 py-1", children: [
10017
+ /* @__PURE__ */ jsx(
10018
+ "button",
10019
+ {
10020
+ type: "button",
10021
+ "aria-label": "Menos um",
10022
+ onClick: () => setQuantity((q) => Math.max(1, q - 1)),
10023
+ className: "grid h-9 w-9 place-items-center rounded-full disabled:opacity-40",
10024
+ disabled: quantity <= 1,
10025
+ children: /* @__PURE__ */ jsx(Minus, { className: "h-4 w-4" })
10026
+ }
10027
+ ),
10028
+ /* @__PURE__ */ jsx("span", { "data-testid": TID.itemSheetQty, className: "min-w-4 text-center tabular-nums", children: quantity }),
10029
+ /* @__PURE__ */ jsx(
10030
+ "button",
10031
+ {
10032
+ type: "button",
10033
+ "aria-label": "Mais um",
10034
+ onClick: () => setQuantity((q) => Math.min(stock || Infinity, q + 1)),
10035
+ className: "grid h-9 w-9 place-items-center rounded-full disabled:opacity-40",
10036
+ disabled: stock > 0 && quantity >= stock,
10037
+ children: /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" })
10038
+ }
10039
+ )
10040
+ ] }),
10041
+ /* @__PURE__ */ jsx(
10042
+ "button",
10043
+ {
10044
+ type: "button",
10045
+ "data-testid": TID.itemSheetAdd,
10046
+ disabled: blocked || !ready,
10047
+ onClick: () => onAdd({ quantity, options, notes: notes.trim() || void 0 }),
10048
+ className: "flex h-12 flex-1 items-center justify-center rounded-full bg-primary px-6 text-sm font-bold uppercase tracking-wide text-primary-foreground disabled:opacity-40",
10049
+ children: ready ? `Adicionar \u2022 ${money(unit * quantity)}` : `Escolha ${missing[0].label}`
10050
+ }
10051
+ )
10052
+ ] })
10053
+ ] })
10054
+ ] })
10055
+ }
10056
+ );
10057
+ }
10058
+ function OptionGroup({
10059
+ product,
10060
+ group,
10061
+ selection,
10062
+ onSelect
10063
+ }) {
10064
+ const config = useStorefrontConfig();
10065
+ const money = (v) => formatMoney(v, config.currency, config.locale);
10066
+ const chosen = selection[group.label];
10067
+ const prices = group.values.map(
10068
+ (value) => selectionPrice(product, { ...selection, [group.label]: value })
10069
+ );
10070
+ const base = Math.min(...prices);
10071
+ return /* @__PURE__ */ jsxs("section", { "data-testid": TID.itemSheetGroup(group.label), className: "mt-6", children: [
10072
+ /* @__PURE__ */ jsxs("div", { className: "border-y border-border bg-muted/40 px-4 py-3", children: [
10073
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-bold", children: group.label }),
10074
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Escolha 1 op\xE7\xE3o." })
10075
+ ] }),
10076
+ /* @__PURE__ */ jsx("ul", { children: group.values.map((value, index) => {
10077
+ const delta = prices[index] - base;
10078
+ const on = chosen === value;
10079
+ return /* @__PURE__ */ jsx("li", { className: "border-b border-border", children: /* @__PURE__ */ jsxs(
10080
+ "button",
10081
+ {
10082
+ type: "button",
10083
+ role: "radio",
10084
+ "aria-checked": on,
10085
+ onClick: () => onSelect(value),
10086
+ className: "flex min-h-14 w-full items-center gap-3 px-4 py-3 text-left",
10087
+ children: [
10088
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
10089
+ /* @__PURE__ */ jsx("span", { className: "block text-sm", children: value }),
10090
+ delta > 0 && /* @__PURE__ */ jsxs("span", { className: "block text-sm font-semibold", children: [
10091
+ "+ ",
10092
+ money(delta)
10093
+ ] })
10094
+ ] }),
10095
+ /* @__PURE__ */ jsx(
10096
+ "span",
10097
+ {
10098
+ "aria-hidden": true,
10099
+ className: `grid h-5 w-5 shrink-0 place-items-center rounded-full border-2 ${on ? "border-primary" : "border-muted-foreground/40"}`,
10100
+ children: on && /* @__PURE__ */ jsx("span", { className: "h-2.5 w-2.5 rounded-full bg-primary" })
10101
+ }
10102
+ )
10103
+ ]
10104
+ }
10105
+ ) }, value);
10106
+ }) })
10107
+ ] });
10108
+ }
10109
+ function ScheduleSheet({ onClose }) {
10110
+ const fulfillmentType = useDeliveryStore((s2) => s2.fulfillmentType);
10111
+ const scheduledFor = useDeliveryStore((s2) => s2.scheduledFor);
10112
+ const setScheduledFor = useDeliveryStore((s2) => s2.setScheduledFor);
10113
+ const timezone = useServiceStore((s2) => s2.service?.timezone) ?? "America/Sao_Paulo";
10114
+ const [slots, setSlots] = React37.useState(null);
10115
+ React37.useEffect(() => {
10116
+ let alive = true;
10117
+ void (async () => {
10118
+ const rows = await getShopProvider()?.listScheduleSlots?.(fulfillmentType) ?? [];
10119
+ if (alive) setSlots(rows);
10120
+ })();
10121
+ return () => {
10122
+ alive = false;
10123
+ };
10124
+ }, [fulfillmentType]);
10125
+ const days = React37.useMemo(() => {
10126
+ const fmtDay = new Intl.DateTimeFormat("pt-BR", {
10127
+ weekday: "long",
10128
+ day: "2-digit",
10129
+ month: "2-digit",
10130
+ timeZone: timezone
10131
+ });
10132
+ const map = /* @__PURE__ */ new Map();
10133
+ for (const slot of slots ?? []) {
10134
+ const key = fmtDay.format(new Date(slot.startsAt));
10135
+ const list = map.get(key) ?? [];
10136
+ list.push(slot);
10137
+ map.set(key, list);
10138
+ }
10139
+ return [...map.entries()];
10140
+ }, [slots, timezone]);
10141
+ const fmtTime = new Intl.DateTimeFormat("pt-BR", {
10142
+ hour: "2-digit",
10143
+ minute: "2-digit",
10144
+ timeZone: timezone
10145
+ });
10146
+ return /* @__PURE__ */ jsx(
10147
+ "div",
10148
+ {
10149
+ role: "dialog",
10150
+ "aria-modal": "true",
10151
+ "aria-label": "Agendar",
10152
+ "data-testid": TID.scheduleSheet,
10153
+ className: "fixed inset-0 z-50 flex flex-col justify-end bg-black/50 sm:items-center sm:justify-center sm:p-6",
10154
+ children: /* @__PURE__ */ jsxs("div", { className: "max-h-[80vh] w-full overflow-hidden rounded-t-2xl bg-background sm:max-w-md sm:rounded-2xl", children: [
10155
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4 border-b border-border p-4", children: [
10156
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: "Agendar" }),
10157
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, "aria-label": "Fechar", className: "-mr-1 -mt-1 p-1", children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" }) })
10158
+ ] }),
10159
+ /* @__PURE__ */ jsxs("div", { className: "max-h-[60vh] overflow-y-auto p-4", children: [
10160
+ /* @__PURE__ */ jsx(
10161
+ "button",
10162
+ {
10163
+ type: "button",
10164
+ onClick: () => {
10165
+ setScheduledFor(null);
10166
+ onClose();
10167
+ },
10168
+ "aria-pressed": !scheduledFor,
10169
+ className: `mb-4 w-full rounded-lg border px-4 py-3 text-left text-sm ${!scheduledFor ? "border-primary font-medium" : "border-border"}`,
10170
+ children: "O quanto antes"
10171
+ }
10172
+ ),
10173
+ slots === null && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Carregando hor\xE1rios\u2026" }),
10174
+ slots !== null && !slots.length && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "N\xE3o h\xE1 hor\xE1rios dispon\xEDveis para agendar agora." }),
10175
+ days.map(([day, daySlots]) => /* @__PURE__ */ jsxs("div", { className: "mb-5", children: [
10176
+ /* @__PURE__ */ jsx("h3", { className: "mb-2 text-sm font-semibold capitalize", children: day }),
10177
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-2", children: daySlots.map((slot) => {
10178
+ const active2 = scheduledFor === slot.startsAt;
10179
+ return /* @__PURE__ */ jsxs(
10180
+ "button",
10181
+ {
10182
+ type: "button",
10183
+ "data-testid": TID.scheduleSlot(slot.startsAt),
10184
+ "aria-pressed": active2,
10185
+ onClick: () => {
10186
+ setScheduledFor(slot.startsAt);
10187
+ onClose();
10188
+ },
10189
+ className: `rounded-lg border px-3 py-2 text-sm ${active2 ? "border-primary font-medium" : "border-border"}`,
10190
+ children: [
10191
+ fmtTime.format(new Date(slot.startsAt)),
10192
+ " \u2013 ",
10193
+ fmtTime.format(new Date(slot.endsAt))
10194
+ ]
10195
+ },
10196
+ slot.startsAt
10197
+ );
10198
+ }) })
10199
+ ] }, day))
10200
+ ] })
10201
+ ] })
10202
+ }
10203
+ );
10204
+ }
10205
+ var WEEKDAYS = ["dom", "seg", "ter", "qua", "qui", "sex", "s\xE1b"];
10206
+ function hhmm(time) {
10207
+ return time.slice(0, 5);
10208
+ }
10209
+ function ServiceHoursModal({ onClose }) {
10210
+ const fulfillmentType = useServiceStore((s2) => s2.fulfillmentType);
10211
+ const [hours, setHours] = React37.useState(null);
10212
+ React37.useEffect(() => {
10213
+ let alive = true;
10214
+ void (async () => {
10215
+ const rows = await getShopProvider()?.listServiceHours?.(fulfillmentType) ?? [];
10216
+ if (alive) setHours(rows);
10217
+ })();
10218
+ return () => {
10219
+ alive = false;
10220
+ };
10221
+ }, [fulfillmentType]);
10222
+ const byDay = React37.useMemo(() => {
10223
+ const map = /* @__PURE__ */ new Map();
10224
+ for (const h of hours ?? []) {
10225
+ const list = map.get(h.weekday) ?? [];
10226
+ list.push(h);
10227
+ map.set(h.weekday, list);
10228
+ }
10229
+ return map;
10230
+ }, [hours]);
10231
+ return /* @__PURE__ */ jsx(
10232
+ "div",
10233
+ {
10234
+ role: "dialog",
10235
+ "aria-modal": "true",
10236
+ "aria-label": "Hor\xE1rios de funcionamento",
10237
+ "data-testid": TID.serviceHoursModal,
10238
+ className: "fixed inset-0 z-50 grid place-items-center bg-black/50 p-4",
10239
+ children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded-2xl bg-background p-6", children: [
10240
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
10241
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: "Hor\xE1rios de funcionamento" }),
10242
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, "aria-label": "Fechar", className: "-mr-1 -mt-1 p-1", children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" }) })
10243
+ ] }),
10244
+ hours === null && /* @__PURE__ */ jsx("p", { className: "mt-4 text-sm text-muted-foreground", children: "Carregando\u2026" }),
10245
+ hours !== null && /* @__PURE__ */ jsx("ul", { className: "mt-4 space-y-2 text-sm", children: WEEKDAYS.map((label, day) => {
10246
+ const windows = byDay.get(day) ?? [];
10247
+ return /* @__PURE__ */ jsxs("li", { className: "flex items-baseline justify-between gap-4", children: [
10248
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label }),
10249
+ /* @__PURE__ */ jsx("span", { className: "text-right", children: windows.length ? windows.map((w) => `${hhmm(w.opensAt)} \xE0s ${hhmm(w.closesAt)}`).join(" \xB7 ") : (
10250
+ // Um dia sem janela não é um erro nem um vazio: é uma
10251
+ // informação, e escrevê-la evita que alguém volte amanhã
10252
+ // para descobrir o mesmo.
10253
+ "Fechado"
10254
+ ) })
10255
+ ] }, day);
10256
+ }) })
10257
+ ] })
10258
+ }
10259
+ );
10260
+ }
10261
+ function ClosedStoreDialog({
10262
+ onClose,
10263
+ onShowHours
10264
+ }) {
10265
+ const state = useServiceStore();
10266
+ const tz = state.service?.timezone ?? "America/Sao_Paulo";
10267
+ const next = state.service?.nextOpenAt;
10268
+ return /* @__PURE__ */ jsx(
10269
+ "div",
10270
+ {
10271
+ role: "dialog",
10272
+ "aria-modal": "true",
10273
+ "data-testid": TID.serviceClosedGate,
10274
+ className: "fixed inset-0 z-[60] grid place-items-center bg-black/50 p-4",
10275
+ children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded-2xl bg-background p-6 text-center", children: [
10276
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: "Nossa loja est\xE1 fechada no momento" }),
10277
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: next ? `Abrimos ${storeTime(next, tz, { weekday: true })}. Enquanto isso, fique \xE0 vontade para olhar o card\xE1pio.` : "Enquanto isso, fique \xE0 vontade para olhar o card\xE1pio." }),
10278
+ /* @__PURE__ */ jsx(
10279
+ "button",
10280
+ {
10281
+ type: "button",
10282
+ onClick: onShowHours,
10283
+ className: "mt-5 w-full rounded-full bg-primary px-6 py-3 font-semibold text-primary-foreground",
10284
+ children: "Ver hor\xE1rios de funcionamento"
10285
+ }
10286
+ ),
10287
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, className: "mt-3 w-full py-2 text-sm font-medium", children: "OK, entendi" })
10288
+ ] })
10289
+ }
10290
+ );
10291
+ }
10292
+ var LABELS3 = ["Casa", "Trabalho"];
10293
+ function AddressSheet({ onClose }) {
10294
+ const saved = useDeliveryStore((s2) => s2.savedAddresses);
10295
+ const selectedId = useDeliveryStore((s2) => s2.selectedAddressId);
10296
+ const selectAddress = useDeliveryStore((s2) => s2.selectAddress);
10297
+ const removeAddress = useDeliveryStore((s2) => s2.removeAddress);
10298
+ const subtotal = selectSubtotal(useCartStore());
10299
+ const [adding, setAdding] = React37.useState(saved.length === 0);
10300
+ return /* @__PURE__ */ jsx(
10301
+ "div",
10302
+ {
10303
+ role: "dialog",
10304
+ "aria-modal": "true",
10305
+ "aria-label": "Endere\xE7o de entrega",
10306
+ "data-testid": TID.addressSheet,
10307
+ className: "fixed inset-0 z-50 flex flex-col justify-end bg-black/50 sm:items-center sm:justify-center sm:p-6",
10308
+ children: /* @__PURE__ */ jsxs("div", { className: "flex max-h-[85vh] w-full flex-col overflow-hidden rounded-t-2xl bg-background sm:max-w-md sm:rounded-2xl", children: [
10309
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-border p-4", children: [
10310
+ adding && saved.length > 0 && /* @__PURE__ */ jsx(
10311
+ "button",
10312
+ {
10313
+ type: "button",
10314
+ onClick: () => setAdding(false),
10315
+ "aria-label": "Voltar",
10316
+ className: "-ml-2 grid h-9 w-9 place-items-center rounded-full",
10317
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-5 w-5" })
10318
+ }
10319
+ ),
10320
+ /* @__PURE__ */ jsx("h2", { className: "flex-1 text-lg font-semibold", children: adding ? "Novo endere\xE7o" : "Onde voc\xEA quer receber?" }),
10321
+ /* @__PURE__ */ jsx(
10322
+ "button",
10323
+ {
10324
+ type: "button",
10325
+ onClick: onClose,
10326
+ "aria-label": "Fechar",
10327
+ className: "-mr-2 grid h-9 w-9 place-items-center rounded-full",
10328
+ children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
10329
+ }
10330
+ )
10331
+ ] }),
10332
+ adding ? /* @__PURE__ */ jsx(
10333
+ AddressForm,
10334
+ {
10335
+ onDone: () => {
10336
+ setAdding(false);
10337
+ onClose();
10338
+ }
10339
+ }
10340
+ ) : /* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children: [
10341
+ /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: saved.map((address) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
10342
+ /* @__PURE__ */ jsxs(
10343
+ "button",
10344
+ {
10345
+ type: "button",
10346
+ "data-testid": TID.savedAddress(address.id),
10347
+ "aria-pressed": selectedId === address.id,
10348
+ onClick: () => {
10349
+ void selectAddress(address.id, subtotal);
10350
+ onClose();
10351
+ },
10352
+ className: `flex min-w-0 flex-1 items-start gap-3 rounded-lg border p-3 text-left ${selectedId === address.id ? "border-primary" : "border-border"}`,
10353
+ children: [
10354
+ /* @__PURE__ */ jsx(MapPin, { className: "mt-0.5 h-5 w-5 shrink-0 text-muted-foreground" }),
10355
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
10356
+ address.label && /* @__PURE__ */ jsx("span", { className: "block text-sm font-semibold", children: address.label }),
10357
+ /* @__PURE__ */ jsxs("span", { className: "block truncate text-sm", children: [
10358
+ address.street,
10359
+ ", ",
10360
+ address.number
10361
+ ] }),
10362
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-xs text-muted-foreground", children: [address.complement, address.district, address.city].filter(Boolean).join(" \xB7 ") }),
10363
+ address.reference && /* @__PURE__ */ jsx("span", { className: "mt-0.5 block truncate text-xs text-muted-foreground", children: address.reference })
10364
+ ] })
10365
+ ]
10366
+ }
10367
+ ),
10368
+ /* @__PURE__ */ jsx(
10369
+ "button",
10370
+ {
10371
+ type: "button",
10372
+ onClick: () => removeAddress(address.id),
10373
+ "aria-label": `Remover ${address.street}, ${address.number}`,
10374
+ className: "mt-1 grid h-10 w-10 shrink-0 place-items-center rounded-full text-muted-foreground",
10375
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
10376
+ }
10377
+ )
10378
+ ] }, address.id)) }),
10379
+ /* @__PURE__ */ jsxs(
10380
+ "button",
10381
+ {
10382
+ type: "button",
10383
+ "data-testid": TID.addressAddNew,
10384
+ onClick: () => setAdding(true),
10385
+ className: "mt-4 flex w-full items-center justify-center gap-2 rounded-full border border-border px-4 py-3 text-sm font-semibold",
10386
+ children: [
10387
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
10388
+ "Adicionar novo endere\xE7o"
10389
+ ]
10390
+ }
10391
+ )
10392
+ ] })
10393
+ ] })
10394
+ }
10395
+ );
10396
+ }
10397
+ function AddressForm({ onDone }) {
10398
+ const resolve = useDeliveryStore((s2) => s2.resolve);
10399
+ const saveAddress = useDeliveryStore((s2) => s2.saveAddress);
10400
+ const looked = useDeliveryStore((s2) => s2.address);
10401
+ const status = useDeliveryStore((s2) => s2.status);
10402
+ const storeError = useDeliveryStore((s2) => s2.error);
10403
+ const refusedZone = useDeliveryStore((s2) => s2.refusedZone);
10404
+ const subtotal = selectSubtotal(useCartStore());
10405
+ const [cep, setCep] = React37.useState("");
10406
+ const [number, setNumber] = React37.useState("");
10407
+ const [complement, setComplement] = React37.useState("");
10408
+ const [reference, setReference] = React37.useState("");
10409
+ const [label, setLabel] = React37.useState("");
10410
+ const [touched, setTouched] = React37.useState(false);
10411
+ const numberRef = React37.useRef(null);
10412
+ const digits = normalizePostalCode(cep);
10413
+ const found = Boolean(looked) && normalizePostalCode(looked.postalCode) === digits;
10414
+ React37.useEffect(() => {
10415
+ if (found) numberRef.current?.focus();
10416
+ }, [found]);
10417
+ async function lookUp() {
10418
+ if (digits.length !== 8) return;
10419
+ await resolve(digits, subtotal);
10420
+ }
10421
+ function submit(event) {
10422
+ event.preventDefault();
10423
+ setTouched(true);
10424
+ if (!found || !number.trim()) return;
10425
+ saveAddress({
10426
+ label: label.trim() || null,
10427
+ postalCode: looked.postalCode,
10428
+ street: looked.street,
10429
+ number: number.trim(),
10430
+ complement: complement.trim() || null,
10431
+ reference: reference.trim() || null,
10432
+ district: looked.district,
10433
+ city: looked.city,
10434
+ state: looked.state
10435
+ });
10436
+ onDone();
10437
+ }
10438
+ const numberMissing = touched && !number.trim();
10439
+ return /* @__PURE__ */ jsxs("form", { onSubmit: submit, className: "min-h-0 flex-1 overflow-y-auto p-4", children: [
10440
+ /* @__PURE__ */ jsx("label", { htmlFor: "address-cep", className: "text-sm font-medium", children: "CEP" }),
10441
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex gap-2", children: [
10442
+ /* @__PURE__ */ jsx(
10443
+ "input",
10444
+ {
10445
+ id: "address-cep",
10446
+ "data-testid": TID.addressCep,
10447
+ value: cep,
10448
+ onChange: (e) => setCep(formatPostalCode(e.target.value)),
10449
+ onBlur: () => void lookUp(),
10450
+ inputMode: "numeric",
10451
+ autoComplete: "postal-code",
10452
+ placeholder: "00000-000",
10453
+ className: "h-12 flex-1 rounded-lg border border-border bg-transparent px-3 text-base"
10454
+ }
10455
+ ),
10456
+ /* @__PURE__ */ jsx(
10457
+ "button",
10458
+ {
10459
+ type: "button",
10460
+ onClick: () => void lookUp(),
10461
+ disabled: digits.length !== 8 || status === "loading",
10462
+ className: "h-12 rounded-lg bg-primary px-4 text-sm font-semibold text-primary-foreground disabled:opacity-40",
10463
+ children: status === "loading" ? "Buscando\u2026" : "Buscar"
10464
+ }
10465
+ )
10466
+ ] }),
10467
+ storeError && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-destructive", children: storeError }),
10468
+ refusedZone && /* @__PURE__ */ jsxs("p", { className: "mt-2 text-sm text-destructive", children: [
10469
+ "Ainda n\xE3o entregamos nessa regi\xE3o (",
10470
+ refusedZone,
10471
+ ")."
10472
+ ] }),
10473
+ found && /* @__PURE__ */ jsxs(Fragment, { children: [
10474
+ /* @__PURE__ */ jsxs("p", { className: "mt-4 rounded-lg bg-muted/40 p-3 text-sm", children: [
10475
+ /* @__PURE__ */ jsx("span", { className: "block font-medium", children: looked.street }),
10476
+ /* @__PURE__ */ jsxs("span", { className: "block text-muted-foreground", children: [
10477
+ looked.district,
10478
+ " \xB7 ",
10479
+ looked.city,
10480
+ " \u2014 ",
10481
+ looked.state
10482
+ ] })
10483
+ ] }),
10484
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 grid grid-cols-2 gap-3", children: [
10485
+ /* @__PURE__ */ jsxs("div", { children: [
10486
+ /* @__PURE__ */ jsx("label", { htmlFor: "address-number", className: "text-sm font-medium", children: "N\xFAmero" }),
10487
+ /* @__PURE__ */ jsx(
10488
+ "input",
10489
+ {
10490
+ id: "address-number",
10491
+ ref: numberRef,
10492
+ "data-testid": TID.addressNumber,
10493
+ value: number,
10494
+ onChange: (e) => setNumber(e.target.value),
10495
+ inputMode: "numeric",
10496
+ autoComplete: "address-line2",
10497
+ "aria-invalid": numberMissing || void 0,
10498
+ "aria-describedby": numberMissing ? "address-number-error" : void 0,
10499
+ className: `mt-1 h-12 w-full rounded-lg border bg-transparent px-3 text-base ${numberMissing ? "border-destructive" : "border-border"}`
10500
+ }
10501
+ ),
10502
+ numberMissing && // Junto do campo e não num resumo no topo: o erro é sobre ESTE
10503
+ // campo, e a pessoa está olhando para ele.
10504
+ /* @__PURE__ */ jsx("p", { id: "address-number-error", className: "mt-1 text-xs text-destructive", children: "Informe o n\xFAmero." })
10505
+ ] }),
10506
+ /* @__PURE__ */ jsxs("div", { children: [
10507
+ /* @__PURE__ */ jsx("label", { htmlFor: "address-complement", className: "text-sm font-medium", children: "Complemento" }),
10508
+ /* @__PURE__ */ jsx(
10509
+ "input",
10510
+ {
10511
+ id: "address-complement",
10512
+ "data-testid": TID.addressComplement,
10513
+ value: complement,
10514
+ onChange: (e) => setComplement(e.target.value),
10515
+ placeholder: "Apto, bloco\u2026",
10516
+ className: "mt-1 h-12 w-full rounded-lg border border-border bg-transparent px-3 text-base"
10517
+ }
10518
+ )
10519
+ ] })
10520
+ ] }),
10521
+ /* @__PURE__ */ jsxs("div", { className: "mt-4", children: [
10522
+ /* @__PURE__ */ jsx("label", { htmlFor: "address-reference", className: "text-sm font-medium", children: "Ponto de refer\xEAncia" }),
10523
+ /* @__PURE__ */ jsx(
10524
+ "input",
10525
+ {
10526
+ id: "address-reference",
10527
+ "data-testid": TID.addressReference,
10528
+ value: reference,
10529
+ onChange: (e) => setReference(e.target.value),
10530
+ placeholder: "Port\xE3o azul, ao lado da padaria",
10531
+ className: "mt-1 h-12 w-full rounded-lg border border-border bg-transparent px-3 text-base"
10532
+ }
10533
+ )
10534
+ ] }),
10535
+ /* @__PURE__ */ jsxs("fieldset", { className: "mt-4", children: [
10536
+ /* @__PURE__ */ jsx("legend", { className: "text-sm font-medium", children: "Salvar como" }),
10537
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap gap-2", children: [
10538
+ LABELS3.map((name) => /* @__PURE__ */ jsx(
10539
+ "button",
10540
+ {
10541
+ type: "button",
10542
+ "aria-pressed": label === name,
10543
+ onClick: () => setLabel(label === name ? "" : name),
10544
+ className: `rounded-full px-4 py-2 text-sm font-medium ${label === name ? "bg-foreground text-background" : "border border-border text-muted-foreground"}`,
10545
+ children: name
10546
+ },
10547
+ name
10548
+ )),
10549
+ /* @__PURE__ */ jsx(
10550
+ "input",
10551
+ {
10552
+ value: LABELS3.includes(label) ? "" : label,
10553
+ onChange: (e) => setLabel(e.target.value),
10554
+ placeholder: "Outro",
10555
+ "aria-label": "Outro apelido",
10556
+ className: "h-10 min-w-24 flex-1 rounded-full border border-border bg-transparent px-4 text-sm"
10557
+ }
10558
+ )
10559
+ ] })
10560
+ ] }),
10561
+ /* @__PURE__ */ jsx(
10562
+ "button",
10563
+ {
10564
+ type: "submit",
10565
+ "data-testid": TID.addressSave,
10566
+ className: "mt-6 h-12 w-full rounded-full bg-primary font-semibold text-primary-foreground",
10567
+ children: "Salvar endere\xE7o"
10568
+ }
10569
+ )
10570
+ ] })
10571
+ ] });
10572
+ }
10573
+ var HIGHLIGHT_LIMIT = 6;
10574
+ function MenuPage() {
10575
+ const config = useStorefrontConfig();
10576
+ const addItem = useCartStore((s2) => s2.addItem);
10577
+ const [openProduct, setOpenProduct] = React37.useState(null);
10578
+ const [showHours, setShowHours] = React37.useState(false);
10579
+ const [showClosed, setShowClosed] = React37.useState(false);
10580
+ const [showAddress, setShowAddress] = React37.useState(false);
10581
+ const [showSchedule, setShowSchedule] = React37.useState(false);
10582
+ const [query, setQuery] = React37.useState("");
10583
+ const serviceState = useServiceStore();
10584
+ const refreshService = useServiceStore((s2) => s2.refresh);
10585
+ const isOpen = selectIsOpen(serviceState);
10586
+ React37.useEffect(() => {
10587
+ void refreshService();
10588
+ }, [refreshService]);
10589
+ const cart = useCartStore();
10590
+ const openDrawer = useCartStore((s2) => s2.openDrawer);
10591
+ const pushToast = useToastStore((s2) => s2.push);
10592
+ const count = selectCount(cart);
10593
+ const subtotal = selectSubtotal(cart);
10594
+ function guarded(run) {
10595
+ if (!isOpen) {
10596
+ setShowClosed(true);
10597
+ return;
10598
+ }
10599
+ run();
10600
+ }
10601
+ const products = useLoader("products.list", {});
10602
+ const categories = useLoader("categories.list", {});
10603
+ const loading = products.status === "loading" || products.status === "idle";
10604
+ const items = products.data ?? [];
10605
+ const cats = categories.data ?? [];
10606
+ const allSections = React37.useMemo(() => {
10607
+ const byCategory = /* @__PURE__ */ new Map();
10608
+ const loose = [];
10609
+ for (const p of items) {
10610
+ const key = p.categoryId ?? p.categoryName ?? "";
10611
+ if (!key) {
10612
+ loose.push(p);
10613
+ continue;
10614
+ }
10615
+ const list = byCategory.get(key) ?? [];
10616
+ list.push(p);
10617
+ byCategory.set(key, list);
10618
+ }
10619
+ const ordered = cats.map((c) => ({
10620
+ id: c.id,
10621
+ slug: c.slug ?? c.id,
10622
+ name: c.name,
10623
+ description: c.description ?? null,
10624
+ products: byCategory.get(c.id) ?? byCategory.get(c.name) ?? []
10625
+ })).filter((s2) => s2.products.length > 0);
10626
+ if (loose.length) {
10627
+ ordered.push({ id: "outros", slug: "outros", name: "Outros", description: null, products: loose });
10628
+ }
10629
+ if (!ordered.length && items.length) {
10630
+ return [{ id: "todos", slug: "todos", name: "Card\xE1pio", description: null, products: items }];
10631
+ }
10632
+ return ordered;
10633
+ }, [items, cats]);
10634
+ const highlights = React37.useMemo(() => {
10635
+ const slugs = config.delivery?.highlightSlugs;
10636
+ if (slugs?.length) {
10637
+ const bySlug = new Map(items.map((p) => [p.slug, p]));
10638
+ return slugs.map((slug) => bySlug.get(slug)).filter((p) => Boolean(p)).slice(0, HIGHLIGHT_LIMIT);
10639
+ }
10640
+ return items.filter((p) => p.compareAtPrice != null && p.compareAtPrice > p.price && p.inventoryCount > 0).slice(0, HIGHLIGHT_LIMIT);
10641
+ }, [items, config.delivery?.highlightSlugs]);
10642
+ const normalized = query.trim().toLowerCase();
10643
+ const sections = React37.useMemo(() => {
10644
+ if (!normalized) return allSections;
10645
+ return allSections.map((section) => ({
10646
+ ...section,
10647
+ products: section.products.filter(
10648
+ (p) => p.name.toLowerCase().includes(normalized) || (p.description ?? "").toLowerCase().includes(normalized)
10649
+ )
10650
+ })).filter((section) => section.products.length > 0);
10651
+ }, [allSections, normalized]);
10652
+ const matches = sections.reduce((n, s2) => n + s2.products.length, 0);
10653
+ function addToCart(product, quantity, options, notes) {
10654
+ addItem(product, quantity, options, notes, { openDrawer: false });
10655
+ pushToast({
10656
+ title: `${quantity}\xD7 ${product.name}`,
10657
+ description: "Adicionado ao carrinho.",
10658
+ variant: "success",
10659
+ action: { label: "Ver carrinho", onClick: openDrawer },
10660
+ // Mais longo que o padrão: o aviso carrega uma decisão, e 3,4s é pouco
10661
+ // para ler, decidir e acertar um alvo com o polegar.
10662
+ durationMs: 5e3
10663
+ });
10664
+ }
10665
+ function quickAddable(product) {
10666
+ return getProductOptionGroups(product).length === 0;
10667
+ }
10668
+ function rowProps(product) {
10669
+ return {
10670
+ onOpen: setOpenProduct,
10671
+ onQuickAdd: quickAddable(product) ? (p) => guarded(() => addToCart(p, 1)) : void 0,
10672
+ // "A partir de" quando o preço mostrado é o de UMA combinação entre
10673
+ // várias: o número na linha não é o que a pessoa vai pagar.
10674
+ fromPrice: getProductOptionGroups(product).length > 0
10675
+ };
10676
+ }
10677
+ return (
10678
+ // pb-28 reserva o espaço da barra fixa do carrinho: conteúdo escondido
10679
+ // atrás de um elemento fixo é a falha mais comum de barra inferior.
10680
+ /* @__PURE__ */ jsxs("main", { "data-testid": TID.menuPage, className: "w-full pb-28", children: [
10681
+ /* @__PURE__ */ jsx(StoreCover, { onShowHours: () => setShowHours(true) }),
10682
+ /* @__PURE__ */ jsx(
10683
+ AddressStrip,
10684
+ {
10685
+ onChangeAddress: () => setShowAddress(true),
10686
+ onSchedule: () => setShowSchedule(true)
10687
+ }
10688
+ ),
10689
+ /* @__PURE__ */ jsx(
10690
+ MenuToolbar,
10691
+ {
10692
+ sections: allSections,
10693
+ query,
10694
+ onQuery: setQuery
10695
+ }
10696
+ ),
10697
+ /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-3xl px-4", children: [
10698
+ loading && // Esqueleto e não "Carregando…": a lista aparece no lugar exato onde
10699
+ // as caixas já estavam, sem o pulo que um texto centralizado causa.
10700
+ /* @__PURE__ */ jsx("ul", { "aria-hidden": true, className: "animate-pulse pt-6", children: [0, 1, 2, 3, 4].map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-3 border-b border-border py-4", children: [
10701
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-2", children: [
10702
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-2/3 rounded bg-muted" }),
10703
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-full rounded bg-muted" }),
10704
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-1/2 rounded bg-muted" })
10705
+ ] }),
10706
+ /* @__PURE__ */ jsx("div", { className: "h-24 w-24 shrink-0 rounded-lg bg-muted" })
10707
+ ] }, i)) }),
10708
+ !loading && !sections.length && /* @__PURE__ */ jsx("p", { className: "py-12 text-center text-muted-foreground", children: normalized ? `Nada no card\xE1pio para "${query.trim()}".` : "Nenhum item dispon\xEDvel no momento." }),
10709
+ normalized && matches > 0 && /* @__PURE__ */ jsxs("p", { className: "pt-4 text-sm text-muted-foreground", children: [
10710
+ matches,
10711
+ " ",
10712
+ matches === 1 ? "item encontrado" : "itens encontrados"
10713
+ ] }),
10714
+ !normalized && highlights.length > 0 && /* @__PURE__ */ jsxs("section", { "data-testid": TID.menuHighlights, className: "mb-10 pt-6", children: [
10715
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold uppercase tracking-wide", children: "Destaques" }),
10716
+ /* @__PURE__ */ jsx("ul", { className: "mt-2", children: highlights.map((product) => /* @__PURE__ */ jsx(
10717
+ MenuItemRow,
10718
+ {
10719
+ product,
10720
+ badge: product.compareAtPrice != null && product.compareAtPrice > product.price ? "Em promo\xE7\xE3o" : void 0,
10721
+ accent: true,
10722
+ ...rowProps(product)
10723
+ },
10724
+ product.id
10725
+ )) })
10726
+ ] }),
10727
+ sections.map((section) => /* @__PURE__ */ jsxs(
10728
+ "section",
10729
+ {
10730
+ "data-testid": TID.menuSection(section.slug),
10731
+ "data-section-slug": section.slug,
10732
+ className: "mb-10 scroll-mt-32",
10733
+ id: `menu-${section.slug}`,
10734
+ children: [
10735
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold uppercase tracking-wide", children: section.name }),
10736
+ section.description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: section.description }),
10737
+ /* @__PURE__ */ jsx("ul", { className: "mt-2", children: section.products.map((product) => /* @__PURE__ */ jsx(MenuItemRow, { product, ...rowProps(product) }, product.id)) })
10738
+ ]
10739
+ },
10740
+ section.id
10741
+ ))
10742
+ ] }),
10743
+ openProduct && /* @__PURE__ */ jsx(
10744
+ ItemSheet,
10745
+ {
10746
+ product: openProduct,
10747
+ onClose: () => setOpenProduct(null),
10748
+ disabled: !isOpen,
10749
+ disabledReason: !isOpen ? "A loja est\xE1 fechada no momento." : void 0,
10750
+ onAdd: ({ quantity, options, notes }) => {
10751
+ guarded(() => {
10752
+ addToCart(openProduct, quantity, options, notes);
10753
+ setOpenProduct(null);
10754
+ });
10755
+ }
10756
+ }
10757
+ ),
10758
+ count > 0 && /* @__PURE__ */ jsx(BottomBarSpace, {}),
10759
+ count > 0 && /* @__PURE__ */ jsx(
10760
+ "div",
10761
+ {
10762
+ "data-testid": TID.menuCartBar,
10763
+ className: "fixed inset-x-0 bottom-0 z-40 border-t border-border bg-background p-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]",
10764
+ children: /* @__PURE__ */ jsxs(
10765
+ "button",
10766
+ {
10767
+ type: "button",
10768
+ onClick: openDrawer,
10769
+ className: "mx-auto flex h-12 w-full max-w-3xl items-center justify-between rounded-full bg-primary px-5 font-semibold text-primary-foreground",
10770
+ children: [
10771
+ /* @__PURE__ */ jsx("span", { className: "grid h-7 min-w-7 place-items-center rounded-full bg-primary-foreground/20 px-2 text-sm tabular-nums", children: count }),
10772
+ /* @__PURE__ */ jsx("span", { children: "Ver carrinho" }),
10773
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: formatMoney(subtotal, config.currency, config.locale) })
10774
+ ]
10775
+ }
10776
+ )
10777
+ }
10778
+ ),
10779
+ showAddress && /* @__PURE__ */ jsx(AddressSheet, { onClose: () => setShowAddress(false) }),
10780
+ showHours && /* @__PURE__ */ jsx(ServiceHoursModal, { onClose: () => setShowHours(false) }),
10781
+ showSchedule && /* @__PURE__ */ jsx(ScheduleSheet, { onClose: () => setShowSchedule(false) }),
10782
+ showClosed && /* @__PURE__ */ jsx(
10783
+ ClosedStoreDialog,
10784
+ {
10785
+ onClose: () => setShowClosed(false),
10786
+ onShowHours: () => {
10787
+ setShowClosed(false);
10788
+ setShowHours(true);
10789
+ }
10790
+ }
10791
+ )
10792
+ ] })
10793
+ );
10794
+ }
10795
+ function BottomBarSpace() {
10796
+ React37.useEffect(() => {
10797
+ const root = document.documentElement;
10798
+ root.style.setProperty("--fayz-bottom-bar", "4.5rem");
10799
+ return () => {
10800
+ root.style.removeProperty("--fayz-bottom-bar");
10801
+ };
10802
+ }, []);
10803
+ return null;
10804
+ }
10805
+
10806
+ // src/data/order-status.ts
10807
+ var FINANCIAL = {
10808
+ pending: { label: "Pendente", tone: "pending" },
10809
+ paid: { label: "Pago", tone: "positive" },
10810
+ partially_paid: { label: "Parcialmente pago", tone: "pending" },
10811
+ partially_refunded: { label: "Parcialmente reembolsado", tone: "negative" },
10812
+ refunded: { label: "Reembolsado", tone: "negative" },
10813
+ voided: { label: "Cancelado", tone: "negative" }
10814
+ };
10815
+ var TONE_CLASS = {
10816
+ positive: "bg-emerald-100 text-emerald-800",
10817
+ pending: "bg-amber-100 text-amber-800",
10818
+ negative: "bg-rose-100 text-rose-800"
10819
+ };
10820
+ function financialStatusBadge(status) {
10821
+ const entry = FINANCIAL[status] ?? { label: status, tone: "pending" };
10822
+ return { label: entry.label, className: TONE_CLASS[entry.tone] };
10823
+ }
10824
+ function isPaid(status) {
10825
+ return status === "paid" || status === "partially_paid";
10826
+ }
10827
+ function isRefunded(status) {
10828
+ return status === "refunded" || status === "partially_refunded";
10829
+ }
10830
+ function orderTrackingSteps(order) {
10831
+ const received = {
10832
+ id: "received",
10833
+ label: "Pedido recebido",
10834
+ description: `#${order.orderNumber}`,
10835
+ state: "complete",
10836
+ icon: Check
10837
+ };
10838
+ if (order.status === "cancelled" || order.financialStatus === "voided") {
10839
+ return [
10840
+ received,
10841
+ { id: "payment", label: "Pedido cancelado", description: "Este pedido foi cancelado", state: "cancelled", icon: XCircle }
10842
+ ];
10843
+ }
10844
+ if (isRefunded(order.financialStatus)) {
10845
+ return [
10846
+ received,
10847
+ { id: "payment", label: "Pagamento aprovado", description: "Compra confirmada", state: "complete", icon: Check },
10848
+ {
10849
+ id: "refund",
10850
+ label: order.financialStatus === "refunded" ? "Reembolsado" : "Reembolso parcial",
10851
+ description: "Valor devolvido ao cliente",
10852
+ state: "cancelled",
10853
+ icon: RotateCcw
10854
+ }
10855
+ ];
10856
+ }
10857
+ const paid = isPaid(order.financialStatus);
10858
+ const partiallyFulfilled = order.fulfillmentStatus === "partially_fulfilled";
10859
+ const fulfilled = order.fulfillmentStatus === "fulfilled";
10860
+ return [
10861
+ received,
10862
+ {
10863
+ id: "payment",
10864
+ label: paid ? "Pagamento aprovado" : "Aguardando pagamento",
10865
+ // "Processando" descrevia um pagamento que ninguém iniciou.
10866
+ description: paid ? "Compra confirmada" : "Ainda n\xE3o recebemos",
10867
+ state: paid ? "complete" : "current",
10868
+ icon: paid ? Check : Clock
10869
+ },
10870
+ {
10871
+ id: "preparing",
10872
+ label: "Preparando envio",
10873
+ description: paid ? "Separando produtos" : "Come\xE7a ap\xF3s o pagamento",
10874
+ state: fulfilled || partiallyFulfilled ? "complete" : paid ? "current" : "pending",
10875
+ icon: PackageCheck
10876
+ },
10877
+ {
10878
+ id: "delivery",
10879
+ label: fulfilled ? "Pedido enviado" : partiallyFulfilled ? "Envio parcial" : "Entrega",
10880
+ description: fulfilled ? "A caminho do cliente" : partiallyFulfilled ? "Parte do pedido saiu" : "Pr\xF3xima etapa",
10881
+ state: fulfilled ? "complete" : partiallyFulfilled ? "current" : "pending",
10882
+ icon: fulfilled || partiallyFulfilled ? Truck : Clock
10883
+ }
10884
+ ];
10885
+ }
10886
+ var GRID_COLS = {
10887
+ 2: "sm:grid-cols-2",
10888
+ 3: "sm:grid-cols-3",
10889
+ 4: "sm:grid-cols-4"
10890
+ };
10891
+ function OrderTrackingTimeline({ order, compact: compact2 = false }) {
10892
+ const steps = orderTrackingSteps(order);
10893
+ return /* @__PURE__ */ jsx(
10894
+ "ol",
10895
+ {
10896
+ "data-testid": TID.orderTracking,
10897
+ className: `grid ${compact2 ? "gap-2" : "gap-3"} ${GRID_COLS[steps.length] ?? "sm:grid-cols-4"}`,
10898
+ "aria-label": "Acompanhamento do pedido",
10899
+ children: steps.map((step) => {
10900
+ const Icon2 = step.icon;
10901
+ const cancelled = step.state === "cancelled";
10902
+ const done = step.state === "complete";
10903
+ const current = step.state === "current";
10904
+ const active2 = done || current;
10905
+ return /* @__PURE__ */ jsx(
10906
+ "li",
10907
+ {
10908
+ "data-testid": TID.orderTrackingStep(step.id),
10909
+ "data-state": step.state,
10910
+ className: [
10911
+ "relative min-w-0 rounded-xl border p-3",
10912
+ cancelled ? "border-rose-200 bg-rose-50 text-rose-700" : active2 ? "border-primary/35 bg-primary/5" : "bg-muted/20 text-muted-foreground",
10913
+ compact2 ? "text-xs" : "text-sm"
10914
+ ].join(" "),
10915
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
10916
+ /* @__PURE__ */ jsx(
10917
+ "span",
10918
+ {
10919
+ className: [
10920
+ "grid h-7 w-7 shrink-0 place-items-center rounded-full border",
10921
+ 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"
10922
+ ].join(" "),
10923
+ "aria-hidden": true,
10924
+ children: /* @__PURE__ */ jsx(Icon2, { className: "h-3.5 w-3.5" })
10925
+ }
10926
+ ),
10927
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
10928
+ /* @__PURE__ */ jsx("span", { className: "block font-semibold leading-snug text-foreground", children: step.label }),
10929
+ /* @__PURE__ */ jsx("span", { className: "mt-0.5 block leading-snug text-muted-foreground", children: step.description })
10930
+ ] })
10931
+ ] })
10932
+ },
10933
+ step.id
10934
+ );
10935
+ })
10936
+ }
10937
+ );
10938
+ }
10939
+
10940
+ // src/components/account/section.ts
10941
+ var ACCOUNT_SECTIONS = [
10942
+ "profile",
10943
+ "orders",
10944
+ "addresses",
10945
+ "payments"
10946
+ ];
10947
+ var DEFAULT_ACCOUNT_SECTION = "orders";
10948
+ function isAccountSection(value) {
10949
+ return ACCOUNT_SECTIONS.includes(value);
10950
+ }
10951
+ function parseAccountSection(path) {
10952
+ const query = path.split("?")[1];
10953
+ if (!query) return DEFAULT_ACCOUNT_SECTION;
9399
10954
  for (const pair of query.split("&")) {
9400
10955
  const [key, raw = ""] = pair.split("=");
9401
10956
  if (key !== "section") continue;
@@ -9509,7 +11064,7 @@ function OrderConfirmationPage({ orderId }) {
9509
11064
  const [loading, setLoading] = useState(true);
9510
11065
  const money = (v) => formatMoney(v, config.currency, config.locale);
9511
11066
  useStorefrontHead({ title: order ? `Pedido #${order.orderNumber} \u2014 ${config.name}` : config.name });
9512
- const reload = React4.useCallback(() => {
11067
+ const reload = React37.useCallback(() => {
9513
11068
  let cancelled = false;
9514
11069
  getShopProvider().getOrder(orderId).then((o) => {
9515
11070
  if (!cancelled) setOrder(o);
@@ -9538,7 +11093,7 @@ function OrderConfirmationPage({ orderId }) {
9538
11093
  ] }, column)) }),
9539
11094
  /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
9540
11095
  /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-32" }),
9541
- /* @__PURE__ */ jsx("div", { className: "mt-3 flex items-center gap-2", children: [0, 1, 2, 3].map((node) => /* @__PURE__ */ jsxs(React4.Fragment, { children: [
11096
+ /* @__PURE__ */ jsx("div", { className: "mt-3 flex items-center gap-2", children: [0, 1, 2, 3].map((node) => /* @__PURE__ */ jsxs(React37.Fragment, { children: [
9542
11097
  /* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-7 shrink-0 rounded-full" }),
9543
11098
  node < 3 && /* @__PURE__ */ jsx(Skeleton, { className: "h-px flex-1" })
9544
11099
  ] }, node)) })
@@ -9825,7 +11380,7 @@ function OrdersPanel() {
9825
11380
  /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-24 rounded-full" })
9826
11381
  ] }),
9827
11382
  /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-3 w-20" }),
9828
- /* @__PURE__ */ jsx("div", { className: "mt-4 flex items-center gap-2", children: [0, 1, 2, 3].map((node) => /* @__PURE__ */ jsxs(React4.Fragment, { children: [
11383
+ /* @__PURE__ */ jsx("div", { className: "mt-4 flex items-center gap-2", children: [0, 1, 2, 3].map((node) => /* @__PURE__ */ jsxs(React37.Fragment, { children: [
9829
11384
  /* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-6 shrink-0 rounded-full" }),
9830
11385
  node < 3 && /* @__PURE__ */ jsx(Skeleton, { className: "h-px flex-1" })
9831
11386
  ] }, node)) }),
@@ -10081,7 +11636,7 @@ function PolicyPage({ kind }) {
10081
11636
  ] }) })
10082
11637
  ] });
10083
11638
  }
10084
- var StorefrontErrorBoundary = class extends React4.Component {
11639
+ var StorefrontErrorBoundary = class extends React37.Component {
10085
11640
  constructor() {
10086
11641
  super(...arguments);
10087
11642
  this.state = { error: null };
@@ -10138,7 +11693,19 @@ function ToastCard({ toast: toast2 }) {
10138
11693
  /* @__PURE__ */ jsx("span", { className: `grid h-8 w-8 shrink-0 place-items-center rounded-full ${ACCENT[toast2.variant]}`, children: /* @__PURE__ */ jsx(Icon2, { className: "h-4 w-4" }) }),
10139
11694
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
10140
11695
  /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold leading-tight", children: toast2.title }),
10141
- toast2.description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: toast2.description })
11696
+ toast2.description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: toast2.description }),
11697
+ toast2.action && /* @__PURE__ */ jsx(
11698
+ "button",
11699
+ {
11700
+ type: "button",
11701
+ onClick: () => {
11702
+ toast2.action.onClick();
11703
+ dismiss(toast2.id);
11704
+ },
11705
+ className: "mt-2 text-sm font-semibold text-primary underline underline-offset-2",
11706
+ children: toast2.action.label
11707
+ }
11708
+ )
10142
11709
  ] }),
10143
11710
  /* @__PURE__ */ jsx(
10144
11711
  "button",
@@ -10157,14 +11724,29 @@ function ToastCard({ toast: toast2 }) {
10157
11724
  function Toaster() {
10158
11725
  const toasts = useToastStore((s2) => s2.toasts);
10159
11726
  if (toasts.length === 0) return null;
10160
- return /* @__PURE__ */ jsx("div", { className: "pointer-events-none fixed bottom-4 right-4 z-[70] flex flex-col gap-2", children: toasts.map((t) => /* @__PURE__ */ jsx(ToastCard, { toast: t }, t.id)) });
11727
+ return (
11728
+ // `--fayz-bottom-bar` é declarada por quem TEM uma barra fixa embaixo (o
11729
+ // cardápio, quando há carrinho). Sem isto o aviso nasce exatamente atrás
11730
+ // dela — e o aviso que anuncia o item some debaixo do botão que leva a ele.
11731
+ /* @__PURE__ */ jsx(
11732
+ "div",
11733
+ {
11734
+ className: "pointer-events-none fixed right-4 z-[70] flex flex-col gap-2",
11735
+ style: { bottom: "calc(1rem + var(--fayz-bottom-bar, 0px))" },
11736
+ children: toasts.map((t) => /* @__PURE__ */ jsx(ToastCard, { toast: t }, t.id))
11737
+ }
11738
+ )
11739
+ );
10161
11740
  }
10162
- function isFocusedRoute(config, path) {
11741
+ function routeChrome(config, path) {
10163
11742
  const match = resolveStorefrontRoute(config, path);
10164
11743
  if (match.kind === "custom") {
10165
- return match.route.chrome === "focused" || match.route.kind === "checkout";
11744
+ if (match.route.chrome) return match.route.chrome;
11745
+ return match.route.kind === "checkout" ? "focused" : "default";
10166
11746
  }
10167
- return match.kind === "checkout";
11747
+ if (match.kind === "checkout") return "focused";
11748
+ if (match.kind === "menu") return "minimal";
11749
+ return "default";
10168
11750
  }
10169
11751
  function RouteSwitch({ path }) {
10170
11752
  const config = useStorefrontConfig();
@@ -10184,6 +11766,11 @@ function RouteSwitch({ path }) {
10184
11766
  return config.features.checkout ? /* @__PURE__ */ jsx(CheckoutPage, {}) : /* @__PURE__ */ jsx(NotFoundPage, {});
10185
11767
  case "account":
10186
11768
  return config.features.accounts ? /* @__PURE__ */ jsx(MyPurchasesPage, {}) : /* @__PURE__ */ jsx(NotFoundPage, {});
11769
+ // O cardápio do modo delivery. A rota já só resolve dentro do modo
11770
+ // (serving.ts testa `resolved.delivery`), então aqui não há segundo guard —
11771
+ // dois lugares decidindo a mesma coisa é como eles passam a discordar.
11772
+ case "menu":
11773
+ return /* @__PURE__ */ jsx(MenuPage, {});
10187
11774
  case "catalog":
10188
11775
  // The search results page IS the catalog, filtered — where the overlay's
10189
11776
  // CTA lands, carrying `?q=`.
@@ -10287,9 +11874,10 @@ function StorefrontShellBody({ pathOverride }) {
10287
11874
  const livePath = useRoutePath();
10288
11875
  const path = pathOverride ?? livePath;
10289
11876
  useScrollToTopOnNavigate(path);
10290
- const focused = isFocusedRoute(config, path);
11877
+ const chrome = routeChrome(config, path);
11878
+ const focused = chrome === "focused";
10291
11879
  const components = getStorefrontComponents(config);
10292
- const chromeProps = { config, commerceMode: config.commerceMode };
11880
+ const chromeProps = { config, commerceMode: config.commerceMode, chrome };
10293
11881
  const header = resolveOverride(components.Header, StorefrontHeader);
10294
11882
  const footer = resolveOverride(components.Footer, StorefrontFooter);
10295
11883
  const Shell = overrideComponent(components.Shell);
@@ -10334,7 +11922,7 @@ function createStorefront(options) {
10334
11922
 
10335
11923
  // src/document/sections.ts
10336
11924
  var STORE_DOCUMENT_KIND = "fayz.store";
10337
- var STORE_COMMERCE_MODES = ["checkout", "catalog", "enquiry"];
11925
+ var STORE_COMMERCE_MODES = ["checkout", "catalog", "enquiry", "delivery"];
10338
11926
  var STORE_PAYMENT_MODES = ["mock", "pix-mercadopago", "gateway"];
10339
11927
  var STORE_PAYMENT_METHOD_KINDS = [
10340
11928
  "pix",
@@ -10354,7 +11942,7 @@ var STORE_ROUTE_KINDS = [
10354
11942
  "order",
10355
11943
  "custom"
10356
11944
  ];
10357
- var STORE_ROUTE_CHROMES = ["default", "focused"];
11945
+ var STORE_ROUTE_CHROMES = ["default", "minimal", "focused"];
10358
11946
  var STORE_THEME_RADII = ["none", "soft", "round", "pill"];
10359
11947
  var f = (type, description, extra = {}) => ({ type, description, ...extra });
10360
11948
  var STORE_SECTIONS = {
@@ -11551,7 +13139,7 @@ ${formatErrors(report.errors)}`;
11551
13139
  return resolveHandle(name);
11552
13140
  } catch (err) {
11553
13141
  if (onError === "throw") throw err;
11554
- console.error(`[storefront] dropping ${path}: ${err instanceof Error ? err.message : String(err)}`);
13142
+ console.error(`[storefront] dropping ${path}: ${errorMessage(err)}`);
11555
13143
  return void 0;
11556
13144
  }
11557
13145
  };
@@ -11660,6 +13248,6 @@ ${formatErrors(report.errors)}`;
11660
13248
  return config;
11661
13249
  }
11662
13250
 
11663
- 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 };
11664
- //# sourceMappingURL=chunk-STL6MATQ.js.map
11665
- //# sourceMappingURL=chunk-STL6MATQ.js.map
13251
+ export { AddressSheet, AddressStrip, BEFORE_PLACE_ORDER_VALVE, BLOCK_STRUCTURAL_KEYS, BenefitsRow, BlockDataError, CART_LINE_ANNOTATION_POINT, CATALOG_BODY_BLOCK, CHECKOUT_FIELD_POINT, CartDrawer, CatalogPage, CategoryShowcase, CheckoutPage, ClosedStoreDialog, CollectionCta, CollectionHero, ContentPage, CountdownBand, EmailConfirmationRequiredError, EntityListBlock, FaqSection, FiltersPanel, FormBlock, HeroSection, ImageTiles, ItemSheet, Link, ManifestoBlock, MediaCarousel, MenuItemRow, MenuPage, MenuToolbar, 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, ScheduleSheet, SealsBand, SearchOverlay, ServiceHoursModal, Slot, SmoothImage, StepsSection, StoreCover, 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, selectIsOpen, selectRequiresShipping, selectShipping, selectShowsHours, 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, useServiceStore, useSessionStore, useSlotContext, useStorefrontActions, useStorefrontConfig, useStorefrontConfigOptional, useStorefrontHead, useStorefrontPage, useStorefrontPageOptional, usedSlots, validateStore, withFacet, withStaticRenderPass };
13252
+ //# sourceMappingURL=chunk-77V5ZAF5.js.map
13253
+ //# sourceMappingURL=chunk-77V5ZAF5.js.map