@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.
- package/package.json +13 -13
- package/src/lib/components/console/catalog-organization-card.component.js +48 -3
- package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
- package/src/lib/components/console/gift-cards-card.component.d.ts +9 -0
- package/src/lib/components/console/gift-cards-card.component.js +53 -28
- package/src/lib/components/console/gift-cards-card.component.js.map +1 -1
- package/src/lib/components/console/host-orders-card.component.d.ts +26 -0
- package/src/lib/components/console/host-orders-card.component.js +264 -194
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/order-detail-dialog.component.js +12 -0
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/pos-page.component.d.ts +14 -0
- package/src/lib/components/console/pos-page.component.js +86 -45
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/product-editor-dialog.component.js +30 -6
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-slugs.d.ts +43 -0
- package/src/lib/components/console/product-slugs.js +60 -0
- package/src/lib/components/console/product-slugs.js.map +1 -0
- package/src/lib/components/console/products-hub-card.component.d.ts +2 -2
- package/src/lib/components/console/products-hub-card.component.js +229 -282
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/products-hub-zone.component.d.ts +10 -5
- package/src/lib/components/console/products-hub-zone.component.js +32 -9
- package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
- package/src/lib/components/console/recovery-queue-card.component.d.ts +37 -5
- package/src/lib/components/console/recovery-queue-card.component.js +107 -86
- package/src/lib/components/console/recovery-queue-card.component.js.map +1 -1
- package/src/lib/components/console/reservations-card.component.js +15 -3
- package/src/lib/components/console/reservations-card.component.js.map +1 -1
- package/src/lib/components/console/smart-collections.d.ts +32 -0
- package/src/lib/components/console/smart-collections.js +57 -0
- package/src/lib/components/console/smart-collections.js.map +1 -0
- package/src/lib/components/console/stock-movements-card.component.d.ts +13 -4
- package/src/lib/components/console/stock-movements-card.component.js +142 -180
- package/src/lib/components/console/stock-movements-card.component.js.map +1 -1
- package/src/lib/components/product-grid.d.ts +5 -0
- package/src/lib/components/product-grid.js +95 -34
- package/src/lib/components/product-grid.js.map +1 -1
- package/src/lib/constants/orders-list-query.d.ts +40 -0
- package/src/lib/constants/orders-list-query.js +205 -0
- package/src/lib/constants/orders-list-query.js.map +1 -0
- package/src/lib/constants/product-list-query.d.ts +42 -0
- package/src/lib/constants/product-list-query.js +217 -0
- package/src/lib/constants/product-list-query.js.map +1 -0
- package/src/lib/constants/stock-movement-query.d.ts +27 -0
- package/src/lib/constants/stock-movement-query.js +110 -0
- package/src/lib/constants/stock-movement-query.js.map +1 -0
- package/src/lib/constants/storefront-catalog-query.d.ts +103 -0
- package/src/lib/constants/storefront-catalog-query.js +312 -0
- package/src/lib/constants/storefront-catalog-query.js.map +1 -0
- package/src/lib/model/checkout-recovery.d.ts +37 -0
- package/src/lib/model/checkout-recovery.js +44 -0
- package/src/lib/model/checkout-recovery.js.map +1 -0
- package/src/lib/model/commerce-orders.d.ts +7 -0
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce.d.ts +80 -0
- package/src/lib/model/commerce.js +67 -6
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/gift-card-search.d.ts +24 -0
- package/src/lib/model/gift-card-search.js +48 -0
- package/src/lib/model/gift-card-search.js.map +1 -0
- package/src/lib/model/index.d.ts +1 -0
- package/src/lib/model/index.js +1 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/order-list-fields.d.ts +80 -0
- package/src/lib/model/order-list-fields.js +112 -0
- package/src/lib/model/order-list-fields.js.map +1 -0
- package/src/lib/server/billing-webhook.js +180 -99
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cancel-order.js +6 -6
- package/src/lib/server/cancel-order.js.map +1 -1
- package/src/lib/server/cart-checkout.js +9 -2
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/catalog.d.ts +46 -30
- package/src/lib/server/catalog.js +507 -170
- package/src/lib/server/catalog.js.map +1 -1
- package/src/lib/server/checkout.js +3 -1
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/collection-membership.d.ts +35 -0
- package/src/lib/server/collection-membership.js +172 -0
- package/src/lib/server/collection-membership.js.map +1 -0
- package/src/lib/server/draft-order.js +5 -3
- package/src/lib/server/draft-order.js.map +1 -1
- package/src/lib/server/gift-cards.js +7 -1
- package/src/lib/server/gift-cards.js.map +1 -1
- package/src/lib/server/member-name-search.d.ts +17 -0
- package/src/lib/server/member-name-search.js +23 -1
- package/src/lib/server/member-name-search.js.map +1 -1
- package/src/lib/server/membership-account.js +7 -3
- package/src/lib/server/membership-account.js.map +1 -1
- package/src/lib/server/membership-admin-password.js +8 -2
- package/src/lib/server/membership-admin-password.js.map +1 -1
- package/src/lib/server/membership-recover.js +4 -1
- package/src/lib/server/membership-recover.js.map +1 -1
- package/src/lib/server/membership-register.js +3 -1
- package/src/lib/server/membership-register.js.map +1 -1
- package/src/lib/server/pos-order.js +9 -5
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/process-abandoned.js +20 -7
- package/src/lib/server/process-abandoned.js.map +1 -1
- package/src/lib/server/process-restock.js +6 -2
- package/src/lib/server/process-restock.js.map +1 -1
- package/src/lib/server/reserve-stock.js +3 -4
- package/src/lib/server/reserve-stock.js.map +1 -1
- package/src/lib/server/reserve.js +3 -1
- package/src/lib/server/reserve.js.map +1 -1
- package/src/lib/server/site-page-enricher.js +3 -3
- package/src/lib/server/site-page-enricher.js.map +1 -1
- package/src/lib/server.js +4 -0
- 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 `
|
|
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 [
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
236
|
-
if ((showCategories || showPriceFilter) &&
|
|
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)=>
|
|
260
|
+
setItems((prev)=>after && prev ? [
|
|
246
261
|
...prev,
|
|
247
262
|
...pageItems
|
|
248
263
|
] : pageItems);
|
|
249
|
-
|
|
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 (!
|
|
260
|
-
|
|
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(
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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(
|
|
321
|
-
|
|
322
|
-
|
|
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: ()=>
|
|
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: ()=>
|
|
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 &&
|
|
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 (
|
|
590
|
+
if (nextCursor == null || loadingMore) return;
|
|
556
591
|
setLoadingMore(true);
|
|
557
|
-
await loadPage(
|
|
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: ()=>
|
|
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
|
|
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;
|