@fayz-ai/storefront 0.18.0 → 0.19.1

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 (68) 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-ORKXSAZ6.js → chunk-77V5ZAF5.js} +1845 -264
  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/delivery/AddressSheet.d.ts +22 -0
  12. package/dist/components/delivery/AddressSheet.d.ts.map +1 -0
  13. package/dist/components/delivery/AddressStrip.d.ts +18 -0
  14. package/dist/components/delivery/AddressStrip.d.ts.map +1 -0
  15. package/dist/components/delivery/ItemSheet.d.ts +42 -0
  16. package/dist/components/delivery/ItemSheet.d.ts.map +1 -0
  17. package/dist/components/delivery/MenuItemRow.d.ts +29 -0
  18. package/dist/components/delivery/MenuItemRow.d.ts.map +1 -0
  19. package/dist/components/delivery/MenuToolbar.d.ts +29 -0
  20. package/dist/components/delivery/MenuToolbar.d.ts.map +1 -0
  21. package/dist/components/delivery/ScheduleSheet.d.ts +14 -0
  22. package/dist/components/delivery/ScheduleSheet.d.ts.map +1 -0
  23. package/dist/components/delivery/ServiceStatus.d.ts +15 -0
  24. package/dist/components/delivery/ServiceStatus.d.ts.map +1 -0
  25. package/dist/components/delivery/StoreCover.d.ts +22 -0
  26. package/dist/components/delivery/StoreCover.d.ts.map +1 -0
  27. package/dist/components/delivery/store-time.d.ts +15 -0
  28. package/dist/components/delivery/store-time.d.ts.map +1 -0
  29. package/dist/components/layout/StorefrontHeader.d.ts +4 -1
  30. package/dist/components/layout/StorefrontHeader.d.ts.map +1 -1
  31. package/dist/components/layout/Toaster.d.ts.map +1 -1
  32. package/dist/components/marketing/FormBlock.d.ts.map +1 -1
  33. package/dist/components/product/ProductEnquiryForm.d.ts.map +1 -1
  34. package/dist/document/config.d.ts +101 -2
  35. package/dist/document/config.d.ts.map +1 -1
  36. package/dist/document/round-trip.d.ts.map +1 -1
  37. package/dist/document/sections.d.ts +2 -2
  38. package/dist/document/sections.d.ts.map +1 -1
  39. package/dist/document/upgrade.d.ts.map +1 -1
  40. package/dist/extend/component-contracts.d.ts +7 -1
  41. package/dist/extend/component-contracts.d.ts.map +1 -1
  42. package/dist/hooks/use-address.d.ts.map +1 -1
  43. package/dist/hooks/use-checkout.d.ts.map +1 -1
  44. package/dist/hooks/use-order.d.ts.map +1 -1
  45. package/dist/hooks/use-products.d.ts.map +1 -1
  46. package/dist/index.d.ts +11 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +11 -17
  49. package/dist/index.js.map +1 -1
  50. package/dist/lib/testids.d.ts +36 -0
  51. package/dist/lib/testids.d.ts.map +1 -1
  52. package/dist/render.js +3 -3
  53. package/dist/render.js.map +1 -1
  54. package/dist/stores/cart.store.d.ts +22 -1
  55. package/dist/stores/cart.store.d.ts.map +1 -1
  56. package/dist/stores/delivery.store.d.ts +54 -1
  57. package/dist/stores/delivery.store.d.ts.map +1 -1
  58. package/dist/stores/service.store.d.ts +35 -0
  59. package/dist/stores/service.store.d.ts.map +1 -0
  60. package/dist/stores/toast.store.d.ts +14 -0
  61. package/dist/stores/toast.store.d.ts.map +1 -1
  62. package/dist/views/CheckoutPage.d.ts.map +1 -1
  63. package/dist/views/MenuPage.d.ts +23 -0
  64. package/dist/views/MenuPage.d.ts.map +1 -0
  65. package/dist/workflows/checkout.d.ts +15 -2
  66. package/dist/workflows/checkout.d.ts.map +1 -1
  67. package/package.json +7 -7
  68. package/dist/chunk-ORKXSAZ6.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';
@@ -232,6 +232,12 @@ function productOptionSelectionKey(selection) {
232
232
  return entries.length > 0 ? JSON.stringify(entries) : "";
233
233
  }
234
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
+ }
235
241
  var EMPTY = {
236
242
  postalCode: "",
237
243
  address: null,
@@ -246,6 +252,17 @@ var useDeliveryStore = create()(
246
252
  persist(
247
253
  (set, get) => ({
248
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
+ },
249
266
  async resolve(postalCode, subtotal) {
250
267
  const code = normalizePostalCode(postalCode);
251
268
  if (code.length !== 8) {
@@ -304,6 +321,37 @@ var useDeliveryStore = create()(
304
321
  },
305
322
  clear() {
306
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
+ }));
307
355
  }
308
356
  }),
309
357
  {
@@ -311,7 +359,21 @@ var useDeliveryStore = create()(
311
359
  // Deliberately NOT persisting `options`: a rehydrated
312
360
  // price would be shown as current without anything having quoted it. The
313
361
  // address survives, the money is asked for again.
314
- 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
+ }),
315
377
  onRehydrateStorage: () => (state) => {
316
378
  if (state?.address) state.status = "idle";
317
379
  }
@@ -338,10 +400,12 @@ var useCartStore = create()(
338
400
  discountFreeShipping: false,
339
401
  isOpen: false,
340
402
  justAddedLineId: null,
341
- addItem: (product, qty = 1, options) => set((state) => {
403
+ addItem: (product, qty = 1, options, notes, opts) => set((state) => {
342
404
  const normalizedOptions = normalizeProductOptionSelection(options);
343
405
  const optionKey = productOptionSelectionKey(normalizedOptions);
344
- 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;
345
409
  const variant = resolveVariantForSelection(product, normalizedOptions);
346
410
  const unitPrice = variant?.price ?? product.price;
347
411
  const stock = variant ? variant.inventoryCount : product.inventoryCount;
@@ -351,7 +415,7 @@ var useCartStore = create()(
351
415
  return {
352
416
  // Auto-open the cart on add — the standard premium "slide-in cart"
353
417
  // signal — and flag the line so the drawer can highlight it.
354
- isOpen: true,
418
+ isOpen: opts?.openDrawer ?? true,
355
419
  justAddedLineId: lineId,
356
420
  lines: state.lines.map(
357
421
  (l) => (l.lineId ?? l.productId) === lineId ? { ...l, lineId, quantity: nextQty } : l
@@ -378,9 +442,14 @@ var useCartStore = create()(
378
442
  heightCm: variant?.heightCm ?? product.heightCm ?? null,
379
443
  widthCm: variant?.widthCm ?? product.widthCm ?? null,
380
444
  lengthCm: variant?.lengthCm ?? product.lengthCm ?? null,
381
- 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]
382
452
  };
383
- return { isOpen: true, justAddedLineId: lineId, lines: [...state.lines, line] };
384
453
  }),
385
454
  setQuantity: (lineId, qty) => set((state) => {
386
455
  if (qty <= 0) return { lines: state.lines.filter((l) => (l.lineId ?? l.productId) !== lineId) };
@@ -743,7 +812,7 @@ function resolveConfig(config) {
743
812
  const homePage = config.pages?.home ?? (config.home ? { blocks: config.home.sections } : void 0);
744
813
  const catalogPath = homePage ? "/catalog" : "/";
745
814
  const commerceMode = config.commerceMode ?? "checkout";
746
- const checkoutEnabled = commerceMode === "checkout";
815
+ const checkoutEnabled = commerceMode === "checkout" || commerceMode === "delivery";
747
816
  return {
748
817
  ...config,
749
818
  pages: { ...config.pages, ...homePage ? { home: homePage } : {} },
@@ -759,6 +828,22 @@ function resolveConfig(config) {
759
828
  terms: config.payments?.terms
760
829
  },
761
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,
762
847
  enquiry: {
763
848
  label: config.enquiry?.label ?? "Contact me",
764
849
  successMessage: config.enquiry?.successMessage ?? "Thanks. We received your enquiry.",
@@ -980,7 +1065,7 @@ function Link({ to, children, onClick, target, ...rest }) {
980
1065
  event.preventDefault();
981
1066
  adapter.navigate(to);
982
1067
  };
983
- return React4.createElement(
1068
+ return React37.createElement(
984
1069
  "a",
985
1070
  { href: adapter.href(to), onClick: handleClick, target, ...rest },
986
1071
  children
@@ -1099,6 +1184,43 @@ var TID = {
1099
1184
  cartTotal: "cart-total",
1100
1185
  goCheckout: "go-checkout",
1101
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}`,
1102
1224
  // delivery estimate (product page + cart)
1103
1225
  deliveryEstimator: "delivery-estimator",
1104
1226
  deliveryCepInput: "delivery-cep-input",
@@ -1183,9 +1305,9 @@ function SmoothImage({
1183
1305
  const easing = resolved.easing ?? "cubic-bezier(0.22, 1, 0.36, 1)";
1184
1306
  const blur = resolved.blur ?? true;
1185
1307
  const transform = resolved.transform ?? "none";
1186
- const ref = React4.useRef(null);
1187
- const [loaded, setLoaded] = React4.useState(() => mode === "none" || !clientMounted);
1188
- const delivered = React4.useMemo(() => {
1308
+ const ref = React37.useRef(null);
1309
+ const [loaded, setLoaded] = React37.useState(() => mode === "none" || !clientMounted);
1310
+ const delivered = React37.useMemo(() => {
1189
1311
  if (transform !== "supabase" || !src || srcSet || !isStorageImageUrl(src)) return { src, srcSet, sizes };
1190
1312
  if (variant?.width) {
1191
1313
  const w = variant.width;
@@ -1203,10 +1325,10 @@ function SmoothImage({
1203
1325
  sizes: variant?.sizes ?? sizes ?? "100vw"
1204
1326
  };
1205
1327
  }, [transform, src, srcSet, sizes, variant?.width, variant?.sizes, variant?.widths, variant?.quality]);
1206
- React4.useEffect(() => {
1328
+ React37.useEffect(() => {
1207
1329
  clientMounted = true;
1208
1330
  }, []);
1209
- React4.useEffect(() => {
1331
+ React37.useEffect(() => {
1210
1332
  if (mode === "none") {
1211
1333
  setLoaded(true);
1212
1334
  return;
@@ -1562,7 +1684,7 @@ async function resolvePageDataNeed(need) {
1562
1684
  const data = await impl(need.params, { shop: getShopProvider() });
1563
1685
  return { status: "ready", data };
1564
1686
  } catch (err) {
1565
- const message = err instanceof Error ? err.message : String(err);
1687
+ const message = errorMessage(err);
1566
1688
  console.error(`[storefront] loader "${need.loader}" failed:`, err);
1567
1689
  return { status: "error", error: message };
1568
1690
  }
@@ -2474,8 +2596,8 @@ var BADGE_TONE_CLASSES = {
2474
2596
  warning: "bg-amber-500 text-white"
2475
2597
  };
2476
2598
  function useExtensionBadges(product) {
2477
- const [badges, setBadges] = React4.useState([]);
2478
- React4.useEffect(() => {
2599
+ const [badges, setBadges] = React37.useState([]);
2600
+ React37.useEffect(() => {
2479
2601
  let cancelled = false;
2480
2602
  getProductCardBadges(product).then((next) => {
2481
2603
  if (!cancelled && next.length > 0) setBadges(next);
@@ -3671,7 +3793,7 @@ function scalarToString(value) {
3671
3793
  }
3672
3794
  function ProductSpecs({ product }) {
3673
3795
  const config = useStorefrontConfig();
3674
- const extraExcluded = React4.useMemo(
3796
+ const extraExcluded = React37.useMemo(
3675
3797
  () => new Set(config.productSpecs?.exclude ?? []),
3676
3798
  [config.productSpecs?.exclude]
3677
3799
  );
@@ -3778,7 +3900,7 @@ function useProducts(opts) {
3778
3900
  setProducts(data);
3779
3901
  setError(null);
3780
3902
  }).catch((e) => {
3781
- if (!cancelled) setError(e instanceof Error ? e.message : String(e));
3903
+ if (!cancelled) setError(errorMessage(e));
3782
3904
  }).finally(() => {
3783
3905
  if (!cancelled) setLoading(false);
3784
3906
  });
@@ -3831,7 +3953,7 @@ function ProductEnquiryForm({ product, onSuccess }) {
3831
3953
  onSuccess?.();
3832
3954
  } catch (e) {
3833
3955
  setStatus("error");
3834
- setError(e instanceof Error ? e.message : String(e));
3956
+ setError(errorMessage(e));
3835
3957
  }
3836
3958
  };
3837
3959
  return /* @__PURE__ */ jsxs(
@@ -4543,7 +4665,7 @@ function FormBlock({
4543
4665
  setValues({});
4544
4666
  } catch (e) {
4545
4667
  setStatus("error");
4546
- setError(e instanceof Error ? e.message : String(e));
4668
+ setError(errorMessage(e));
4547
4669
  }
4548
4670
  };
4549
4671
  return /* @__PURE__ */ jsxs("section", { "data-testid": TID.formBlock, className: "mx-auto max-w-2xl px-4 py-14 sm:px-6", children: [
@@ -5323,7 +5445,7 @@ function CollectionHero({
5323
5445
  {
5324
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"}`,
5325
5447
  children: /* @__PURE__ */ jsxs(Reveal, { className: centered ? "max-w-3xl" : "max-w-2xl", children: [
5326
- 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: [
5327
5449
  i > 0 && /* @__PURE__ */ jsx("span", { className: "text-2xl font-light opacity-60", "aria-hidden": true, children: "\xD7" }),
5328
5450
  /* @__PURE__ */ jsx("img", { src: logo.src, alt: logo.alt, className: "h-10 w-auto object-contain sm:h-14" })
5329
5451
  ] }, logo.src)) }),
@@ -6080,8 +6202,8 @@ function StorefrontConfigHost({
6080
6202
  config,
6081
6203
  children
6082
6204
  }) {
6083
- const [policy, setPolicy] = React4.useState(null);
6084
- React4.useEffect(() => {
6205
+ const [policy, setPolicy] = React37.useState(null);
6206
+ React37.useEffect(() => {
6085
6207
  let cancelled = false;
6086
6208
  let provider;
6087
6209
  try {
@@ -6100,7 +6222,7 @@ function StorefrontConfigHost({
6100
6222
  cancelled = true;
6101
6223
  };
6102
6224
  }, [config]);
6103
- const resolved = React4.useMemo(() => applyStoreSettings(config, policy), [config, policy]);
6225
+ const resolved = React37.useMemo(() => applyStoreSettings(config, policy), [config, policy]);
6104
6226
  return /* @__PURE__ */ jsx(StorefrontConfigProvider, { value: resolved, children });
6105
6227
  }
6106
6228
  var RADIUS_MAP = {
@@ -6220,7 +6342,7 @@ function StorefrontThemeStyle({
6220
6342
  document.getElementById(linkId)?.remove();
6221
6343
  };
6222
6344
  }, [fontsHref, linkId, selector]);
6223
- return React4.createElement(
6345
+ return React37.createElement(
6224
6346
  "style",
6225
6347
  { "data-sf-theme": theme.name },
6226
6348
  themeToCss(theme, { selector })
@@ -6319,7 +6441,7 @@ function SearchOverlay({ onClose }) {
6319
6441
  "\u201D."
6320
6442
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
6321
6443
  /* @__PURE__ */ jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4", children: suggestions.map(
6322
- (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)
6323
6445
  ) }),
6324
6446
  /* @__PURE__ */ jsx(
6325
6447
  "button",
@@ -6525,11 +6647,11 @@ function HeaderActions({ onMenuClick }) {
6525
6647
  const openDrawer = useCartStore((s2) => s2.openDrawer);
6526
6648
  const session = useSessionStore();
6527
6649
  const [accountOpen, setAccountOpen] = useState(false);
6528
- const accountRef = React4.useRef(null);
6650
+ const accountRef = React37.useRef(null);
6529
6651
  const pop = usePopOnChange(count);
6530
6652
  const firstName = session.name?.split(/\s+/)[0];
6531
6653
  const initial2 = (firstName ?? session.email ?? "C").slice(0, 1).toUpperCase();
6532
- React4.useEffect(() => {
6654
+ React37.useEffect(() => {
6533
6655
  if (!accountOpen) return;
6534
6656
  const onPointerDown = (event) => {
6535
6657
  if (!accountRef.current?.contains(event.target)) setAccountOpen(false);
@@ -6696,7 +6818,7 @@ function MobileMenuDrawer({ open, onClose }) {
6696
6818
  navigateTo(config.catalogPath);
6697
6819
  onClose();
6698
6820
  };
6699
- React4.useEffect(() => {
6821
+ React37.useEffect(() => {
6700
6822
  if (!open) return;
6701
6823
  const onKey = (e) => {
6702
6824
  if (e.key === "Escape") onClose();
@@ -6766,8 +6888,9 @@ function MobileMenuDrawer({ open, onClose }) {
6766
6888
  )
6767
6889
  ] });
6768
6890
  }
6769
- function StorefrontHeader() {
6891
+ function StorefrontHeader({ chrome } = {}) {
6770
6892
  const config = useStorefrontConfig();
6893
+ const compact2 = chrome === "minimal";
6771
6894
  const variant = config.theme?.header?.variant ?? "classic";
6772
6895
  const scrolled = useScrolled();
6773
6896
  const showSearch = config.theme?.header?.showSearch !== false;
@@ -6781,8 +6904,27 @@ function StorefrontHeader() {
6781
6904
  style: headerStyle,
6782
6905
  children: [
6783
6906
  /* @__PURE__ */ jsx(AnnouncementBar, {}),
6784
- /* @__PURE__ */ jsx(UtilityBar, {}),
6785
- 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" ? (
6786
6928
  // Rio/Flex pattern: centered logo row, nav row below
6787
6929
  /* @__PURE__ */ jsxs(Fragment, { children: [
6788
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: [
@@ -7002,7 +7144,8 @@ var useToastStore = create((set) => ({
7002
7144
  title: input.title,
7003
7145
  description: input.description,
7004
7146
  variant: input.variant ?? "info",
7005
- durationMs: input.durationMs ?? 3400
7147
+ durationMs: input.durationMs ?? 3400,
7148
+ action: input.action
7006
7149
  };
7007
7150
  set((s2) => ({ toasts: [...s2.toasts, toast2] }));
7008
7151
  return id;
@@ -7133,7 +7276,7 @@ function CartDrawer() {
7133
7276
  "div",
7134
7277
  {
7135
7278
  "data-testid": TID.cartLinesList,
7136
- 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",
7137
7280
  style: { scrollbarGutter: "stable" },
7138
7281
  children: [
7139
7282
  /* @__PURE__ */ jsx(Slot, { area: "cart", name: "beforeLines", context: cartSlotContext }),
@@ -7163,6 +7306,11 @@ function CartDrawer() {
7163
7306
  ] }),
7164
7307
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: line.sku }),
7165
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
+ ] }),
7166
7314
  /* @__PURE__ */ jsx(CartLineAnnotations, { line }),
7167
7315
  /* @__PURE__ */ jsxs("div", { className: "mt-auto flex items-center justify-between pt-2", children: [
7168
7316
  /* @__PURE__ */ jsx(
@@ -7197,7 +7345,7 @@ function CartDrawer() {
7197
7345
  "div",
7198
7346
  {
7199
7347
  "data-testid": TID.cartFooter,
7200
- 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",
7201
7349
  children: [
7202
7350
  config.features.discounts && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
7203
7351
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
@@ -7340,6 +7488,7 @@ function resolveStorefrontRoute(config, rawPath) {
7340
7488
  if (matchPath("/checkout", path)) return { kind: "checkout" };
7341
7489
  if (matchPath("/account", path)) return { kind: "account" };
7342
7490
  if (matchPath("/catalog", path)) return { kind: "catalog" };
7491
+ if (resolved.delivery && matchPath(resolved.delivery.menuPath, path)) return { kind: "menu" };
7343
7492
  if (resolved.search.mode === "overlay" && matchPath(resolved.search.resultsPath, path)) {
7344
7493
  return { kind: "searchResults" };
7345
7494
  }
@@ -7354,7 +7503,7 @@ function resolveStorefrontRoute(config, rawPath) {
7354
7503
  function isKnownPath(config, path) {
7355
7504
  return resolveStorefrontRoute(config, path).kind !== "notFound";
7356
7505
  }
7357
- var STATIC_PATTERNS = ["/", "/catalog", "/privacy", "/terms", "/refunds"];
7506
+ var STATIC_PATTERNS = ["/", "/catalog", "/menu", "/privacy", "/terms", "/refunds"];
7358
7507
  function ensureResolved(config) {
7359
7508
  return "features" in config && "pages" in config ? config : resolveConfig(config);
7360
7509
  }
@@ -7408,7 +7557,7 @@ function listLocalOrderIds() {
7408
7557
  }
7409
7558
 
7410
7559
  // src/lib/errors.ts
7411
- function errorMessage(err) {
7560
+ function errorMessage5(err) {
7412
7561
  if (err instanceof Error) return err.message;
7413
7562
  if (err && typeof err === "object" && typeof err.message === "string") {
7414
7563
  return err.message;
@@ -7425,7 +7574,7 @@ function formatDeliveryNotes(address, shippingAddress) {
7425
7574
  return parts.length > 0 ? `Entrega: ${parts.join(", ")}` : void 0;
7426
7575
  }
7427
7576
  function isStaleCustomerError(err) {
7428
- const message = errorMessage(err).toLowerCase();
7577
+ const message = errorMessage5(err).toLowerCase();
7429
7578
  return message.includes("customer email mismatch") || message.includes("customer does not belong to this store") || message.includes("customer belongs to another account");
7430
7579
  }
7431
7580
  async function placeStorefrontOrder({
@@ -7435,6 +7584,8 @@ async function placeStorefrontOrder({
7435
7584
  customer,
7436
7585
  address,
7437
7586
  shippingAddress,
7587
+ fulfillmentType,
7588
+ scheduledFor,
7438
7589
  paymentMethod,
7439
7590
  markPaid = false
7440
7591
  }) {
@@ -7453,9 +7604,12 @@ async function placeStorefrontOrder({
7453
7604
  // from it and decrements ITS stock, so two sizes never share a counter.
7454
7605
  variantId: line.variantId || void 0,
7455
7606
  quantity: line.quantity,
7456
- 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
7457
7611
  }));
7458
- const shippingTotal = selectShipping(cart, config);
7612
+ const shippingTotal = fulfillmentType === "pickup" ? 0 : selectShipping(cart, config);
7459
7613
  const replaced = await runBeforePlaceOrderValve({
7460
7614
  items,
7461
7615
  customer: { name, email },
@@ -7488,6 +7642,8 @@ async function placeStorefrontOrder({
7488
7642
  shippingAddress,
7489
7643
  paymentMethod,
7490
7644
  metadata,
7645
+ fulfillmentType,
7646
+ scheduledFor,
7491
7647
  items
7492
7648
  });
7493
7649
  let order;
@@ -7656,47 +7812,61 @@ var LABELS2 = {
7656
7812
  3: "Pagamento",
7657
7813
  4: "Pronto"
7658
7814
  };
7659
- function CheckoutSteps({ current, onGoBackTo }) {
7660
- const steps = [1, 2, 3, 4];
7815
+ function CheckoutSteps({ current, onGoBackTo, omit }) {
7816
+ const steps = [1, 2, 3, 4].filter((s2) => !omit?.includes(s2));
7661
7817
  return /* @__PURE__ */ jsxs("nav", { "data-testid": TID.checkoutSteps, "aria-label": "Etapas do checkout", className: "mb-8", children: [
7662
7818
  /* @__PURE__ */ jsx("ol", { className: "flex items-center gap-1.5 sm:gap-2", children: steps.map((step, index) => {
7663
7819
  const done = step < current;
7664
7820
  const active2 = step === current;
7665
7821
  const canGoBack = done && !!onGoBackTo;
7666
7822
  const Tag = canGoBack ? "button" : "div";
7667
- 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.
7668
7829
  /* @__PURE__ */ jsxs(
7669
- Tag,
7830
+ "li",
7670
7831
  {
7671
- ...canGoBack ? { type: "button", onClick: () => onGoBackTo(step) } : {},
7672
- "data-testid": TID.checkoutStep(step),
7673
- "data-state": done ? "done" : active2 ? "current" : "pending",
7674
- "aria-current": active2 ? "step" : void 0,
7675
- className: [
7676
- "flex min-w-0 items-center gap-2 rounded-lg px-2 py-1.5 text-left transition",
7677
- canGoBack ? "hover:bg-muted" : "",
7678
- active2 ? "text-foreground" : done ? "text-foreground" : "text-muted-foreground"
7679
- ].join(" "),
7832
+ className: `flex min-w-0 items-center gap-1.5 sm:gap-2 ${index < steps.length - 1 ? "flex-1" : ""}`,
7680
7833
  children: [
7681
- /* @__PURE__ */ jsx(
7682
- "span",
7834
+ /* @__PURE__ */ jsxs(
7835
+ Tag,
7683
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,
7684
7841
  className: [
7685
- "grid h-7 w-7 shrink-0 place-items-center rounded-full border text-xs font-semibold",
7686
- 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"
7687
7845
  ].join(" "),
7688
- 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
+ ]
7689
7859
  }
7690
7860
  ),
7691
- /* @__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"}` })
7692
7862
  ]
7693
- }
7694
- ),
7695
- index < steps.length - 1 && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: `h-px flex-1 ${done ? "bg-primary" : "bg-border"}` })
7696
- ] }, step);
7863
+ },
7864
+ step
7865
+ )
7866
+ );
7697
7867
  }) }),
7698
7868
  /* @__PURE__ */ jsxs("p", { className: "mt-2 text-sm font-semibold sm:hidden", children: [
7699
- current,
7869
+ steps.indexOf(current) + 1,
7700
7870
  ". ",
7701
7871
  LABELS2[current]
7702
7872
  ] })
@@ -7851,13 +8021,13 @@ function CardBrick({ publicKey, amount, email, onSubmit, onError }) {
7851
8021
  var POLL_MS = 3e3;
7852
8022
  var RESUME_KEY = "fz.checkout.resume";
7853
8023
  function useExpiry(expiresAt) {
7854
- const target = React4.useMemo(() => {
8024
+ const target = React37.useMemo(() => {
7855
8025
  if (!expiresAt) return null;
7856
8026
  const t = new Date(expiresAt).getTime();
7857
8027
  return Number.isFinite(t) ? t : null;
7858
8028
  }, [expiresAt]);
7859
- const [now, setNow] = React4.useState(() => Date.now());
7860
- React4.useEffect(() => {
8029
+ const [now, setNow] = React37.useState(() => Date.now());
8030
+ React37.useEffect(() => {
7861
8031
  if (target === null) return;
7862
8032
  const id = setInterval(() => setNow(Date.now()), 1e3);
7863
8033
  return () => clearInterval(id);
@@ -8403,7 +8573,7 @@ var PAYMENT_LABELS = {
8403
8573
  other: { label: "Combinar com a loja", hint: "A loja entra em contato para acertar o pagamento" }
8404
8574
  };
8405
8575
  function placeOrderMessage(err) {
8406
- const message = errorMessage(err);
8576
+ const message = errorMessage5(err);
8407
8577
  if (/customer (email mismatch|does not belong|belongs to another)/i.test(message)) {
8408
8578
  return "Sua sess\xE3o ficou presa a outro cadastro. Recarregue a p\xE1gina e tente de novo.";
8409
8579
  }
@@ -8415,9 +8585,16 @@ function placeOrderMessage(err) {
8415
8585
  }
8416
8586
  return message.replace(/^shop_[a-z_]+:\s*/i, "") || "N\xE3o foi poss\xEDvel concluir o pedido.";
8417
8587
  }
8588
+ var DEV_BUILD = (() => {
8589
+ try {
8590
+ return import.meta.env?.DEV === true;
8591
+ } catch {
8592
+ return false;
8593
+ }
8594
+ })();
8418
8595
  function field(label, testId, value, onChange, props) {
8419
8596
  return /* @__PURE__ */ jsxs("label", { className: "block", children: [
8420
- /* @__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 }),
8421
8598
  /* @__PURE__ */ jsx(
8422
8599
  "input",
8423
8600
  {
@@ -8477,7 +8654,7 @@ function CheckoutPage() {
8477
8654
  district: "",
8478
8655
  state: ""
8479
8656
  });
8480
- React4.useEffect(() => {
8657
+ React37.useEffect(() => {
8481
8658
  if (!known.ready) return;
8482
8659
  setForm((current) => ({
8483
8660
  ...current,
@@ -8493,6 +8670,26 @@ function CheckoutPage() {
8493
8670
  }));
8494
8671
  }, [known.ready, known.phone, known.document, known.address]);
8495
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;
8496
8693
  const [paidOrderId, setPaidOrderId] = useState(null);
8497
8694
  const [error, setError] = useState(null);
8498
8695
  const [discountCode, setDiscountCode] = useState("");
@@ -8501,14 +8698,15 @@ function CheckoutPage() {
8501
8698
  const [applyingDiscount, setApplyingDiscount] = useState(false);
8502
8699
  const [showSignin, setShowSignin] = useState(false);
8503
8700
  const [placing, setPlacing] = useState(false);
8504
- const leaving = React4.useRef(false);
8701
+ const leaving = React37.useRef(false);
8505
8702
  const [zipLookup, setZipLookup] = useState("idle");
8506
8703
  const subtotal = selectSubtotal(cart);
8507
8704
  const discountTotal = selectDiscountTotal(cart);
8508
- const shipping = selectShipping(cart, config);
8509
- 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);
8510
8708
  const checkoutSlotContext = { subtotal, total };
8511
- const checkoutSessionInput = React4.useMemo(() => ({
8709
+ const checkoutSessionInput = React37.useMemo(() => ({
8512
8710
  items: cart.lines.map((line) => ({
8513
8711
  productId: line.productId,
8514
8712
  variantId: line.variantId ?? null,
@@ -8658,10 +8856,23 @@ function CheckoutPage() {
8658
8856
  }, [session.customerId]);
8659
8857
  useEffect(() => {
8660
8858
  if (addressMode !== "new") return;
8859
+ const kept = delivery.savedAddresses.find((a) => a.id === delivery.selectedAddressId);
8661
8860
  const looked = delivery.address;
8662
- if (!looked) return;
8861
+ if (!kept && !looked) return;
8663
8862
  setForm((current) => {
8664
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
+ }
8665
8876
  return {
8666
8877
  ...current,
8667
8878
  zip: formatPostalCode(looked.postalCode),
@@ -8671,7 +8882,7 @@ function CheckoutPage() {
8671
8882
  state: looked.state
8672
8883
  };
8673
8884
  });
8674
- }, [delivery.address, addressMode]);
8885
+ }, [delivery.address, delivery.savedAddresses, delivery.selectedAddressId, addressMode]);
8675
8886
  function applySavedAddress(address) {
8676
8887
  setAddressMode("saved");
8677
8888
  setSelectedAddressId(address.id);
@@ -8731,6 +8942,7 @@ function CheckoutPage() {
8731
8942
  }
8732
8943
  function validateDelivery() {
8733
8944
  setError(null);
8945
+ if (isPickup) return true;
8734
8946
  if (!selectRequiresShipping(cart)) return true;
8735
8947
  if (!form.street.trim() || !form.city.trim() || !form.zip.trim() || !form.district.trim()) {
8736
8948
  return fail("Preencha ou selecione o endere\xE7o de entrega.", TID.checkoutStreet);
@@ -8755,13 +8967,33 @@ function CheckoutPage() {
8755
8967
  setStep(next);
8756
8968
  if (typeof window !== "undefined") window.scrollTo({ top: 0, behavior: "smooth" });
8757
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
+ }
8758
8990
  function advance() {
8759
8991
  if (step === 1 && validateContact()) {
8760
8992
  void getShopProvider().saveCustomerContact?.({
8761
8993
  phone: form.phone.trim() || null,
8762
8994
  document: documentDigits(form.document) || null
8763
8995
  });
8764
- goToStep(2);
8996
+ goToStep(isPickup ? 3 : 2);
8765
8997
  } else if (step === 2 && validateDelivery()) goToStep(3);
8766
8998
  }
8767
8999
  async function confirmWithoutCharge() {
@@ -8773,21 +9005,26 @@ function CheckoutPage() {
8773
9005
  cart,
8774
9006
  session,
8775
9007
  customer: { email: form.email, name: form.name },
8776
- address: { street: form.street, city: form.city, zip: form.zip },
8777
- shippingAddress: {
8778
- postalCode: form.zip.trim(),
8779
- street: form.street.trim(),
8780
- number: form.number.trim() || void 0,
8781
- complement: form.complement.trim() || void 0,
8782
- district: form.district.trim() || void 0,
8783
- city: form.city.trim(),
8784
- state: form.state.trim().toUpperCase(),
8785
- phone: form.phone.trim() || void 0,
8786
- // Viaja no endereço porque a assinatura do shop_place_order não pode
8787
- // ganhar parâmetro sem DROP (0050); uma trigger o levanta para a
8788
- // coluna e o tira do jsonb, então nunca vira linha de endereço.
8789
- 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
8790
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,
8791
9028
  paymentMethod
8792
9029
  });
8793
9030
  if (customerId) session.setSession({ customerId, email: form.email, name: form.name });
@@ -8820,6 +9057,7 @@ function CheckoutPage() {
8820
9057
  TID.checkoutDocument
8821
9058
  );
8822
9059
  }
9060
+ if (isPickup) return true;
8823
9061
  if (!form.street.trim() || !form.city.trim() || !form.zip.trim() || !form.district.trim()) {
8824
9062
  return fail("Preencha ou selecione o endere\xE7o de entrega.", TID.checkoutStreet);
8825
9063
  }
@@ -8867,7 +9105,8 @@ function CheckoutPage() {
8867
9105
  CheckoutSteps,
8868
9106
  {
8869
9107
  current: step,
8870
- 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)
8871
9110
  }
8872
9111
  ),
8873
9112
  /* @__PURE__ */ jsxs(
@@ -9099,6 +9338,27 @@ function CheckoutPage() {
9099
9338
  }
9100
9339
  ),
9101
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
+ ] })
9102
9362
  ] })
9103
9363
  ] }),
9104
9364
  step === 4 && paidOrderId && /* @__PURE__ */ jsxs("section", { "data-testid": TID.checkoutDone, className: "rounded-xl border bg-card p-8 text-center", children: [
@@ -9123,7 +9383,7 @@ function CheckoutPage() {
9123
9383
  type: "submit",
9124
9384
  "data-testid": TID.checkoutContinue,
9125
9385
  className: "min-h-12 rounded-lg bg-primary px-6 py-4 text-sm font-bold text-primary-foreground transition hover:opacity-90",
9126
- 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)}`
9127
9387
  }
9128
9388
  ),
9129
9389
  step === 2 && /* @__PURE__ */ jsx(
@@ -9140,12 +9400,7 @@ function CheckoutPage() {
9140
9400
  ] })
9141
9401
  ]
9142
9402
  }
9143
- ),
9144
- /* @__PURE__ */ jsxs("footer", { className: "mt-10 flex flex-wrap gap-4 border-t pt-5 text-xs text-muted-foreground", children: [
9145
- /* @__PURE__ */ jsx(Link, { to: "/privacy", className: "hover:text-foreground", children: "Privacidade" }),
9146
- /* @__PURE__ */ jsx(Link, { to: "/terms", className: "hover:text-foreground", children: "Termos" }),
9147
- /* @__PURE__ */ jsx(Link, { to: "/refunds", className: "hover:text-foreground", children: "Trocas e devolu\xE7\xF5es" })
9148
- ] })
9403
+ )
9149
9404
  ] }),
9150
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: [
9151
9406
  /* @__PURE__ */ jsx("h2", { className: "sr-only", children: "Resumo do pedido" }),
@@ -9157,7 +9412,12 @@ function CheckoutPage() {
9157
9412
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
9158
9413
  /* @__PURE__ */ jsx("p", { className: "truncate font-medium", children: line.name }),
9159
9414
  line.sku && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: line.sku }),
9160
- 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
+ ] })
9161
9421
  ] }),
9162
9422
  /* @__PURE__ */ jsx("span", { "data-price": (line.unitPrice * line.quantity).toFixed(2), children: money(line.unitPrice * line.quantity) })
9163
9423
  ] }, line.lineId ?? line.productId)) }),
@@ -9223,7 +9483,7 @@ function CheckoutPage() {
9223
9483
  ] }),
9224
9484
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
9225
9485
  /* @__PURE__ */ jsxs("dt", { className: cart.discountFreeShipping ? "text-emerald-700" : "text-muted-foreground", children: [
9226
- "Entrega",
9486
+ isPickup ? "Retirada na loja" : "Entrega",
9227
9487
  cart.discountFreeShipping && /* @__PURE__ */ jsxs(
9228
9488
  "button",
9229
9489
  {
@@ -9238,7 +9498,7 @@ function CheckoutPage() {
9238
9498
  }
9239
9499
  )
9240
9500
  ] }),
9241
- /* @__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) })
9242
9502
  ] }),
9243
9503
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between border-t pt-5 text-lg font-bold", children: [
9244
9504
  /* @__PURE__ */ jsx("dt", { children: "Total" }),
@@ -9250,163 +9510,1451 @@ function CheckoutPage() {
9250
9510
  "Compra protegida e dados criptografados."
9251
9511
  ] }),
9252
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" })
9253
9518
  ] })
9254
9519
  ] })
9255
9520
  ] });
9256
9521
  }
9257
-
9258
- // src/data/order-status.ts
9259
- var FINANCIAL = {
9260
- pending: { label: "Pendente", tone: "pending" },
9261
- paid: { label: "Pago", tone: "positive" },
9262
- partially_paid: { label: "Parcialmente pago", tone: "pending" },
9263
- partially_refunded: { label: "Parcialmente reembolsado", tone: "negative" },
9264
- refunded: { label: "Reembolsado", tone: "negative" },
9265
- voided: { label: "Cancelado", tone: "negative" }
9266
- };
9267
- var TONE_CLASS = {
9268
- positive: "bg-emerald-100 text-emerald-800",
9269
- pending: "bg-amber-100 text-amber-800",
9270
- negative: "bg-rose-100 text-rose-800"
9271
- };
9272
- function financialStatusBadge(status) {
9273
- const entry = FINANCIAL[status] ?? { label: status, tone: "pending" };
9274
- return { label: entry.label, className: TONE_CLASS[entry.tone] };
9275
- }
9276
- function isPaid(status) {
9277
- 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;
9278
9526
  }
9279
- function isRefunded(status) {
9280
- return status === "refunded" || status === "partially_refunded";
9527
+ function selectShowsHours(state) {
9528
+ return Boolean(state.service?.configured);
9281
9529
  }
9282
- function orderTrackingSteps(order) {
9283
- const received = {
9284
- id: "received",
9285
- label: "Pedido recebido",
9286
- description: `#${order.orderNumber}`,
9287
- state: "complete",
9288
- icon: Check
9289
- };
9290
- if (order.status === "cancelled" || order.financialStatus === "voided") {
9291
- return [
9292
- received,
9293
- { id: "payment", label: "Pedido cancelado", description: "Este pedido foi cancelado", state: "cancelled", icon: XCircle }
9294
- ];
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
+ }
9295
9544
  }
9296
- if (isRefunded(order.financialStatus)) {
9297
- return [
9298
- received,
9299
- { 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",
9300
9575
  {
9301
- id: "refund",
9302
- label: order.financialStatus === "refunded" ? "Reembolsado" : "Reembolso parcial",
9303
- description: "Valor devolvido ao cliente",
9304
- state: "cancelled",
9305
- icon: RotateCcw
9576
+ src: cover,
9577
+ alt: "",
9578
+ "aria-hidden": true,
9579
+ className: "absolute inset-0 -z-10 h-full w-full object-cover"
9306
9580
  }
9307
- ];
9308
- }
9309
- const paid = isPaid(order.financialStatus);
9310
- const partiallyFulfilled = order.fulfillmentStatus === "partially_fulfilled";
9311
- const fulfilled = order.fulfillmentStatus === "fulfilled";
9312
- return [
9313
- received,
9314
- {
9315
- id: "payment",
9316
- label: paid ? "Pagamento aprovado" : "Aguardando pagamento",
9317
- // "Processando" descrevia um pagamento que ninguém iniciou.
9318
- description: paid ? "Compra confirmada" : "Ainda n\xE3o recebemos",
9319
- state: paid ? "complete" : "current",
9320
- icon: paid ? Check : Clock
9321
- },
9322
- {
9323
- id: "preparing",
9324
- label: "Preparando envio",
9325
- description: paid ? "Separando produtos" : "Come\xE7a ap\xF3s o pagamento",
9326
- state: fulfilled || partiallyFulfilled ? "complete" : paid ? "current" : "pending",
9327
- icon: PackageCheck
9328
- },
9329
- {
9330
- id: "delivery",
9331
- label: fulfilled ? "Pedido enviado" : partiallyFulfilled ? "Envio parcial" : "Entrega",
9332
- description: fulfilled ? "A caminho do cliente" : partiallyFulfilled ? "Parte do pedido saiu" : "Pr\xF3xima etapa",
9333
- state: fulfilled ? "complete" : partiallyFulfilled ? "current" : "pending",
9334
- icon: fulfilled || partiallyFulfilled ? Truck : Clock
9335
- }
9336
- ];
9337
- }
9338
- var GRID_COLS = {
9339
- 2: "sm:grid-cols-2",
9340
- 3: "sm:grid-cols-3",
9341
- 4: "sm:grid-cols-4"
9342
- };
9343
- function OrderTrackingTimeline({ order, compact: compact2 = false }) {
9344
- const steps = orderTrackingSteps(order);
9345
- return /* @__PURE__ */ jsx(
9346
- "ol",
9347
- {
9348
- "data-testid": TID.orderTracking,
9349
- className: `grid ${compact2 ? "gap-2" : "gap-3"} ${GRID_COLS[steps.length] ?? "sm:grid-cols-4"}`,
9350
- "aria-label": "Acompanhamento do pedido",
9351
- children: steps.map((step) => {
9352
- const Icon2 = step.icon;
9353
- const cancelled = step.state === "cancelled";
9354
- const done = step.state === "complete";
9355
- const current = step.state === "current";
9356
- 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;
9357
9622
  return /* @__PURE__ */ jsx(
9358
- "li",
9623
+ "button",
9359
9624
  {
9360
- "data-testid": TID.orderTrackingStep(step.id),
9361
- "data-state": step.state,
9362
- className: [
9363
- "relative min-w-0 rounded-xl border p-3",
9364
- cancelled ? "border-rose-200 bg-rose-50 text-rose-700" : active2 ? "border-primary/35 bg-primary/5" : "bg-muted/20 text-muted-foreground",
9365
- compact2 ? "text-xs" : "text-sm"
9366
- ].join(" "),
9367
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
9368
- /* @__PURE__ */ jsx(
9369
- "span",
9370
- {
9371
- className: [
9372
- "grid h-7 w-7 shrink-0 place-items-center rounded-full border",
9373
- 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"
9374
- ].join(" "),
9375
- "aria-hidden": true,
9376
- children: /* @__PURE__ */ jsx(Icon2, { className: "h-3.5 w-3.5" })
9377
- }
9378
- ),
9379
- /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
9380
- /* @__PURE__ */ jsx("span", { className: "block font-semibold leading-snug text-foreground", children: step.label }),
9381
- /* @__PURE__ */ jsx("span", { className: "mt-0.5 block leading-snug text-muted-foreground", children: step.description })
9382
- ] })
9383
- ] })
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"
9384
9631
  },
9385
- step.id
9632
+ mode
9386
9633
  );
9387
- })
9388
- }
9389
- );
9634
+ }) })
9635
+ ] })
9636
+ ] });
9390
9637
  }
9391
-
9392
- // src/components/account/section.ts
9393
- var ACCOUNT_SECTIONS = [
9394
- "profile",
9395
- "orders",
9396
- "addresses",
9397
- "payments"
9398
- ];
9399
- var DEFAULT_ACCOUNT_SECTION = "orders";
9400
- function isAccountSection(value) {
9401
- 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
+ ] });
9402
9645
  }
9403
- function parseAccountSection(path) {
9404
- const query = path.split("?")[1];
9405
- if (!query) return DEFAULT_ACCOUNT_SECTION;
9406
- for (const pair of query.split("&")) {
9407
- const [key, raw = ""] = pair.split("=");
9408
- if (key !== "section") continue;
9409
- const value = decodeURIComponent(raw);
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;
10954
+ for (const pair of query.split("&")) {
10955
+ const [key, raw = ""] = pair.split("=");
10956
+ if (key !== "section") continue;
10957
+ const value = decodeURIComponent(raw);
9410
10958
  return isAccountSection(value) ? value : DEFAULT_ACCOUNT_SECTION;
9411
10959
  }
9412
10960
  return DEFAULT_ACCOUNT_SECTION;
@@ -9516,7 +11064,7 @@ function OrderConfirmationPage({ orderId }) {
9516
11064
  const [loading, setLoading] = useState(true);
9517
11065
  const money = (v) => formatMoney(v, config.currency, config.locale);
9518
11066
  useStorefrontHead({ title: order ? `Pedido #${order.orderNumber} \u2014 ${config.name}` : config.name });
9519
- const reload = React4.useCallback(() => {
11067
+ const reload = React37.useCallback(() => {
9520
11068
  let cancelled = false;
9521
11069
  getShopProvider().getOrder(orderId).then((o) => {
9522
11070
  if (!cancelled) setOrder(o);
@@ -9545,7 +11093,7 @@ function OrderConfirmationPage({ orderId }) {
9545
11093
  ] }, column)) }),
9546
11094
  /* @__PURE__ */ jsxs("div", { className: "mt-6", children: [
9547
11095
  /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-32" }),
9548
- /* @__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: [
9549
11097
  /* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-7 shrink-0 rounded-full" }),
9550
11098
  node < 3 && /* @__PURE__ */ jsx(Skeleton, { className: "h-px flex-1" })
9551
11099
  ] }, node)) })
@@ -9832,7 +11380,7 @@ function OrdersPanel() {
9832
11380
  /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-24 rounded-full" })
9833
11381
  ] }),
9834
11382
  /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-3 w-20" }),
9835
- /* @__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: [
9836
11384
  /* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-6 shrink-0 rounded-full" }),
9837
11385
  node < 3 && /* @__PURE__ */ jsx(Skeleton, { className: "h-px flex-1" })
9838
11386
  ] }, node)) }),
@@ -10088,7 +11636,7 @@ function PolicyPage({ kind }) {
10088
11636
  ] }) })
10089
11637
  ] });
10090
11638
  }
10091
- var StorefrontErrorBoundary = class extends React4.Component {
11639
+ var StorefrontErrorBoundary = class extends React37.Component {
10092
11640
  constructor() {
10093
11641
  super(...arguments);
10094
11642
  this.state = { error: null };
@@ -10145,7 +11693,19 @@ function ToastCard({ toast: toast2 }) {
10145
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" }) }),
10146
11694
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
10147
11695
  /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold leading-tight", children: toast2.title }),
10148
- 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
+ )
10149
11709
  ] }),
10150
11710
  /* @__PURE__ */ jsx(
10151
11711
  "button",
@@ -10164,14 +11724,29 @@ function ToastCard({ toast: toast2 }) {
10164
11724
  function Toaster() {
10165
11725
  const toasts = useToastStore((s2) => s2.toasts);
10166
11726
  if (toasts.length === 0) return null;
10167
- 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
+ );
10168
11740
  }
10169
- function isFocusedRoute(config, path) {
11741
+ function routeChrome(config, path) {
10170
11742
  const match = resolveStorefrontRoute(config, path);
10171
11743
  if (match.kind === "custom") {
10172
- 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";
10173
11746
  }
10174
- return match.kind === "checkout";
11747
+ if (match.kind === "checkout") return "focused";
11748
+ if (match.kind === "menu") return "minimal";
11749
+ return "default";
10175
11750
  }
10176
11751
  function RouteSwitch({ path }) {
10177
11752
  const config = useStorefrontConfig();
@@ -10191,6 +11766,11 @@ function RouteSwitch({ path }) {
10191
11766
  return config.features.checkout ? /* @__PURE__ */ jsx(CheckoutPage, {}) : /* @__PURE__ */ jsx(NotFoundPage, {});
10192
11767
  case "account":
10193
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, {});
10194
11774
  case "catalog":
10195
11775
  // The search results page IS the catalog, filtered — where the overlay's
10196
11776
  // CTA lands, carrying `?q=`.
@@ -10294,9 +11874,10 @@ function StorefrontShellBody({ pathOverride }) {
10294
11874
  const livePath = useRoutePath();
10295
11875
  const path = pathOverride ?? livePath;
10296
11876
  useScrollToTopOnNavigate(path);
10297
- const focused = isFocusedRoute(config, path);
11877
+ const chrome = routeChrome(config, path);
11878
+ const focused = chrome === "focused";
10298
11879
  const components = getStorefrontComponents(config);
10299
- const chromeProps = { config, commerceMode: config.commerceMode };
11880
+ const chromeProps = { config, commerceMode: config.commerceMode, chrome };
10300
11881
  const header = resolveOverride(components.Header, StorefrontHeader);
10301
11882
  const footer = resolveOverride(components.Footer, StorefrontFooter);
10302
11883
  const Shell = overrideComponent(components.Shell);
@@ -10341,7 +11922,7 @@ function createStorefront(options) {
10341
11922
 
10342
11923
  // src/document/sections.ts
10343
11924
  var STORE_DOCUMENT_KIND = "fayz.store";
10344
- var STORE_COMMERCE_MODES = ["checkout", "catalog", "enquiry"];
11925
+ var STORE_COMMERCE_MODES = ["checkout", "catalog", "enquiry", "delivery"];
10345
11926
  var STORE_PAYMENT_MODES = ["mock", "pix-mercadopago", "gateway"];
10346
11927
  var STORE_PAYMENT_METHOD_KINDS = [
10347
11928
  "pix",
@@ -10361,7 +11942,7 @@ var STORE_ROUTE_KINDS = [
10361
11942
  "order",
10362
11943
  "custom"
10363
11944
  ];
10364
- var STORE_ROUTE_CHROMES = ["default", "focused"];
11945
+ var STORE_ROUTE_CHROMES = ["default", "minimal", "focused"];
10365
11946
  var STORE_THEME_RADII = ["none", "soft", "round", "pill"];
10366
11947
  var f = (type, description, extra = {}) => ({ type, description, ...extra });
10367
11948
  var STORE_SECTIONS = {
@@ -11558,7 +13139,7 @@ ${formatErrors(report.errors)}`;
11558
13139
  return resolveHandle(name);
11559
13140
  } catch (err) {
11560
13141
  if (onError === "throw") throw err;
11561
- console.error(`[storefront] dropping ${path}: ${err instanceof Error ? err.message : String(err)}`);
13142
+ console.error(`[storefront] dropping ${path}: ${errorMessage(err)}`);
11562
13143
  return void 0;
11563
13144
  }
11564
13145
  };
@@ -11667,6 +13248,6 @@ ${formatErrors(report.errors)}`;
11667
13248
  return config;
11668
13249
  }
11669
13250
 
11670
- 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 };
11671
- //# sourceMappingURL=chunk-ORKXSAZ6.js.map
11672
- //# sourceMappingURL=chunk-ORKXSAZ6.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