@aglyn/plugins-marketplace 1.0.0-beta.198 → 1.0.0-beta.200

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 (57) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/marketplace-browse.component.js +104 -169
  3. package/src/lib/components/marketplace-browse.component.js.map +1 -1
  4. package/src/lib/components/marketplace-reports-page.component.js +9 -0
  5. package/src/lib/components/marketplace-reports-page.component.js.map +1 -1
  6. package/src/lib/components/org-licences-panel.component.js +212 -170
  7. package/src/lib/components/org-licences-panel.component.js.map +1 -1
  8. package/src/lib/components/org-seller-panel.component.js +23 -5
  9. package/src/lib/components/org-seller-panel.component.js.map +1 -1
  10. package/src/lib/components/plugin-reviews-queue.component.d.ts +9 -0
  11. package/src/lib/components/plugin-reviews-queue.component.js +190 -114
  12. package/src/lib/components/plugin-reviews-queue.component.js.map +1 -1
  13. package/src/lib/hooks/use-listing-name-lookup.d.ts +34 -0
  14. package/src/lib/hooks/use-listing-name-lookup.js +131 -0
  15. package/src/lib/hooks/use-listing-name-lookup.js.map +1 -0
  16. package/src/lib/model/listing-query.d.ts +140 -0
  17. package/src/lib/model/listing-query.js +393 -0
  18. package/src/lib/model/listing-query.js.map +1 -0
  19. package/src/lib/model/marketplace.d.ts +11 -0
  20. package/src/lib/model/marketplace.js.map +1 -1
  21. package/src/lib/server/admin-reports.js +30 -12
  22. package/src/lib/server/admin-reports.js.map +1 -1
  23. package/src/lib/server/admin-reviews.js +180 -128
  24. package/src/lib/server/admin-reviews.js.map +1 -1
  25. package/src/lib/server/admin-trust.js +2 -1
  26. package/src/lib/server/admin-trust.js.map +1 -1
  27. package/src/lib/server/billing-webhook.js +13 -1
  28. package/src/lib/server/billing-webhook.js.map +1 -1
  29. package/src/lib/server/install-email-starter.js +9 -2
  30. package/src/lib/server/install-email-starter.js.map +1 -1
  31. package/src/lib/server/install-layout.js +17 -5
  32. package/src/lib/server/install-layout.js.map +1 -1
  33. package/src/lib/server/install-template.js +15 -5
  34. package/src/lib/server/install-template.js.map +1 -1
  35. package/src/lib/server/install.js +10 -2
  36. package/src/lib/server/install.js.map +1 -1
  37. package/src/lib/server/listing-query-fields.d.ts +38 -0
  38. package/src/lib/server/listing-query-fields.js +50 -0
  39. package/src/lib/server/listing-query-fields.js.map +1 -0
  40. package/src/lib/server/publish-dataset-schema.js +6 -2
  41. package/src/lib/server/publish-dataset-schema.js.map +1 -1
  42. package/src/lib/server/publish-email-starter.js +6 -2
  43. package/src/lib/server/publish-email-starter.js.map +1 -1
  44. package/src/lib/server/publish-email-template.js +6 -2
  45. package/src/lib/server/publish-email-template.js.map +1 -1
  46. package/src/lib/server/publish-layout.js +6 -2
  47. package/src/lib/server/publish-layout.js.map +1 -1
  48. package/src/lib/server/publish-plugin.js +75 -7
  49. package/src/lib/server/publish-plugin.js.map +1 -1
  50. package/src/lib/server/publish-template.js +6 -2
  51. package/src/lib/server/publish-template.js.map +1 -1
  52. package/src/lib/server/publish-theme.js +6 -2
  53. package/src/lib/server/publish-theme.js.map +1 -1
  54. package/src/lib/server/publish.js +6 -2
  55. package/src/lib/server/publish.js.map +1 -1
  56. package/src/lib/server/update-artifact.js +18 -4
  57. package/src/lib/server/update-artifact.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-marketplace",
3
- "version": "1.0.0-beta.198",
3
+ "version": "1.0.0-beta.200",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,19 +25,19 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.198",
29
- "@aglyn/aglyn-markdown-editor": "1.0.0-beta.198",
30
- "@aglyn/shared-data-enums": "1.0.0-beta.198",
31
- "@aglyn/shared-data-mdi": "1.0.0-beta.198",
32
- "@aglyn/shared-ui-jsx": "1.0.0-beta.198",
33
- "@aglyn/shared-ui-next": "1.0.0-beta.198",
34
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.198",
35
- "@aglyn/shared-util-email": "1.0.0-beta.198",
36
- "@aglyn/shared-util-http": "1.0.0-beta.198",
37
- "@aglyn/shared-util-timestamp": "1.0.0-beta.198",
38
- "@aglyn/tenant-data-admin": "1.0.0-beta.198",
39
- "@aglyn/tenant-feature-instance": "1.0.0-beta.198",
40
- "@aglyn/tenant-runtime": "1.0.0-beta.198",
28
+ "@aglyn/aglyn": "1.0.0-beta.200",
29
+ "@aglyn/aglyn-markdown-editor": "1.0.0-beta.200",
30
+ "@aglyn/shared-data-enums": "1.0.0-beta.200",
31
+ "@aglyn/shared-data-mdi": "1.0.0-beta.200",
32
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.200",
33
+ "@aglyn/shared-ui-next": "1.0.0-beta.200",
34
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.200",
35
+ "@aglyn/shared-util-email": "1.0.0-beta.200",
36
+ "@aglyn/shared-util-http": "1.0.0-beta.200",
37
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.200",
38
+ "@aglyn/tenant-data-admin": "1.0.0-beta.200",
39
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.200",
40
+ "@aglyn/tenant-runtime": "1.0.0-beta.200",
41
41
  "@swc/helpers": "0.5.23"
42
42
  },
43
43
  "peerDependencies": {
@@ -19,19 +19,22 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
19
19
  import { mdiCheckDecagram } from "@aglyn/shared-data-mdi";
20
20
  import { AppLink, CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
21
21
  import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
22
- import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
22
+ import ListQueryNotices, { listQueryRefusals } from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
23
+ import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
23
24
  import { Alert, Chip, Tooltip, Grid, MenuItem, Rating, Stack, TextField, Typography } from "@mui/material";
24
25
  import { collection, doc, documentId, getDoc, limit, orderBy, query, where } from "firebase/firestore";
26
+ import { useListQuery } from "@aglyn/tenant-feature-instance/hooks/use-list-query";
25
27
  import { useEffect, useMemo, useRef, useState } from "react";
26
28
  import { buildRoute, PLATFORM_BRAND_NAME, pluginDocsHelp, Route } from "@aglyn/aglyn";
27
- import { useConsoleHostRoute, useFirestore, useFirestoreCollection, useFirestoreDoc, useHostOrgId, useUser, useScopeTokens } from "@aglyn/tenant-feature-instance";
29
+ import { useConsoleHostRoute, useFirestore, useFirestoreCollection, useFirestoreDoc, useHostOrgId, useScopeTokens } from "@aglyn/tenant-feature-instance";
28
30
  /*
29
31
  * The MODULE, not the barrel, for the two PURE helpers — the specs that render
30
32
  * this grid mock `@aglyn/tenant-feature-instance` wholesale to stage their
31
33
  * Firestore hooks, and a query builder imported through that barrel disappears
32
34
  * under the mock. Neither of these is a hook.
33
35
  */ import { ceilingedWindow, collectionCeiling } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
34
- import { isListingBrowsable, isListingDeleted, isPrivateListing, listingArtifactType, listingArtifactLabel, resolvePluginInstallState } from "../model/marketplace.js";
36
+ import { LISTING_CATEGORIES, listingArtifactType, listingArtifactLabel, resolvePluginInstallState } from "../model/marketplace.js";
37
+ import { BROWSE_SORTS, browseBase, MARKETPLACE_BROWSE_QUERY } from "../model/listing-query.js";
35
38
  import { ListingImage } from "./listing-image.component.js";
36
39
  // The console route table is shared (AGL-685), so these go through
37
40
  // buildRoute rather than being reassembled from a base string — the shape
@@ -45,6 +48,10 @@ import { ListingImage } from "./listing-image.component.js";
45
48
  * bounds an install-state LOOKUP, not the shelf: the listings query has its
46
49
  * own cap, and the grid pages what survives the filters.
47
50
  */ const INSTALL_STATE_CEILING = 100;
51
+ /** How the browse clauses read in a refusal. */ const BROWSE_HEADERS = {
52
+ category: 'Category'
53
+ };
54
+ /** How long the search box waits for typing to stop before it asks again. */ const SEARCH_SETTLE_MS = 300;
48
55
  /**
49
56
  * Marketplace components browse (AGL-44). A read-only catalogue: each card links
50
57
  * to the listing's detail page, which is the only place an install happens
@@ -52,21 +59,29 @@ import { ListingImage } from "./listing-image.component.js";
52
59
  * site-targeting choice. The card still shows install STATE (installed,
53
60
  * org-wide) so the shelf reads honestly, but carries no install action.
54
61
  */ export function MarketplaceBrowse(props) {
62
+ var _ref;
63
+ var _gridFilter_clauses_find;
55
64
  const { hostId } = props;
56
65
  const firestore = useFirestore();
57
- const { data: user } = useUser();
58
66
  const { orgScoped, orgSlug: orgSlugProp, publisherId } = props;
59
67
  const [handles, setHandles] = useState({});
60
68
  // Listings are org-owned (AGL-652), so "is this mine" is an org comparison.
61
69
  // Resolved from the routing mirror rather than a new prop so the component
62
- // stays self-contained; hostIndex is signed-in readable.
63
- const [viewerOrgId, setViewerOrgId] = useState(null);
70
+ // stays self-contained; hostIndex is signed-in readable. `undefined` while
71
+ // it resolves: the shelf's query names the viewer's org in its audience
72
+ // clause, so it waits for the answer rather than asking twice. A failed
73
+ // lookup settles at `null` — the public shelf, without the viewer's own
74
+ // listings in review.
75
+ const [viewerOrgId, setViewerOrgId] = useState(undefined);
64
76
  useEffect(()=>{
65
77
  let active = true;
78
+ setViewerOrgId(undefined);
66
79
  void getDoc(doc(firestore, 'hostIndex', hostId)).then((snapshot)=>{
67
80
  var _snapshot_get;
68
81
  if (active) setViewerOrgId((_snapshot_get = snapshot.get('orgId')) != null ? _snapshot_get : null);
69
- }).catch(()=>undefined);
82
+ }).catch(()=>{
83
+ if (active) setViewerOrgId(null);
84
+ });
70
85
  return ()=>{
71
86
  active = false;
72
87
  };
@@ -109,64 +124,75 @@ import { ListingImage } from "./listing-image.component.js";
109
124
  handle: (_handles_profileId1 = handles[profileId]) != null ? _handles_profileId1 : profileId
110
125
  }) : undefined;
111
126
  };
112
- /**
113
- * NO `where('deletedAt','==',null)` HERE — soft-deleted listings are dropped
114
- * in `items` below instead (AGL-1196).
115
- *
116
- * `deletedAt` flips every time a publisher unpublishes or republishes. A
117
- * document that stops matching a live query leaves the query target, and the
118
- * client can cache a `noDocument` tombstone at its own path — which is then
119
- * served to every reader of that path, including the detail page, which
120
- * reads `marketplaceListings/{id}` BY ID. So a browse session open across an
121
- * unpublish could 404 a listing that exists, and republishing would not
122
- * clear it: a resumed listen only pulls deltas, so an otherwise-unchanged
123
- * document is never re-sent (AGL-827, AGL-929).
124
- *
125
- * Dropping the predicate removes the mechanism rather than healing it: with
126
- * no `where`, no document can stop matching. The rule permits it —
127
- * `marketplaceListings` is `allow read: if true`, with no `resource.data`
128
- * term, so an unconstrained list is not denied. (That is NOT true of the
129
- * scoped `datasets` query below, which is why this fix does not generalise.)
130
- *
131
- * It also fixes a quieter bug: `== null` matches only an EXPLICIT null, and
132
- * Firestore cannot express "field is absent". Every publish path stamps
133
- * `deletedAt: null` today, so this happens to be exact — but any future
134
- * write path that omits the field would make a live listing silently
135
- * invisible. An in-memory falsy check treats absent as live, which is what
136
- * the dataset and email-template filters in this file already do.
137
- *
138
- * The cap counts CANDIDATES, not results — private and unreviewed listings
139
- * already consume slots and get filtered below, so this is the same class of
140
- * consumption, nudged up to leave room for soft-deleted rows. Measured on
141
- * production 2026-08-03: 7 listings, 0 soft-deleted, 0 missing the field.
142
- *
143
- * ## The cap DOES take an `orderBy`, and it is not the same decision
127
+ /*
128
+ * THE SHELF IS ITS QUERY (AGL-3321).
144
129
  *
145
- * A `limit` with no ordering is answered in document-id order, and listing
146
- * ids are random — so the ninety this loaded were an arbitrary ninety, and
147
- * the client sort below then arranged that sample by date and called it
148
- * "Newest". The window is now the newest ninety, which is what the default
149
- * sort already claimed it was.
130
+ * Browse read the newest ninety listings and resolved everything else in
131
+ * memory — the search box, the category chips, the three sorts, and the
132
+ * gates that keep deleted, private and unreviewed listings off the shelf.
133
+ * A listing that was not among those ninety could not be found by any of
134
+ * them, and "Most installed" sorted a sample.
150
135
  *
151
- * `orderBy` carries the drop the `where` above was removed to avoid — it
152
- * matches only documents that HAVE the field, so a listing without
153
- * `createdAt` would not be mis-ordered, it would be invisible. That is safe
154
- * here for a reason the `deletedAt` predicate could not offer: a listing is
155
- * created by exactly two paths, `publish.ts` and `publish-plugin.ts`, and
156
- * both stamp `createdAt` in the same `set` that brings the document into
157
- * existence. Every other writer resolves an EXISTING listing by id and
158
- * refuses when it is absent, so none of them can produce one without the
159
- * field.
136
+ * Every one of them is on the query now. The gates are one clause, the
137
+ * listing's written `browseAudience` (see `listingBrowseAudience`), asked
138
+ * with `array-contains-any` for everyone (`*`) and the viewer's own org —
139
+ * the owner exemption the in-memory gate carried (AGL-432): a publisher
140
+ * sees their own listing while it waits on review. A publisher page asks
141
+ * the same scopes joined to that publisher. The search folds into the same
142
+ * clause through `browseTokens`, a category is an equality, and each sort
143
+ * is the query's order; `MARKETPLACE_BROWSE_QUERY` declares them and its
144
+ * spec pins the composites that serve them.
160
145
  *
161
- * It also cannot reproduce the tombstone (AGL-827/929) that made a mutable
162
- * `where` unsafe: `createdAt` is written under `existing.empty` and never
163
- * rewritten, so no document can stop matching mid-session and leave a
164
- * `noDocument` cached at a path the detail page reads by id.
165
- */ const { data: listings } = useFirestoreCollection(()=>query(collection(firestore, 'marketplaceListings'), orderBy('createdAt', 'desc'), limit(90)), [
166
- firestore
167
- ], {
168
- idField: '$id'
146
+ * `browseAudience` is MUTABLE — an unpublish, a takedown or a verdict
147
+ * rewrites it — so a document can stop matching mid-session and be cached
148
+ * as a `noDocument` tombstone at the path the detail page reads by id
149
+ * (AGL-827/929). That was why the `deletedAt` predicate came off this query
150
+ * in AGL-1196; `confirmDisappearances` is the repair that hook documents
151
+ * for a predicate that cannot be dropped, and this one cannot.
152
+ */ const gridFilter = useListGridFilter({
153
+ selectFields: [
154
+ 'category'
155
+ ]
156
+ });
157
+ const [search, setSearch] = useState('');
158
+ const [searchWords, setSearchWords] = useState([]);
159
+ useEffect(()=>{
160
+ const timer = setTimeout(()=>setSearchWords(search.split(/\s+/).filter(Boolean)), SEARCH_SETTLE_MS);
161
+ return ()=>clearTimeout(timer);
162
+ }, [
163
+ search
164
+ ]);
165
+ const [sort, setSort] = useState('newest');
166
+ const category = (_ref = (_gridFilter_clauses_find = gridFilter.clauses.find((clause)=>clause.field === 'category')) == null ? void 0 : _gridFilter_clauses_find.value) != null ? _ref : null;
167
+ const listingsRef = useMemo(()=>viewerOrgId === undefined ? null : collection(firestore, 'marketplaceListings'), [
168
+ firestore,
169
+ viewerOrgId
170
+ ]);
171
+ const { rows: shown, status: shelfStatus, hasMore, page, setPage, pageSize, setPageSize, plan } = useListQuery({
172
+ collection: listingsRef,
173
+ declaration: MARKETPLACE_BROWSE_QUERY,
174
+ request: {
175
+ clauses: gridFilter.clauses,
176
+ search: searchWords,
177
+ sort: BROWSE_SORTS[sort],
178
+ base: browseBase(viewerOrgId, publisherId)
179
+ },
180
+ deps: [
181
+ firestore,
182
+ viewerOrgId,
183
+ publisherId
184
+ ],
185
+ idField: '$id',
186
+ confirmDisappearances: true
169
187
  });
188
+ const listings = shown;
189
+ const filtering = Boolean(category) || searchWords.length > 0;
190
+ const refusals = useMemo(()=>listQueryRefusals(plan.refused, {
191
+ fields: MARKETPLACE_BROWSE_QUERY.fields,
192
+ headers: BROWSE_HEADERS
193
+ }), [
194
+ plan
195
+ ]);
170
196
  /*
171
197
  * ## The install-state lookups are ORDERED too, and they are not lists
172
198
  *
@@ -378,89 +404,7 @@ import { ListingImage } from "./listing-image.component.js";
378
404
  listings,
379
405
  firestore
380
406
  ]);
381
- // Browse controls (AGL-95): client-side search/filter/sort over the
382
- // fetched page of listings.
383
- const [search, setSearch] = useState('');
384
- const [category, setCategory] = useState(null);
385
- const [sort, setSort] = useState('newest');
386
- const categories = useMemo(()=>[
387
- ...new Set((listings != null ? listings : []).map((listing)=>listing.category).filter(Boolean))
388
- ].sort(), [
389
- listings
390
- ]);
391
- const items = useMemo(()=>{
392
- const needle = search.trim().toLowerCase();
393
- const filtered = (listings != null ? listings : []).filter((listing)=>{
394
- // Private plugins never appear here, not even for the org that owns
395
- // them (AGL-968/993). The owner exemption below exists so a publisher
396
- // can watch their own SUBMISSION move through review — but a private
397
- // listing is not waiting to be listed, it is deliberately not for the
398
- // marketplace, and showing it in this grid is the one thing "private"
399
- // promises will not happen. Owners reach theirs from
400
- // Marketplace › Listings, whose View opens the same detail page
401
- // installs happen on.
402
- // Soft-deleted listings used to be excluded by the query itself; they
403
- // are dropped here now so no mutable field sits in a `where`
404
- // (AGL-1196). Unconditional — unlike the review gate below, deletion
405
- // has no owner exemption.
406
- if (isListingDeleted(listing)) return false;
407
- if (isPrivateListing(listing)) return false;
408
- // Review queue gate (AGL-432): unreviewed/rejected plugin listings
409
- // stay off the public browse; the owner still sees their own (the
410
- // detail page shows them the status). UX-level only — the docs are
411
- // public-readable by design.
412
- if (!isListingBrowsable(listing) && listing.profileId !== viewerOrgId) {
413
- return false;
414
- }
415
- // Publisher page (AGL-869): only this publisher's listings.
416
- if (publisherId && listing.profileId !== publisherId) return false;
417
- if (category && listing.category !== category) return false;
418
- if (!needle) return true;
419
- return [
420
- listing.displayName,
421
- listing.description,
422
- listing.category
423
- ].filter(Boolean).some((value)=>value.toLowerCase().includes(needle));
424
- });
425
- return [
426
- ...filtered
427
- ].sort((a, b)=>{
428
- var _ref, _ref1;
429
- var _b_createdAt, _a_createdAt;
430
- if (sort === 'installed') {
431
- var _b_installCount, _a_installCount;
432
- return ((_b_installCount = b.installCount) != null ? _b_installCount : 0) - ((_a_installCount = a.installCount) != null ? _a_installCount : 0);
433
- }
434
- if (sort === 'rated') {
435
- var _b_ratingAverage, _a_ratingAverage, _b_ratingCount, _a_ratingCount;
436
- // Unrated listings sort last rather than as zero-stars — a new
437
- // listing has not been judged badly, it has not been judged.
438
- const byAverage = ((_b_ratingAverage = b.ratingAverage) != null ? _b_ratingAverage : -1) - ((_a_ratingAverage = a.ratingAverage) != null ? _a_ratingAverage : -1);
439
- return byAverage || ((_b_ratingCount = b.ratingCount) != null ? _b_ratingCount : 0) - ((_a_ratingCount = a.ratingCount) != null ? _a_ratingCount : 0);
440
- }
441
- return ((_ref = (_b_createdAt = b.createdAt) == null ? void 0 : _b_createdAt.seconds) != null ? _ref : 0) - ((_ref1 = (_a_createdAt = a.createdAt) == null ? void 0 : _a_createdAt.seconds) != null ? _ref1 : 0);
442
- });
443
- }, [
444
- listings,
445
- search,
446
- category,
447
- sort,
448
- publisherId,
449
- user == null ? void 0 : user.uid
450
- ]);
451
- /*
452
- * The shelf PAGES (AGL-2501). It used to render every card the window held
453
- * in one wall, so the only control over how much arrived at once was the
454
- * cap in the query — a number the reader cannot see and cannot change.
455
- *
456
- * Client-side, deliberately, and this is the case `ListPagination`'s
457
- * `count` prop exists for. The search box, the category chips and the
458
- * owner/review gates are all resolved in memory, so a server page would be
459
- * a page of CANDIDATES: a reader filtering to one category would get pages
460
- * that were mostly empty and a count that meant nothing. Paging what is
461
- * left after the filter is the only version whose numbers are about what is
462
- * on screen.
463
- */ /**
407
+ /**
464
408
  * The install-state lookups did not read everything they were asked about.
465
409
  *
466
410
  * Said on the SHELF rather than on the card that is wrong, because there is
@@ -469,25 +413,6 @@ import { ListingImage } from "./listing-image.component.js";
469
413
  * chips are a floor — anything marked installed is installed, and something
470
414
  * unmarked may still be.
471
415
  */ const installStateTruncated = installedTruncated || hostPinsTruncated || orgPinsTruncated || datasetsTruncated || emailsTruncated;
472
- const [page, setPage] = useState(0);
473
- const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
474
- /*
475
- * A new query starts at page one. Page four of a category with two pages
476
- * does not exist, and an out-of-range page renders as an empty grid with
477
- * nothing saying why — which reads as the filter having broken.
478
- */ useEffect(()=>{
479
- setPage(0);
480
- }, [
481
- search,
482
- category,
483
- sort,
484
- pageSize
485
- ]);
486
- const shown = useMemo(()=>items.slice(page * pageSize, (page + 1) * pageSize), [
487
- items,
488
- page,
489
- pageSize
490
- ]);
491
416
  return /*#__PURE__*/ _jsxs(CardDisplay, {
492
417
  header: 'Marketplace components',
493
418
  help: pluginDocsHelp('plugins', {
@@ -515,11 +440,17 @@ import { ListingImage } from "./listing-image.component.js";
515
440
  minWidth: 200
516
441
  }
517
442
  }),
518
- categories.map((value)=>/*#__PURE__*/ _jsx(Chip, {
443
+ LISTING_CATEGORIES.map((value)=>/*#__PURE__*/ _jsx(Chip, {
519
444
  label: value,
520
445
  variant: category === value ? 'filled' : 'outlined',
521
446
  color: category === value ? 'primary' : 'default',
522
- onClick: ()=>setCategory((previous)=>previous === value ? null : value)
447
+ onClick: ()=>gridFilter.setClauses(category === value ? [] : [
448
+ {
449
+ field: 'category',
450
+ op: 'equals',
451
+ value
452
+ }
453
+ ])
523
454
  }, value)),
524
455
  /*#__PURE__*/ _jsxs(TextField, {
525
456
  value: sort,
@@ -554,11 +485,15 @@ import { ListingImage } from "./listing-image.component.js";
554
485
  },
555
486
  children: `Installed state is resolved from the first ${INSTALL_STATE_CEILING} ` + 'install records in this workspace, ordered by id. There are ' + 'more, so a card may not be marked installed even though it is. ' + 'Open a listing to see what this workspace actually runs.'
556
487
  }) : null,
557
- items.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
488
+ /*#__PURE__*/ _jsx(ListQueryNotices, {
489
+ refused: refusals,
490
+ notices: plan.notices
491
+ }),
492
+ shown.length === 0 && page === 0 ? shelfStatus === 'success' ? /*#__PURE__*/ _jsx(Typography, {
558
493
  variant: "body2",
559
494
  color: "text.secondary",
560
- children: 'No marketplace components published yet — publish one of your ' + 'reusable components from the Setup page to be the first.'
561
- }) : /*#__PURE__*/ _jsxs(_Fragment, {
495
+ children: filtering ? 'No components match this search.' : 'No marketplace components published yet — publish one of ' + 'your reusable components from the Setup page to be the first.'
496
+ }) : null : /*#__PURE__*/ _jsxs(_Fragment, {
562
497
  children: [
563
498
  /*#__PURE__*/ _jsx(Grid, {
564
499
  container: true,
@@ -787,7 +722,7 @@ import { ListingImage } from "./listing-image.component.js";
787
722
  page: page,
788
723
  pageSize: pageSize,
789
724
  rowCount: shown.length,
790
- count: items.length,
725
+ hasMore: hasMore,
791
726
  onPageChange: setPage,
792
727
  onPageSizeChange: setPageSize
793
728
  })