@aglyn/plugins-commerce 1.0.0-beta.291 → 1.0.0-beta.293

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 (115) hide show
  1. package/README.md +1 -0
  2. package/package.json +13 -13
  3. package/src/lib/components/cart-fulfillment.d.ts +3 -0
  4. package/src/lib/components/cart-fulfillment.js.map +1 -1
  5. package/src/lib/components/cart.js +14 -4
  6. package/src/lib/components/cart.js.map +1 -1
  7. package/src/lib/components/console/host-quotes-card.component.js +11 -9
  8. package/src/lib/components/console/host-quotes-card.component.js.map +1 -1
  9. package/src/lib/components/console/invoice-send.component.js +1 -1
  10. package/src/lib/components/console/invoice-send.component.js.map +1 -1
  11. package/src/lib/components/console/pos/pos-item-dialog.component.js +3 -2
  12. package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
  13. package/src/lib/components/console/pos-kiosk-page.component.js +2 -1
  14. package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -1
  15. package/src/lib/components/console/pos-page.component.js +1 -1
  16. package/src/lib/components/console/pos-page.component.js.map +1 -1
  17. package/src/lib/components/console/product-add-ons-fields.component.d.ts +41 -0
  18. package/src/lib/components/console/product-add-ons-fields.component.js +356 -0
  19. package/src/lib/components/console/product-add-ons-fields.component.js.map +1 -0
  20. package/src/lib/components/console/product-editor-dialog.component.js +31 -5
  21. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  22. package/src/lib/components/console/product-register-fields.component.d.ts +4 -5
  23. package/src/lib/components/console/product-register-fields.component.js +8 -197
  24. package/src/lib/components/console/product-register-fields.component.js.map +1 -1
  25. package/src/lib/components/console/products-hub-zone.component.js +6 -0
  26. package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
  27. package/src/lib/components/console/quote-pay-link.component.d.ts +8 -1
  28. package/src/lib/components/console/quote-pay-link.component.js +29 -3
  29. package/src/lib/components/console/quote-pay-link.component.js.map +1 -1
  30. package/src/lib/components/console/quote-send.component.js +1 -4
  31. package/src/lib/components/console/quote-send.component.js.map +1 -1
  32. package/src/lib/components/product-add-ons.d.ts +28 -0
  33. package/src/lib/components/product-add-ons.js +136 -0
  34. package/src/lib/components/product-add-ons.js.map +1 -0
  35. package/src/lib/components/product-detail.d.ts +16 -0
  36. package/src/lib/components/product-detail.js +112 -19
  37. package/src/lib/components/product-detail.js.map +1 -1
  38. package/src/lib/components/product-grid.d.ts +8 -2
  39. package/src/lib/components/product-grid.js +6 -1
  40. package/src/lib/components/product-grid.js.map +1 -1
  41. package/src/lib/declarations.console-server.js +15 -0
  42. package/src/lib/declarations.console-server.js.map +1 -1
  43. package/src/lib/model/commerce-cart.d.ts +15 -3
  44. package/src/lib/model/commerce-cart.js +10 -3
  45. package/src/lib/model/commerce-cart.js.map +1 -1
  46. package/src/lib/model/commerce-invoices.d.ts +7 -0
  47. package/src/lib/model/commerce-invoices.js +11 -0
  48. package/src/lib/model/commerce-invoices.js.map +1 -1
  49. package/src/lib/model/commerce-orders.d.ts +6 -3
  50. package/src/lib/model/commerce-orders.js +3 -0
  51. package/src/lib/model/commerce-orders.js.map +1 -1
  52. package/src/lib/model/commerce-pos-kiosk.d.ts +1 -1
  53. package/src/lib/model/commerce-pos-kiosk.js +3 -2
  54. package/src/lib/model/commerce-pos-kiosk.js.map +1 -1
  55. package/src/lib/model/commerce-quote-email.d.ts +14 -0
  56. package/src/lib/model/commerce-quote-email.js +25 -9
  57. package/src/lib/model/commerce-quote-email.js.map +1 -1
  58. package/src/lib/model/commerce-quote-proofs.d.ts +7 -0
  59. package/src/lib/model/commerce-quote-proofs.js +15 -0
  60. package/src/lib/model/commerce-quote-proofs.js.map +1 -1
  61. package/src/lib/model/commerce-quotes.js +1 -1
  62. package/src/lib/model/commerce-quotes.js.map +1 -1
  63. package/src/lib/model/commerce.d.ts +18 -0
  64. package/src/lib/model/commerce.js +26 -0
  65. package/src/lib/model/commerce.js.map +1 -1
  66. package/src/lib/model/product-modifiers.d.ts +74 -4
  67. package/src/lib/model/product-modifiers.js +131 -10
  68. package/src/lib/model/product-modifiers.js.map +1 -1
  69. package/src/lib/server/billing-webhook.js +64 -43
  70. package/src/lib/server/billing-webhook.js.map +1 -1
  71. package/src/lib/server/cart-checkout.js +14 -2
  72. package/src/lib/server/cart-checkout.js.map +1 -1
  73. package/src/lib/server/cart.d.ts +5 -0
  74. package/src/lib/server/cart.js +41 -4
  75. package/src/lib/server/cart.js.map +1 -1
  76. package/src/lib/server/catalog.d.ts +6 -0
  77. package/src/lib/server/catalog.js +2 -0
  78. package/src/lib/server/catalog.js.map +1 -1
  79. package/src/lib/server/checkout-extras.js +3 -1
  80. package/src/lib/server/checkout-extras.js.map +1 -1
  81. package/src/lib/server/checkout.js +28 -3
  82. package/src/lib/server/checkout.js.map +1 -1
  83. package/src/lib/server/local-fulfillment.js +5 -1
  84. package/src/lib/server/local-fulfillment.js.map +1 -1
  85. package/src/lib/server/pos-offline-sync.js +1 -1
  86. package/src/lib/server/pos-offline-sync.js.map +1 -1
  87. package/src/lib/server/pos-order.js +1 -1
  88. package/src/lib/server/pos-order.js.map +1 -1
  89. package/src/lib/server/product.d.ts +26 -0
  90. package/src/lib/server/product.js +18 -0
  91. package/src/lib/server/product.js.map +1 -1
  92. package/src/lib/server/products-write.js +5 -2
  93. package/src/lib/server/products-write.js.map +1 -1
  94. package/src/lib/server/quote-email.js.map +1 -1
  95. package/src/lib/server/quote-link-token.d.ts +4 -2
  96. package/src/lib/server/quote-link-token.js +4 -2
  97. package/src/lib/server/quote-link-token.js.map +1 -1
  98. package/src/lib/server/quote-pay.d.ts +5 -1
  99. package/src/lib/server/quote-pay.js +5 -2
  100. package/src/lib/server/quote-pay.js.map +1 -1
  101. package/src/lib/server/quote-proofs.d.ts +9 -1
  102. package/src/lib/server/quote-proofs.js +92 -23
  103. package/src/lib/server/quote-proofs.js.map +1 -1
  104. package/src/lib/server/quote-upload-keys.d.ts +50 -0
  105. package/src/lib/server/quote-upload-keys.js +86 -0
  106. package/src/lib/server/quote-upload-keys.js.map +1 -0
  107. package/src/lib/server/quote-upload-sweep.d.ts +30 -0
  108. package/src/lib/server/quote-upload-sweep.js +117 -0
  109. package/src/lib/server/quote-upload-sweep.js.map +1 -0
  110. package/src/lib/utils/add-to-cart.d.ts +8 -0
  111. package/src/lib/utils/add-to-cart.js +10 -2
  112. package/src/lib/utils/add-to-cart.js.map +1 -1
  113. package/src/lib/utils/quote-email-content.d.ts +1 -0
  114. package/src/lib/utils/quote-email-content.js +7 -4
  115. package/src/lib/utils/quote-email-content.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/product-grid.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiMagnify, mdiViewGridOutline } from '@aglyn/shared-data-mdi'\nimport {\n ListQueryNotices,\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport type { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Card from '@mui/material/Card'\nimport CardActionArea from '@mui/material/CardActionArea'\nimport CardContent from '@mui/material/CardContent'\nimport CardMedia from '@mui/material/CardMedia'\nimport Chip from '@mui/material/Chip'\nimport InputAdornment from '@mui/material/InputAdornment'\nimport MenuItem from '@mui/material/MenuItem'\nimport Skeleton from '@mui/material/Skeleton'\nimport Slider from '@mui/material/Slider'\nimport SvgIcon from '@mui/material/SvgIcon'\nimport TextField from '@mui/material/TextField'\nimport Typography from '@mui/material/Typography'\nimport { forwardRef, useCallback, useEffect, useRef, useState } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport {\n STOREFRONT_CATALOG_DECLARATION,\n STOREFRONT_CATALOG_HEADERS,\n STOREFRONT_CATALOG_OPTIONS,\n} from '../constants/storefront-catalog-query'\nimport { generatePresetId } from '../utils/generate-preset-id'\nimport { addToCart } from '../utils/add-to-cart'\nimport { ProductImagePlaceholder } from './product-image-placeholder'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'product-grid'\n\nexport interface ProductGridProps {\n /** Filter source; 'all' lists the whole active catalog. */\n source?: 'all' | 'collection' | 'category' | 'tag'\n /** Collection id when source = collection (rename-safe, AGL-343). */\n collectionId?: string\n /** Legacy collection slug; used only when no collectionId is set.\n * Blank follows the /collections/{slug} URL (AGL-298). */\n collectionSlug?: string\n /** Faceted controls above the grid: tags, availability, sort. */\n showFilters?: boolean\n /** Debounced visitor search box above the grid (server-side, AGL-561). */\n showSearch?: boolean\n /** Category filter chips from the host's categories (AGL-561). */\n showCategories?: boolean\n /** Sort select: newest, name, price low→high / high→low (AGL-561). */\n showSort?: boolean\n /** Physical / digital / services filter chips (AGL-561). */\n showTypeFilter?: boolean\n /** Price range slider bounded by the catalog's prices (AGL-564). */\n showPriceFilter?: boolean\n /** Products per page; shows a Load more button while more remain. */\n pageSize?: number\n /** Category id when source = category (rename-safe, AGL-343). */\n categoryId?: string\n /** Legacy category slug; used only when no categoryId is set. */\n categorySlug?: string\n /** Tag when source = tag. */\n tag?: string\n sort?: 'name' | 'price-asc' | 'price-desc' | 'newest'\n /** Grid columns on desktop (1-6); mobile always stacks to 2/1. */\n columns?: number\n maxItems?: number\n /** Empty-state copy when nothing matches. */\n emptyText?: string\n /**\n * The empty state's headline (AGL-3676). Set, a catalog with nothing in it\n * yet opens as a full panel — the headline, `emptyText` and one action —\n * the way a designer store template opens before its first products; the\n * sort and filter controls wait for products to sort.\n */\n emptyTitle?: string\n /** The empty panel's one action: its label… */\n emptyActionLabel?: string\n /** …and where it goes, a path on the site (\"/contact\"). */\n emptyActionHref?: string\n /**\n * How each product is drawn (AGL-3676): `outlined`, the default, a card\n * with a short photo strip; `photo`, the photo first and tall, with the\n * name and price under it and no card around it — the way a storefront\n * leads with what it sells.\n */\n cardStyle?: 'outlined' | 'photo'\n /**\n * A compact button under each card: \"Add to cart\" for a product that sells\n * as one priced variant in stock, \"Choose options\" (to its page) for one\n * with several variants, and nothing for a product with no price yet or\n * sold out. Off by default.\n */\n quickAdd?: boolean\n}\n\ninterface CatalogItem {\n id: string\n name: string\n slug: string\n priceUsd: number\n maxPriceUsd: number\n compareAtPriceUsd?: number\n imageUrl?: string\n soldOut: boolean\n tags?: string[]\n /** Listed before it has a price (AGL-3676): no price to print yet. */\n priceComingSoon?: boolean\n /** How many variants it sells as (absent from an older payload). */\n variantCount?: number\n /** The one variant a quick add puts in the cart. */\n defaultVariantId?: string\n}\n\n/** What a card's quick add offers for one product. */\nexport function quickAddAction(\n item: Pick<CatalogItem, 'priceComingSoon' | 'soldOut' | 'variantCount' | 'defaultVariantId'>,\n): 'add' | 'options' | null {\n if (item.priceComingSoon || item.soldOut) return null\n if (item.defaultVariantId) return 'add'\n if ((item.variantCount ?? 0) > 1) return 'options'\n return null\n}\n\n/**\n * The chip and tag a browsing grid opens on, from the page's address:\n * `?category=<slug>` and `?tag=<tag>`. Read after hydration only — the\n * server renders the unfiltered first page and has no address to read.\n */\nexport function browseParamsFromSearch(search: string): { categorySlug: string; tag: string } {\n const params = new URLSearchParams(search)\n return {\n categorySlug: (params.get('category') ?? '').trim(),\n tag: (params.get('tag') ?? '').trim(),\n }\n}\n\n/** What a card says where a product listed before it has a price would show one (AGL-3676). */\nconst PRICE_COMING_SOON_LABEL = 'Price coming soon'\n\ninterface CatalogCategory {\n id: string\n name: string\n slug: string\n}\n\n/** Price facet from the catalog handler, in cents (AGL-564). */\ninterface CatalogPriceBounds {\n minCents: number\n maxCents: number\n}\n\n/**\n * A control the catalog query could not apply, and why (AGL-3321): the\n * plan's refusal as the handler returns it, named here by\n * `listQueryRefusals` in the words of this grid's controls.\n */\ninterface CatalogRefusal {\n clause: ListFilterClause | 'search'\n reason: string\n}\n\n/**\n * This grid's first page, resolved server-side by the commerce site-page\n * enricher (AGL-659). Same shape the catalog endpoint returns, because both\n * go through `queryPublicCatalog`.\n */\ninterface CatalogSeed {\n items?: CatalogItem[]\n categories?: CatalogCategory[]\n priceBounds?: CatalogPriceBounds\n nextCursor?: string\n refused?: CatalogRefusal[]\n notices?: string[]\n}\n\nconst SAMPLE_ITEMS: CatalogItem[] = [\n { id: 's1', name: 'Sample product', slug: '#', priceUsd: 29, maxPriceUsd: 29, soldOut: false },\n { id: 's2', name: 'Another product', slug: '#', priceUsd: 49, maxPriceUsd: 59, compareAtPriceUsd: 79, soldOut: false },\n { id: 's3', name: 'Third product', slug: '#', priceUsd: 12, maxPriceUsd: 12, soldOut: true },\n]\n\n/** Inert chips shown on the besigner canvas when categories are on. */\nconst SAMPLE_CATEGORIES: CatalogCategory[] = [\n { id: 'sc1', name: 'Apparel', slug: 'apparel' },\n { id: 'sc2', name: 'Accessories', slug: 'accessories' },\n]\n\n/** Inert slider bounds on the canvas, matching the sample cards. */\nconst SAMPLE_PRICE_BOUNDS: CatalogPriceBounds = {\n minCents: 1200,\n maxCents: 4900,\n}\n\nconst TYPE_OPTIONS = [\n { value: 'physical', label: 'Physical' },\n { value: 'digital', label: 'Digital' },\n { value: 'service', label: 'Services' },\n] as const\n\nconst SORT_OPTIONS = [\n { value: 'newest', label: 'Newest' },\n { value: 'name', label: 'Name' },\n { value: 'price-asc', label: 'Price: low to high' },\n { value: 'price-desc', label: 'Price: high to low' },\n] as const\n\nfunction collectionSlugFromLocation(): string {\n if (typeof window === 'undefined') return ''\n const match = window.location.pathname.match(/\\/collections\\/([^/?#]+)/)\n return match ? decodeURIComponent(match[1]) : ''\n}\n\nfunction priceLabel(item: CatalogItem): string {\n if (item.priceComingSoon) return PRICE_COMING_SOON_LABEL\n return item.priceUsd === item.maxPriceUsd\n ? `$${item.priceUsd}`\n : `From $${item.priceUsd}`\n}\n\n/**\n * Product grid / PLP block (AGL-291): active catalog items from the\n * public listing API, filterable by collection/category/tag, designable\n * in besigner with sample cards (no SiteContext = inert canvas, same\n * convention as the mui Product block). Cards navigate to\n * `/products/{slug}` (PDP routes, AGL-292). Catalog UX (AGL-561):\n * optional debounced search, category chips, type chips, a sort\n * select, and Load more paging — all resolved server-side by the\n * catalog handler so large catalogs stay cheap.\n *\n * Nothing is narrowed here (AGL-3321): every control is a parameter of the\n * catalog query, and the rows shown are the rows it returned. A control the\n * query could not combine with another comes back in `refused` and is said\n * above the grid, rather than applied to the cards that happen to be loaded.\n */\nconst ProductGrid = forwardRef<HTMLDivElement, ProductGridProps>(\n (props, ref) => {\n const {\n source,\n collectionId,\n collectionSlug: collectionSlugProp,\n categoryId,\n categorySlug,\n tag,\n sort: sortProp,\n columns,\n maxItems,\n emptyText,\n emptyTitle,\n emptyActionLabel,\n emptyActionHref,\n showFilters,\n showSearch,\n showCategories,\n showSort,\n showTypeFilter,\n showPriceFilter,\n pageSize,\n cardStyle,\n quickAdd,\n ...rest\n } = props\n const photoCards = cardStyle === 'photo'\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const site = Aglyn.useSite()\n const siteFetch = Aglyn.useSiteFetch()\n const { hostId } = site\n // Quick add (AGL-3676 follow-up): which card is adding, which just did.\n const [quickAdding, setQuickAdding] = useState('')\n const [quickAdded, setQuickAdded] = useState('')\n // Server-rendered first page (AGL-659). The site-page enricher ran this\n // grid's own query on the server and keyed the result by node id — two\n // grids on a page have different queries, so an unkeyed seed would put\n // the wrong products in one of them. Absent in the besigner and preview,\n // where there is no pageData at all and the effect below still fetches.\n const nodeId = Aglyn.useNodeId()\n const seed = (\n site.pageData as\n | { commerce?: { grids?: Record<string, CatalogSeed> } }\n | undefined\n )?.commerce?.grids?.[nodeId]\n\n const [items, setItems] = useState<CatalogItem[] | null>(\n seed?.items ?? null,\n )\n const [sort, setSort] = useState(sortProp)\n // The In stock chip, asked of the catalog (`inStock=1`), never matched\n // over the cards on screen (AGL-3321).\n const [inStockOnly, setInStockOnly] = useState(false)\n const [activeTag, setActiveTag] = useState('')\n // Catalog UX state (AGL-561): the search box debounces into\n // `query`; a chip pick overrides any pinned category ('*' = All);\n // paging appends via the handler's `nextCursor` (AGL-3321).\n const [searchInput, setSearchInput] = useState('')\n const [query, setQuery] = useState('')\n const [activeCategoryId, setActiveCategoryId] = useState('')\n const [activeType, setActiveType] = useState('')\n const [categories, setCategories] = useState<CatalogCategory[]>(\n seed?.categories ?? [],\n )\n // Price filter (AGL-564): the slider drags into `priceInput` (whole\n // dollars) and debounces into `priceFilter`, which drives the query;\n // null = untouched (no price params sent). Bounds come from the\n // handler's price facets.\n const [priceBounds, setPriceBounds] = useState<CatalogPriceBounds | null>(\n seed?.priceBounds ?? null,\n )\n const [priceInput, setPriceInput] = useState<number[] | null>(null)\n const [priceFilter, setPriceFilter] = useState<number[] | null>(null)\n const [nextCursor, setNextCursor] = useState<string | null>(\n seed?.nextCursor ?? null,\n )\n // What the query could not apply, said above the grid (AGL-3321).\n const [refused, setRefused] = useState<CatalogRefusal[]>(seed?.refused ?? [])\n const [notices, setNotices] = useState<string[]>(seed?.notices ?? [])\n const [loadingMore, setLoadingMore] = useState(false)\n const fetchSeq = useRef(0)\n const collectionSlug =\n source === 'collection'\n ? collectionSlugProp || collectionSlugFromLocation()\n : collectionSlugProp\n const pageLimit =\n pageSize && Number(pageSize) > 0 ? Math.floor(Number(pageSize)) : 0\n const pinnedCategoryId = source === 'category' ? categoryId ?? '' : ''\n\n // A browsing grid opens on the address's `?category=` / `?tag=`: a shop\n // page linked as /shop?category=candles shows that chip picked. Read in\n // an effect, so the server and the first client render agree.\n const [urlCategorySlug, setUrlCategorySlug] = useState('')\n useEffect(() => {\n if (!hostId || typeof window === 'undefined') return\n const asked = browseParamsFromSearch(window.location.search)\n if (showCategories && asked.categorySlug) setUrlCategorySlug(asked.categorySlug)\n if (showFilters && asked.tag) setActiveTag(asked.tag)\n // Once, on arrival: later chip picks are the visitor's.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hostId])\n useEffect(() => {\n if (!urlCategorySlug || !categories.length) return\n const match = categories.find((category) => category.slug === urlCategorySlug)\n if (match) setActiveCategoryId(match.id)\n setUrlCategorySlug('')\n }, [urlCategorySlug, categories])\n\n // Debounce keystrokes so large catalogs aren't fetched per letter.\n useEffect(() => {\n const timer = setTimeout(() => setQuery(searchInput.trim()), 300)\n return () => clearTimeout(timer)\n }, [searchInput])\n\n // Debounce slider drags the same way (AGL-564): one fetch per\n // settled range, not one per pixel of thumb movement.\n useEffect(() => {\n const timer = setTimeout(() => setPriceFilter(priceInput), 300)\n return () => clearTimeout(timer)\n }, [priceInput])\n\n const loadPage = useCallback(\n async (after?: string): Promise<void> => {\n if (!hostId) return\n const seq = ++fetchSeq.current\n const params = new URLSearchParams({ hostId })\n // Ids first (rename-safe, AGL-343); slugs remain as the legacy path.\n if (source === 'collection') {\n if (collectionId) params.set('collectionId', collectionId)\n else if (collectionSlug) params.set('collection', collectionSlug)\n }\n const effectiveCategoryId =\n activeCategoryId === '*' ? '' : activeCategoryId || pinnedCategoryId\n if (effectiveCategoryId) {\n params.set('categoryId', effectiveCategoryId)\n } else if (!activeCategoryId && source === 'category' && categorySlug) {\n params.set('category', categorySlug)\n }\n // A tag chip overrides a pinned tag, as a category chip does.\n const effectiveTag = activeTag || (source === 'tag' ? tag ?? '' : '')\n if (effectiveTag) params.set('tag', effectiveTag)\n if (query) params.set('q', query)\n if (activeType) params.set('type', activeType)\n if (inStockOnly) params.set('inStock', '1')\n // Slider dollars → API cents (AGL-564).\n if (priceFilter) {\n params.set('minPriceCents', String(priceFilter[0] * 100))\n params.set('maxPriceCents', String(priceFilter[1] * 100))\n }\n if (sort) params.set('sort', sort)\n const limit = pageLimit || maxItems\n if (limit) params.set('limit', String(limit))\n if (after) params.set('after', after)\n if ((showCategories || showPriceFilter) && !after) {\n params.set('facets', '1')\n }\n try {\n const response = await fetch(\n `/api/commerce/catalog?${params.toString()}`,\n )\n const payload = await response.json()\n if (seq !== fetchSeq.current) return\n const pageItems: CatalogItem[] = payload?.items ?? []\n setItems((prev) =>\n after && prev ? [...prev, ...pageItems] : pageItems,\n )\n setNextCursor(\n typeof payload?.nextCursor === 'string' ? payload.nextCursor : null,\n )\n if (!after) {\n setRefused(Array.isArray(payload?.refused) ? payload.refused : [])\n setNotices(Array.isArray(payload?.notices) ? payload.notices : [])\n }\n if (Array.isArray(payload?.categories)) {\n setCategories(payload.categories)\n }\n const bounds = payload?.priceBounds\n if (\n typeof bounds?.minCents === 'number' &&\n typeof bounds?.maxCents === 'number'\n ) {\n setPriceBounds(bounds)\n }\n } catch {\n if (seq !== fetchSeq.current) return\n if (!after) setItems([])\n setNextCursor(null)\n }\n },\n [\n hostId,\n source,\n collectionId,\n collectionSlug,\n categorySlug,\n tag,\n sort,\n maxItems,\n query,\n activeCategoryId,\n activeTag,\n activeType,\n inStockOnly,\n priceFilter,\n pageLimit,\n pinnedCategoryId,\n showCategories,\n showPriceFilter,\n ],\n )\n\n // A seeded grid already holds exactly what `loadPage(0)` would return,\n // so the first pass skips the fetch (AGL-659) — every later run, after\n // a filter or sort change, goes to the API as before.\n const seededRef = useRef(Boolean(seed))\n useEffect(() => {\n if (!hostId) return\n if (seededRef.current) {\n seededRef.current = false\n return\n }\n void loadPage()\n // Invalidate any in-flight request on dep change or unmount.\n return () => {\n fetchSeq.current++\n }\n }, [hostId, loadPage])\n\n const desktopColumns = Math.min(6, Math.max(1, columns ?? 3))\n const gridSx = {\n display: 'grid',\n gap: 2,\n gridTemplateColumns: {\n // Photo cards pair up on a phone, as a shop's catalog does; an\n // outlined card with its photo strip takes the row.\n xs: photoCards ? 'repeat(2, minmax(0, 1fr))' : 'repeat(1, minmax(0, 1fr))',\n sm: 'repeat(2, minmax(0, 1fr))',\n md: `repeat(${desktopColumns}, minmax(0, 1fr))`,\n },\n }\n // The rows the query returned, as returned (AGL-3321).\n const visible = hostId ? items : SAMPLE_ITEMS\n // Tag chips: the tags on the cards showing, as CHOICES — picking one asks\n // the catalog for that tag, never narrows these cards. The picked one\n // stays offered whatever the page it brought back carries.\n const facetTags = showFilters\n ? [\n ...new Set([\n ...(activeTag ? [activeTag] : []),\n ...(items ?? []).flatMap((item) => item.tags ?? []),\n ]),\n ].slice(0, 12)\n : []\n\n if (hostId && items === null) {\n return (\n // MERGE, never replace (AGL-1450). The skeleton branch replaced the\n // node's sx outright, so an authored grid flashed unstyled for as\n // long as the first fetch took and then snapped into place.\n <Box ref={ref} {...rest} sx={[gridSx, ...nodeSx]}>\n {Array.from({ length: desktopColumns }, (_item, index) => (\n <Card key={index} variant=\"outlined\">\n <Skeleton variant=\"rectangular\" height={160} />\n <CardContent>\n <Skeleton width=\"70%\" />\n <Skeleton width=\"30%\" />\n </CardContent>\n </Card>\n ))}\n </Box>\n )\n }\n\n // Catalog controls (AGL-561): search on top, category/type chips\n // under it with the sort select right-aligned — the layout every\n // mainstream commerce PLP uses. Server-side; sample data inert.\n const chipCategories = categories.length\n ? categories\n : !hostId && showCategories\n ? SAMPLE_CATEGORIES\n : []\n const selectedCategoryId =\n activeCategoryId === '*' ? '' : activeCategoryId || pinnedCategoryId\n const hasCatalogControls = Boolean(\n showSearch || showCategories || showSort || showTypeFilter ||\n showPriceFilter,\n )\n // Price slider bounds (AGL-564), widened to whole dollars so both\n // ends stay reachable; sample bounds keep the canvas designable.\n const sliderBounds = hostId ? priceBounds : SAMPLE_PRICE_BOUNDS\n const sliderMin = sliderBounds ? Math.floor(sliderBounds.minCents / 100) : 0\n const sliderMax = sliderBounds ? Math.ceil(sliderBounds.maxCents / 100) : 0\n const showSlider = Boolean(showPriceFilter && sliderMin < sliderMax)\n const controls = hasCatalogControls ? (\n <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, mb: 2 }}>\n {showSearch ? (\n <TextField\n value={searchInput}\n onChange={(event) => setSearchInput(event.target.value)}\n placeholder=\"Search products…\"\n size=\"small\"\n fullWidth\n slotProps={{\n input: {\n startAdornment: (\n <InputAdornment position=\"start\">\n <SvgIcon fontSize=\"small\" color=\"disabled\">\n <path d={mdiMagnify.path} />\n </SvgIcon>\n </InputAdornment>\n ),\n },\n htmlInput: { 'aria-label': 'Search products', type: 'search' },\n }}\n />\n ) : null}\n {showCategories || showTypeFilter || showSort ? (\n <Box\n sx={{\n display: 'flex',\n gap: 1,\n flexWrap: 'wrap',\n alignItems: 'center',\n }}\n >\n {showCategories && chipCategories.length ? (\n <>\n <Chip\n label=\"All\"\n size=\"small\"\n variant={selectedCategoryId ? 'outlined' : 'filled'}\n color={selectedCategoryId ? 'default' : 'primary'}\n onClick={() => {\n // One array clause per query (AGL-3321): a chip asked\n // for while searching would be refused, so picking one\n // clears the search — and a tag chip — rather than\n // leaving a chip that does nothing.\n setSearchInput('')\n setQuery('')\n setActiveTag('')\n setActiveCategoryId('*')\n }}\n />\n {chipCategories.map((category) => (\n <Chip\n key={category.id}\n label={category.name}\n size=\"small\"\n variant={\n selectedCategoryId === category.id ? 'filled' : 'outlined'\n }\n color={\n selectedCategoryId === category.id\n ? 'secondary'\n : 'default'\n }\n onClick={() => {\n setSearchInput('')\n setQuery('')\n setActiveTag('')\n setActiveCategoryId((prev) =>\n (prev || pinnedCategoryId) === category.id\n ? '*'\n : category.id,\n )\n }}\n />\n ))}\n </>\n ) : null}\n {showTypeFilter\n ? TYPE_OPTIONS.map((option) => (\n <Chip\n key={option.value}\n label={option.label}\n size=\"small\"\n variant={activeType === option.value ? 'filled' : 'outlined'}\n color={\n activeType === option.value ? 'secondary' : 'default'\n }\n onClick={() =>\n setActiveType((prev) =>\n prev === option.value ? '' : option.value,\n )\n }\n />\n ))\n : null}\n {showSort ? (\n <>\n <Box sx={{ flex: 1 }} />\n <TextField\n select\n size=\"small\"\n value={\n SORT_OPTIONS.some((option) => option.value === sort)\n ? sort\n : 'name'\n }\n onChange={(event) =>\n setSort(event.target.value as ProductGridProps['sort'])\n }\n sx={{ minWidth: 180 }}\n slotProps={{\n htmlInput: { 'aria-label': 'Sort products' },\n }}\n >\n {SORT_OPTIONS.map((option) => (\n <MenuItem key={option.value} value={option.value}>\n {option.label}\n </MenuItem>\n ))}\n </TextField>\n </>\n ) : null}\n </Box>\n ) : null}\n {showSlider ? (\n <Box\n sx={{\n display: 'flex',\n alignItems: 'center',\n gap: 1.5,\n maxWidth: 360,\n px: 1,\n }}\n >\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ whiteSpace: 'nowrap' }}\n >\n {`$${sliderMin}`}\n </Typography>\n <Slider\n size=\"small\"\n value={priceInput ?? [sliderMin, sliderMax]}\n min={sliderMin}\n max={sliderMax}\n step={1}\n disableSwap\n disabled={!hostId}\n onChange={(_event, value) =>\n setPriceInput(Array.isArray(value) ? value : [value, value])\n }\n valueLabelDisplay=\"auto\"\n valueLabelFormat={(value) => `$${value}`}\n getAriaLabel={(index) =>\n index === 0 ? 'Minimum price' : 'Maximum price'\n }\n />\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ whiteSpace: 'nowrap' }}\n >\n {`$${sliderMax}`}\n </Typography>\n </Box>\n ) : null}\n </Box>\n ) : null\n\n const filtering = Boolean(\n query ||\n activeType ||\n selectedCategoryId ||\n activeTag ||\n inStockOnly ||\n priceFilter,\n )\n const emptyCopy = emptyText || 'No products here yet — check back soon.'\n // A store with nothing listed yet (AGL-3676): a headline, a line and one\n // way to hear more — never sample products, never a sort over nothing.\n const emptyPanel = emptyTitle ? (\n <Box\n sx={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n gap: 1.5,\n px: 3,\n py: { xs: 6, md: 10 },\n textAlign: 'center',\n borderRadius: 2,\n bgcolor: 'action.hover',\n }}\n >\n <Typography variant=\"h5\" component=\"h3\">\n {emptyTitle}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\" sx={{ maxWidth: 480 }}>\n {emptyCopy}\n </Typography>\n {emptyActionLabel && emptyActionHref ? (\n <Button variant=\"outlined\" href={emptyActionHref} sx={{ mt: 1 }}>\n {emptyActionLabel}\n </Button>\n ) : null}\n </Box>\n ) : null\n // An empty catalog with nothing asked of it shows its empty state alone:\n // controls are for narrowing products, and there are none to narrow.\n if (visible && visible.length === 0 && (!hasCatalogControls || !filtering)) {\n return emptyPanel ? (\n <Box ref={ref} {...rest} sx={nodeSx}>\n {emptyPanel}\n </Box>\n ) : (\n <Box ref={ref} {...rest} sx={[{ p: 3, textAlign: 'center' }, ...nodeSx]}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {emptyCopy}\n </Typography>\n </Box>\n )\n }\n // With controls visible the empty state renders inline, so a\n // no-result search still leaves the box available to clear.\n const emptyState = (\n <Box sx={{ p: 3, textAlign: 'center' }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {filtering ? 'No products match — try clearing a filter.' : emptyCopy}\n </Typography>\n </Box>\n )\n const loadMore =\n hostId && pageLimit && nextCursor != null ? (\n <Box sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}>\n <Button\n variant=\"outlined\"\n disabled={loadingMore}\n onClick={async () => {\n if (nextCursor == null || loadingMore) return\n setLoadingMore(true)\n await loadPage(nextCursor)\n setLoadingMore(false)\n }}\n >\n {loadingMore ? 'Loading…' : 'Load more'}\n </Button>\n </Box>\n ) : null\n\n const grid = (\n <Box sx={gridSx}>\n {(visible ?? []).map((item) => {\n // AGL-1726: resolved BEFORE the guard, so a stored value that is\n // truthy but resolves to nothing renders the placeholder rather\n // than an `<img>` with no `src`.\n const imageUrl = Aglyn.siteRelativeMediaSrc(item.imageUrl, { hostId })\n return (\n <Card\n key={item.id}\n variant={photoCards ? 'elevation' : 'outlined'}\n elevation={0}\n sx={photoCards ? { bgcolor: 'transparent', overflow: 'visible', minWidth: 0 } : { minWidth: 0 }}\n >\n <CardActionArea\n href={hostId ? `/products/${item.slug}` : undefined}\n disabled={!hostId}\n sx={photoCards ? { borderRadius: 2 } : undefined}\n >\n {imageUrl ? (\n <CardMedia\n component=\"img\"\n image={imageUrl}\n alt={item.name}\n sx={\n photoCards\n ? {\n width: '100%',\n aspectRatio: '4 / 5',\n objectFit: 'cover',\n borderRadius: 2,\n // A storefront's photo answers the pointer (AGL-3676).\n transition: 'opacity 200ms ease',\n '.MuiCardActionArea-root:hover &': { opacity: 0.88 },\n }\n : { height: 160, objectFit: 'cover' }\n }\n // Deferred (AGL-2486), and this is the worst offender of\n // the set: a grid renders one image PER PRODUCT, all of\n // them previously unhinted, so a storefront section below\n // the fold opened N eager default-priority fetches that\n // outranked everything above it.\n {...Aglyn.DEFERRED_IMAGE_ATTRIBUTES}\n />\n ) : (\n // Never a blank tile (AGL-3676 follow-up).\n <ProductImagePlaceholder\n name={item.name}\n sx={\n photoCards\n ? { aspectRatio: '4 / 5', borderRadius: 2 }\n : { height: 160 }\n }\n />\n )}\n <CardContent sx={photoCards ? { px: 0.5, pt: 1.5, pb: 1 } : undefined}>\n <Typography variant={photoCards ? 'subtitle1' : 'subtitle2'} noWrap>\n {item.name}\n </Typography>\n <Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {priceLabel(item)}\n </Typography>\n {item.compareAtPriceUsd ? (\n <Typography\n variant=\"caption\"\n color=\"text.disabled\"\n sx={{ textDecoration: 'line-through' }}\n >\n {`$${item.compareAtPriceUsd}`}\n </Typography>\n ) : null}\n {item.soldOut ? (\n <Chip label=\"Sold out\" size=\"small\" variant=\"outlined\" />\n ) : null}\n </Box>\n </CardContent>\n </CardActionArea>\n {quickAdd ? (\n <QuickAddButton\n item={item}\n inert={!hostId}\n busy={quickAdding === item.id}\n added={quickAdded === item.id}\n photoCards={photoCards}\n onAdd={async () => {\n if (!hostId || !item.defaultVariantId || quickAdding) return\n setQuickAdding(item.id)\n const ok = await addToCart({\n hostId,\n siteFetch,\n productId: item.id,\n variantId: item.defaultVariantId,\n quantity: 1,\n item: {\n item_id: item.id,\n item_name: item.name,\n price: item.priceUsd,\n quantity: 1,\n },\n })\n setQuickAdding('')\n if (ok) setQuickAdded(item.id)\n }}\n />\n ) : null}\n </Card>\n )\n })}\n </Box>\n )\n\n const body = visible && visible.length === 0 ? emptyState : grid\n const unapplied =\n hostId && (refused.length || notices.length) ? (\n <Box sx={{ mb: 2 }}>\n <ListQueryNotices\n refused={listQueryRefusals(refused, {\n fields: STOREFRONT_CATALOG_DECLARATION.fields,\n headers: STOREFRONT_CATALOG_HEADERS,\n options: {\n ...STOREFRONT_CATALOG_OPTIONS,\n category: categories.map((category) => ({\n value: category.id,\n label: category.name,\n })),\n },\n })}\n notices={notices}\n />\n </Box>\n ) : null\n if (!showFilters) {\n return (\n <Box ref={ref} {...rest}>\n {controls}\n {unapplied}\n {body}\n {loadMore}\n </Box>\n )\n }\n return (\n <Box ref={ref} {...rest}>\n {controls}\n <Box\n sx={{\n display: 'flex',\n gap: 1,\n flexWrap: 'wrap',\n alignItems: 'center',\n mb: 2,\n }}\n >\n <Chip\n label=\"In stock\"\n size=\"small\"\n variant={inStockOnly ? 'filled' : 'outlined'}\n color={inStockOnly ? 'primary' : 'default'}\n onClick={() => setInStockOnly((prev) => !prev)}\n />\n {facetTags.map((facetTag) => (\n <Chip\n key={facetTag}\n label={facetTag}\n size=\"small\"\n variant={activeTag === facetTag ? 'filled' : 'outlined'}\n color={activeTag === facetTag ? 'primary' : 'default'}\n onClick={() => {\n // The same one-array-clause rule as the category chips.\n setSearchInput('')\n setQuery('')\n setActiveCategoryId((prev) => (prev && prev !== '*' ? '' : prev))\n setActiveTag((prev) => (prev === facetTag ? '' : facetTag))\n }}\n />\n ))}\n <Box sx={{ flex: 1 }} />\n <Chip\n label={\n sort === 'price-asc'\n ? 'Price ↑'\n : sort === 'price-desc'\n ? 'Price ↓'\n : 'A–Z'\n }\n size=\"small\"\n variant=\"outlined\"\n onClick={() =>\n setSort((prev) =>\n prev === 'price-asc'\n ? 'price-desc'\n : prev === 'price-desc'\n ? 'name'\n : 'price-asc',\n )\n }\n />\n </Box>\n {unapplied}\n {body}\n {loadMore}\n </Box>\n )\n },\n)\n/**\n * A card's quick add: outside the card's link, so a click never follows it.\n * Sample cards on the canvas show the button inert.\n */\nfunction QuickAddButton({\n item,\n inert,\n busy,\n added,\n photoCards,\n onAdd,\n}: {\n item: CatalogItem\n inert: boolean\n busy: boolean\n added: boolean\n photoCards: boolean\n onAdd: () => void\n}) {\n const action = inert ? 'add' : quickAddAction(item)\n if (!action) return null\n const sx = photoCards ? { mt: 0.5, mx: 0.5, mb: 1 } : { mx: 2, mb: 2 }\n if (action === 'options') {\n return (\n <Box sx={sx}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n fullWidth\n href={`/products/${item.slug}`}\n onClick={(event) => event.stopPropagation()}\n >\n {'Choose options'}\n </Button>\n </Box>\n )\n }\n return (\n <Box sx={sx}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n fullWidth\n disabled={inert || busy}\n aria-label={`Add ${item.name} to cart`}\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n onAdd()\n }}\n >\n {added ? 'Added ✓' : busy ? 'Adding…' : 'Add to cart'}\n </Button>\n </Box>\n )\n}\n\nProductGrid.displayName = 'AglynProductGrid'\n\nexport const schema: Aglyn.ComponentSchema<ProductGridProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Product grid',\n description:\n 'Your catalog as a grid, with optional search, category chips, sort and paging.',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiViewGridOutline.path, sx: { color: '#2e7d32' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'source',\n label: 'Show',\n description: 'Which products to list.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { label: 'All products', value: 'all' },\n { label: 'A collection', value: 'collection' },\n { label: 'A category', value: 'category' },\n { label: 'A tag', value: 'tag' },\n ],\n },\n {\n name: 'collectionId',\n label: 'Collection',\n description:\n 'Pick the collection — stored by id, so renaming it never breaks ' +\n 'this grid. Leave empty on collection template pages to follow ' +\n 'the /collections/{slug} URL.',\n component: Aglyn.FieldComponentType.COLLECTION_SELECT,\n },\n {\n name: 'collectionSlug',\n label: 'Collection slug (legacy)',\n description: 'Used only when no collection is picked above.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'categoryId',\n label: 'Category',\n description:\n 'Pick the category — stored by id, rename-safe.',\n component: Aglyn.FieldComponentType.CATEGORY_SELECT,\n },\n {\n name: 'categorySlug',\n label: 'Category slug (legacy)',\n description: 'Used only when no category is picked above.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'tag',\n label: 'Tag',\n description: 'Products tagged with this value.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'sort',\n label: 'Sort',\n description: 'Listing order.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { label: 'Name', value: 'name' },\n { label: 'Price (low → high)', value: 'price-asc' },\n { label: 'Price (high → low)', value: 'price-desc' },\n { label: 'Newest', value: 'newest' },\n ],\n },\n {\n name: 'columns',\n label: 'Columns',\n description: 'Desktop columns (1-6).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'maxItems',\n label: 'Max items',\n description: 'Cap the number of cards.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'emptyText',\n label: 'Empty text',\n description: 'Copy when nothing matches.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'emptyTitle',\n label: 'Empty headline',\n description:\n 'Headline while the store lists nothing yet; set, the empty state ' +\n 'is a full panel with the empty text and one button.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'emptyActionLabel',\n label: 'Empty button label',\n description: 'The empty panel’s button, e.g. “Get in touch”.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'emptyActionHref',\n label: 'Empty button link',\n description: 'Where the empty panel’s button goes, e.g. /contact.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'showFilters',\n label: 'Show filters',\n description: 'Tag chips, in-stock toggle, and price sort above the grid.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n {\n name: 'showSearch',\n label: 'Search box',\n description:\n 'Visitor search box above the grid — matches words at the start of ' +\n 'product names across the whole catalog (server-side).',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n {\n name: 'showCategories',\n label: 'Category chips',\n description:\n 'Filter chips built from your product categories, with an All chip.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n {\n name: 'showSort',\n label: 'Sort select',\n description:\n 'Visitor sort: newest, name, price low→high, price high→low.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n {\n name: 'showTypeFilter',\n label: 'Type filter',\n description: 'Physical / digital / services chips.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n {\n name: 'showPriceFilter',\n label: 'Price filter',\n description:\n 'Price range slider between your lowest and highest product prices.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n {\n name: 'pageSize',\n label: 'Page size',\n description:\n 'Products per page with a Load more button; blank loads once ' +\n '(capped by Max items).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'cardStyle',\n label: 'Card style',\n description: 'Outlined cards, or tall photos with the name and price under them.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { label: 'Outlined', value: 'outlined' },\n { label: 'Photo', value: 'photo' },\n ],\n },\n {\n // Off by default, so no existing grid changes shape on deploy.\n name: 'quickAdd',\n label: 'Quick add to cart',\n description:\n 'An Add to cart button on each card for a product with one variant; ' +\n '\"Choose options\" for one with several.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Product grid',\n pluginId: BUNDLE_ID,\n description: 'Responsive grid of catalog products',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiViewGridOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: { source: 'all', sort: 'name', columns: 3 },\n },\n },\n {\n // Full shop page (AGL-561): the grid with every catalog control on\n // — search, category chips, sort, and Load more paging.\n $id: generatePresetId(ID, 'shop'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Shop catalog',\n pluginId: BUNDLE_ID,\n description: 'Product grid with search, category chips, sort, and paging',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiViewGridOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {\n source: 'all',\n sort: 'newest',\n columns: 3,\n showSearch: true,\n showCategories: true,\n showSort: true,\n pageSize: 12,\n },\n },\n },\n]\n\nexport default ProductGrid\n"],"names":["Aglyn","mdiMagnify","mdiViewGridOutline","ListQueryNotices","listQueryRefusals","Box","Button","Card","CardActionArea","CardContent","CardMedia","Chip","InputAdornment","MenuItem","Skeleton","Slider","SvgIcon","TextField","Typography","forwardRef","useCallback","useEffect","useRef","useState","BUNDLE_ID","STOREFRONT_CATALOG_DECLARATION","STOREFRONT_CATALOG_HEADERS","STOREFRONT_CATALOG_OPTIONS","generatePresetId","addToCart","ProductImagePlaceholder","ID","quickAddAction","item","priceComingSoon","soldOut","defaultVariantId","variantCount","browseParamsFromSearch","search","params","URLSearchParams","categorySlug","get","trim","tag","PRICE_COMING_SOON_LABEL","SAMPLE_ITEMS","id","name","slug","priceUsd","maxPriceUsd","compareAtPriceUsd","SAMPLE_CATEGORIES","SAMPLE_PRICE_BOUNDS","minCents","maxCents","TYPE_OPTIONS","value","label","SORT_OPTIONS","collectionSlugFromLocation","window","match","location","pathname","decodeURIComponent","priceLabel","ProductGrid","props","ref","site","source","collectionId","collectionSlug","collectionSlugProp","categoryId","sort","sortProp","columns","maxItems","emptyText","emptyTitle","emptyActionLabel","emptyActionHref","showFilters","showSearch","showCategories","showSort","showTypeFilter","showPriceFilter","pageSize","cardStyle","quickAdd","rest","photoCards","nodeSx","Array","isArray","useSite","siteFetch","useSiteFetch","hostId","quickAdding","setQuickAdding","quickAdded","setQuickAdded","nodeId","useNodeId","seed","pageData","commerce","grids","items","setItems","setSort","inStockOnly","setInStockOnly","activeTag","setActiveTag","searchInput","setSearchInput","query","setQuery","activeCategoryId","setActiveCategoryId","activeType","setActiveType","categories","setCategories","priceBounds","setPriceBounds","priceInput","setPriceInput","priceFilter","setPriceFilter","nextCursor","setNextCursor","refused","setRefused","notices","setNotices","loadingMore","setLoadingMore","fetchSeq","pageLimit","Number","Math","floor","pinnedCategoryId","urlCategorySlug","setUrlCategorySlug","asked","length","find","category","timer","setTimeout","clearTimeout","loadPage","after","seq","current","set","effectiveCategoryId","effectiveTag","String","limit","response","fetch","toString","payload","json","pageItems","prev","bounds","seededRef","Boolean","desktopColumns","min","max","gridSx","display","gap","gridTemplateColumns","xs","sm","md","visible","facetTags","Set","flatMap","tags","slice","sx","from","_item","index","variant","height","width","chipCategories","selectedCategoryId","hasCatalogControls","sliderBounds","sliderMin","sliderMax","ceil","showSlider","controls","flexDirection","mb","onChange","event","target","placeholder","size","fullWidth","slotProps","input","startAdornment","position","fontSize","color","path","d","htmlInput","type","flexWrap","alignItems","onClick","map","option","flex","select","some","minWidth","maxWidth","px","whiteSpace","step","disableSwap","disabled","_event","valueLabelDisplay","valueLabelFormat","getAriaLabel","filtering","emptyCopy","emptyPanel","py","textAlign","borderRadius","bgcolor","component","href","mt","p","emptyState","loadMore","justifyContent","grid","imageUrl","siteRelativeMediaSrc","elevation","overflow","undefined","image","alt","aspectRatio","objectFit","transition","opacity","DEFERRED_IMAGE_ATTRIBUTES","pt","pb","noWrap","textDecoration","QuickAddButton","inert","busy","added","onAdd","ok","productId","variantId","quantity","item_id","item_name","price","body","unapplied","fields","headers","options","facetTag","action","mx","stopPropagation","aria-label","preventDefault","displayName","schema","$id","pluginId","description","ComponentCategory","COMMERCE","icon","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","FieldComponentType","SELECT","COLLECTION_SELECT","TEXT_FIELD","CATEGORY_SELECT","CHECKBOX","presets","NodeType","PRESET","data","componentId"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/product-grid.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiMagnify, mdiViewGridOutline } from '@aglyn/shared-data-mdi'\nimport {\n ListQueryNotices,\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport type { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport Card from '@mui/material/Card'\nimport CardActionArea from '@mui/material/CardActionArea'\nimport CardContent from '@mui/material/CardContent'\nimport CardMedia from '@mui/material/CardMedia'\nimport Chip from '@mui/material/Chip'\nimport InputAdornment from '@mui/material/InputAdornment'\nimport MenuItem from '@mui/material/MenuItem'\nimport Skeleton from '@mui/material/Skeleton'\nimport Slider from '@mui/material/Slider'\nimport SvgIcon from '@mui/material/SvgIcon'\nimport TextField from '@mui/material/TextField'\nimport Typography from '@mui/material/Typography'\nimport { forwardRef, useCallback, useEffect, useRef, useState } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport {\n STOREFRONT_CATALOG_DECLARATION,\n STOREFRONT_CATALOG_HEADERS,\n STOREFRONT_CATALOG_OPTIONS,\n} from '../constants/storefront-catalog-query'\nimport { generatePresetId } from '../utils/generate-preset-id'\nimport { addToCart } from '../utils/add-to-cart'\nimport { ProductImagePlaceholder } from './product-image-placeholder'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'product-grid'\n\nexport interface ProductGridProps {\n /** Filter source; 'all' lists the whole active catalog. */\n source?: 'all' | 'collection' | 'category' | 'tag'\n /** Collection id when source = collection (rename-safe, AGL-343). */\n collectionId?: string\n /** Legacy collection slug; used only when no collectionId is set.\n * Blank follows the /collections/{slug} URL (AGL-298). */\n collectionSlug?: string\n /** Faceted controls above the grid: tags, availability, sort. */\n showFilters?: boolean\n /** Debounced visitor search box above the grid (server-side, AGL-561). */\n showSearch?: boolean\n /** Category filter chips from the host's categories (AGL-561). */\n showCategories?: boolean\n /** Sort select: newest, name, price low→high / high→low (AGL-561). */\n showSort?: boolean\n /** Physical / digital / services filter chips (AGL-561). */\n showTypeFilter?: boolean\n /** Price range slider bounded by the catalog's prices (AGL-564). */\n showPriceFilter?: boolean\n /** Products per page; shows a Load more button while more remain. */\n pageSize?: number\n /** Category id when source = category (rename-safe, AGL-343). */\n categoryId?: string\n /** Legacy category slug; used only when no categoryId is set. */\n categorySlug?: string\n /** Tag when source = tag. */\n tag?: string\n sort?: 'name' | 'price-asc' | 'price-desc' | 'newest'\n /** Grid columns on desktop (1-6); mobile always stacks to 2/1. */\n columns?: number\n maxItems?: number\n /** Empty-state copy when nothing matches. */\n emptyText?: string\n /**\n * The empty state's headline (AGL-3676). Set, a catalog with nothing in it\n * yet opens as a full panel — the headline, `emptyText` and one action —\n * the way a designer store template opens before its first products; the\n * sort and filter controls wait for products to sort.\n */\n emptyTitle?: string\n /** The empty panel's one action: its label… */\n emptyActionLabel?: string\n /** …and where it goes, a path on the site (\"/contact\"). */\n emptyActionHref?: string\n /**\n * How each product is drawn (AGL-3676): `outlined`, the default, a card\n * with a short photo strip; `photo`, the photo first and tall, with the\n * name and price under it and no card around it — the way a storefront\n * leads with what it sells.\n */\n cardStyle?: 'outlined' | 'photo'\n /**\n * A compact button under each card: \"Add to cart\" for a product that sells\n * as one priced variant in stock, \"Choose options\" (to its page) for one\n * with several variants, and nothing for a product with no price yet or\n * sold out. Off by default.\n */\n quickAdd?: boolean\n}\n\ninterface CatalogItem {\n id: string\n name: string\n slug: string\n priceUsd: number\n maxPriceUsd: number\n compareAtPriceUsd?: number\n imageUrl?: string\n soldOut: boolean\n tags?: string[]\n /** Listed before it has a price (AGL-3676): no price to print yet. */\n priceComingSoon?: boolean\n /** How many variants it sells as (absent from an older payload). */\n variantCount?: number\n /** The one variant a quick add puts in the cart. */\n defaultVariantId?: string\n /** Its page asks a required add-on first (AGL-4100). */\n requiresChoices?: boolean\n}\n\n/**\n * What a card's quick add offers for one product. A product whose page asks\n * a required add-on (AGL-4100) sends the shopper there to choose it, even\n * with one variant: the cart refuses the line without the choice.\n */\nexport function quickAddAction(\n item: Pick<\n CatalogItem,\n 'priceComingSoon' | 'soldOut' | 'variantCount' | 'defaultVariantId' | 'requiresChoices'\n >,\n): 'add' | 'options' | null {\n if (item.priceComingSoon || item.soldOut) return null\n if (item.requiresChoices) return 'options'\n if (item.defaultVariantId) return 'add'\n if ((item.variantCount ?? 0) > 1) return 'options'\n return null\n}\n\n/**\n * The chip and tag a browsing grid opens on, from the page's address:\n * `?category=<slug>` and `?tag=<tag>`. Read after hydration only — the\n * server renders the unfiltered first page and has no address to read.\n */\nexport function browseParamsFromSearch(search: string): { categorySlug: string; tag: string } {\n const params = new URLSearchParams(search)\n return {\n categorySlug: (params.get('category') ?? '').trim(),\n tag: (params.get('tag') ?? '').trim(),\n }\n}\n\n/** What a card says where a product listed before it has a price would show one (AGL-3676). */\nconst PRICE_COMING_SOON_LABEL = 'Price coming soon'\n\ninterface CatalogCategory {\n id: string\n name: string\n slug: string\n}\n\n/** Price facet from the catalog handler, in cents (AGL-564). */\ninterface CatalogPriceBounds {\n minCents: number\n maxCents: number\n}\n\n/**\n * A control the catalog query could not apply, and why (AGL-3321): the\n * plan's refusal as the handler returns it, named here by\n * `listQueryRefusals` in the words of this grid's controls.\n */\ninterface CatalogRefusal {\n clause: ListFilterClause | 'search'\n reason: string\n}\n\n/**\n * This grid's first page, resolved server-side by the commerce site-page\n * enricher (AGL-659). Same shape the catalog endpoint returns, because both\n * go through `queryPublicCatalog`.\n */\ninterface CatalogSeed {\n items?: CatalogItem[]\n categories?: CatalogCategory[]\n priceBounds?: CatalogPriceBounds\n nextCursor?: string\n refused?: CatalogRefusal[]\n notices?: string[]\n}\n\nconst SAMPLE_ITEMS: CatalogItem[] = [\n { id: 's1', name: 'Sample product', slug: '#', priceUsd: 29, maxPriceUsd: 29, soldOut: false },\n { id: 's2', name: 'Another product', slug: '#', priceUsd: 49, maxPriceUsd: 59, compareAtPriceUsd: 79, soldOut: false },\n { id: 's3', name: 'Third product', slug: '#', priceUsd: 12, maxPriceUsd: 12, soldOut: true },\n]\n\n/** Inert chips shown on the besigner canvas when categories are on. */\nconst SAMPLE_CATEGORIES: CatalogCategory[] = [\n { id: 'sc1', name: 'Apparel', slug: 'apparel' },\n { id: 'sc2', name: 'Accessories', slug: 'accessories' },\n]\n\n/** Inert slider bounds on the canvas, matching the sample cards. */\nconst SAMPLE_PRICE_BOUNDS: CatalogPriceBounds = {\n minCents: 1200,\n maxCents: 4900,\n}\n\nconst TYPE_OPTIONS = [\n { value: 'physical', label: 'Physical' },\n { value: 'digital', label: 'Digital' },\n { value: 'service', label: 'Services' },\n] as const\n\nconst SORT_OPTIONS = [\n { value: 'newest', label: 'Newest' },\n { value: 'name', label: 'Name' },\n { value: 'price-asc', label: 'Price: low to high' },\n { value: 'price-desc', label: 'Price: high to low' },\n] as const\n\nfunction collectionSlugFromLocation(): string {\n if (typeof window === 'undefined') return ''\n const match = window.location.pathname.match(/\\/collections\\/([^/?#]+)/)\n return match ? decodeURIComponent(match[1]) : ''\n}\n\nfunction priceLabel(item: CatalogItem): string {\n if (item.priceComingSoon) return PRICE_COMING_SOON_LABEL\n return item.priceUsd === item.maxPriceUsd\n ? `$${item.priceUsd}`\n : `From $${item.priceUsd}`\n}\n\n/**\n * Product grid / PLP block (AGL-291): active catalog items from the\n * public listing API, filterable by collection/category/tag, designable\n * in besigner with sample cards (no SiteContext = inert canvas, same\n * convention as the mui Product block). Cards navigate to\n * `/products/{slug}` (PDP routes, AGL-292). Catalog UX (AGL-561):\n * optional debounced search, category chips, type chips, a sort\n * select, and Load more paging — all resolved server-side by the\n * catalog handler so large catalogs stay cheap.\n *\n * Nothing is narrowed here (AGL-3321): every control is a parameter of the\n * catalog query, and the rows shown are the rows it returned. A control the\n * query could not combine with another comes back in `refused` and is said\n * above the grid, rather than applied to the cards that happen to be loaded.\n */\nconst ProductGrid = forwardRef<HTMLDivElement, ProductGridProps>(\n (props, ref) => {\n const {\n source,\n collectionId,\n collectionSlug: collectionSlugProp,\n categoryId,\n categorySlug,\n tag,\n sort: sortProp,\n columns,\n maxItems,\n emptyText,\n emptyTitle,\n emptyActionLabel,\n emptyActionHref,\n showFilters,\n showSearch,\n showCategories,\n showSort,\n showTypeFilter,\n showPriceFilter,\n pageSize,\n cardStyle,\n quickAdd,\n ...rest\n } = props\n const photoCards = cardStyle === 'photo'\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const site = Aglyn.useSite()\n const siteFetch = Aglyn.useSiteFetch()\n const { hostId } = site\n // Quick add (AGL-3676 follow-up): which card is adding, which just did.\n const [quickAdding, setQuickAdding] = useState('')\n const [quickAdded, setQuickAdded] = useState('')\n // Server-rendered first page (AGL-659). The site-page enricher ran this\n // grid's own query on the server and keyed the result by node id — two\n // grids on a page have different queries, so an unkeyed seed would put\n // the wrong products in one of them. Absent in the besigner and preview,\n // where there is no pageData at all and the effect below still fetches.\n const nodeId = Aglyn.useNodeId()\n const seed = (\n site.pageData as\n | { commerce?: { grids?: Record<string, CatalogSeed> } }\n | undefined\n )?.commerce?.grids?.[nodeId]\n\n const [items, setItems] = useState<CatalogItem[] | null>(\n seed?.items ?? null,\n )\n const [sort, setSort] = useState(sortProp)\n // The In stock chip, asked of the catalog (`inStock=1`), never matched\n // over the cards on screen (AGL-3321).\n const [inStockOnly, setInStockOnly] = useState(false)\n const [activeTag, setActiveTag] = useState('')\n // Catalog UX state (AGL-561): the search box debounces into\n // `query`; a chip pick overrides any pinned category ('*' = All);\n // paging appends via the handler's `nextCursor` (AGL-3321).\n const [searchInput, setSearchInput] = useState('')\n const [query, setQuery] = useState('')\n const [activeCategoryId, setActiveCategoryId] = useState('')\n const [activeType, setActiveType] = useState('')\n const [categories, setCategories] = useState<CatalogCategory[]>(\n seed?.categories ?? [],\n )\n // Price filter (AGL-564): the slider drags into `priceInput` (whole\n // dollars) and debounces into `priceFilter`, which drives the query;\n // null = untouched (no price params sent). Bounds come from the\n // handler's price facets.\n const [priceBounds, setPriceBounds] = useState<CatalogPriceBounds | null>(\n seed?.priceBounds ?? null,\n )\n const [priceInput, setPriceInput] = useState<number[] | null>(null)\n const [priceFilter, setPriceFilter] = useState<number[] | null>(null)\n const [nextCursor, setNextCursor] = useState<string | null>(\n seed?.nextCursor ?? null,\n )\n // What the query could not apply, said above the grid (AGL-3321).\n const [refused, setRefused] = useState<CatalogRefusal[]>(seed?.refused ?? [])\n const [notices, setNotices] = useState<string[]>(seed?.notices ?? [])\n const [loadingMore, setLoadingMore] = useState(false)\n const fetchSeq = useRef(0)\n const collectionSlug =\n source === 'collection'\n ? collectionSlugProp || collectionSlugFromLocation()\n : collectionSlugProp\n const pageLimit =\n pageSize && Number(pageSize) > 0 ? Math.floor(Number(pageSize)) : 0\n const pinnedCategoryId = source === 'category' ? categoryId ?? '' : ''\n\n // A browsing grid opens on the address's `?category=` / `?tag=`: a shop\n // page linked as /shop?category=candles shows that chip picked. Read in\n // an effect, so the server and the first client render agree.\n const [urlCategorySlug, setUrlCategorySlug] = useState('')\n useEffect(() => {\n if (!hostId || typeof window === 'undefined') return\n const asked = browseParamsFromSearch(window.location.search)\n if (showCategories && asked.categorySlug) setUrlCategorySlug(asked.categorySlug)\n if (showFilters && asked.tag) setActiveTag(asked.tag)\n // Once, on arrival: later chip picks are the visitor's.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hostId])\n useEffect(() => {\n if (!urlCategorySlug || !categories.length) return\n const match = categories.find((category) => category.slug === urlCategorySlug)\n if (match) setActiveCategoryId(match.id)\n setUrlCategorySlug('')\n }, [urlCategorySlug, categories])\n\n // Debounce keystrokes so large catalogs aren't fetched per letter.\n useEffect(() => {\n const timer = setTimeout(() => setQuery(searchInput.trim()), 300)\n return () => clearTimeout(timer)\n }, [searchInput])\n\n // Debounce slider drags the same way (AGL-564): one fetch per\n // settled range, not one per pixel of thumb movement.\n useEffect(() => {\n const timer = setTimeout(() => setPriceFilter(priceInput), 300)\n return () => clearTimeout(timer)\n }, [priceInput])\n\n const loadPage = useCallback(\n async (after?: string): Promise<void> => {\n if (!hostId) return\n const seq = ++fetchSeq.current\n const params = new URLSearchParams({ hostId })\n // Ids first (rename-safe, AGL-343); slugs remain as the legacy path.\n if (source === 'collection') {\n if (collectionId) params.set('collectionId', collectionId)\n else if (collectionSlug) params.set('collection', collectionSlug)\n }\n const effectiveCategoryId =\n activeCategoryId === '*' ? '' : activeCategoryId || pinnedCategoryId\n if (effectiveCategoryId) {\n params.set('categoryId', effectiveCategoryId)\n } else if (!activeCategoryId && source === 'category' && categorySlug) {\n params.set('category', categorySlug)\n }\n // A tag chip overrides a pinned tag, as a category chip does.\n const effectiveTag = activeTag || (source === 'tag' ? tag ?? '' : '')\n if (effectiveTag) params.set('tag', effectiveTag)\n if (query) params.set('q', query)\n if (activeType) params.set('type', activeType)\n if (inStockOnly) params.set('inStock', '1')\n // Slider dollars → API cents (AGL-564).\n if (priceFilter) {\n params.set('minPriceCents', String(priceFilter[0] * 100))\n params.set('maxPriceCents', String(priceFilter[1] * 100))\n }\n if (sort) params.set('sort', sort)\n const limit = pageLimit || maxItems\n if (limit) params.set('limit', String(limit))\n if (after) params.set('after', after)\n if ((showCategories || showPriceFilter) && !after) {\n params.set('facets', '1')\n }\n try {\n const response = await fetch(\n `/api/commerce/catalog?${params.toString()}`,\n )\n const payload = await response.json()\n if (seq !== fetchSeq.current) return\n const pageItems: CatalogItem[] = payload?.items ?? []\n setItems((prev) =>\n after && prev ? [...prev, ...pageItems] : pageItems,\n )\n setNextCursor(\n typeof payload?.nextCursor === 'string' ? payload.nextCursor : null,\n )\n if (!after) {\n setRefused(Array.isArray(payload?.refused) ? payload.refused : [])\n setNotices(Array.isArray(payload?.notices) ? payload.notices : [])\n }\n if (Array.isArray(payload?.categories)) {\n setCategories(payload.categories)\n }\n const bounds = payload?.priceBounds\n if (\n typeof bounds?.minCents === 'number' &&\n typeof bounds?.maxCents === 'number'\n ) {\n setPriceBounds(bounds)\n }\n } catch {\n if (seq !== fetchSeq.current) return\n if (!after) setItems([])\n setNextCursor(null)\n }\n },\n [\n hostId,\n source,\n collectionId,\n collectionSlug,\n categorySlug,\n tag,\n sort,\n maxItems,\n query,\n activeCategoryId,\n activeTag,\n activeType,\n inStockOnly,\n priceFilter,\n pageLimit,\n pinnedCategoryId,\n showCategories,\n showPriceFilter,\n ],\n )\n\n // A seeded grid already holds exactly what `loadPage(0)` would return,\n // so the first pass skips the fetch (AGL-659) — every later run, after\n // a filter or sort change, goes to the API as before.\n const seededRef = useRef(Boolean(seed))\n useEffect(() => {\n if (!hostId) return\n if (seededRef.current) {\n seededRef.current = false\n return\n }\n void loadPage()\n // Invalidate any in-flight request on dep change or unmount.\n return () => {\n fetchSeq.current++\n }\n }, [hostId, loadPage])\n\n const desktopColumns = Math.min(6, Math.max(1, columns ?? 3))\n const gridSx = {\n display: 'grid',\n gap: 2,\n gridTemplateColumns: {\n // Photo cards pair up on a phone, as a shop's catalog does; an\n // outlined card with its photo strip takes the row.\n xs: photoCards ? 'repeat(2, minmax(0, 1fr))' : 'repeat(1, minmax(0, 1fr))',\n sm: 'repeat(2, minmax(0, 1fr))',\n md: `repeat(${desktopColumns}, minmax(0, 1fr))`,\n },\n }\n // The rows the query returned, as returned (AGL-3321).\n const visible = hostId ? items : SAMPLE_ITEMS\n // Tag chips: the tags on the cards showing, as CHOICES — picking one asks\n // the catalog for that tag, never narrows these cards. The picked one\n // stays offered whatever the page it brought back carries.\n const facetTags = showFilters\n ? [\n ...new Set([\n ...(activeTag ? [activeTag] : []),\n ...(items ?? []).flatMap((item) => item.tags ?? []),\n ]),\n ].slice(0, 12)\n : []\n\n if (hostId && items === null) {\n return (\n // MERGE, never replace (AGL-1450). The skeleton branch replaced the\n // node's sx outright, so an authored grid flashed unstyled for as\n // long as the first fetch took and then snapped into place.\n <Box ref={ref} {...rest} sx={[gridSx, ...nodeSx]}>\n {Array.from({ length: desktopColumns }, (_item, index) => (\n <Card key={index} variant=\"outlined\">\n <Skeleton variant=\"rectangular\" height={160} />\n <CardContent>\n <Skeleton width=\"70%\" />\n <Skeleton width=\"30%\" />\n </CardContent>\n </Card>\n ))}\n </Box>\n )\n }\n\n // Catalog controls (AGL-561): search on top, category/type chips\n // under it with the sort select right-aligned — the layout every\n // mainstream commerce PLP uses. Server-side; sample data inert.\n const chipCategories = categories.length\n ? categories\n : !hostId && showCategories\n ? SAMPLE_CATEGORIES\n : []\n const selectedCategoryId =\n activeCategoryId === '*' ? '' : activeCategoryId || pinnedCategoryId\n const hasCatalogControls = Boolean(\n showSearch || showCategories || showSort || showTypeFilter ||\n showPriceFilter,\n )\n // Price slider bounds (AGL-564), widened to whole dollars so both\n // ends stay reachable; sample bounds keep the canvas designable.\n const sliderBounds = hostId ? priceBounds : SAMPLE_PRICE_BOUNDS\n const sliderMin = sliderBounds ? Math.floor(sliderBounds.minCents / 100) : 0\n const sliderMax = sliderBounds ? Math.ceil(sliderBounds.maxCents / 100) : 0\n const showSlider = Boolean(showPriceFilter && sliderMin < sliderMax)\n const controls = hasCatalogControls ? (\n <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, mb: 2 }}>\n {showSearch ? (\n <TextField\n value={searchInput}\n onChange={(event) => setSearchInput(event.target.value)}\n placeholder=\"Search products…\"\n size=\"small\"\n fullWidth\n slotProps={{\n input: {\n startAdornment: (\n <InputAdornment position=\"start\">\n <SvgIcon fontSize=\"small\" color=\"disabled\">\n <path d={mdiMagnify.path} />\n </SvgIcon>\n </InputAdornment>\n ),\n },\n htmlInput: { 'aria-label': 'Search products', type: 'search' },\n }}\n />\n ) : null}\n {showCategories || showTypeFilter || showSort ? (\n <Box\n sx={{\n display: 'flex',\n gap: 1,\n flexWrap: 'wrap',\n alignItems: 'center',\n }}\n >\n {showCategories && chipCategories.length ? (\n <>\n <Chip\n label=\"All\"\n size=\"small\"\n variant={selectedCategoryId ? 'outlined' : 'filled'}\n color={selectedCategoryId ? 'default' : 'primary'}\n onClick={() => {\n // One array clause per query (AGL-3321): a chip asked\n // for while searching would be refused, so picking one\n // clears the search — and a tag chip — rather than\n // leaving a chip that does nothing.\n setSearchInput('')\n setQuery('')\n setActiveTag('')\n setActiveCategoryId('*')\n }}\n />\n {chipCategories.map((category) => (\n <Chip\n key={category.id}\n label={category.name}\n size=\"small\"\n variant={\n selectedCategoryId === category.id ? 'filled' : 'outlined'\n }\n color={\n selectedCategoryId === category.id\n ? 'secondary'\n : 'default'\n }\n onClick={() => {\n setSearchInput('')\n setQuery('')\n setActiveTag('')\n setActiveCategoryId((prev) =>\n (prev || pinnedCategoryId) === category.id\n ? '*'\n : category.id,\n )\n }}\n />\n ))}\n </>\n ) : null}\n {showTypeFilter\n ? TYPE_OPTIONS.map((option) => (\n <Chip\n key={option.value}\n label={option.label}\n size=\"small\"\n variant={activeType === option.value ? 'filled' : 'outlined'}\n color={\n activeType === option.value ? 'secondary' : 'default'\n }\n onClick={() =>\n setActiveType((prev) =>\n prev === option.value ? '' : option.value,\n )\n }\n />\n ))\n : null}\n {showSort ? (\n <>\n <Box sx={{ flex: 1 }} />\n <TextField\n select\n size=\"small\"\n value={\n SORT_OPTIONS.some((option) => option.value === sort)\n ? sort\n : 'name'\n }\n onChange={(event) =>\n setSort(event.target.value as ProductGridProps['sort'])\n }\n sx={{ minWidth: 180 }}\n slotProps={{\n htmlInput: { 'aria-label': 'Sort products' },\n }}\n >\n {SORT_OPTIONS.map((option) => (\n <MenuItem key={option.value} value={option.value}>\n {option.label}\n </MenuItem>\n ))}\n </TextField>\n </>\n ) : null}\n </Box>\n ) : null}\n {showSlider ? (\n <Box\n sx={{\n display: 'flex',\n alignItems: 'center',\n gap: 1.5,\n maxWidth: 360,\n px: 1,\n }}\n >\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ whiteSpace: 'nowrap' }}\n >\n {`$${sliderMin}`}\n </Typography>\n <Slider\n size=\"small\"\n value={priceInput ?? [sliderMin, sliderMax]}\n min={sliderMin}\n max={sliderMax}\n step={1}\n disableSwap\n disabled={!hostId}\n onChange={(_event, value) =>\n setPriceInput(Array.isArray(value) ? value : [value, value])\n }\n valueLabelDisplay=\"auto\"\n valueLabelFormat={(value) => `$${value}`}\n getAriaLabel={(index) =>\n index === 0 ? 'Minimum price' : 'Maximum price'\n }\n />\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ whiteSpace: 'nowrap' }}\n >\n {`$${sliderMax}`}\n </Typography>\n </Box>\n ) : null}\n </Box>\n ) : null\n\n const filtering = Boolean(\n query ||\n activeType ||\n selectedCategoryId ||\n activeTag ||\n inStockOnly ||\n priceFilter,\n )\n const emptyCopy = emptyText || 'No products here yet — check back soon.'\n // A store with nothing listed yet (AGL-3676): a headline, a line and one\n // way to hear more — never sample products, never a sort over nothing.\n const emptyPanel = emptyTitle ? (\n <Box\n sx={{\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n gap: 1.5,\n px: 3,\n py: { xs: 6, md: 10 },\n textAlign: 'center',\n borderRadius: 2,\n bgcolor: 'action.hover',\n }}\n >\n <Typography variant=\"h5\" component=\"h3\">\n {emptyTitle}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\" sx={{ maxWidth: 480 }}>\n {emptyCopy}\n </Typography>\n {emptyActionLabel && emptyActionHref ? (\n <Button variant=\"outlined\" href={emptyActionHref} sx={{ mt: 1 }}>\n {emptyActionLabel}\n </Button>\n ) : null}\n </Box>\n ) : null\n // An empty catalog with nothing asked of it shows its empty state alone:\n // controls are for narrowing products, and there are none to narrow.\n if (visible && visible.length === 0 && (!hasCatalogControls || !filtering)) {\n return emptyPanel ? (\n <Box ref={ref} {...rest} sx={nodeSx}>\n {emptyPanel}\n </Box>\n ) : (\n <Box ref={ref} {...rest} sx={[{ p: 3, textAlign: 'center' }, ...nodeSx]}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {emptyCopy}\n </Typography>\n </Box>\n )\n }\n // With controls visible the empty state renders inline, so a\n // no-result search still leaves the box available to clear.\n const emptyState = (\n <Box sx={{ p: 3, textAlign: 'center' }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {filtering ? 'No products match — try clearing a filter.' : emptyCopy}\n </Typography>\n </Box>\n )\n const loadMore =\n hostId && pageLimit && nextCursor != null ? (\n <Box sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}>\n <Button\n variant=\"outlined\"\n disabled={loadingMore}\n onClick={async () => {\n if (nextCursor == null || loadingMore) return\n setLoadingMore(true)\n await loadPage(nextCursor)\n setLoadingMore(false)\n }}\n >\n {loadingMore ? 'Loading…' : 'Load more'}\n </Button>\n </Box>\n ) : null\n\n const grid = (\n <Box sx={gridSx}>\n {(visible ?? []).map((item) => {\n // AGL-1726: resolved BEFORE the guard, so a stored value that is\n // truthy but resolves to nothing renders the placeholder rather\n // than an `<img>` with no `src`.\n const imageUrl = Aglyn.siteRelativeMediaSrc(item.imageUrl, { hostId })\n return (\n <Card\n key={item.id}\n variant={photoCards ? 'elevation' : 'outlined'}\n elevation={0}\n sx={photoCards ? { bgcolor: 'transparent', overflow: 'visible', minWidth: 0 } : { minWidth: 0 }}\n >\n <CardActionArea\n href={hostId ? `/products/${item.slug}` : undefined}\n disabled={!hostId}\n sx={photoCards ? { borderRadius: 2 } : undefined}\n >\n {imageUrl ? (\n <CardMedia\n component=\"img\"\n image={imageUrl}\n alt={item.name}\n sx={\n photoCards\n ? {\n width: '100%',\n aspectRatio: '4 / 5',\n objectFit: 'cover',\n borderRadius: 2,\n // A storefront's photo answers the pointer (AGL-3676).\n transition: 'opacity 200ms ease',\n '.MuiCardActionArea-root:hover &': { opacity: 0.88 },\n }\n : { height: 160, objectFit: 'cover' }\n }\n // Deferred (AGL-2486), and this is the worst offender of\n // the set: a grid renders one image PER PRODUCT, all of\n // them previously unhinted, so a storefront section below\n // the fold opened N eager default-priority fetches that\n // outranked everything above it.\n {...Aglyn.DEFERRED_IMAGE_ATTRIBUTES}\n />\n ) : (\n // Never a blank tile (AGL-3676 follow-up).\n <ProductImagePlaceholder\n name={item.name}\n sx={\n photoCards\n ? { aspectRatio: '4 / 5', borderRadius: 2 }\n : { height: 160 }\n }\n />\n )}\n <CardContent sx={photoCards ? { px: 0.5, pt: 1.5, pb: 1 } : undefined}>\n <Typography variant={photoCards ? 'subtitle1' : 'subtitle2'} noWrap>\n {item.name}\n </Typography>\n <Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {priceLabel(item)}\n </Typography>\n {item.compareAtPriceUsd ? (\n <Typography\n variant=\"caption\"\n color=\"text.disabled\"\n sx={{ textDecoration: 'line-through' }}\n >\n {`$${item.compareAtPriceUsd}`}\n </Typography>\n ) : null}\n {item.soldOut ? (\n <Chip label=\"Sold out\" size=\"small\" variant=\"outlined\" />\n ) : null}\n </Box>\n </CardContent>\n </CardActionArea>\n {quickAdd ? (\n <QuickAddButton\n item={item}\n inert={!hostId}\n busy={quickAdding === item.id}\n added={quickAdded === item.id}\n photoCards={photoCards}\n onAdd={async () => {\n if (!hostId || !item.defaultVariantId || quickAdding) return\n setQuickAdding(item.id)\n const ok = await addToCart({\n hostId,\n siteFetch,\n productId: item.id,\n variantId: item.defaultVariantId,\n quantity: 1,\n item: {\n item_id: item.id,\n item_name: item.name,\n price: item.priceUsd,\n quantity: 1,\n },\n })\n setQuickAdding('')\n if (ok) setQuickAdded(item.id)\n }}\n />\n ) : null}\n </Card>\n )\n })}\n </Box>\n )\n\n const body = visible && visible.length === 0 ? emptyState : grid\n const unapplied =\n hostId && (refused.length || notices.length) ? (\n <Box sx={{ mb: 2 }}>\n <ListQueryNotices\n refused={listQueryRefusals(refused, {\n fields: STOREFRONT_CATALOG_DECLARATION.fields,\n headers: STOREFRONT_CATALOG_HEADERS,\n options: {\n ...STOREFRONT_CATALOG_OPTIONS,\n category: categories.map((category) => ({\n value: category.id,\n label: category.name,\n })),\n },\n })}\n notices={notices}\n />\n </Box>\n ) : null\n if (!showFilters) {\n return (\n <Box ref={ref} {...rest}>\n {controls}\n {unapplied}\n {body}\n {loadMore}\n </Box>\n )\n }\n return (\n <Box ref={ref} {...rest}>\n {controls}\n <Box\n sx={{\n display: 'flex',\n gap: 1,\n flexWrap: 'wrap',\n alignItems: 'center',\n mb: 2,\n }}\n >\n <Chip\n label=\"In stock\"\n size=\"small\"\n variant={inStockOnly ? 'filled' : 'outlined'}\n color={inStockOnly ? 'primary' : 'default'}\n onClick={() => setInStockOnly((prev) => !prev)}\n />\n {facetTags.map((facetTag) => (\n <Chip\n key={facetTag}\n label={facetTag}\n size=\"small\"\n variant={activeTag === facetTag ? 'filled' : 'outlined'}\n color={activeTag === facetTag ? 'primary' : 'default'}\n onClick={() => {\n // The same one-array-clause rule as the category chips.\n setSearchInput('')\n setQuery('')\n setActiveCategoryId((prev) => (prev && prev !== '*' ? '' : prev))\n setActiveTag((prev) => (prev === facetTag ? '' : facetTag))\n }}\n />\n ))}\n <Box sx={{ flex: 1 }} />\n <Chip\n label={\n sort === 'price-asc'\n ? 'Price ↑'\n : sort === 'price-desc'\n ? 'Price ↓'\n : 'A–Z'\n }\n size=\"small\"\n variant=\"outlined\"\n onClick={() =>\n setSort((prev) =>\n prev === 'price-asc'\n ? 'price-desc'\n : prev === 'price-desc'\n ? 'name'\n : 'price-asc',\n )\n }\n />\n </Box>\n {unapplied}\n {body}\n {loadMore}\n </Box>\n )\n },\n)\n/**\n * A card's quick add: outside the card's link, so a click never follows it.\n * Sample cards on the canvas show the button inert.\n */\nfunction QuickAddButton({\n item,\n inert,\n busy,\n added,\n photoCards,\n onAdd,\n}: {\n item: CatalogItem\n inert: boolean\n busy: boolean\n added: boolean\n photoCards: boolean\n onAdd: () => void\n}) {\n const action = inert ? 'add' : quickAddAction(item)\n if (!action) return null\n const sx = photoCards ? { mt: 0.5, mx: 0.5, mb: 1 } : { mx: 2, mb: 2 }\n if (action === 'options') {\n return (\n <Box sx={sx}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n fullWidth\n href={`/products/${item.slug}`}\n onClick={(event) => event.stopPropagation()}\n >\n {'Choose options'}\n </Button>\n </Box>\n )\n }\n return (\n <Box sx={sx}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n fullWidth\n disabled={inert || busy}\n aria-label={`Add ${item.name} to cart`}\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n onAdd()\n }}\n >\n {added ? 'Added ✓' : busy ? 'Adding…' : 'Add to cart'}\n </Button>\n </Box>\n )\n}\n\nProductGrid.displayName = 'AglynProductGrid'\n\nexport const schema: Aglyn.ComponentSchema<ProductGridProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Product grid',\n description:\n 'Your catalog as a grid, with optional search, category chips, sort and paging.',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiViewGridOutline.path, sx: { color: '#2e7d32' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'source',\n label: 'Show',\n description: 'Which products to list.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { label: 'All products', value: 'all' },\n { label: 'A collection', value: 'collection' },\n { label: 'A category', value: 'category' },\n { label: 'A tag', value: 'tag' },\n ],\n },\n {\n name: 'collectionId',\n label: 'Collection',\n description:\n 'Pick the collection — stored by id, so renaming it never breaks ' +\n 'this grid. Leave empty on collection template pages to follow ' +\n 'the /collections/{slug} URL.',\n component: Aglyn.FieldComponentType.COLLECTION_SELECT,\n },\n {\n name: 'collectionSlug',\n label: 'Collection slug (legacy)',\n description: 'Used only when no collection is picked above.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'categoryId',\n label: 'Category',\n description:\n 'Pick the category — stored by id, rename-safe.',\n component: Aglyn.FieldComponentType.CATEGORY_SELECT,\n },\n {\n name: 'categorySlug',\n label: 'Category slug (legacy)',\n description: 'Used only when no category is picked above.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'tag',\n label: 'Tag',\n description: 'Products tagged with this value.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'sort',\n label: 'Sort',\n description: 'Listing order.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { label: 'Name', value: 'name' },\n { label: 'Price (low → high)', value: 'price-asc' },\n { label: 'Price (high → low)', value: 'price-desc' },\n { label: 'Newest', value: 'newest' },\n ],\n },\n {\n name: 'columns',\n label: 'Columns',\n description: 'Desktop columns (1-6).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'maxItems',\n label: 'Max items',\n description: 'Cap the number of cards.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'emptyText',\n label: 'Empty text',\n description: 'Copy when nothing matches.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'emptyTitle',\n label: 'Empty headline',\n description:\n 'Headline while the store lists nothing yet; set, the empty state ' +\n 'is a full panel with the empty text and one button.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'emptyActionLabel',\n label: 'Empty button label',\n description: 'The empty panel’s button, e.g. “Get in touch”.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'emptyActionHref',\n label: 'Empty button link',\n description: 'Where the empty panel’s button goes, e.g. /contact.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'showFilters',\n label: 'Show filters',\n description: 'Tag chips, in-stock toggle, and price sort above the grid.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n {\n name: 'showSearch',\n label: 'Search box',\n description:\n 'Visitor search box above the grid — matches words at the start of ' +\n 'product names across the whole catalog (server-side).',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n {\n name: 'showCategories',\n label: 'Category chips',\n description:\n 'Filter chips built from your product categories, with an All chip.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n {\n name: 'showSort',\n label: 'Sort select',\n description:\n 'Visitor sort: newest, name, price low→high, price high→low.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n {\n name: 'showTypeFilter',\n label: 'Type filter',\n description: 'Physical / digital / services chips.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n {\n name: 'showPriceFilter',\n label: 'Price filter',\n description:\n 'Price range slider between your lowest and highest product prices.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n {\n name: 'pageSize',\n label: 'Page size',\n description:\n 'Products per page with a Load more button; blank loads once ' +\n '(capped by Max items).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'cardStyle',\n label: 'Card style',\n description: 'Outlined cards, or tall photos with the name and price under them.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { label: 'Outlined', value: 'outlined' },\n { label: 'Photo', value: 'photo' },\n ],\n },\n {\n // Off by default, so no existing grid changes shape on deploy.\n name: 'quickAdd',\n label: 'Quick add to cart',\n description:\n 'An Add to cart button on each card for a product with one variant; ' +\n '\"Choose options\" for one with several.',\n component: Aglyn.FieldComponentType.CHECKBOX,\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Product grid',\n pluginId: BUNDLE_ID,\n description: 'Responsive grid of catalog products',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiViewGridOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: { source: 'all', sort: 'name', columns: 3 },\n },\n },\n {\n // Full shop page (AGL-561): the grid with every catalog control on\n // — search, category chips, sort, and Load more paging.\n $id: generatePresetId(ID, 'shop'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Shop catalog',\n pluginId: BUNDLE_ID,\n description: 'Product grid with search, category chips, sort, and paging',\n category: Aglyn.ComponentCategory.COMMERCE,\n icon: { path: mdiViewGridOutline.path, sx: { color: '#2e7d32' } },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {\n source: 'all',\n sort: 'newest',\n columns: 3,\n showSearch: true,\n showCategories: true,\n showSort: true,\n pageSize: 12,\n },\n },\n },\n]\n\nexport default ProductGrid\n"],"names":["Aglyn","mdiMagnify","mdiViewGridOutline","ListQueryNotices","listQueryRefusals","Box","Button","Card","CardActionArea","CardContent","CardMedia","Chip","InputAdornment","MenuItem","Skeleton","Slider","SvgIcon","TextField","Typography","forwardRef","useCallback","useEffect","useRef","useState","BUNDLE_ID","STOREFRONT_CATALOG_DECLARATION","STOREFRONT_CATALOG_HEADERS","STOREFRONT_CATALOG_OPTIONS","generatePresetId","addToCart","ProductImagePlaceholder","ID","quickAddAction","item","priceComingSoon","soldOut","requiresChoices","defaultVariantId","variantCount","browseParamsFromSearch","search","params","URLSearchParams","categorySlug","get","trim","tag","PRICE_COMING_SOON_LABEL","SAMPLE_ITEMS","id","name","slug","priceUsd","maxPriceUsd","compareAtPriceUsd","SAMPLE_CATEGORIES","SAMPLE_PRICE_BOUNDS","minCents","maxCents","TYPE_OPTIONS","value","label","SORT_OPTIONS","collectionSlugFromLocation","window","match","location","pathname","decodeURIComponent","priceLabel","ProductGrid","props","ref","site","source","collectionId","collectionSlug","collectionSlugProp","categoryId","sort","sortProp","columns","maxItems","emptyText","emptyTitle","emptyActionLabel","emptyActionHref","showFilters","showSearch","showCategories","showSort","showTypeFilter","showPriceFilter","pageSize","cardStyle","quickAdd","rest","photoCards","nodeSx","Array","isArray","useSite","siteFetch","useSiteFetch","hostId","quickAdding","setQuickAdding","quickAdded","setQuickAdded","nodeId","useNodeId","seed","pageData","commerce","grids","items","setItems","setSort","inStockOnly","setInStockOnly","activeTag","setActiveTag","searchInput","setSearchInput","query","setQuery","activeCategoryId","setActiveCategoryId","activeType","setActiveType","categories","setCategories","priceBounds","setPriceBounds","priceInput","setPriceInput","priceFilter","setPriceFilter","nextCursor","setNextCursor","refused","setRefused","notices","setNotices","loadingMore","setLoadingMore","fetchSeq","pageLimit","Number","Math","floor","pinnedCategoryId","urlCategorySlug","setUrlCategorySlug","asked","length","find","category","timer","setTimeout","clearTimeout","loadPage","after","seq","current","set","effectiveCategoryId","effectiveTag","String","limit","response","fetch","toString","payload","json","pageItems","prev","bounds","seededRef","Boolean","desktopColumns","min","max","gridSx","display","gap","gridTemplateColumns","xs","sm","md","visible","facetTags","Set","flatMap","tags","slice","sx","from","_item","index","variant","height","width","chipCategories","selectedCategoryId","hasCatalogControls","sliderBounds","sliderMin","sliderMax","ceil","showSlider","controls","flexDirection","mb","onChange","event","target","placeholder","size","fullWidth","slotProps","input","startAdornment","position","fontSize","color","path","d","htmlInput","type","flexWrap","alignItems","onClick","map","option","flex","select","some","minWidth","maxWidth","px","whiteSpace","step","disableSwap","disabled","_event","valueLabelDisplay","valueLabelFormat","getAriaLabel","filtering","emptyCopy","emptyPanel","py","textAlign","borderRadius","bgcolor","component","href","mt","p","emptyState","loadMore","justifyContent","grid","imageUrl","siteRelativeMediaSrc","elevation","overflow","undefined","image","alt","aspectRatio","objectFit","transition","opacity","DEFERRED_IMAGE_ATTRIBUTES","pt","pb","noWrap","textDecoration","QuickAddButton","inert","busy","added","onAdd","ok","productId","variantId","quantity","item_id","item_name","price","body","unapplied","fields","headers","options","facetTag","action","mx","stopPropagation","aria-label","preventDefault","displayName","schema","$id","pluginId","description","ComponentCategory","COMMERCE","icon","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","FieldComponentType","SELECT","COLLECTION_SELECT","TEXT_FIELD","CATEGORY_SELECT","CHECKBOX","presets","NodeType","PRESET","data","componentId"],"mappings":"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"}
@@ -24,6 +24,7 @@ import { registerClosureNoticeSource } from "@aglyn/aglyn/plugin-manager/plugin-
24
24
  import { registerCustomerDisplayVerifier } from "@aglyn/aglyn/plugin-manager/plugin-customer-display-verifier";
25
25
  import { registerPluginEventHandler } from "@aglyn/aglyn/plugin-manager/plugin-events";
26
26
  import { registerPluginSearchSources } from "@aglyn/aglyn/plugin-manager/plugin-search-sources";
27
+ import { registerPluginConsoleCron } from "@aglyn/aglyn/plugin-manager/plugin-console-crons";
27
28
  import { COMMERCE_SERVER_SEARCH_SOURCES } from "./model/commerce-server-search-sources.js";
28
29
  import { BUNDLE_ID } from "./constants/bundle-common.js";
29
30
  import { ORDER_HISTORY_CONTRACT } from "./model/order-history.js";
@@ -50,6 +51,20 @@ import { registerCommerceTransferResources } from "./transfer/register-transfer-
50
51
  * descriptions when the document is first built. Registering again replaces
51
52
  * this plugin's own entries.
52
53
  */ export function registerCommerceConsoleServerDeclarations() {
54
+ // Files a customer attached to an answer on a quote and never sent
55
+ // (AGL-4096): deleted a day after their latest upload, on the same tick as
56
+ // the forms plugin's sweep of forms never sent.
57
+ registerPluginConsoleCron({
58
+ id: 'commerce-quote-upload-sweep',
59
+ label: 'Unsent quote files',
60
+ drives: 'Deletes the files a customer attached to an answer on a quote and never sent, a day after the last one went up. If it stops, those files stay in the site’s private storage.',
61
+ run: async (context)=>{
62
+ const { defaultQuoteUploadSweepDeps, sweepQuoteUploads } = await import("./server/quote-upload-sweep.js");
63
+ return sweepQuoteUploads(defaultQuoteUploadSweepDeps(), context);
64
+ }
65
+ }, {
66
+ pluginId: BUNDLE_ID
67
+ });
53
68
  // Commerce's share of a person erasure (AGL-2623, AGL-3080): every order the
54
69
  // person placed keeps its amounts, with the buyer taken off it. Required,
55
70
  // and loaded with the first erasure.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/declarations.console-server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The registry's own module, not the data layer's barrel: boot needs the\n// registry, not the whole server surface.\nimport { registerApiV1SiteResource } from '@aglyn/tenant-data-admin/server/api-v1-resources'\nimport { registerPluginOrgEraser } from '@aglyn/aglyn/plugin-manager/plugin-org-erasure'\nimport { registerPluginPersonEraser } from '@aglyn/aglyn/plugin-manager/plugin-person-erasure'\nimport { registerPluginUserEraser } from '@aglyn/aglyn/plugin-manager/plugin-user-erasure'\nimport { registerPluginService } from '@aglyn/aglyn/plugin-manager/plugin-services'\nimport { registerClosureNoticeSource } from '@aglyn/aglyn/plugin-manager/plugin-closure-notices'\nimport { registerCustomerDisplayVerifier } from '@aglyn/aglyn/plugin-manager/plugin-customer-display-verifier'\nimport { registerPluginEventHandler } from '@aglyn/aglyn/plugin-manager/plugin-events'\nimport { registerPluginSearchSources } from '@aglyn/aglyn/plugin-manager/plugin-search-sources'\nimport { COMMERCE_SERVER_SEARCH_SOURCES } from './model/commerce-server-search-sources'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { ORDER_HISTORY_CONTRACT } from './model/order-history'\nimport { parsePosHardwareInPersonConfig } from './model/pos-hardware'\nimport { registerCommerceTransferResources } from './transfer/register-transfer-resources'\n\n/**\n * The commerce plugin's CONSOLE-ONLY server declarations, named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot. The tenant runtime registers none of it.\n *\n * It serves a site's products and orders on the customer REST API,\n * `/v1/sites/{siteId}/products/…` and `/v1/sites/{siteId}/orders/…`: the\n * console's router owns the pipeline in front — the key, the plan's API\n * access, the quota, the rate limit, the error envelope — and the site's\n * ownership, and hands every request under each resource here.\n *\n * No plan feature is named on the registrations, though both need\n * `commerce`: the handlers ask the key's scope FIRST and the plan second\n * (AGL-900, AGL-1928), so a key without the scope is told about the scope\n * whatever plan its organization is on, and the router would ask in the\n * opposite order.\n *\n * Light at boot: the handlers are imported with their first request and the\n * descriptions when the document is first built. Registering again replaces\n * this plugin's own entries.\n */\nexport function registerCommerceConsoleServerDeclarations(): void {\n // Commerce's share of a person erasure (AGL-2623, AGL-3080): every order the\n // person placed keeps its amounts, with the buyer taken off it. Required,\n // and loaded with the first erasure.\n registerPluginPersonEraser(\n async (request) => (await import('./server/person-eraser')).commercePersonEraser(request),\n { pluginId: BUNDLE_ID },\n )\n // Commerce's share of an ACCOUNT erasure (AGL-4038): every POS hardware\n // order the person placed that has not shipped is canceled and refunded in\n // full, then every one keeps its amounts and its tax region, with the\n // buyer taken off it. Loaded with the first erasure.\n registerPluginUserEraser(\n async (request) => (await import('./server/pos-hardware-user-eraser')).posHardwareUserEraser(request),\n { pluginId: BUNDLE_ID },\n )\n // Commerce's share of a WORKSPACE erasure (AGL-4038): every hardware order\n // the workspace bought that has not shipped is canceled and refunded in\n // full before the erasure deletes it, and the buyer and staff are told.\n // Never fails the erasure. Loaded with the first erasure.\n registerPluginOrgEraser(\n async (request) => (await import('./server/pos-hardware-closure')).posHardwareOrgEraser(request),\n { pluginId: BUNDLE_ID },\n )\n // A workspace erasure turns 4G off at Stripe for every one of its stores\n // that had it (AGL-4038), so the platform stops paying for readers nobody\n // will be charged for. Never fails the erasure. Loaded with the first one.\n registerPluginOrgEraser(\n async (request) => (await import('./server/pos-cellular-billing')).posCellularOrgEraser(request),\n { pluginId: BUNDLE_ID },\n )\n // …and says so BEFORE the closure (AGL-4038): the delete-workspace and\n // close-account screens list the unshipped hardware orders a closure will\n // cancel and refund. Loaded with the first screen that asks.\n registerClosureNoticeSource(\n {\n notices: async (subject) =>\n (await import('./server/pos-hardware-closure-notices')).posHardwareClosureNoticeSource.notices(subject),\n },\n { pluginId: BUNDLE_ID },\n )\n // A card reader bought in person before its buyer had a workspace\n // (AGL-4038) joins the workspace they create, by their account or their\n // verified address. Loaded with the first workspace created while the\n // in-person sale is on.\n registerPluginEventHandler(\n 'org.created',\n async (payload) => {\n const config = parsePosHardwareInPersonConfig(process.env)\n if (!config) return\n const { claimPosHardwareForNewWorkspace } = await import('./server/pos-hardware-attach')\n await claimPosHardwareForNewWorkspace({\n config,\n orgId: payload.orgId,\n ownerUid: payload.owner.uid,\n ownerEmail: payload.owner.email,\n emailVerified: payload.owner.emailVerified,\n })\n },\n { pluginId: BUNDLE_ID },\n )\n // Who a paired customer display is (AGL-4042), for another plugin's\n // server acting for the screen the register put on it: the display's own\n // token is the credential. Loaded with the first screen that asks.\n registerCustomerDisplayVerifier(\n { verify: async (credential) => (await import('./server/pos-display')).verifyPosCustomerDisplay(credential) },\n { pluginId: BUNDLE_ID },\n )\n // A site's earlier orders, for a plugin that reads history behind the\n // order events (AGL-3614); the reader is loaded with the first page asked.\n registerPluginService(\n ORDER_HISTORY_CONTRACT,\n { listOrders: async (request) => (await import('./server/order-history')).listOrderHistory(request) },\n { pluginId: BUNDLE_ID },\n )\n registerApiV1SiteResource(\n 'products',\n {\n handle: async (...args) =>\n (await import('./server/api-v1/orders-and-products')).handleProducts(...args),\n describe: async () =>\n (await import('./server/api-v1/openapi')).PRODUCTS_API_V1_DESCRIPTION,\n },\n { pluginId: BUNDLE_ID },\n )\n registerApiV1SiteResource(\n 'orders',\n {\n handle: async (...args) =>\n (await import('./server/api-v1/orders-and-products')).handleOrders(...args),\n describe: async () =>\n (await import('./server/api-v1/openapi')).ORDERS_API_V1_DESCRIPTION,\n },\n { pluginId: BUNDLE_ID },\n )\n // Products, categories, orders, discounts, coupons and gift cards on the\n // import wizard and the export dialog (AGL-3531): the transfer routes are\n // the console's alone, so they register here.\n registerCommerceTransferResources()\n // What the native apps' one box finds in a store (AGL-3943): products and\n // orders, each under its own plan flag and, for orders, the site role that\n // reads them.\n registerPluginSearchSources(COMMERCE_SERVER_SEARCH_SOURCES, { pluginId: BUNDLE_ID })\n}\n"],"names":["registerApiV1SiteResource","registerPluginOrgEraser","registerPluginPersonEraser","registerPluginUserEraser","registerPluginService","registerClosureNoticeSource","registerCustomerDisplayVerifier","registerPluginEventHandler","registerPluginSearchSources","COMMERCE_SERVER_SEARCH_SOURCES","BUNDLE_ID","ORDER_HISTORY_CONTRACT","parsePosHardwareInPersonConfig","registerCommerceTransferResources","registerCommerceConsoleServerDeclarations","request","commercePersonEraser","pluginId","posHardwareUserEraser","posHardwareOrgEraser","posCellularOrgEraser","notices","subject","posHardwareClosureNoticeSource","payload","config","process","env","claimPosHardwareForNewWorkspace","orgId","ownerUid","owner","uid","ownerEmail","email","emailVerified","verify","credential","verifyPosCustomerDisplay","listOrders","listOrderHistory","handle","args","handleProducts","describe","PRODUCTS_API_V1_DESCRIPTION","handleOrders","ORDERS_API_V1_DESCRIPTION"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,yEAAyE;AACzE,0CAA0C;AAC1C,SAASA,yBAAyB,QAAQ,mDAAkD;AAC5F,SAASC,uBAAuB,QAAQ,iDAAgD;AACxF,SAASC,0BAA0B,QAAQ,oDAAmD;AAC9F,SAASC,wBAAwB,QAAQ,kDAAiD;AAC1F,SAASC,qBAAqB,QAAQ,8CAA6C;AACnF,SAASC,2BAA2B,QAAQ,qDAAoD;AAChG,SAASC,+BAA+B,QAAQ,+DAA8D;AAC9G,SAASC,0BAA0B,QAAQ,4CAA2C;AACtF,SAASC,2BAA2B,QAAQ,oDAAmD;AAC/F,SAASC,8BAA8B,QAAQ,4CAAwC;AACvF,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,sBAAsB,QAAQ,2BAAuB;AAC9D,SAASC,8BAA8B,QAAQ,0BAAsB;AACrE,SAASC,iCAAiC,QAAQ,4CAAwC;AAE1F;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASC;IACd,6EAA6E;IAC7E,0EAA0E;IAC1E,qCAAqC;IACrCZ,2BACE,OAAOa,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,4BAAwB,EAAGC,oBAAoB,CAACD,UACjF;QAAEE,UAAUP;IAAU;IAExB,wEAAwE;IACxE,2EAA2E;IAC3E,sEAAsE;IACtE,qDAAqD;IACrDP,yBACE,OAAOY,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,uCAAmC,EAAGG,qBAAqB,CAACH,UAC7F;QAAEE,UAAUP;IAAU;IAExB,2EAA2E;IAC3E,wEAAwE;IACxE,wEAAwE;IACxE,0DAA0D;IAC1DT,wBACE,OAAOc,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,mCAA+B,EAAGI,oBAAoB,CAACJ,UACxF;QAAEE,UAAUP;IAAU;IAExB,yEAAyE;IACzE,0EAA0E;IAC1E,2EAA2E;IAC3ET,wBACE,OAAOc,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,mCAA+B,EAAGK,oBAAoB,CAACL,UACxF;QAAEE,UAAUP;IAAU;IAExB,uEAAuE;IACvE,0EAA0E;IAC1E,6DAA6D;IAC7DL,4BACE;QACEgB,SAAS,OAAOC,UACd,AAAC,CAAA,MAAM,MAAM,CAAC,2CAAuC,EAAGC,8BAA8B,CAACF,OAAO,CAACC;IACnG,GACA;QAAEL,UAAUP;IAAU;IAExB,kEAAkE;IAClE,wEAAwE;IACxE,sEAAsE;IACtE,wBAAwB;IACxBH,2BACE,eACA,OAAOiB;QACL,MAAMC,SAASb,+BAA+Bc,QAAQC,GAAG;QACzD,IAAI,CAACF,QAAQ;QACb,MAAM,EAAEG,+BAA+B,EAAE,GAAG,MAAM,MAAM,CAAC;QACzD,MAAMA,gCAAgC;YACpCH;YACAI,OAAOL,QAAQK,KAAK;YACpBC,UAAUN,QAAQO,KAAK,CAACC,GAAG;YAC3BC,YAAYT,QAAQO,KAAK,CAACG,KAAK;YAC/BC,eAAeX,QAAQO,KAAK,CAACI,aAAa;QAC5C;IACF,GACA;QAAElB,UAAUP;IAAU;IAExB,oEAAoE;IACpE,yEAAyE;IACzE,mEAAmE;IACnEJ,gCACE;QAAE8B,QAAQ,OAAOC,aAAe,AAAC,CAAA,MAAM,MAAM,CAAC,0BAAsB,EAAGC,wBAAwB,CAACD;IAAY,GAC5G;QAAEpB,UAAUP;IAAU;IAExB,sEAAsE;IACtE,2EAA2E;IAC3EN,sBACEO,wBACA;QAAE4B,YAAY,OAAOxB,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,4BAAwB,EAAGyB,gBAAgB,CAACzB;IAAS,GACpG;QAAEE,UAAUP;IAAU;IAExBV,0BACE,YACA;QACEyC,QAAQ,OAAO,GAAGC,OAChB,AAAC,CAAA,MAAM,MAAM,CAAC,yCAAqC,EAAGC,cAAc,IAAID;QAC1EE,UAAU,UACR,AAAC,CAAA,MAAM,MAAM,CAAC,6BAAyB,EAAGC,2BAA2B;IACzE,GACA;QAAE5B,UAAUP;IAAU;IAExBV,0BACE,UACA;QACEyC,QAAQ,OAAO,GAAGC,OAChB,AAAC,CAAA,MAAM,MAAM,CAAC,yCAAqC,EAAGI,YAAY,IAAIJ;QACxEE,UAAU,UACR,AAAC,CAAA,MAAM,MAAM,CAAC,6BAAyB,EAAGG,yBAAyB;IACvE,GACA;QAAE9B,UAAUP;IAAU;IAExB,yEAAyE;IACzE,0EAA0E;IAC1E,8CAA8C;IAC9CG;IACA,0EAA0E;IAC1E,2EAA2E;IAC3E,cAAc;IACdL,4BAA4BC,gCAAgC;QAAEQ,UAAUP;IAAU;AACpF"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/declarations.console-server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// The registry's own module, not the data layer's barrel: boot needs the\n// registry, not the whole server surface.\nimport { registerApiV1SiteResource } from '@aglyn/tenant-data-admin/server/api-v1-resources'\nimport { registerPluginOrgEraser } from '@aglyn/aglyn/plugin-manager/plugin-org-erasure'\nimport { registerPluginPersonEraser } from '@aglyn/aglyn/plugin-manager/plugin-person-erasure'\nimport { registerPluginUserEraser } from '@aglyn/aglyn/plugin-manager/plugin-user-erasure'\nimport { registerPluginService } from '@aglyn/aglyn/plugin-manager/plugin-services'\nimport { registerClosureNoticeSource } from '@aglyn/aglyn/plugin-manager/plugin-closure-notices'\nimport { registerCustomerDisplayVerifier } from '@aglyn/aglyn/plugin-manager/plugin-customer-display-verifier'\nimport { registerPluginEventHandler } from '@aglyn/aglyn/plugin-manager/plugin-events'\nimport { registerPluginSearchSources } from '@aglyn/aglyn/plugin-manager/plugin-search-sources'\nimport { registerPluginConsoleCron } from '@aglyn/aglyn/plugin-manager/plugin-console-crons'\nimport { COMMERCE_SERVER_SEARCH_SOURCES } from './model/commerce-server-search-sources'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { ORDER_HISTORY_CONTRACT } from './model/order-history'\nimport { parsePosHardwareInPersonConfig } from './model/pos-hardware'\nimport { registerCommerceTransferResources } from './transfer/register-transfer-resources'\n\n/**\n * The commerce plugin's CONSOLE-ONLY server declarations, named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot. The tenant runtime registers none of it.\n *\n * It serves a site's products and orders on the customer REST API,\n * `/v1/sites/{siteId}/products/…` and `/v1/sites/{siteId}/orders/…`: the\n * console's router owns the pipeline in front — the key, the plan's API\n * access, the quota, the rate limit, the error envelope — and the site's\n * ownership, and hands every request under each resource here.\n *\n * No plan feature is named on the registrations, though both need\n * `commerce`: the handlers ask the key's scope FIRST and the plan second\n * (AGL-900, AGL-1928), so a key without the scope is told about the scope\n * whatever plan its organization is on, and the router would ask in the\n * opposite order.\n *\n * Light at boot: the handlers are imported with their first request and the\n * descriptions when the document is first built. Registering again replaces\n * this plugin's own entries.\n */\nexport function registerCommerceConsoleServerDeclarations(): void {\n // Files a customer attached to an answer on a quote and never sent\n // (AGL-4096): deleted a day after their latest upload, on the same tick as\n // the forms plugin's sweep of forms never sent.\n registerPluginConsoleCron(\n {\n id: 'commerce-quote-upload-sweep',\n label: 'Unsent quote files',\n drives:\n 'Deletes the files a customer attached to an answer on a quote and never sent, a day after the last one went up. If it stops, those files stay in the site’s private storage.',\n run: async (context) => {\n const { defaultQuoteUploadSweepDeps, sweepQuoteUploads } = await import('./server/quote-upload-sweep')\n return sweepQuoteUploads(defaultQuoteUploadSweepDeps(), context)\n },\n },\n { pluginId: BUNDLE_ID },\n )\n // Commerce's share of a person erasure (AGL-2623, AGL-3080): every order the\n // person placed keeps its amounts, with the buyer taken off it. Required,\n // and loaded with the first erasure.\n registerPluginPersonEraser(\n async (request) => (await import('./server/person-eraser')).commercePersonEraser(request),\n { pluginId: BUNDLE_ID },\n )\n // Commerce's share of an ACCOUNT erasure (AGL-4038): every POS hardware\n // order the person placed that has not shipped is canceled and refunded in\n // full, then every one keeps its amounts and its tax region, with the\n // buyer taken off it. Loaded with the first erasure.\n registerPluginUserEraser(\n async (request) => (await import('./server/pos-hardware-user-eraser')).posHardwareUserEraser(request),\n { pluginId: BUNDLE_ID },\n )\n // Commerce's share of a WORKSPACE erasure (AGL-4038): every hardware order\n // the workspace bought that has not shipped is canceled and refunded in\n // full before the erasure deletes it, and the buyer and staff are told.\n // Never fails the erasure. Loaded with the first erasure.\n registerPluginOrgEraser(\n async (request) => (await import('./server/pos-hardware-closure')).posHardwareOrgEraser(request),\n { pluginId: BUNDLE_ID },\n )\n // A workspace erasure turns 4G off at Stripe for every one of its stores\n // that had it (AGL-4038), so the platform stops paying for readers nobody\n // will be charged for. Never fails the erasure. Loaded with the first one.\n registerPluginOrgEraser(\n async (request) => (await import('./server/pos-cellular-billing')).posCellularOrgEraser(request),\n { pluginId: BUNDLE_ID },\n )\n // …and says so BEFORE the closure (AGL-4038): the delete-workspace and\n // close-account screens list the unshipped hardware orders a closure will\n // cancel and refund. Loaded with the first screen that asks.\n registerClosureNoticeSource(\n {\n notices: async (subject) =>\n (await import('./server/pos-hardware-closure-notices')).posHardwareClosureNoticeSource.notices(subject),\n },\n { pluginId: BUNDLE_ID },\n )\n // A card reader bought in person before its buyer had a workspace\n // (AGL-4038) joins the workspace they create, by their account or their\n // verified address. Loaded with the first workspace created while the\n // in-person sale is on.\n registerPluginEventHandler(\n 'org.created',\n async (payload) => {\n const config = parsePosHardwareInPersonConfig(process.env)\n if (!config) return\n const { claimPosHardwareForNewWorkspace } = await import('./server/pos-hardware-attach')\n await claimPosHardwareForNewWorkspace({\n config,\n orgId: payload.orgId,\n ownerUid: payload.owner.uid,\n ownerEmail: payload.owner.email,\n emailVerified: payload.owner.emailVerified,\n })\n },\n { pluginId: BUNDLE_ID },\n )\n // Who a paired customer display is (AGL-4042), for another plugin's\n // server acting for the screen the register put on it: the display's own\n // token is the credential. Loaded with the first screen that asks.\n registerCustomerDisplayVerifier(\n { verify: async (credential) => (await import('./server/pos-display')).verifyPosCustomerDisplay(credential) },\n { pluginId: BUNDLE_ID },\n )\n // A site's earlier orders, for a plugin that reads history behind the\n // order events (AGL-3614); the reader is loaded with the first page asked.\n registerPluginService(\n ORDER_HISTORY_CONTRACT,\n { listOrders: async (request) => (await import('./server/order-history')).listOrderHistory(request) },\n { pluginId: BUNDLE_ID },\n )\n registerApiV1SiteResource(\n 'products',\n {\n handle: async (...args) =>\n (await import('./server/api-v1/orders-and-products')).handleProducts(...args),\n describe: async () =>\n (await import('./server/api-v1/openapi')).PRODUCTS_API_V1_DESCRIPTION,\n },\n { pluginId: BUNDLE_ID },\n )\n registerApiV1SiteResource(\n 'orders',\n {\n handle: async (...args) =>\n (await import('./server/api-v1/orders-and-products')).handleOrders(...args),\n describe: async () =>\n (await import('./server/api-v1/openapi')).ORDERS_API_V1_DESCRIPTION,\n },\n { pluginId: BUNDLE_ID },\n )\n // Products, categories, orders, discounts, coupons and gift cards on the\n // import wizard and the export dialog (AGL-3531): the transfer routes are\n // the console's alone, so they register here.\n registerCommerceTransferResources()\n // What the native apps' one box finds in a store (AGL-3943): products and\n // orders, each under its own plan flag and, for orders, the site role that\n // reads them.\n registerPluginSearchSources(COMMERCE_SERVER_SEARCH_SOURCES, { pluginId: BUNDLE_ID })\n}\n"],"names":["registerApiV1SiteResource","registerPluginOrgEraser","registerPluginPersonEraser","registerPluginUserEraser","registerPluginService","registerClosureNoticeSource","registerCustomerDisplayVerifier","registerPluginEventHandler","registerPluginSearchSources","registerPluginConsoleCron","COMMERCE_SERVER_SEARCH_SOURCES","BUNDLE_ID","ORDER_HISTORY_CONTRACT","parsePosHardwareInPersonConfig","registerCommerceTransferResources","registerCommerceConsoleServerDeclarations","id","label","drives","run","context","defaultQuoteUploadSweepDeps","sweepQuoteUploads","pluginId","request","commercePersonEraser","posHardwareUserEraser","posHardwareOrgEraser","posCellularOrgEraser","notices","subject","posHardwareClosureNoticeSource","payload","config","process","env","claimPosHardwareForNewWorkspace","orgId","ownerUid","owner","uid","ownerEmail","email","emailVerified","verify","credential","verifyPosCustomerDisplay","listOrders","listOrderHistory","handle","args","handleProducts","describe","PRODUCTS_API_V1_DESCRIPTION","handleOrders","ORDERS_API_V1_DESCRIPTION"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,yEAAyE;AACzE,0CAA0C;AAC1C,SAASA,yBAAyB,QAAQ,mDAAkD;AAC5F,SAASC,uBAAuB,QAAQ,iDAAgD;AACxF,SAASC,0BAA0B,QAAQ,oDAAmD;AAC9F,SAASC,wBAAwB,QAAQ,kDAAiD;AAC1F,SAASC,qBAAqB,QAAQ,8CAA6C;AACnF,SAASC,2BAA2B,QAAQ,qDAAoD;AAChG,SAASC,+BAA+B,QAAQ,+DAA8D;AAC9G,SAASC,0BAA0B,QAAQ,4CAA2C;AACtF,SAASC,2BAA2B,QAAQ,oDAAmD;AAC/F,SAASC,yBAAyB,QAAQ,mDAAkD;AAC5F,SAASC,8BAA8B,QAAQ,4CAAwC;AACvF,SAASC,SAAS,QAAQ,+BAA2B;AACrD,SAASC,sBAAsB,QAAQ,2BAAuB;AAC9D,SAASC,8BAA8B,QAAQ,0BAAsB;AACrE,SAASC,iCAAiC,QAAQ,4CAAwC;AAE1F;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASC;IACd,mEAAmE;IACnE,2EAA2E;IAC3E,gDAAgD;IAChDN,0BACE;QACEO,IAAI;QACJC,OAAO;QACPC,QACE;QACFC,KAAK,OAAOC;YACV,MAAM,EAAEC,2BAA2B,EAAEC,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC;YACxE,OAAOA,kBAAkBD,+BAA+BD;QAC1D;IACF,GACA;QAAEG,UAAUZ;IAAU;IAExB,6EAA6E;IAC7E,0EAA0E;IAC1E,qCAAqC;IACrCT,2BACE,OAAOsB,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,4BAAwB,EAAGC,oBAAoB,CAACD,UACjF;QAAED,UAAUZ;IAAU;IAExB,wEAAwE;IACxE,2EAA2E;IAC3E,sEAAsE;IACtE,qDAAqD;IACrDR,yBACE,OAAOqB,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,uCAAmC,EAAGE,qBAAqB,CAACF,UAC7F;QAAED,UAAUZ;IAAU;IAExB,2EAA2E;IAC3E,wEAAwE;IACxE,wEAAwE;IACxE,0DAA0D;IAC1DV,wBACE,OAAOuB,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,mCAA+B,EAAGG,oBAAoB,CAACH,UACxF;QAAED,UAAUZ;IAAU;IAExB,yEAAyE;IACzE,0EAA0E;IAC1E,2EAA2E;IAC3EV,wBACE,OAAOuB,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,mCAA+B,EAAGI,oBAAoB,CAACJ,UACxF;QAAED,UAAUZ;IAAU;IAExB,uEAAuE;IACvE,0EAA0E;IAC1E,6DAA6D;IAC7DN,4BACE;QACEwB,SAAS,OAAOC,UACd,AAAC,CAAA,MAAM,MAAM,CAAC,2CAAuC,EAAGC,8BAA8B,CAACF,OAAO,CAACC;IACnG,GACA;QAAEP,UAAUZ;IAAU;IAExB,kEAAkE;IAClE,wEAAwE;IACxE,sEAAsE;IACtE,wBAAwB;IACxBJ,2BACE,eACA,OAAOyB;QACL,MAAMC,SAASpB,+BAA+BqB,QAAQC,GAAG;QACzD,IAAI,CAACF,QAAQ;QACb,MAAM,EAAEG,+BAA+B,EAAE,GAAG,MAAM,MAAM,CAAC;QACzD,MAAMA,gCAAgC;YACpCH;YACAI,OAAOL,QAAQK,KAAK;YACpBC,UAAUN,QAAQO,KAAK,CAACC,GAAG;YAC3BC,YAAYT,QAAQO,KAAK,CAACG,KAAK;YAC/BC,eAAeX,QAAQO,KAAK,CAACI,aAAa;QAC5C;IACF,GACA;QAAEpB,UAAUZ;IAAU;IAExB,oEAAoE;IACpE,yEAAyE;IACzE,mEAAmE;IACnEL,gCACE;QAAEsC,QAAQ,OAAOC,aAAe,AAAC,CAAA,MAAM,MAAM,CAAC,0BAAsB,EAAGC,wBAAwB,CAACD;IAAY,GAC5G;QAAEtB,UAAUZ;IAAU;IAExB,sEAAsE;IACtE,2EAA2E;IAC3EP,sBACEQ,wBACA;QAAEmC,YAAY,OAAOvB,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,4BAAwB,EAAGwB,gBAAgB,CAACxB;IAAS,GACpG;QAAED,UAAUZ;IAAU;IAExBX,0BACE,YACA;QACEiD,QAAQ,OAAO,GAAGC,OAChB,AAAC,CAAA,MAAM,MAAM,CAAC,yCAAqC,EAAGC,cAAc,IAAID;QAC1EE,UAAU,UACR,AAAC,CAAA,MAAM,MAAM,CAAC,6BAAyB,EAAGC,2BAA2B;IACzE,GACA;QAAE9B,UAAUZ;IAAU;IAExBX,0BACE,UACA;QACEiD,QAAQ,OAAO,GAAGC,OAChB,AAAC,CAAA,MAAM,MAAM,CAAC,yCAAqC,EAAGI,YAAY,IAAIJ;QACxEE,UAAU,UACR,AAAC,CAAA,MAAM,MAAM,CAAC,6BAAyB,EAAGG,yBAAyB;IACvE,GACA;QAAEhC,UAAUZ;IAAU;IAExB,yEAAyE;IACzE,0EAA0E;IAC1E,8CAA8C;IAC9CG;IACA,0EAA0E;IAC1E,2EAA2E;IAC3E,cAAc;IACdN,4BAA4BE,gCAAgC;QAAEa,UAAUZ;IAAU;AACpF"}
@@ -21,6 +21,7 @@
21
21
  * product docs at read/checkout time, so a stale cart can never charge
22
22
  * a stale price. Pure helpers; the cart API owns I/O.
23
23
  */
24
+ import { type ModifierSelection } from './product-modifiers';
24
25
  export interface CartLine {
25
26
  productId: string;
26
27
  /** Absent = the product's default variant. */
@@ -32,6 +33,13 @@ export interface CartLine {
32
33
  * different days of one pass are two lines.
33
34
  */
34
35
  useDayKey?: string;
36
+ /**
37
+ * The add-ons the shopper chose on the product's page (AGL-4100), ids
38
+ * only, in the product's group order: priced from the product on every
39
+ * read and at checkout, never stored with a price. Absent: none. The same
40
+ * product with different choices is a different line.
41
+ */
42
+ modifiers?: ModifierSelection[];
35
43
  }
36
44
  /** `hosts/{hostId}/carts/{cartId}` doc. */
37
45
  export interface HostCart {
@@ -43,14 +51,18 @@ export interface HostCart {
43
51
  }
44
52
  export declare const CART_MAX_LINES = 50;
45
53
  export declare const CART_MAX_QUANTITY = 99;
46
- /** One cart line's identity: product, variant and use day. */
47
- export declare function cartLineKey(line: Pick<CartLine, 'productId' | 'variantId' | 'useDayKey'>): string;
54
+ /**
55
+ * One cart line's identity: product, variant, use day and add-ons
56
+ * (AGL-4100). A line with no add-ons keeps the key it always had, so a cart
57
+ * stored before add-ons were sold online reads, merges and removes as it did.
58
+ */
59
+ export declare function cartLineKey(line: Pick<CartLine, 'productId' | 'variantId' | 'useDayKey' | 'modifiers'>): string;
48
60
  /**
49
61
  * Adds/merges a line (quantities accumulate), clamped to per-line and
50
62
  * line-count caps. Quantity ≤ 0 removes the line.
51
63
  */
52
64
  export declare function upsertCartLine(cart: Pick<HostCart, 'lines'>, line: CartLine, mode?: 'add' | 'set'): CartLine[];
53
- export declare function removeCartLine(cart: Pick<HostCart, 'lines'>, line: Pick<CartLine, 'productId' | 'variantId' | 'useDayKey'>): CartLine[];
65
+ export declare function removeCartLine(cart: Pick<HostCart, 'lines'>, line: Pick<CartLine, 'productId' | 'variantId' | 'useDayKey' | 'modifiers'>): CartLine[];
54
66
  /** Total units across lines (the mini-cart badge). */
55
67
  export declare function cartCount(cart: Pick<HostCart, 'lines'> | undefined): number;
56
68
  /**
@@ -1,3 +1,4 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
1
2
  /**
2
3
  * @license
3
4
  * Copyright 2026 Aglyn LLC
@@ -19,12 +20,18 @@
19
20
  * store only ids + quantity — display data and prices resolve from the
20
21
  * product docs at read/checkout time, so a stale cart can never charge
21
22
  * a stale price. Pure helpers; the cart API owns I/O.
22
- */ import { _ as _extends } from "@swc/helpers/_/_extends";
23
+ */ import { modifierSelectionKey } from "./product-modifiers.js";
23
24
  export const CART_MAX_LINES = 50;
24
25
  export const CART_MAX_QUANTITY = 99;
25
- /** One cart line's identity: product, variant and use day. */ export function cartLineKey(line) {
26
+ /**
27
+ * One cart line's identity: product, variant, use day and add-ons
28
+ * (AGL-4100). A line with no add-ons keeps the key it always had, so a cart
29
+ * stored before add-ons were sold online reads, merges and removes as it did.
30
+ */ export function cartLineKey(line) {
26
31
  var _line_variantId, _line_useDayKey;
27
- return `${line.productId}:${(_line_variantId = line.variantId) != null ? _line_variantId : ''}:${(_line_useDayKey = line.useDayKey) != null ? _line_useDayKey : ''}`;
32
+ const base = `${line.productId}:${(_line_variantId = line.variantId) != null ? _line_variantId : ''}:${(_line_useDayKey = line.useDayKey) != null ? _line_useDayKey : ''}`;
33
+ const choices = modifierSelectionKey(line.modifiers);
34
+ return choices ? `${base}:${choices}` : base;
28
35
  }
29
36
  const lineKey = cartLineKey;
30
37
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-cart.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Cart v1 (AGL-293): a server-backed cart doc at\n * `hosts/{hostId}/carts/{cartId}`, keyed by an httpOnly cookie. Lines\n * store only ids + quantity — display data and prices resolve from the\n * product docs at read/checkout time, so a stale cart can never charge\n * a stale price. Pure helpers; the cart API owns I/O.\n */\n\nexport interface CartLine {\n productId: string\n /** Absent = the product's default variant. */\n variantId?: string\n quantity: number\n /**\n * The day the shopper chose to use a check-in item (AGL-4043),\n * `YYYY-MM-DD` on the store's calendar. Absent: the day they buy. Two\n * different days of one pass are two lines.\n */\n useDayKey?: string\n}\n\n/** `hosts/{hostId}/carts/{cartId}` doc. */\nexport interface HostCart {\n lines: CartLine[]\n /** Storefront customer once signed in (AGL-294 merge). */\n customerId?: string\n updatedAtMs?: number\n createdAtMs?: number\n}\n\nexport const CART_MAX_LINES = 50\nexport const CART_MAX_QUANTITY = 99\n\n/** One cart line's identity: product, variant and use day. */\nexport function cartLineKey(line: Pick<CartLine, 'productId' | 'variantId' | 'useDayKey'>): string {\n return `${line.productId}:${line.variantId ?? ''}:${line.useDayKey ?? ''}`\n}\n\nconst lineKey = cartLineKey\n\n/**\n * A line quantity, coerced to a whole finite number (AGL-2285).\n *\n * `Math.round(line.quantity)` was the whole sanitiser, and `Math.round(NaN)` is\n * `NaN` — which then walked past the guard below, because `NaN <= 0` is\n * `false`. A cart line with `quantity: NaN` (a legal Firestore double) was\n * stored, and from there `cartCount` reported `NaN` in the mini-cart badge and\n * `cart-checkout.ts` computed `itemsCents`, the platform fee and the Stripe\n * `line_items[n][quantity]` as `NaN` too — so Stripe 400s and the shopper\n * cannot check out AT ALL until they find the poisoned line and remove it.\n *\n * Answering 0 is what makes the existing `<= 0` guard do its job: an\n * unusable quantity removes the line (or refuses to add one) rather than\n * writing a value nothing downstream can compare against. The same lesson as\n * AGL-2229 one collection over — `Math.max`/`Math.min`/`Math.round` propagate\n * `NaN`, they do not discard it.\n */\nfunction wholeQuantity(value: unknown): number {\n const quantity = Math.round(Number(value ?? 0))\n return Number.isFinite(quantity) ? quantity : 0\n}\n\n/**\n * Adds/merges a line (quantities accumulate), clamped to per-line and\n * line-count caps. Quantity ≤ 0 removes the line.\n */\nexport function upsertCartLine(\n cart: Pick<HostCart, 'lines'>,\n line: CartLine,\n mode: 'add' | 'set' = 'add',\n): CartLine[] {\n const quantity = wholeQuantity(line.quantity)\n const key = lineKey(line)\n const existing = cart.lines.find((item) => lineKey(item) === key)\n if (!existing) {\n if (quantity <= 0) return cart.lines\n if (cart.lines.length >= CART_MAX_LINES) return cart.lines\n return [\n ...cart.lines,\n { ...line, quantity: Math.min(CART_MAX_QUANTITY, quantity) },\n ]\n }\n // The STORED side is coerced too (AGL-2285): a line written before this\n // guard, or by any other writer, can still hold a non-finite quantity, and\n // adding to it would carry that forward forever.\n const nextQuantity =\n mode === 'add' ? wholeQuantity(existing.quantity) + quantity : quantity\n if (nextQuantity <= 0) {\n return cart.lines.filter((item) => lineKey(item) !== key)\n }\n return cart.lines.map((item) =>\n lineKey(item) === key\n ? { ...item, quantity: Math.min(CART_MAX_QUANTITY, nextQuantity) }\n : item,\n )\n}\n\nexport function removeCartLine(\n cart: Pick<HostCart, 'lines'>,\n line: Pick<CartLine, 'productId' | 'variantId' | 'useDayKey'>,\n): CartLine[] {\n const key = lineKey(line)\n return cart.lines.filter((item) => lineKey(item) !== key)\n}\n\n/** Total units across lines (the mini-cart badge). */\nexport function cartCount(cart: Pick<HostCart, 'lines'> | undefined): number {\n // Coerced per line (AGL-2285) so one stored `NaN` cannot make the badge read\n // `NaN` for a cart that otherwise has real items in it.\n return (cart?.lines ?? []).reduce(\n (sum, line) => sum + wholeQuantity(line?.quantity),\n 0,\n )\n}\n\n/**\n * Merges a guest cart into a customer cart on sign-in (AGL-294):\n * quantities accumulate per line, capped as usual.\n */\nexport function mergeCarts(\n customerCart: Pick<HostCart, 'lines'>,\n guestCart: Pick<HostCart, 'lines'>,\n): CartLine[] {\n let lines = [...customerCart.lines]\n for (const line of guestCart.lines) {\n lines = upsertCartLine({ lines }, line, 'add')\n }\n return lines\n}\n"],"names":["CART_MAX_LINES","CART_MAX_QUANTITY","cartLineKey","line","productId","variantId","useDayKey","lineKey","wholeQuantity","value","quantity","Math","round","Number","isFinite","upsertCartLine","cart","mode","key","existing","lines","find","item","length","min","nextQuantity","filter","map","removeCartLine","cartCount","reduce","sum","mergeCarts","customerCart","guestCart"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;CAMC;AAwBD,OAAO,MAAMA,iBAAiB,GAAE;AAChC,OAAO,MAAMC,oBAAoB,GAAE;AAEnC,4DAA4D,GAC5D,OAAO,SAASC,YAAYC,IAA6D;QAC3DA,iBAAwBA;IAApD,OAAO,GAAGA,KAAKC,SAAS,CAAC,CAAC,GAAED,kBAAAA,KAAKE,SAAS,YAAdF,kBAAkB,GAAG,CAAC,GAAEA,kBAAAA,KAAKG,SAAS,YAAdH,kBAAkB,IAAI;AAC5E;AAEA,MAAMI,UAAUL;AAEhB;;;;;;;;;;;;;;;;CAgBC,GACD,SAASM,cAAcC,KAAc;IACnC,MAAMC,WAAWC,KAAKC,KAAK,CAACC,OAAOJ,gBAAAA,QAAS;IAC5C,OAAOI,OAAOC,QAAQ,CAACJ,YAAYA,WAAW;AAChD;AAEA;;;CAGC,GACD,OAAO,SAASK,eACdC,IAA6B,EAC7Bb,IAAc,EACdc,OAAsB,KAAK;IAE3B,MAAMP,WAAWF,cAAcL,KAAKO,QAAQ;IAC5C,MAAMQ,MAAMX,QAAQJ;IACpB,MAAMgB,WAAWH,KAAKI,KAAK,CAACC,IAAI,CAAC,CAACC,OAASf,QAAQe,UAAUJ;IAC7D,IAAI,CAACC,UAAU;QACb,IAAIT,YAAY,GAAG,OAAOM,KAAKI,KAAK;QACpC,IAAIJ,KAAKI,KAAK,CAACG,MAAM,IAAIvB,gBAAgB,OAAOgB,KAAKI,KAAK;QAC1D,OAAO;eACFJ,KAAKI,KAAK;YACb,aAAKjB;gBAAMO,UAAUC,KAAKa,GAAG,CAACvB,mBAAmBS;;SAClD;IACH;IACA,wEAAwE;IACxE,2EAA2E;IAC3E,iDAAiD;IACjD,MAAMe,eACJR,SAAS,QAAQT,cAAcW,SAAST,QAAQ,IAAIA,WAAWA;IACjE,IAAIe,gBAAgB,GAAG;QACrB,OAAOT,KAAKI,KAAK,CAACM,MAAM,CAAC,CAACJ,OAASf,QAAQe,UAAUJ;IACvD;IACA,OAAOF,KAAKI,KAAK,CAACO,GAAG,CAAC,CAACL,OACrBf,QAAQe,UAAUJ,MACd,aAAKI;YAAMZ,UAAUC,KAAKa,GAAG,CAACvB,mBAAmBwB;aACjDH;AAER;AAEA,OAAO,SAASM,eACdZ,IAA6B,EAC7Bb,IAA6D;IAE7D,MAAMe,MAAMX,QAAQJ;IACpB,OAAOa,KAAKI,KAAK,CAACM,MAAM,CAAC,CAACJ,OAASf,QAAQe,UAAUJ;AACvD;AAEA,oDAAoD,GACpD,OAAO,SAASW,UAAUb,IAAyC;;IACjE,6EAA6E;IAC7E,wDAAwD;IACxD,OAAO,SAACA,wBAAAA,KAAMI,KAAK,mBAAI,EAAE,EAAEU,MAAM,CAC/B,CAACC,KAAK5B,OAAS4B,MAAMvB,cAAcL,wBAAAA,KAAMO,QAAQ,GACjD;AAEJ;AAEA;;;CAGC,GACD,OAAO,SAASsB,WACdC,YAAqC,EACrCC,SAAkC;IAElC,IAAId,QAAQ;WAAIa,aAAab,KAAK;KAAC;IACnC,KAAK,MAAMjB,QAAQ+B,UAAUd,KAAK,CAAE;QAClCA,QAAQL,eAAe;YAAEK;QAAM,GAAGjB,MAAM;IAC1C;IACA,OAAOiB;AACT"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-cart.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Cart v1 (AGL-293): a server-backed cart doc at\n * `hosts/{hostId}/carts/{cartId}`, keyed by an httpOnly cookie. Lines\n * store only ids + quantity — display data and prices resolve from the\n * product docs at read/checkout time, so a stale cart can never charge\n * a stale price. Pure helpers; the cart API owns I/O.\n */\n\nimport { modifierSelectionKey, type ModifierSelection } from './product-modifiers'\n\nexport interface CartLine {\n productId: string\n /** Absent = the product's default variant. */\n variantId?: string\n quantity: number\n /**\n * The day the shopper chose to use a check-in item (AGL-4043),\n * `YYYY-MM-DD` on the store's calendar. Absent: the day they buy. Two\n * different days of one pass are two lines.\n */\n useDayKey?: string\n /**\n * The add-ons the shopper chose on the product's page (AGL-4100), ids\n * only, in the product's group order: priced from the product on every\n * read and at checkout, never stored with a price. Absent: none. The same\n * product with different choices is a different line.\n */\n modifiers?: ModifierSelection[]\n}\n\n/** `hosts/{hostId}/carts/{cartId}` doc. */\nexport interface HostCart {\n lines: CartLine[]\n /** Storefront customer once signed in (AGL-294 merge). */\n customerId?: string\n updatedAtMs?: number\n createdAtMs?: number\n}\n\nexport const CART_MAX_LINES = 50\nexport const CART_MAX_QUANTITY = 99\n\n/**\n * One cart line's identity: product, variant, use day and add-ons\n * (AGL-4100). A line with no add-ons keeps the key it always had, so a cart\n * stored before add-ons were sold online reads, merges and removes as it did.\n */\nexport function cartLineKey(\n line: Pick<CartLine, 'productId' | 'variantId' | 'useDayKey' | 'modifiers'>,\n): string {\n const base = `${line.productId}:${line.variantId ?? ''}:${line.useDayKey ?? ''}`\n const choices = modifierSelectionKey(line.modifiers)\n return choices ? `${base}:${choices}` : base\n}\n\nconst lineKey = cartLineKey\n\n/**\n * A line quantity, coerced to a whole finite number (AGL-2285).\n *\n * `Math.round(line.quantity)` was the whole sanitiser, and `Math.round(NaN)` is\n * `NaN` — which then walked past the guard below, because `NaN <= 0` is\n * `false`. A cart line with `quantity: NaN` (a legal Firestore double) was\n * stored, and from there `cartCount` reported `NaN` in the mini-cart badge and\n * `cart-checkout.ts` computed `itemsCents`, the platform fee and the Stripe\n * `line_items[n][quantity]` as `NaN` too — so Stripe 400s and the shopper\n * cannot check out AT ALL until they find the poisoned line and remove it.\n *\n * Answering 0 is what makes the existing `<= 0` guard do its job: an\n * unusable quantity removes the line (or refuses to add one) rather than\n * writing a value nothing downstream can compare against. The same lesson as\n * AGL-2229 one collection over — `Math.max`/`Math.min`/`Math.round` propagate\n * `NaN`, they do not discard it.\n */\nfunction wholeQuantity(value: unknown): number {\n const quantity = Math.round(Number(value ?? 0))\n return Number.isFinite(quantity) ? quantity : 0\n}\n\n/**\n * Adds/merges a line (quantities accumulate), clamped to per-line and\n * line-count caps. Quantity ≤ 0 removes the line.\n */\nexport function upsertCartLine(\n cart: Pick<HostCart, 'lines'>,\n line: CartLine,\n mode: 'add' | 'set' = 'add',\n): CartLine[] {\n const quantity = wholeQuantity(line.quantity)\n const key = lineKey(line)\n const existing = cart.lines.find((item) => lineKey(item) === key)\n if (!existing) {\n if (quantity <= 0) return cart.lines\n if (cart.lines.length >= CART_MAX_LINES) return cart.lines\n return [\n ...cart.lines,\n { ...line, quantity: Math.min(CART_MAX_QUANTITY, quantity) },\n ]\n }\n // The STORED side is coerced too (AGL-2285): a line written before this\n // guard, or by any other writer, can still hold a non-finite quantity, and\n // adding to it would carry that forward forever.\n const nextQuantity =\n mode === 'add' ? wholeQuantity(existing.quantity) + quantity : quantity\n if (nextQuantity <= 0) {\n return cart.lines.filter((item) => lineKey(item) !== key)\n }\n return cart.lines.map((item) =>\n lineKey(item) === key\n ? { ...item, quantity: Math.min(CART_MAX_QUANTITY, nextQuantity) }\n : item,\n )\n}\n\nexport function removeCartLine(\n cart: Pick<HostCart, 'lines'>,\n line: Pick<CartLine, 'productId' | 'variantId' | 'useDayKey' | 'modifiers'>,\n): CartLine[] {\n const key = lineKey(line)\n return cart.lines.filter((item) => lineKey(item) !== key)\n}\n\n/** Total units across lines (the mini-cart badge). */\nexport function cartCount(cart: Pick<HostCart, 'lines'> | undefined): number {\n // Coerced per line (AGL-2285) so one stored `NaN` cannot make the badge read\n // `NaN` for a cart that otherwise has real items in it.\n return (cart?.lines ?? []).reduce(\n (sum, line) => sum + wholeQuantity(line?.quantity),\n 0,\n )\n}\n\n/**\n * Merges a guest cart into a customer cart on sign-in (AGL-294):\n * quantities accumulate per line, capped as usual.\n */\nexport function mergeCarts(\n customerCart: Pick<HostCart, 'lines'>,\n guestCart: Pick<HostCart, 'lines'>,\n): CartLine[] {\n let lines = [...customerCart.lines]\n for (const line of guestCart.lines) {\n lines = upsertCartLine({ lines }, line, 'add')\n }\n return lines\n}\n"],"names":["modifierSelectionKey","CART_MAX_LINES","CART_MAX_QUANTITY","cartLineKey","line","base","productId","variantId","useDayKey","choices","modifiers","lineKey","wholeQuantity","value","quantity","Math","round","Number","isFinite","upsertCartLine","cart","mode","key","existing","lines","find","item","length","min","nextQuantity","filter","map","removeCartLine","cartCount","reduce","sum","mergeCarts","customerCart","guestCart"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;CAMC,GAED,SAASA,oBAAoB,QAAgC,yBAAqB;AA+BlF,OAAO,MAAMC,iBAAiB,GAAE;AAChC,OAAO,MAAMC,oBAAoB,GAAE;AAEnC;;;;CAIC,GACD,OAAO,SAASC,YACdC,IAA2E;QAEzCA,iBAAwBA;IAA1D,MAAMC,OAAO,GAAGD,KAAKE,SAAS,CAAC,CAAC,GAAEF,kBAAAA,KAAKG,SAAS,YAAdH,kBAAkB,GAAG,CAAC,GAAEA,kBAAAA,KAAKI,SAAS,YAAdJ,kBAAkB,IAAI;IAChF,MAAMK,UAAUT,qBAAqBI,KAAKM,SAAS;IACnD,OAAOD,UAAU,GAAGJ,KAAK,CAAC,EAAEI,SAAS,GAAGJ;AAC1C;AAEA,MAAMM,UAAUR;AAEhB;;;;;;;;;;;;;;;;CAgBC,GACD,SAASS,cAAcC,KAAc;IACnC,MAAMC,WAAWC,KAAKC,KAAK,CAACC,OAAOJ,gBAAAA,QAAS;IAC5C,OAAOI,OAAOC,QAAQ,CAACJ,YAAYA,WAAW;AAChD;AAEA;;;CAGC,GACD,OAAO,SAASK,eACdC,IAA6B,EAC7BhB,IAAc,EACdiB,OAAsB,KAAK;IAE3B,MAAMP,WAAWF,cAAcR,KAAKU,QAAQ;IAC5C,MAAMQ,MAAMX,QAAQP;IACpB,MAAMmB,WAAWH,KAAKI,KAAK,CAACC,IAAI,CAAC,CAACC,OAASf,QAAQe,UAAUJ;IAC7D,IAAI,CAACC,UAAU;QACb,IAAIT,YAAY,GAAG,OAAOM,KAAKI,KAAK;QACpC,IAAIJ,KAAKI,KAAK,CAACG,MAAM,IAAI1B,gBAAgB,OAAOmB,KAAKI,KAAK;QAC1D,OAAO;eACFJ,KAAKI,KAAK;YACb,aAAKpB;gBAAMU,UAAUC,KAAKa,GAAG,CAAC1B,mBAAmBY;;SAClD;IACH;IACA,wEAAwE;IACxE,2EAA2E;IAC3E,iDAAiD;IACjD,MAAMe,eACJR,SAAS,QAAQT,cAAcW,SAAST,QAAQ,IAAIA,WAAWA;IACjE,IAAIe,gBAAgB,GAAG;QACrB,OAAOT,KAAKI,KAAK,CAACM,MAAM,CAAC,CAACJ,OAASf,QAAQe,UAAUJ;IACvD;IACA,OAAOF,KAAKI,KAAK,CAACO,GAAG,CAAC,CAACL,OACrBf,QAAQe,UAAUJ,MACd,aAAKI;YAAMZ,UAAUC,KAAKa,GAAG,CAAC1B,mBAAmB2B;aACjDH;AAER;AAEA,OAAO,SAASM,eACdZ,IAA6B,EAC7BhB,IAA2E;IAE3E,MAAMkB,MAAMX,QAAQP;IACpB,OAAOgB,KAAKI,KAAK,CAACM,MAAM,CAAC,CAACJ,OAASf,QAAQe,UAAUJ;AACvD;AAEA,oDAAoD,GACpD,OAAO,SAASW,UAAUb,IAAyC;;IACjE,6EAA6E;IAC7E,wDAAwD;IACxD,OAAO,SAACA,wBAAAA,KAAMI,KAAK,mBAAI,EAAE,EAAEU,MAAM,CAC/B,CAACC,KAAK/B,OAAS+B,MAAMvB,cAAcR,wBAAAA,KAAMU,QAAQ,GACjD;AAEJ;AAEA;;;CAGC,GACD,OAAO,SAASsB,WACdC,YAAqC,EACrCC,SAAkC;IAElC,IAAId,QAAQ;WAAIa,aAAab,KAAK;KAAC;IACnC,KAAK,MAAMpB,QAAQkC,UAAUd,KAAK,CAAE;QAClCA,QAAQL,eAAe;YAAEK;QAAM,GAAGpB,MAAM;IAC1C;IACA,OAAOoB;AACT"}