@aglyn/plugins-commerce 1.0.0-beta.199 → 1.0.0-beta.201

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 (111) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/console/catalog-organization-card.component.js +48 -3
  3. package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
  4. package/src/lib/components/console/gift-cards-card.component.d.ts +9 -0
  5. package/src/lib/components/console/gift-cards-card.component.js +53 -28
  6. package/src/lib/components/console/gift-cards-card.component.js.map +1 -1
  7. package/src/lib/components/console/host-orders-card.component.d.ts +26 -0
  8. package/src/lib/components/console/host-orders-card.component.js +264 -194
  9. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  10. package/src/lib/components/console/order-detail-dialog.component.js +12 -0
  11. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  12. package/src/lib/components/console/pos-page.component.d.ts +14 -0
  13. package/src/lib/components/console/pos-page.component.js +86 -45
  14. package/src/lib/components/console/pos-page.component.js.map +1 -1
  15. package/src/lib/components/console/product-editor-dialog.component.js +30 -6
  16. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  17. package/src/lib/components/console/product-slugs.d.ts +43 -0
  18. package/src/lib/components/console/product-slugs.js +60 -0
  19. package/src/lib/components/console/product-slugs.js.map +1 -0
  20. package/src/lib/components/console/products-hub-card.component.d.ts +2 -2
  21. package/src/lib/components/console/products-hub-card.component.js +229 -282
  22. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  23. package/src/lib/components/console/products-hub-zone.component.d.ts +10 -5
  24. package/src/lib/components/console/products-hub-zone.component.js +32 -9
  25. package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
  26. package/src/lib/components/console/recovery-queue-card.component.d.ts +37 -5
  27. package/src/lib/components/console/recovery-queue-card.component.js +107 -86
  28. package/src/lib/components/console/recovery-queue-card.component.js.map +1 -1
  29. package/src/lib/components/console/reservations-card.component.js +15 -3
  30. package/src/lib/components/console/reservations-card.component.js.map +1 -1
  31. package/src/lib/components/console/smart-collections.d.ts +32 -0
  32. package/src/lib/components/console/smart-collections.js +57 -0
  33. package/src/lib/components/console/smart-collections.js.map +1 -0
  34. package/src/lib/components/console/stock-movements-card.component.d.ts +13 -4
  35. package/src/lib/components/console/stock-movements-card.component.js +142 -180
  36. package/src/lib/components/console/stock-movements-card.component.js.map +1 -1
  37. package/src/lib/components/product-grid.d.ts +5 -0
  38. package/src/lib/components/product-grid.js +95 -34
  39. package/src/lib/components/product-grid.js.map +1 -1
  40. package/src/lib/constants/orders-list-query.d.ts +40 -0
  41. package/src/lib/constants/orders-list-query.js +205 -0
  42. package/src/lib/constants/orders-list-query.js.map +1 -0
  43. package/src/lib/constants/product-list-query.d.ts +42 -0
  44. package/src/lib/constants/product-list-query.js +217 -0
  45. package/src/lib/constants/product-list-query.js.map +1 -0
  46. package/src/lib/constants/stock-movement-query.d.ts +27 -0
  47. package/src/lib/constants/stock-movement-query.js +110 -0
  48. package/src/lib/constants/stock-movement-query.js.map +1 -0
  49. package/src/lib/constants/storefront-catalog-query.d.ts +103 -0
  50. package/src/lib/constants/storefront-catalog-query.js +312 -0
  51. package/src/lib/constants/storefront-catalog-query.js.map +1 -0
  52. package/src/lib/model/checkout-recovery.d.ts +37 -0
  53. package/src/lib/model/checkout-recovery.js +44 -0
  54. package/src/lib/model/checkout-recovery.js.map +1 -0
  55. package/src/lib/model/commerce-orders.d.ts +7 -0
  56. package/src/lib/model/commerce-orders.js.map +1 -1
  57. package/src/lib/model/commerce.d.ts +80 -0
  58. package/src/lib/model/commerce.js +67 -6
  59. package/src/lib/model/commerce.js.map +1 -1
  60. package/src/lib/model/gift-card-search.d.ts +24 -0
  61. package/src/lib/model/gift-card-search.js +48 -0
  62. package/src/lib/model/gift-card-search.js.map +1 -0
  63. package/src/lib/model/index.d.ts +1 -0
  64. package/src/lib/model/index.js +1 -0
  65. package/src/lib/model/index.js.map +1 -1
  66. package/src/lib/model/order-list-fields.d.ts +80 -0
  67. package/src/lib/model/order-list-fields.js +112 -0
  68. package/src/lib/model/order-list-fields.js.map +1 -0
  69. package/src/lib/server/billing-webhook.js +180 -99
  70. package/src/lib/server/billing-webhook.js.map +1 -1
  71. package/src/lib/server/cancel-order.js +6 -6
  72. package/src/lib/server/cancel-order.js.map +1 -1
  73. package/src/lib/server/cart-checkout.js +9 -2
  74. package/src/lib/server/cart-checkout.js.map +1 -1
  75. package/src/lib/server/catalog.d.ts +46 -30
  76. package/src/lib/server/catalog.js +507 -170
  77. package/src/lib/server/catalog.js.map +1 -1
  78. package/src/lib/server/checkout.js +3 -1
  79. package/src/lib/server/checkout.js.map +1 -1
  80. package/src/lib/server/collection-membership.d.ts +35 -0
  81. package/src/lib/server/collection-membership.js +172 -0
  82. package/src/lib/server/collection-membership.js.map +1 -0
  83. package/src/lib/server/draft-order.js +5 -3
  84. package/src/lib/server/draft-order.js.map +1 -1
  85. package/src/lib/server/gift-cards.js +7 -1
  86. package/src/lib/server/gift-cards.js.map +1 -1
  87. package/src/lib/server/member-name-search.d.ts +17 -0
  88. package/src/lib/server/member-name-search.js +23 -1
  89. package/src/lib/server/member-name-search.js.map +1 -1
  90. package/src/lib/server/membership-account.js +7 -3
  91. package/src/lib/server/membership-account.js.map +1 -1
  92. package/src/lib/server/membership-admin-password.js +8 -2
  93. package/src/lib/server/membership-admin-password.js.map +1 -1
  94. package/src/lib/server/membership-recover.js +4 -1
  95. package/src/lib/server/membership-recover.js.map +1 -1
  96. package/src/lib/server/membership-register.js +3 -1
  97. package/src/lib/server/membership-register.js.map +1 -1
  98. package/src/lib/server/pos-order.js +9 -5
  99. package/src/lib/server/pos-order.js.map +1 -1
  100. package/src/lib/server/process-abandoned.js +20 -7
  101. package/src/lib/server/process-abandoned.js.map +1 -1
  102. package/src/lib/server/process-restock.js +6 -2
  103. package/src/lib/server/process-restock.js.map +1 -1
  104. package/src/lib/server/reserve-stock.js +3 -4
  105. package/src/lib/server/reserve-stock.js.map +1 -1
  106. package/src/lib/server/reserve.js +3 -1
  107. package/src/lib/server/reserve.js.map +1 -1
  108. package/src/lib/server/site-page-enricher.js +3 -3
  109. package/src/lib/server/site-page-enricher.js.map +1 -1
  110. package/src/lib/server.js +4 -0
  111. package/src/lib/server.js.map +1 -1
@@ -18,6 +18,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
18
18
  * limitations under the License.
19
19
  */ import * as Aglyn from "@aglyn/aglyn";
20
20
  import { mdiMagnify, mdiViewGridOutline } from "@aglyn/shared-data-mdi";
21
+ import { ListQueryNotices, listQueryRefusals } from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
21
22
  import Box from "@mui/material/Box";
22
23
  import Button from "@mui/material/Button";
23
24
  import Card from "@mui/material/Card";
@@ -34,6 +35,7 @@ import TextField from "@mui/material/TextField";
34
35
  import Typography from "@mui/material/Typography";
35
36
  import { forwardRef, useCallback, useEffect, useRef, useState } from "react";
36
37
  import { BUNDLE_ID } from "../constants/bundle-common.js";
38
+ import { STOREFRONT_CATALOG_DECLARATION, STOREFRONT_CATALOG_HEADERS, STOREFRONT_CATALOG_OPTIONS } from "../constants/storefront-catalog-query.js";
37
39
  import { generatePresetId } from "../utils/generate-preset-id.js";
38
40
  // Component ids are persisted in screen documents; never rename.
39
41
  export const ID = 'product-grid';
@@ -129,8 +131,13 @@ function priceLabel(item) {
129
131
  * optional debounced search, category chips, type chips, a sort
130
132
  * select, and Load more paging — all resolved server-side by the
131
133
  * catalog handler so large catalogs stay cheap.
134
+ *
135
+ * Nothing is narrowed here (AGL-3321): every control is a parameter of the
136
+ * catalog query, and the rows shown are the rows it returned. A control the
137
+ * query could not combine with another comes back in `refused` and is said
138
+ * above the grid, rather than applied to the cards that happen to be loaded.
132
139
  */ const ProductGrid = /*#__PURE__*/ forwardRef((props, ref)=>{
133
- var _ref, _ref1, _ref2, _ref3;
140
+ var _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
134
141
  var _site_pageData_commerce_grids, _site_pageData_commerce, _site_pageData;
135
142
  const { source, collectionId, collectionSlug: collectionSlugProp, categoryId, categorySlug, tag, sort: sortProp, columns, maxItems, emptyText, showFilters, showSearch, showCategories, showSort, showTypeFilter, showPriceFilter, pageSize } = props, rest = _object_without_properties_loose(props, [
136
143
  "source",
@@ -166,11 +173,13 @@ function priceLabel(item) {
166
173
  const seed = (_site_pageData = site.pageData) == null ? void 0 : (_site_pageData_commerce = _site_pageData.commerce) == null ? void 0 : (_site_pageData_commerce_grids = _site_pageData_commerce.grids) == null ? void 0 : _site_pageData_commerce_grids[nodeId];
167
174
  const [items, setItems] = useState((_ref = seed == null ? void 0 : seed.items) != null ? _ref : null);
168
175
  const [sort, setSort] = useState(sortProp);
176
+ // The In stock chip, asked of the catalog (`inStock=1`), never matched
177
+ // over the cards on screen (AGL-3321).
169
178
  const [inStockOnly, setInStockOnly] = useState(false);
170
179
  const [activeTag, setActiveTag] = useState('');
171
180
  // Catalog UX state (AGL-561): the search box debounces into
172
181
  // `query`; a chip pick overrides any pinned category ('*' = All);
173
- // paging appends via `nextOffset` from the catalog handler.
182
+ // paging appends via the handler's `nextCursor` (AGL-3321).
174
183
  const [searchInput, setSearchInput] = useState('');
175
184
  const [query, setQuery] = useState('');
176
185
  const [activeCategoryId, setActiveCategoryId] = useState('');
@@ -183,7 +192,10 @@ function priceLabel(item) {
183
192
  const [priceBounds, setPriceBounds] = useState((_ref2 = seed == null ? void 0 : seed.priceBounds) != null ? _ref2 : null);
184
193
  const [priceInput, setPriceInput] = useState(null);
185
194
  const [priceFilter, setPriceFilter] = useState(null);
186
- const [nextOffset, setNextOffset] = useState((_ref3 = seed == null ? void 0 : seed.nextOffset) != null ? _ref3 : null);
195
+ const [nextCursor, setNextCursor] = useState((_ref3 = seed == null ? void 0 : seed.nextCursor) != null ? _ref3 : null);
196
+ // What the query could not apply, said above the grid (AGL-3321).
197
+ const [refused, setRefused] = useState((_ref4 = seed == null ? void 0 : seed.refused) != null ? _ref4 : []);
198
+ const [notices, setNotices] = useState((_ref5 = seed == null ? void 0 : seed.notices) != null ? _ref5 : []);
187
199
  const [loadingMore, setLoadingMore] = useState(false);
188
200
  const fetchSeq = useRef(0);
189
201
  const collectionSlug = source === 'collection' ? collectionSlugProp || collectionSlugFromLocation() : collectionSlugProp;
@@ -204,7 +216,7 @@ function priceLabel(item) {
204
216
  }, [
205
217
  priceInput
206
218
  ]);
207
- const loadPage = useCallback(async (offset)=>{
219
+ const loadPage = useCallback(async (after)=>{
208
220
  if (!hostId) return;
209
221
  const seq = ++fetchSeq.current;
210
222
  const params = new URLSearchParams({
@@ -221,9 +233,12 @@ function priceLabel(item) {
221
233
  } else if (!activeCategoryId && source === 'category' && categorySlug) {
222
234
  params.set('category', categorySlug);
223
235
  }
224
- if (source === 'tag' && tag) params.set('tag', tag);
236
+ // A tag chip overrides a pinned tag, as a category chip does.
237
+ const effectiveTag = activeTag || (source === 'tag' ? tag != null ? tag : '' : '');
238
+ if (effectiveTag) params.set('tag', effectiveTag);
225
239
  if (query) params.set('q', query);
226
240
  if (activeType) params.set('type', activeType);
241
+ if (inStockOnly) params.set('inStock', '1');
227
242
  // Slider dollars → API cents (AGL-564).
228
243
  if (priceFilter) {
229
244
  params.set('minPriceCents', String(priceFilter[0] * 100));
@@ -232,8 +247,8 @@ function priceLabel(item) {
232
247
  if (sort) params.set('sort', sort);
233
248
  const limit = pageLimit || maxItems;
234
249
  if (limit) params.set('limit', String(limit));
235
- if (offset) params.set('offset', String(offset));
236
- if ((showCategories || showPriceFilter) && offset === 0) {
250
+ if (after) params.set('after', after);
251
+ if ((showCategories || showPriceFilter) && !after) {
237
252
  params.set('facets', '1');
238
253
  }
239
254
  try {
@@ -242,11 +257,15 @@ function priceLabel(item) {
242
257
  const payload = await response.json();
243
258
  if (seq !== fetchSeq.current) return;
244
259
  const pageItems = (_ref = payload == null ? void 0 : payload.items) != null ? _ref : [];
245
- setItems((prev)=>offset && prev ? [
260
+ setItems((prev)=>after && prev ? [
246
261
  ...prev,
247
262
  ...pageItems
248
263
  ] : pageItems);
249
- setNextOffset(typeof (payload == null ? void 0 : payload.nextOffset) === 'number' ? payload.nextOffset : null);
264
+ setNextCursor(typeof (payload == null ? void 0 : payload.nextCursor) === 'string' ? payload.nextCursor : null);
265
+ if (!after) {
266
+ setRefused(Array.isArray(payload == null ? void 0 : payload.refused) ? payload.refused : []);
267
+ setNotices(Array.isArray(payload == null ? void 0 : payload.notices) ? payload.notices : []);
268
+ }
250
269
  if (Array.isArray(payload == null ? void 0 : payload.categories)) {
251
270
  setCategories(payload.categories);
252
271
  }
@@ -256,8 +275,8 @@ function priceLabel(item) {
256
275
  }
257
276
  } catch (unused) {
258
277
  if (seq !== fetchSeq.current) return;
259
- if (!offset) setItems([]);
260
- setNextOffset(null);
278
+ if (!after) setItems([]);
279
+ setNextCursor(null);
261
280
  }
262
281
  }, [
263
282
  hostId,
@@ -270,7 +289,9 @@ function priceLabel(item) {
270
289
  maxItems,
271
290
  query,
272
291
  activeCategoryId,
292
+ activeTag,
273
293
  activeType,
294
+ inStockOnly,
274
295
  priceFilter,
275
296
  pageLimit,
276
297
  pinnedCategoryId,
@@ -287,7 +308,7 @@ function priceLabel(item) {
287
308
  seededRef.current = false;
288
309
  return;
289
310
  }
290
- void loadPage(0);
311
+ void loadPage();
291
312
  // Invalidate any in-flight request on dep change or unmount.
292
313
  return ()=>{
293
314
  fetchSeq.current++;
@@ -306,21 +327,21 @@ function priceLabel(item) {
306
327
  md: `repeat(${desktopColumns}, 1fr)`
307
328
  }
308
329
  };
309
- let visible = hostId ? items : SAMPLE_ITEMS;
310
- if (visible && showFilters) {
311
- if (inStockOnly) visible = visible.filter((item)=>!item.soldOut);
312
- if (activeTag) {
313
- visible = visible.filter((item)=>{
314
- var _item_tags;
315
- return ((_item_tags = item.tags) != null ? _item_tags : []).includes(activeTag);
316
- });
317
- }
318
- }
330
+ // The rows the query returned, as returned (AGL-3321).
331
+ const visible = hostId ? items : SAMPLE_ITEMS;
332
+ // Tag chips: the tags on the cards showing, as CHOICES — picking one asks
333
+ // the catalog for that tag, never narrows these cards. The picked one
334
+ // stays offered whatever the page it brought back carries.
319
335
  const facetTags = showFilters ? [
320
- ...new Set((items != null ? items : []).flatMap((item)=>{
321
- var _item_tags;
322
- return (_item_tags = item.tags) != null ? _item_tags : [];
323
- }))
336
+ ...new Set([
337
+ ...activeTag ? [
338
+ activeTag
339
+ ] : [],
340
+ ...(items != null ? items : []).flatMap((item)=>{
341
+ var _item_tags;
342
+ return (_item_tags = item.tags) != null ? _item_tags : [];
343
+ })
344
+ ])
324
345
  ].slice(0, 12) : [];
325
346
  if (hostId && items === null) {
326
347
  return(// MERGE, never replace (AGL-1450). The skeleton branch replaced the
@@ -416,14 +437,28 @@ function priceLabel(item) {
416
437
  size: "small",
417
438
  variant: selectedCategoryId ? 'outlined' : 'filled',
418
439
  color: selectedCategoryId ? 'default' : 'primary',
419
- onClick: ()=>setActiveCategoryId('*')
440
+ onClick: ()=>{
441
+ // One array clause per query (AGL-3321): a chip asked
442
+ // for while searching would be refused, so picking one
443
+ // clears the search — and a tag chip — rather than
444
+ // leaving a chip that does nothing.
445
+ setSearchInput('');
446
+ setQuery('');
447
+ setActiveTag('');
448
+ setActiveCategoryId('*');
449
+ }
420
450
  }),
421
451
  chipCategories.map((category)=>/*#__PURE__*/ _jsx(Chip, {
422
452
  label: category.name,
423
453
  size: "small",
424
454
  variant: selectedCategoryId === category.id ? 'filled' : 'outlined',
425
455
  color: selectedCategoryId === category.id ? 'secondary' : 'default',
426
- onClick: ()=>setActiveCategoryId((prev)=>(prev || pinnedCategoryId) === category.id ? '*' : category.id)
456
+ onClick: ()=>{
457
+ setSearchInput('');
458
+ setQuery('');
459
+ setActiveTag('');
460
+ setActiveCategoryId((prev)=>(prev || pinnedCategoryId) === category.id ? '*' : category.id);
461
+ }
427
462
  }, category.id))
428
463
  ]
429
464
  }) : null,
@@ -539,10 +574,10 @@ function priceLabel(item) {
539
574
  children: /*#__PURE__*/ _jsx(Typography, {
540
575
  variant: "body2",
541
576
  color: "text.secondary",
542
- children: query || activeType || selectedCategoryId || priceFilter ? 'No products match — try clearing a filter.' : emptyText || 'No products here yet — check back soon.'
577
+ children: query || activeType || selectedCategoryId || activeTag || inStockOnly || priceFilter ? 'No products match — try clearing a filter.' : emptyText || 'No products here yet — check back soon.'
543
578
  })
544
579
  });
545
- const loadMore = hostId && pageLimit && nextOffset != null ? /*#__PURE__*/ _jsx(Box, {
580
+ const loadMore = hostId && pageLimit && nextCursor != null ? /*#__PURE__*/ _jsx(Box, {
546
581
  sx: {
547
582
  display: 'flex',
548
583
  justifyContent: 'center',
@@ -552,9 +587,9 @@ function priceLabel(item) {
552
587
  variant: "outlined",
553
588
  disabled: loadingMore,
554
589
  onClick: async ()=>{
555
- if (nextOffset == null || loadingMore) return;
590
+ if (nextCursor == null || loadingMore) return;
556
591
  setLoadingMore(true);
557
- await loadPage(nextOffset);
592
+ await loadPage(nextCursor);
558
593
  setLoadingMore(false);
559
594
  },
560
595
  children: loadingMore ? 'Loading…' : 'Load more'
@@ -631,12 +666,31 @@ function priceLabel(item) {
631
666
  })
632
667
  });
633
668
  const body = visible && visible.length === 0 ? emptyState : grid;
669
+ const unapplied = hostId && (refused.length || notices.length) ? /*#__PURE__*/ _jsx(Box, {
670
+ sx: {
671
+ mb: 2
672
+ },
673
+ children: /*#__PURE__*/ _jsx(ListQueryNotices, {
674
+ refused: listQueryRefusals(refused, {
675
+ fields: STOREFRONT_CATALOG_DECLARATION.fields,
676
+ headers: STOREFRONT_CATALOG_HEADERS,
677
+ options: _extends({}, STOREFRONT_CATALOG_OPTIONS, {
678
+ category: categories.map((category)=>({
679
+ value: category.id,
680
+ label: category.name
681
+ }))
682
+ })
683
+ }),
684
+ notices: notices
685
+ })
686
+ }) : null;
634
687
  if (!showFilters) {
635
688
  return /*#__PURE__*/ _jsxs(Box, _extends({
636
689
  ref: ref
637
690
  }, rest, {
638
691
  children: [
639
692
  controls,
693
+ unapplied,
640
694
  body,
641
695
  loadMore
642
696
  ]
@@ -668,7 +722,13 @@ function priceLabel(item) {
668
722
  size: "small",
669
723
  variant: activeTag === facetTag ? 'filled' : 'outlined',
670
724
  color: activeTag === facetTag ? 'primary' : 'default',
671
- onClick: ()=>setActiveTag((prev)=>prev === facetTag ? '' : facetTag)
725
+ onClick: ()=>{
726
+ // The same one-array-clause rule as the category chips.
727
+ setSearchInput('');
728
+ setQuery('');
729
+ setActiveCategoryId((prev)=>prev && prev !== '*' ? '' : prev);
730
+ setActiveTag((prev)=>prev === facetTag ? '' : facetTag);
731
+ }
672
732
  }, facetTag)),
673
733
  /*#__PURE__*/ _jsx(Box, {
674
734
  sx: {
@@ -683,6 +743,7 @@ function priceLabel(item) {
683
743
  })
684
744
  ]
685
745
  }),
746
+ unapplied,
686
747
  body,
687
748
  loadMore
688
749
  ]
@@ -812,7 +873,7 @@ export const schema = {
812
873
  {
813
874
  name: 'showSearch',
814
875
  label: 'Search box',
815
- description: 'Visitor search box above the grid — matches product names, ' + 'descriptions, and tags (server-side).',
876
+ description: 'Visitor search box above the grid — matches words at the start of ' + 'product names across the whole catalog (server-side).',
816
877
  component: Aglyn.FieldComponentType.CHECKBOX
817
878
  },
818
879
  {
@@ -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 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 { generatePresetId } from '../utils/generate-preset-id'\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\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}\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 * 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 nextOffset?: number\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 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 */\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 showFilters,\n showSearch,\n showCategories,\n showSort,\n showTypeFilter,\n showPriceFilter,\n pageSize,\n ...rest\n } = props\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 { hostId } = site\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 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 `nextOffset` from the catalog handler.\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 [nextOffset, setNextOffset] = useState<number | null>(\n seed?.nextOffset ?? null,\n )\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 // 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 (offset: number): 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 if (source === 'tag' && tag) params.set('tag', tag)\n if (query) params.set('q', query)\n if (activeType) params.set('type', activeType)\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 (offset) params.set('offset', String(offset))\n if ((showCategories || showPriceFilter) && offset === 0) {\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 offset && prev ? [...prev, ...pageItems] : pageItems,\n )\n setNextOffset(\n typeof payload?.nextOffset === 'number' ? payload.nextOffset : null,\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 (!offset) setItems([])\n setNextOffset(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 activeType,\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(0)\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 xs: 'repeat(1, 1fr)',\n sm: 'repeat(2, 1fr)',\n md: `repeat(${desktopColumns}, 1fr)`,\n },\n }\n let visible = hostId ? items : SAMPLE_ITEMS\n if (visible && showFilters) {\n if (inStockOnly) visible = visible.filter((item) => !item.soldOut)\n if (activeTag) {\n visible = visible.filter((item) =>\n (item.tags ?? []).includes(activeTag),\n )\n }\n }\n const facetTags = showFilters\n ? [...new Set((items ?? []).flatMap((item) => item.tags ?? []))].slice(\n 0,\n 12,\n )\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={() => setActiveCategoryId('*')}\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 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 if (visible && visible.length === 0 && !hasCatalogControls) {\n return (\n <Box ref={ref} {...rest} sx={[{ p: 3, textAlign: 'center' }, ...nodeSx]}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {emptyText || 'No products here yet — check back soon.'}\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 {query || activeType || selectedCategoryId || priceFilter\n ? 'No products match — try clearing a filter.'\n : emptyText || 'No products here yet — check back soon.'}\n </Typography>\n </Box>\n )\n const loadMore =\n hostId && pageLimit && nextOffset != null ? (\n <Box sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}>\n <Button\n variant=\"outlined\"\n disabled={loadingMore}\n onClick={async () => {\n if (nextOffset == null || loadingMore) return\n setLoadingMore(true)\n await loadPage(nextOffset)\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 key={item.id} variant=\"outlined\">\n <CardActionArea\n href={hostId ? `/products/${item.slug}` : undefined}\n disabled={!hostId}\n >\n {imageUrl ? (\n <CardMedia\n component=\"img\"\n image={imageUrl}\n alt={item.name}\n sx={{ height: 160, objectFit: 'cover' }}\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 <Box sx={{ height: 160, bgcolor: 'action.hover' }} />\n )}\n <CardContent>\n <Typography variant=\"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 </Card>\n )\n })}\n </Box>\n )\n\n const body = visible && visible.length === 0 ? emptyState : grid\n if (!showFilters) {\n return (\n <Box ref={ref} {...rest}>\n {controls}\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 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 {body}\n {loadMore}\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 screens 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: '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 product names, ' +\n 'descriptions, and tags (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}\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","Box","Button","Card","CardActionArea","CardContent","CardMedia","Chip","InputAdornment","MenuItem","Skeleton","Slider","SvgIcon","TextField","Typography","forwardRef","useCallback","useEffect","useRef","useState","BUNDLE_ID","generatePresetId","ID","SAMPLE_ITEMS","id","name","slug","priceUsd","maxPriceUsd","soldOut","compareAtPriceUsd","SAMPLE_CATEGORIES","SAMPLE_PRICE_BOUNDS","minCents","maxCents","TYPE_OPTIONS","value","label","SORT_OPTIONS","collectionSlugFromLocation","window","match","location","pathname","decodeURIComponent","priceLabel","item","ProductGrid","props","ref","site","source","collectionId","collectionSlug","collectionSlugProp","categoryId","categorySlug","tag","sort","sortProp","columns","maxItems","emptyText","showFilters","showSearch","showCategories","showSort","showTypeFilter","showPriceFilter","pageSize","rest","nodeSx","Array","isArray","useSite","hostId","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","nextOffset","setNextOffset","loadingMore","setLoadingMore","fetchSeq","pageLimit","Number","Math","floor","pinnedCategoryId","timer","setTimeout","trim","clearTimeout","loadPage","offset","seq","current","params","URLSearchParams","set","effectiveCategoryId","String","limit","response","fetch","toString","payload","json","pageItems","prev","bounds","seededRef","Boolean","desktopColumns","min","max","gridSx","display","gap","gridTemplateColumns","xs","sm","md","visible","filter","tags","includes","facetTags","Set","flatMap","slice","sx","from","length","_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","category","option","flex","select","some","minWidth","maxWidth","px","whiteSpace","step","disableSwap","disabled","_event","valueLabelDisplay","valueLabelFormat","getAriaLabel","p","textAlign","emptyState","loadMore","justifyContent","mt","grid","imageUrl","siteRelativeMediaSrc","href","undefined","component","image","alt","objectFit","DEFERRED_IMAGE_ATTRIBUTES","bgcolor","noWrap","textDecoration","body","facetTag","displayName","schema","$id","pluginId","description","ComponentCategory","COMMERCE","icon","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","FieldComponentType","SELECT","options","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'\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\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}\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 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 showFilters,\n showSearch,\n showCategories,\n showSort,\n showTypeFilter,\n showPriceFilter,\n pageSize,\n ...rest\n } = props\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 { hostId } = site\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 // 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 xs: 'repeat(1, 1fr)',\n sm: 'repeat(2, 1fr)',\n md: `repeat(${desktopColumns}, 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 if (visible && visible.length === 0 && !hasCatalogControls) {\n return (\n <Box ref={ref} {...rest} sx={[{ p: 3, textAlign: 'center' }, ...nodeSx]}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {emptyText || 'No products here yet — check back soon.'}\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 {query ||\n activeType ||\n selectedCategoryId ||\n activeTag ||\n inStockOnly ||\n priceFilter\n ? 'No products match — try clearing a filter.'\n : emptyText || 'No products here yet — check back soon.'}\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 key={item.id} variant=\"outlined\">\n <CardActionArea\n href={hostId ? `/products/${item.slug}` : undefined}\n disabled={!hostId}\n >\n {imageUrl ? (\n <CardMedia\n component=\"img\"\n image={imageUrl}\n alt={item.name}\n sx={{ height: 160, objectFit: 'cover' }}\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 <Box sx={{ height: 160, bgcolor: 'action.hover' }} />\n )}\n <CardContent>\n <Typography variant=\"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 </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)\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 screens 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: '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}\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","ID","SAMPLE_ITEMS","id","name","slug","priceUsd","maxPriceUsd","soldOut","compareAtPriceUsd","SAMPLE_CATEGORIES","SAMPLE_PRICE_BOUNDS","minCents","maxCents","TYPE_OPTIONS","value","label","SORT_OPTIONS","collectionSlugFromLocation","window","match","location","pathname","decodeURIComponent","priceLabel","item","ProductGrid","props","ref","site","source","collectionId","collectionSlug","collectionSlugProp","categoryId","categorySlug","tag","sort","sortProp","columns","maxItems","emptyText","showFilters","showSearch","showCategories","showSort","showTypeFilter","showPriceFilter","pageSize","rest","nodeSx","Array","isArray","useSite","hostId","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","timer","setTimeout","trim","clearTimeout","loadPage","after","seq","current","params","URLSearchParams","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","length","_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","category","option","flex","select","some","minWidth","maxWidth","px","whiteSpace","step","disableSwap","disabled","_event","valueLabelDisplay","valueLabelFormat","getAriaLabel","p","textAlign","emptyState","loadMore","justifyContent","mt","grid","imageUrl","siteRelativeMediaSrc","href","undefined","component","image","alt","objectFit","DEFERRED_IMAGE_ATTRIBUTES","bgcolor","noWrap","textDecoration","body","unapplied","fields","headers","options","facetTag","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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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter';
18
+ import type { ListFilterClause, ListFilterOption } from '@aglyn/shared-ui-jsx/const/list-grid-filter';
19
+ import type { ListQueryDeclaration } from '@aglyn/shared-ui-jsx/const/list-query-plan';
20
+ /** The grid columns, each as the query reads it. */
21
+ export declare const ORDER_LIST_FIELDS: readonly ListFilterField[];
22
+ /** The list's query: its fields, its one order and its search. */
23
+ export declare const ORDER_LIST_QUERY: ListQueryDeclaration;
24
+ export declare const ORDER_LIST_HEADERS: Readonly<Record<string, string>>;
25
+ /** The fields picked from choices, which the panel shows as a select. */
26
+ export declare const ORDER_LIST_SELECT_FIELDS: readonly string[];
27
+ export declare const ORDER_STATUS_OPTIONS: readonly ListFilterOption[];
28
+ export declare const ORDER_CHANNEL_OPTIONS: readonly ListFilterOption[];
29
+ export declare const ORDER_DISPUTE_OPTIONS: readonly ListFilterOption[];
30
+ /** The clause the open-dispute banner's "Show them" sets. */
31
+ export declare const OPEN_DISPUTE_CLAUSE: ListFilterClause;
32
+ /**
33
+ * The Customer clause the CRM's link seeds from `?email=` (AGL-2622).
34
+ *
35
+ * A whole address is `is`, served by the lower-cased key, so it finds that
36
+ * buyer and no one whose address merely begins the same way. Anything else —
37
+ * a domain, `@acme.com`, a name — is `contains`, a word prefix of the
38
+ * address, so a domain finds every buyer at a company.
39
+ */
40
+ export declare function ordersCustomerClause(raw: string): ListFilterClause | null;