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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) 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/checkout.js +3 -1
  30. package/src/lib/server/checkout.js.map +1 -1
  31. package/src/lib/server/install-email-starter.js +9 -2
  32. package/src/lib/server/install-email-starter.js.map +1 -1
  33. package/src/lib/server/install-layout.js +17 -5
  34. package/src/lib/server/install-layout.js.map +1 -1
  35. package/src/lib/server/install-template.js +15 -5
  36. package/src/lib/server/install-template.js.map +1 -1
  37. package/src/lib/server/install.js +10 -2
  38. package/src/lib/server/install.js.map +1 -1
  39. package/src/lib/server/listing-query-fields.d.ts +38 -0
  40. package/src/lib/server/listing-query-fields.js +50 -0
  41. package/src/lib/server/listing-query-fields.js.map +1 -0
  42. package/src/lib/server/publish-dataset-schema.js +6 -2
  43. package/src/lib/server/publish-dataset-schema.js.map +1 -1
  44. package/src/lib/server/publish-email-starter.js +6 -2
  45. package/src/lib/server/publish-email-starter.js.map +1 -1
  46. package/src/lib/server/publish-email-template.js +6 -2
  47. package/src/lib/server/publish-email-template.js.map +1 -1
  48. package/src/lib/server/publish-layout.js +6 -2
  49. package/src/lib/server/publish-layout.js.map +1 -1
  50. package/src/lib/server/publish-plugin.js +75 -7
  51. package/src/lib/server/publish-plugin.js.map +1 -1
  52. package/src/lib/server/publish-template.js +6 -2
  53. package/src/lib/server/publish-template.js.map +1 -1
  54. package/src/lib/server/publish-theme.js +6 -2
  55. package/src/lib/server/publish-theme.js.map +1 -1
  56. package/src/lib/server/publish.js +6 -2
  57. package/src/lib/server/publish.js.map +1 -1
  58. package/src/lib/server/update-artifact.js +18 -4
  59. package/src/lib/server/update-artifact.js.map +1 -1
@@ -18,55 +18,45 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
19
  import { ICON_VARIANT_COMPONENT } from "@aglyn/shared-data-enums";
20
20
  import { AppLink, CardDisplay } from "@aglyn/shared-ui-jsx";
21
- import { useFirestore, useFirestoreCollection, useUser } from "@aglyn/tenant-feature-instance";
21
+ import { useFirestore, useUser } from "@aglyn/tenant-feature-instance";
22
+ import { useListQuery } from "@aglyn/tenant-feature-instance/hooks/use-list-query";
22
23
  import { Alert, Chip, Stack } from "@mui/material";
23
24
  import ListFilterChips from "@aglyn/shared-ui-jsx/components/list-filter-chips.component";
25
+ import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
26
+ import ListQueryNotices, { listQueryRefusals } from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
24
27
  import { ListTable } from "@aglyn/shared-ui-jsx/components/list-table.component";
25
- import { inMemoryListField } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
26
- import { useListRowsFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-rows-filter";
27
- import { collection, documentId, limit, orderBy, query, where } from "firebase/firestore";
28
+ import { listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
29
+ import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
30
+ import { collection } from "firebase/firestore";
28
31
  import { useMemo } from "react";
29
32
  import { pluginDocsHelp } from "@aglyn/aglyn";
30
33
  import { TABLE_ROW_HEIGHT } from "@aglyn/shared-ui-jsx/const/table-pagination";
31
34
  import { listingPath } from "../model/marketplace-paths.js";
35
+ import { EVERY_WORKSPACE, HELD_LICENCE_QUERY, licenceBase, MINE_LICENCE_QUERY, mineLicenceClauses } from "../model/listing-query.js";
36
+ import { useListingNameLookup, useListingNames } from "../hooks/use-listing-name-lookup.js";
32
37
  import EmptyState from "@aglyn/shared-ui-jsx/components/read-gated-empty-state.component";
33
38
  /*
34
- * What each license grid's Filters panel offers. Both lists hold every row
35
- * they describe, so the panel and the search answer over all of them. The
36
- * Listing column filters by the listing's name; who bought a license and
37
- * which workspace one landed in are picked.
38
- */ const HELD_FILTER_FIELDS = [
39
- inMemoryListField('listingId', 'text', 'listingName'),
40
- inMemoryListField('buyerUid', 'select', 'boughtBy')
41
- ];
42
- const HELD_FILTER_HEADERS = {
43
- listingId: 'Listing',
39
+ * What each license grid's Filters panel offers, and how its search reads —
40
+ * every clause and the search on the QUERY (AGL-3321). They were matched over
41
+ * the rows each list had loaded (the retired window filter), which answered "no
42
+ * match" for a license past what was read.
43
+ *
44
+ * - "This workspace" filters by who bought it — You, by uid equality. "A
45
+ * colleague" was an in-memory negation; Firestore's `!=` would lead the
46
+ * order by buyer and needs a composite per clause beside it, so it is not
47
+ * offered.
48
+ * - "Bought by you" filters by the workspace a license landed in, or the
49
+ * every-workspace grant a purchase naming none carries (`buyerOrgId`
50
+ * null).
51
+ * - The search is the listing's NAME: the listings holding the word are
52
+ * asked first, and the license query asks for their ids
53
+ * (`useListingNameLookup`).
54
+ */ const HELD_FILTER_HEADERS = {
44
55
  buyerUid: 'Bought by'
45
56
  };
46
- const HELD_FILTER_OPTIONS = {
47
- buyerUid: [
48
- {
49
- value: 'you',
50
- label: 'You'
51
- },
52
- {
53
- value: 'colleague',
54
- label: 'A colleague'
55
- }
56
- ]
57
- };
58
- const MINE_FILTER_FIELDS = [
59
- inMemoryListField('listingId', 'text', 'listingName'),
60
- inMemoryListField('buyerOrgId', 'select', 'licensedTo')
61
- ];
62
57
  const MINE_FILTER_HEADERS = {
63
- listingId: 'Listing',
64
58
  buyerOrgId: 'Licensed to'
65
59
  };
66
- /** A purchase that names no organization, which entitles its buyer everywhere. */ const EVERY_WORKSPACE = 'every';
67
- /** What the quick search reads on a license row. */ const LICENSE_SEARCH_FIELDS = [
68
- 'listingName'
69
- ];
70
60
  /**
71
61
  * WHICH WORKSPACE HOLDS A LICENCE (AGL-2331).
72
62
  *
@@ -118,57 +108,11 @@ const MINE_FILTER_HEADERS = {
118
108
  * cache with `status: 'success'`, which is why `serverDenied` is checked
119
109
  * alongside it rather than trusting the status alone.
120
110
  */ export function OrgLicencesPanel({ orgId, basePath, viewerOrgs }) {
111
+ var _heldLookup_ids, _mineLookup_ids;
112
+ var _heldLookup_ids1, _mineLookup_ids1, _heldLookup_ids2, _mineLookup_ids2;
121
113
  const firestore = useFirestore();
122
114
  const { data: user } = useUser();
123
115
  const uid = user == null ? void 0 : user.uid;
124
- /** The licences this workspace holds — whoever in it did the buying. */ const orgLicencesRead = useFirestoreCollection(()=>orgId ? query(collection(firestore, 'marketplacePurchases'), where('buyerOrgId', '==', orgId)) : null, [
125
- firestore,
126
- orgId
127
- ], {
128
- idField: '$id'
129
- });
130
- /** Everything this person has ever bought, in any workspace. */ const myPurchasesRead = useFirestoreCollection(()=>uid ? query(collection(firestore, 'marketplacePurchases'), where('buyerUid', '==', uid)) : null, [
131
- firestore,
132
- uid
133
- ], {
134
- idField: '$id'
135
- });
136
- /**
137
- * Listing id → display name.
138
- *
139
- * One subscription rather than a read per row: `marketplaceListings` is
140
- * world-readable (it is the catalogue), the browse grid on the sibling tab
141
- * already listens to it, and a per-row hook would change the hook count
142
- * between renders as licences arrive.
143
- */ const { data: listings } = useFirestoreCollection(/*
144
- * ORDERED, so the window is a reachable 200 rather than a sample.
145
- *
146
- * A capped read with no `orderBy` is answered in document-id order
147
- * anyway, so naming it changes no row — what it changes is that the
148
- * ordering is a decision rather than an accident, and that the obvious
149
- * next edit is caught: `orderBy('displayName')` would drop every listing
150
- * saved without one out of the map, and a licence whose listing is
151
- * missing from the map is a row that names itself by raw id.
152
- *
153
- * Ordering on the document NAME, which is what a listing id already is
154
- * here, keeps the map's keys and the walk in the same space. Past 200
155
- * listings a licence still renders — the fallback below prints the
156
- * listing id — so the cap degrades the label and never the row.
157
- */ ()=>query(collection(firestore, 'marketplaceListings'), orderBy(documentId()), limit(200)), [
158
- firestore
159
- ], {
160
- idField: '$id'
161
- });
162
- const listingNames = useMemo(()=>{
163
- const names = {};
164
- for (const listing of listings != null ? listings : []){
165
- var _listing_displayName;
166
- names[listing.$id] = String((_listing_displayName = listing.displayName) != null ? _listing_displayName : listing.$id);
167
- }
168
- return names;
169
- }, [
170
- listings
171
- ]);
172
116
  /** Workspace id → the name the member already sees in the org switcher. */ const orgNames = useMemo(()=>{
173
117
  const names = {};
174
118
  for (const workspace of viewerOrgs != null ? viewerOrgs : [])names[workspace.id] = workspace.name;
@@ -176,33 +120,16 @@ const MINE_FILTER_HEADERS = {
176
120
  }, [
177
121
  viewerOrgs
178
122
  ]);
179
- /**
180
- * A refunded purchase is not a licence (AGL-1546) and must not be listed as
181
- * one — the whole point of this panel is deciding whether to buy, and a row
182
- * that says "you own this" when the install route answers 402 is worse than
183
- * no row.
184
- */ const live = (rows)=>(rows != null ? rows : []).filter((row)=>!(row == null ? void 0 : row.refundedAt));
185
- const held = useMemo(()=>live(orgLicencesRead.data).map((row)=>{
186
- var _listingNames_String, _row_listingId, _row_listingId1;
187
- return _extends({}, row, {
188
- listingName: (_listingNames_String = listingNames[String((_row_listingId = row.listingId) != null ? _row_listingId : '')]) != null ? _listingNames_String : String((_row_listingId1 = row.listingId) != null ? _row_listingId1 : ''),
189
- boughtBy: row.buyerUid === uid ? 'you' : 'colleague'
190
- });
123
+ const heldOptions = useMemo(()=>({
124
+ buyerUid: uid ? [
125
+ {
126
+ value: uid,
127
+ label: 'You'
128
+ }
129
+ ] : []
191
130
  }), [
192
- orgLicencesRead.data,
193
- listingNames,
194
131
  uid
195
132
  ]);
196
- const mine = useMemo(()=>live(myPurchasesRead.data).map((row)=>{
197
- var _listingNames_String, _row_listingId, _row_listingId1, _row_buyerOrgId;
198
- return _extends({}, row, {
199
- listingName: (_listingNames_String = listingNames[String((_row_listingId = row.listingId) != null ? _row_listingId : '')]) != null ? _listingNames_String : String((_row_listingId1 = row.listingId) != null ? _row_listingId1 : ''),
200
- licensedTo: String((_row_buyerOrgId = row.buyerOrgId) != null ? _row_buyerOrgId : '') || EVERY_WORKSPACE
201
- });
202
- }), [
203
- myPurchasesRead.data,
204
- listingNames
205
- ]);
206
133
  const mineOptions = useMemo(()=>({
207
134
  buyerOrgId: [
208
135
  ...orgId ? [
@@ -224,35 +151,99 @@ const MINE_FILTER_HEADERS = {
224
151
  orgId,
225
152
  viewerOrgs
226
153
  ]);
227
- const heldFilter = useListRowsFilter({
228
- rows: held,
229
- fields: HELD_FILTER_FIELDS,
230
- options: HELD_FILTER_OPTIONS,
231
- headers: HELD_FILTER_HEADERS,
232
- search: LICENSE_SEARCH_FIELDS
154
+ const heldFilter = useListGridFilter({
155
+ selectFields: [
156
+ 'buyerUid'
157
+ ]
233
158
  });
234
- const mineFilter = useListRowsFilter({
235
- rows: mine,
236
- fields: MINE_FILTER_FIELDS,
237
- options: mineOptions,
238
- headers: MINE_FILTER_HEADERS,
239
- search: LICENSE_SEARCH_FIELDS
159
+ const mineFilter = useListGridFilter({
160
+ selectFields: [
161
+ 'buyerOrgId'
162
+ ]
240
163
  });
241
- /*==========================================
242
- * BOTH LISTS ARE WHOLE, so the grid pages them and its counts are TOTALS.
243
- *
244
- * Neither query is capped: each reads one workspace's purchases or one
245
- * person's, whole. So the card holds the entire list it is describing, the
246
- * refund filter above has already run over all of it, and the grid's
247
- * footer counts the collection's real size rather than a window's length —
248
- * the one case where a client page can state a total without qualifying it.
164
+ const heldLookup = useListingNameLookup(heldFilter.searchWords);
165
+ const mineLookup = useListingNameLookup(mineFilter.searchWords);
166
+ /*
167
+ * Both lists read `marketplacePurchases`, which is buyer/org/seller-gated —
168
+ * the org clause landed with AGL-2331 for exactly this list. Held at null
169
+ * until the org and uid resolve: a rules-shaped LIST is evaluated against
170
+ * the QUERY, so a sentinel value is a guaranteed denial retried on the
171
+ * refusal cadence (AGL-1440), not an empty list. A search whose listing
172
+ * lookup is still out holds too, and one that named no listing is answered
173
+ * with no rows rather than an `in []` Firestore refuses.
249
174
  *
250
- * Server-paging either one would break that. `refundedAt` is filtered after
251
- * reading, so a ten-document page arrives holding anywhere from zero to ten
252
- * licences, and an agency deciding whether to buy a component again is
253
- * exactly the reader who must not be shown a short page as a complete
254
- * answer.
255
- *=========================================*/ const heldColumns = useMemo(()=>[
175
+ * A refunded purchase is not a licence (AGL-1546) and must not be listed as
176
+ * one — the whole point of this panel is deciding whether to buy, and a row
177
+ * that says "you own this" when the install route answers 402 is worse than
178
+ * no row. `refundedAt == null` is on both queries (`licenceBase`).
179
+ */ const purchases = useMemo(()=>collection(firestore, 'marketplacePurchases'), [
180
+ firestore
181
+ ]);
182
+ const heldRead = useListQuery({
183
+ collection: orgId && !heldLookup.pending && ((_heldLookup_ids1 = heldLookup.ids) == null ? void 0 : _heldLookup_ids1.length) !== 0 ? purchases : null,
184
+ declaration: HELD_LICENCE_QUERY,
185
+ request: {
186
+ clauses: heldFilter.clauses,
187
+ base: orgId ? licenceBase({
188
+ path: 'buyerOrgId',
189
+ value: orgId
190
+ }, heldLookup.ids) : []
191
+ },
192
+ deps: [
193
+ firestore,
194
+ orgId,
195
+ heldLookup.pending,
196
+ ((_heldLookup_ids = heldLookup.ids) != null ? _heldLookup_ids : []).join(',')
197
+ ],
198
+ idField: '$id'
199
+ });
200
+ const mineRead = useListQuery({
201
+ collection: uid && !mineLookup.pending && ((_mineLookup_ids1 = mineLookup.ids) == null ? void 0 : _mineLookup_ids1.length) !== 0 ? purchases : null,
202
+ declaration: MINE_LICENCE_QUERY,
203
+ request: {
204
+ clauses: mineLicenceClauses(mineFilter.clauses),
205
+ base: uid ? licenceBase({
206
+ path: 'buyerUid',
207
+ value: uid
208
+ }, mineLookup.ids) : []
209
+ },
210
+ deps: [
211
+ firestore,
212
+ uid,
213
+ mineLookup.pending,
214
+ ((_mineLookup_ids = mineLookup.ids) != null ? _mineLookup_ids : []).join(',')
215
+ ],
216
+ idField: '$id'
217
+ });
218
+ const heldNamedNone = ((_heldLookup_ids2 = heldLookup.ids) == null ? void 0 : _heldLookup_ids2.length) === 0;
219
+ const mineNamedNone = ((_mineLookup_ids2 = mineLookup.ids) == null ? void 0 : _mineLookup_ids2.length) === 0;
220
+ const heldRows = useMemo(()=>heldNamedNone ? [] : heldRead.rows, [
221
+ heldNamedNone,
222
+ heldRead.rows
223
+ ]);
224
+ const mineRows = useMemo(()=>mineNamedNone ? [] : mineRead.rows, [
225
+ mineNamedNone,
226
+ mineRead.rows
227
+ ]);
228
+ /**
229
+ * Listing id → display name, read by id for the rows on screen. It was a
230
+ * window of the first two hundred listings, past which a license printed
231
+ * its listing's raw id.
232
+ */ const listingNames = useListingNames([
233
+ ...heldRows,
234
+ ...mineRows
235
+ ].map((row)=>{
236
+ var _row_listingId;
237
+ return String((_row_listingId = row.listingId) != null ? _row_listingId : '');
238
+ }));
239
+ const held = useMemo(()=>heldRows.map((row)=>_extends({}, row, {
240
+ boughtBy: row.buyerUid === uid ? 'You' : 'A colleague'
241
+ })), [
242
+ heldRows,
243
+ uid
244
+ ]);
245
+ const mine = mineRows;
246
+ const heldColumns = useMemo(()=>listFilterGridColumns([
256
247
  {
257
248
  field: 'listingId',
258
249
  headerName: 'Listing',
@@ -274,8 +265,8 @@ const MINE_FILTER_HEADERS = {
274
265
  field: 'buyerUid',
275
266
  headerName: 'Bought by',
276
267
  width: 150,
277
- // The select's value; the grid shows its label, You or A colleague.
278
- valueGetter: (_value, row)=>row.boughtBy
268
+ // Drawn as who it was, whatever the select's one choice is.
269
+ renderCell: ({ row })=>row.boughtBy
279
270
  },
280
271
  {
281
272
  field: 'amountCents',
@@ -291,11 +282,12 @@ const MINE_FILTER_HEADERS = {
291
282
  },
292
283
  valueFormatter: (value)=>`$${value.toFixed(2)}`
293
284
  }
294
- ], [
285
+ ], HELD_LICENCE_QUERY.fields, heldOptions, HELD_FILTER_HEADERS), [
295
286
  listingNames,
296
- basePath
287
+ basePath,
288
+ heldOptions
297
289
  ]);
298
- const mineColumns = useMemo(()=>[
290
+ const mineColumns = useMemo(()=>listFilterGridColumns([
299
291
  {
300
292
  field: 'listingId',
301
293
  headerName: 'Listing',
@@ -340,10 +332,11 @@ const MINE_FILTER_HEADERS = {
340
332
  });
341
333
  }
342
334
  }
343
- ], [
335
+ ], MINE_LICENCE_QUERY.fields, mineOptions, MINE_FILTER_HEADERS), [
344
336
  listingNames,
345
337
  orgId,
346
- orgNames
338
+ orgNames,
339
+ mineOptions
347
340
  ]);
348
341
  /**
349
342
  * "You own nothing here" is a claim about this workspace's purchases, and a
@@ -354,6 +347,7 @@ const MINE_FILTER_HEADERS = {
354
347
  * has refused past its retry budget keeps painting whatever the local cache
355
348
  * holds, and `status` alone still reads `success` for it.
356
349
  */ const outcome = (read)=>read.status === 'error' || read.serverDenied === true ? 'unavailable' : read.status === 'success' ? 'loaded' : 'loading';
350
+ const filtering = (filter)=>filter.clauses.length > 0 || filter.searchWords.length > 0;
357
351
  return /*#__PURE__*/ _jsxs(Stack, {
358
352
  spacing: 3,
359
353
  children: [
@@ -361,8 +355,8 @@ const MINE_FILTER_HEADERS = {
361
355
  severity: "info",
362
356
  children: 'A marketplace purchase licenses one organization. Any member with ' + 'install permission can install what this workspace owns — and a ' + 'second workspace needs its own license.'
363
357
  }),
364
- held.length === 0 ? /*#__PURE__*/ _jsx(EmptyState, {
365
- read: outcome(orgLicencesRead),
358
+ held.length === 0 && heldRead.page === 0 && !filtering(heldFilter) ? /*#__PURE__*/ _jsx(EmptyState, {
359
+ read: outcome(heldRead),
366
360
  subject: "this workspace’s licenses",
367
361
  iconPath: ICON_VARIANT_COMPONENT.path,
368
362
  title: 'This workspace holds no licenses',
@@ -376,23 +370,20 @@ const MINE_FILTER_HEADERS = {
376
370
  subheader: 'Installable by any member with install permission, whoever ' + 'on the team bought it',
377
371
  contentGutterX: true,
378
372
  contentGutterY: true,
379
- children: /*#__PURE__*/ _jsxs(Stack, {
380
- spacing: 1,
381
- children: [
382
- /*#__PURE__*/ _jsx(ListFilterChips, _extends({}, heldFilter.chipsProps)),
383
- /*#__PURE__*/ _jsx(ListTable, _extends({
384
- "aria-label": "Licenses this workspace holds",
385
- rows: heldFilter.rows,
386
- columns: heldFilter.filterColumns(heldColumns)
387
- }, heldFilter.gridProps, {
388
- rowHeight: TABLE_ROW_HEIGHT,
389
- noRowsLabel: "No licenses match these filters"
390
- }))
391
- ]
373
+ children: /*#__PURE__*/ _jsx(LicenceTable, {
374
+ label: "Licenses this workspace holds",
375
+ declaration: HELD_LICENCE_QUERY,
376
+ headers: HELD_FILTER_HEADERS,
377
+ options: heldOptions,
378
+ gridFilter: heldFilter,
379
+ lookup: heldLookup,
380
+ read: heldRead,
381
+ rows: held,
382
+ columns: heldColumns
392
383
  })
393
384
  }),
394
- mine.length === 0 ? /*#__PURE__*/ _jsx(EmptyState, {
395
- read: outcome(myPurchasesRead),
385
+ mine.length === 0 && mineRead.page === 0 && !filtering(mineFilter) ? /*#__PURE__*/ _jsx(EmptyState, {
386
+ read: outcome(mineRead),
396
387
  subject: "your purchases",
397
388
  iconPath: ICON_VARIANT_COMPONENT.path,
398
389
  title: 'You have not bought anything yet',
@@ -406,24 +397,75 @@ const MINE_FILTER_HEADERS = {
406
397
  subheader: 'Your own receipts, and which workspace each license landed in',
407
398
  contentGutterX: true,
408
399
  contentGutterY: true,
409
- children: /*#__PURE__*/ _jsxs(Stack, {
410
- spacing: 1,
411
- children: [
412
- /*#__PURE__*/ _jsx(ListFilterChips, _extends({}, mineFilter.chipsProps)),
413
- /*#__PURE__*/ _jsx(ListTable, _extends({
414
- "aria-label": "Licenses you bought",
415
- rows: mineFilter.rows,
416
- columns: mineFilter.filterColumns(mineColumns)
417
- }, mineFilter.gridProps, {
418
- rowHeight: TABLE_ROW_HEIGHT,
419
- noRowsLabel: "No licenses match these filters"
420
- }))
421
- ]
400
+ children: /*#__PURE__*/ _jsx(LicenceTable, {
401
+ label: "Licenses you bought",
402
+ declaration: MINE_LICENCE_QUERY,
403
+ headers: MINE_FILTER_HEADERS,
404
+ options: mineOptions,
405
+ gridFilter: mineFilter,
406
+ lookup: mineLookup,
407
+ read: mineRead,
408
+ rows: mine,
409
+ columns: mineColumns
422
410
  })
423
411
  })
424
412
  ]
425
413
  });
426
414
  }
415
+ /**
416
+ * One license grid: its clause chips, what the query refused or said, the
417
+ * grid (filtered and searched by the query, not by itself) and its pager.
418
+ */ function LicenceTable(props) {
419
+ const { label, declaration, headers, options, gridFilter, lookup, read, rows, columns } = props;
420
+ const refused = listQueryRefusals([
421
+ ...lookup.refused,
422
+ ...read.plan.refused
423
+ ], {
424
+ fields: declaration.fields,
425
+ headers,
426
+ options
427
+ });
428
+ return /*#__PURE__*/ _jsxs(Stack, {
429
+ spacing: 1,
430
+ children: [
431
+ /*#__PURE__*/ _jsx(ListFilterChips, {
432
+ fields: declaration.fields,
433
+ headers: headers,
434
+ clauses: gridFilter.clauses,
435
+ onChange: gridFilter.setClauses,
436
+ options: options
437
+ }),
438
+ /*#__PURE__*/ _jsx(ListQueryNotices, {
439
+ refused: refused,
440
+ notices: lookup.notices
441
+ }),
442
+ /*#__PURE__*/ _jsx(ListTable, {
443
+ "aria-label": label,
444
+ rows: rows,
445
+ columns: columns,
446
+ filterMode: "server",
447
+ filterModel: gridFilter.filterModel,
448
+ onFilterModelChange: gridFilter.onFilterModelChange,
449
+ quickFilter: true,
450
+ // The query's order is the purchases' own; the grid does not re-sort a page.
451
+ sortingMode: "server",
452
+ disableColumnSorting: true,
453
+ // `ListPagination` below pages the query.
454
+ hideFooter: true,
455
+ rowHeight: TABLE_ROW_HEIGHT,
456
+ noRowsLabel: "No licenses match these filters"
457
+ }),
458
+ /*#__PURE__*/ _jsx(ListPagination, {
459
+ page: read.page,
460
+ pageSize: read.pageSize,
461
+ rowCount: rows.length,
462
+ hasMore: read.hasMore,
463
+ onPageChange: read.setPage,
464
+ onPageSizeChange: read.setPageSize
465
+ })
466
+ ]
467
+ });
468
+ }
427
469
  export default OrgLicencesPanel;
428
470
 
429
471
  //# sourceMappingURL=org-licences-panel.component.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/org-licences-panel.component.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'use client'\n\nimport { ICON_VARIANT_COMPONENT } from '@aglyn/shared-data-enums'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n useFirestore,\n useFirestoreCollection,\n useUser,\n} from '@aglyn/tenant-feature-instance'\nimport { Alert, Chip, Stack } from '@mui/material'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { inMemoryListField } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListRowsFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-rows-filter'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport {\n collection,\n documentId,\n limit,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { listingPath } from '../model/marketplace-paths'\nimport EmptyState from '@aglyn/shared-ui-jsx/components/read-gated-empty-state.component'\nimport type { ReadOutcome } from '@aglyn/shared-ui-jsx/utils/read-outcome'\n\n/*\n * What each license grid's Filters panel offers. Both lists hold every row\n * they describe, so the panel and the search answer over all of them. The\n * Listing column filters by the listing's name; who bought a license and\n * which workspace one landed in are picked.\n */\nconst HELD_FILTER_FIELDS = [\n inMemoryListField('listingId', 'text', 'listingName'),\n inMemoryListField('buyerUid', 'select', 'boughtBy'),\n]\nconst HELD_FILTER_HEADERS: Readonly<Record<string, string>> = {\n listingId: 'Listing',\n buyerUid: 'Bought by',\n}\nconst HELD_FILTER_OPTIONS = {\n buyerUid: [\n { value: 'you', label: 'You' },\n { value: 'colleague', label: 'A colleague' },\n ],\n}\nconst MINE_FILTER_FIELDS = [\n inMemoryListField('listingId', 'text', 'listingName'),\n inMemoryListField('buyerOrgId', 'select', 'licensedTo'),\n]\nconst MINE_FILTER_HEADERS: Readonly<Record<string, string>> = {\n listingId: 'Listing',\n buyerOrgId: 'Licensed to',\n}\n/** A purchase that names no organization, which entitles its buyer everywhere. */\nconst EVERY_WORKSPACE = 'every'\n/** What the quick search reads on a license row. */\nconst LICENSE_SEARCH_FIELDS = ['listingName'] as const\n\n/**\n * WHICH WORKSPACE HOLDS A LICENCE (AGL-2331).\n *\n * A marketplace purchase licenses the installing ORGANIZATION, not the person\n * who paid. That is the model the Publisher Agreement already publishes and\n * the model the install routes now enforce — and it is unusable without a\n * surface, because the two questions it creates have no answer anywhere else\n * in the console:\n *\n * \"Does THIS workspace own that component, or was it the other client's?\"\n * \"I bought this once — which workspace did the licence land in?\"\n *\n * An agency is the population this matters to most, and an agency is exactly\n * the population that cannot answer either question from memory. Before this\n * panel the only signal was a Buy button, and a Buy button is the same\n * whether you own nothing or own three of them in other workspaces.\n *\n * TWO LISTS, deliberately, because they are two different facts:\n *\n * `This workspace` is the org's inventory — what any member with install\n * rights may install here, including licences a colleague bought and\n * licences bought by someone who has since left. It is the list that\n * decides whether a Buy is needed.\n *\n * `Bought by you` is the buyer's own receipt trail across every workspace\n * they belong to. It is what turns \"I already bought this\" into \"…for\n * Northwind, and this is Contoso\".\n *\n * Both read `marketplacePurchases`, which is buyer/org/seller-gated — the org\n * clause landed with AGL-2331 for exactly this list. Held at null until the\n * org and uid resolve: a rules-shaped LIST is evaluated against the QUERY, so\n * a sentinel value is a guaranteed denial retried on the refusal cadence\n * (AGL-1440), not an empty list.\n *\n * ## Presentation (AGL-2486)\n *\n * Each list is a card, like every sibling Marketplace tab — it shipped as two\n * bare `subtitle1` headings with a loose sentence under each, which on a page\n * built from cards reads as an unfinished tab rather than an empty one.\n *\n * More than cosmetic: the sentences became `EmptyState`s, so they are now\n * GATED on the read having succeeded (AGL-1066). \"You have not bought\n * anything\" is a claim about someone's purchase history, and a refused or\n * unfinished read supports no such claim — on a tab whose whole purpose is\n * answering \"do we already own this?\", a zero-state produced by a dead\n * session is an invitation to buy something twice. Both queries are held at\n * `null` until the scope resolves, which reads as `loading` and can never\n * reach the zero-state; a listen the server has REFUSED keeps painting from\n * cache with `status: 'success'`, which is why `serverDenied` is checked\n * alongside it rather than trusting the status alone.\n */\nexport function OrgLicencesPanel({\n orgId,\n basePath,\n viewerOrgs,\n}: {\n orgId?: string | null\n /**\n * Where the marketplace hub is mounted under this organization, from the\n * shell (AGL-3080) — each row links to its listing beneath it. It replaced\n * an org slug and the console's route table, which a plugin may not\n * import.\n */\n basePath: string\n /**\n * Every workspace the reader belongs to, from the shell, so the \"Licensed\n * to\" column can name the one a purchase landed in instead of printing its\n * id. A purchase licenses an ORGANIZATION (AGL-2331), which is the whole\n * subject of this panel, and the reader's other workspaces are the answer\n * to half of it.\n */\n viewerOrgs?: readonly { id: string; name: string }[]\n}) {\n const firestore = useFirestore()\n const { data: user } = useUser()\n const uid = (user as { uid?: string } | null | undefined)?.uid\n\n /** The licences this workspace holds — whoever in it did the buying. */\n const orgLicencesRead = useFirestoreCollection<any>(\n () =>\n orgId\n ? query(\n collection(firestore, 'marketplacePurchases'),\n where('buyerOrgId', '==', orgId),\n )\n : null,\n [firestore, orgId],\n { idField: '$id' },\n )\n /** Everything this person has ever bought, in any workspace. */\n const myPurchasesRead = useFirestoreCollection<any>(\n () =>\n uid\n ? query(\n collection(firestore, 'marketplacePurchases'),\n where('buyerUid', '==', uid),\n )\n : null,\n [firestore, uid],\n { idField: '$id' },\n )\n\n /**\n * Listing id → display name.\n *\n * One subscription rather than a read per row: `marketplaceListings` is\n * world-readable (it is the catalogue), the browse grid on the sibling tab\n * already listens to it, and a per-row hook would change the hook count\n * between renders as licences arrive.\n */\n const { data: listings } = useFirestoreCollection<any>(\n /*\n * ORDERED, so the window is a reachable 200 rather than a sample.\n *\n * A capped read with no `orderBy` is answered in document-id order\n * anyway, so naming it changes no row — what it changes is that the\n * ordering is a decision rather than an accident, and that the obvious\n * next edit is caught: `orderBy('displayName')` would drop every listing\n * saved without one out of the map, and a licence whose listing is\n * missing from the map is a row that names itself by raw id.\n *\n * Ordering on the document NAME, which is what a listing id already is\n * here, keeps the map's keys and the walk in the same space. Past 200\n * listings a licence still renders — the fallback below prints the\n * listing id — so the cap degrades the label and never the row.\n */\n () =>\n query(\n collection(firestore, 'marketplaceListings'),\n orderBy(documentId()),\n limit(200),\n ),\n [firestore],\n { idField: '$id' },\n )\n const listingNames = useMemo(() => {\n const names: Record<string, string> = {}\n for (const listing of listings ?? []) {\n names[listing.$id] = String(listing.displayName ?? listing.$id)\n }\n return names\n }, [listings])\n\n /** Workspace id → the name the member already sees in the org switcher. */\n const orgNames = useMemo(() => {\n const names: Record<string, string> = {}\n for (const workspace of viewerOrgs ?? []) names[workspace.id] = workspace.name\n return names\n }, [viewerOrgs])\n\n /**\n * A refunded purchase is not a licence (AGL-1546) and must not be listed as\n * one — the whole point of this panel is deciding whether to buy, and a row\n * that says \"you own this\" when the install route answers 402 is worse than\n * no row.\n */\n const live = (rows: any[] | undefined): any[] =>\n (rows ?? []).filter((row) => !row?.refundedAt)\n\n const held = useMemo(\n () =>\n live(orgLicencesRead.data).map((row) => ({\n ...row,\n listingName:\n listingNames[String(row.listingId ?? '')] ?? String(row.listingId ?? ''),\n boughtBy: row.buyerUid === uid ? 'you' : 'colleague',\n })),\n [orgLicencesRead.data, listingNames, uid],\n )\n const mine = useMemo(\n () =>\n live(myPurchasesRead.data).map((row) => ({\n ...row,\n listingName:\n listingNames[String(row.listingId ?? '')] ?? String(row.listingId ?? ''),\n licensedTo: String(row.buyerOrgId ?? '') || EVERY_WORKSPACE,\n })),\n [myPurchasesRead.data, listingNames],\n )\n\n const mineOptions = useMemo(\n () => ({\n buyerOrgId: [\n ...(orgId ? [{ value: orgId, label: 'This workspace' }] : []),\n ...(viewerOrgs ?? [])\n .filter((workspace) => workspace.id !== orgId)\n .map((workspace) => ({ value: workspace.id, label: workspace.name })),\n { value: EVERY_WORKSPACE, label: 'Every workspace you belong to' },\n ],\n }),\n [orgId, viewerOrgs],\n )\n const heldFilter = useListRowsFilter({\n rows: held,\n fields: HELD_FILTER_FIELDS,\n options: HELD_FILTER_OPTIONS,\n headers: HELD_FILTER_HEADERS,\n search: LICENSE_SEARCH_FIELDS,\n })\n const mineFilter = useListRowsFilter({\n rows: mine,\n fields: MINE_FILTER_FIELDS,\n options: mineOptions,\n headers: MINE_FILTER_HEADERS,\n search: LICENSE_SEARCH_FIELDS,\n })\n\n /*==========================================\n * BOTH LISTS ARE WHOLE, so the grid pages them and its counts are TOTALS.\n *\n * Neither query is capped: each reads one workspace's purchases or one\n * person's, whole. So the card holds the entire list it is describing, the\n * refund filter above has already run over all of it, and the grid's\n * footer counts the collection's real size rather than a window's length —\n * the one case where a client page can state a total without qualifying it.\n *\n * Server-paging either one would break that. `refundedAt` is filtered after\n * reading, so a ten-document page arrives holding anywhere from zero to ten\n * licences, and an agency deciding whether to buy a component again is\n * exactly the reader who must not be shown a short page as a complete\n * answer.\n *=========================================*/\n const heldColumns = useMemo<GridColDef[]>(\n () => [\n {\n field: 'listingId',\n headerName: 'Listing',\n flex: 1,\n minWidth: 220,\n valueGetter: (_value, row) =>\n listingNames[String(row.listingId ?? '')] ?? String(row.listingId ?? ''),\n renderCell: ({ row, value }) => (\n <AppLink href={listingPath(basePath, String(row.listingId ?? ''))}>\n {value}\n </AppLink>\n ),\n },\n {\n field: 'buyerUid',\n headerName: 'Bought by',\n width: 150,\n // The select's value; the grid shows its label, You or A colleague.\n valueGetter: (_value, row) => row.boughtBy,\n },\n {\n field: 'amountCents',\n headerName: 'Paid',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 120,\n // What the licence cost before tax, which is what a reader compares.\n valueGetter: (_value, row) =>\n (Number(row.amountCents ?? 0) - Number(row.taxCents ?? 0)) / 100,\n valueFormatter: (value: number) => `$${value.toFixed(2)}`,\n },\n ],\n [listingNames, basePath],\n )\n const mineColumns = useMemo<GridColDef[]>(\n () => [\n {\n field: 'listingId',\n headerName: 'Listing',\n flex: 1,\n minWidth: 220,\n valueGetter: (_value, row) =>\n listingNames[String(row.listingId ?? '')] ?? String(row.listingId ?? ''),\n },\n {\n field: 'buyerOrgId',\n headerName: 'Licensed to',\n flex: 1,\n minWidth: 220,\n valueGetter: (_value, row) => {\n const licensedOrg = String(row.buyerOrgId ?? '')\n if (!licensedOrg) return 'Every workspace you belong to'\n return licensedOrg === orgId\n ? 'This workspace'\n : (orgNames[licensedOrg] ?? licensedOrg)\n },\n renderCell: ({ row, value }) => {\n const licensedOrg = String(row.buyerOrgId ?? '')\n // A purchase made before AGL-2331 named no organization, so it is\n // not reinterpreted as belonging to one — it keeps entitling this\n // buyer everywhere, exactly as it did when they paid for it. Saying\n // \"every workspace\" rather than guessing an org is the whole reason\n // nobody loses access here.\n if (!licensedOrg) return <Chip size=\"small\" label={value} />\n return licensedOrg === orgId ? (\n <Chip size=\"small\" color=\"primary\" label={value} />\n ) : (\n <Chip size=\"small\" variant=\"outlined\" label={value} />\n )\n },\n },\n ],\n [listingNames, orgId, orgNames],\n )\n\n /**\n * \"You own nothing here\" is a claim about this workspace's purchases, and a\n * refused read supports no such claim (AGL-1066). Both queries are ALSO\n * held at `null` until the org and the uid resolve, which the hook reports\n * as `loading` — so an unresolved scope can never reach the zero-state\n * either. `serverDenied` is folded in beside `status`: a listen the server\n * has refused past its retry budget keeps painting whatever the local cache\n * holds, and `status` alone still reads `success` for it.\n */\n const outcome = (read: {\n status: string\n serverDenied: boolean\n }): ReadOutcome =>\n read.status === 'error' || read.serverDenied === true\n ? 'unavailable'\n : read.status === 'success'\n ? 'loaded'\n : 'loading'\n\n return (\n <Stack spacing={3}>\n <Alert severity=\"info\">\n {'A marketplace purchase licenses one organization. Any member with ' +\n 'install permission can install what this workspace owns — and a ' +\n 'second workspace needs its own license.'}\n </Alert>\n\n {held.length === 0 ? (\n <EmptyState\n read={outcome(orgLicencesRead)}\n subject=\"this workspace’s licenses\"\n iconPath={ICON_VARIANT_COMPONENT.path}\n title={'This workspace holds no licenses'}\n description={\n 'Anything bought for this workspace shows up here, whoever on ' +\n 'the team paid for it.'\n }\n />\n ) : (\n <CardDisplay\n header={'This workspace'}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#how-installs-work-the-buyer-side',\n excerpt:\n 'What this workspace owns — installable by any member with ' +\n 'install permission, whoever on the team paid for it.',\n })}\n subheader={\n 'Installable by any member with install permission, whoever ' +\n 'on the team bought it'\n }\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <ListFilterChips {...heldFilter.chipsProps} />\n <ListTable\n aria-label=\"Licenses this workspace holds\"\n rows={heldFilter.rows}\n columns={heldFilter.filterColumns(heldColumns)}\n {...heldFilter.gridProps}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No licenses match these filters\"\n />\n </Stack>\n </CardDisplay>\n )}\n\n {mine.length === 0 ? (\n <EmptyState\n read={outcome(myPurchasesRead)}\n subject=\"your purchases\"\n iconPath={ICON_VARIANT_COMPONENT.path}\n title={'You have not bought anything yet'}\n description={\n 'Your own marketplace receipts appear here, across every ' +\n 'workspace you belong to.'\n }\n />\n ) : (\n <CardDisplay\n header={'Bought by you'}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#how-installs-work-the-buyer-side',\n excerpt:\n 'Your own purchases across every workspace you belong to — a ' +\n 'purchase licenses one organization, so this says which.',\n })}\n subheader={\n 'Your own receipts, and which workspace each license landed in'\n }\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <ListFilterChips {...mineFilter.chipsProps} />\n <ListTable\n aria-label=\"Licenses you bought\"\n rows={mineFilter.rows}\n columns={mineFilter.filterColumns(mineColumns)}\n {...mineFilter.gridProps}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No licenses match these filters\"\n />\n </Stack>\n </CardDisplay>\n )}\n </Stack>\n )\n}\n\nexport default OrgLicencesPanel\n"],"names":["ICON_VARIANT_COMPONENT","AppLink","CardDisplay","useFirestore","useFirestoreCollection","useUser","Alert","Chip","Stack","ListFilterChips","ListTable","inMemoryListField","useListRowsFilter","collection","documentId","limit","orderBy","query","where","useMemo","pluginDocsHelp","TABLE_ROW_HEIGHT","listingPath","EmptyState","HELD_FILTER_FIELDS","HELD_FILTER_HEADERS","listingId","buyerUid","HELD_FILTER_OPTIONS","value","label","MINE_FILTER_FIELDS","MINE_FILTER_HEADERS","buyerOrgId","EVERY_WORKSPACE","LICENSE_SEARCH_FIELDS","OrgLicencesPanel","orgId","basePath","viewerOrgs","firestore","data","user","uid","orgLicencesRead","idField","myPurchasesRead","listings","listingNames","names","listing","$id","String","displayName","orgNames","workspace","id","name","live","rows","filter","row","refundedAt","held","map","listingName","boughtBy","mine","licensedTo","mineOptions","heldFilter","fields","options","headers","search","mineFilter","heldColumns","field","headerName","flex","minWidth","valueGetter","_value","renderCell","href","width","type","align","headerAlign","Number","amountCents","taxCents","valueFormatter","toFixed","mineColumns","licensedOrg","size","color","variant","outcome","read","status","serverDenied","spacing","severity","length","subject","iconPath","path","title","description","header","help","anchor","excerpt","subheader","contentGutterX","contentGutterY","chipsProps","aria-label","columns","filterColumns","gridProps","rowHeight","noRowsLabel"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/org-licences-panel.component.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'use client'\n\nimport { ICON_VARIANT_COMPONENT } from '@aglyn/shared-data-enums'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n useFirestore,\n useUser,\n} from '@aglyn/tenant-feature-instance'\nimport {\n useListQuery,\n type UseListQueryResult,\n} from '@aglyn/tenant-feature-instance/hooks/use-list-query'\nimport { Alert, Chip, Stack } from '@mui/material'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport {\n type ListFilterOption,\n listFilterGridColumns,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport type { ListQueryDeclaration } from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport {\n type ListGridFilter,\n useListGridFilter,\n} from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { collection } from 'firebase/firestore'\nimport { type ReactNode, useMemo } from 'react'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { listingPath } from '../model/marketplace-paths'\nimport {\n EVERY_WORKSPACE,\n HELD_LICENCE_QUERY,\n licenceBase,\n MINE_LICENCE_QUERY,\n mineLicenceClauses,\n} from '../model/listing-query'\nimport {\n useListingNameLookup,\n useListingNames,\n} from '../hooks/use-listing-name-lookup'\nimport EmptyState from '@aglyn/shared-ui-jsx/components/read-gated-empty-state.component'\nimport type { ReadOutcome } from '@aglyn/shared-ui-jsx/utils/read-outcome'\n\n/*\n * What each license grid's Filters panel offers, and how its search reads —\n * every clause and the search on the QUERY (AGL-3321). They were matched over\n * the rows each list had loaded (the retired window filter), which answered \"no\n * match\" for a license past what was read.\n *\n * - \"This workspace\" filters by who bought it — You, by uid equality. \"A\n * colleague\" was an in-memory negation; Firestore's `!=` would lead the\n * order by buyer and needs a composite per clause beside it, so it is not\n * offered.\n * - \"Bought by you\" filters by the workspace a license landed in, or the\n * every-workspace grant a purchase naming none carries (`buyerOrgId`\n * null).\n * - The search is the listing's NAME: the listings holding the word are\n * asked first, and the license query asks for their ids\n * (`useListingNameLookup`).\n */\nconst HELD_FILTER_HEADERS: Readonly<Record<string, string>> = {\n buyerUid: 'Bought by',\n}\nconst MINE_FILTER_HEADERS: Readonly<Record<string, string>> = {\n buyerOrgId: 'Licensed to',\n}\n\n/**\n * WHICH WORKSPACE HOLDS A LICENCE (AGL-2331).\n *\n * A marketplace purchase licenses the installing ORGANIZATION, not the person\n * who paid. That is the model the Publisher Agreement already publishes and\n * the model the install routes now enforce — and it is unusable without a\n * surface, because the two questions it creates have no answer anywhere else\n * in the console:\n *\n * \"Does THIS workspace own that component, or was it the other client's?\"\n * \"I bought this once — which workspace did the licence land in?\"\n *\n * An agency is the population this matters to most, and an agency is exactly\n * the population that cannot answer either question from memory. Before this\n * panel the only signal was a Buy button, and a Buy button is the same\n * whether you own nothing or own three of them in other workspaces.\n *\n * TWO LISTS, deliberately, because they are two different facts:\n *\n * `This workspace` is the org's inventory — what any member with install\n * rights may install here, including licences a colleague bought and\n * licences bought by someone who has since left. It is the list that\n * decides whether a Buy is needed.\n *\n * `Bought by you` is the buyer's own receipt trail across every workspace\n * they belong to. It is what turns \"I already bought this\" into \"…for\n * Northwind, and this is Contoso\".\n *\n * Both read `marketplacePurchases`, which is buyer/org/seller-gated — the org\n * clause landed with AGL-2331 for exactly this list. Held at null until the\n * org and uid resolve: a rules-shaped LIST is evaluated against the QUERY, so\n * a sentinel value is a guaranteed denial retried on the refusal cadence\n * (AGL-1440), not an empty list.\n *\n * ## Presentation (AGL-2486)\n *\n * Each list is a card, like every sibling Marketplace tab — it shipped as two\n * bare `subtitle1` headings with a loose sentence under each, which on a page\n * built from cards reads as an unfinished tab rather than an empty one.\n *\n * More than cosmetic: the sentences became `EmptyState`s, so they are now\n * GATED on the read having succeeded (AGL-1066). \"You have not bought\n * anything\" is a claim about someone's purchase history, and a refused or\n * unfinished read supports no such claim — on a tab whose whole purpose is\n * answering \"do we already own this?\", a zero-state produced by a dead\n * session is an invitation to buy something twice. Both queries are held at\n * `null` until the scope resolves, which reads as `loading` and can never\n * reach the zero-state; a listen the server has REFUSED keeps painting from\n * cache with `status: 'success'`, which is why `serverDenied` is checked\n * alongside it rather than trusting the status alone.\n */\nexport function OrgLicencesPanel({\n orgId,\n basePath,\n viewerOrgs,\n}: {\n orgId?: string | null\n /**\n * Where the marketplace hub is mounted under this organization, from the\n * shell (AGL-3080) — each row links to its listing beneath it. It replaced\n * an org slug and the console's route table, which a plugin may not\n * import.\n */\n basePath: string\n /**\n * Every workspace the reader belongs to, from the shell, so the \"Licensed\n * to\" column can name the one a purchase landed in instead of printing its\n * id. A purchase licenses an ORGANIZATION (AGL-2331), which is the whole\n * subject of this panel, and the reader's other workspaces are the answer\n * to half of it.\n */\n viewerOrgs?: readonly { id: string; name: string }[]\n}) {\n const firestore = useFirestore()\n const { data: user } = useUser()\n const uid = (user as { uid?: string } | null | undefined)?.uid\n\n /** Workspace id → the name the member already sees in the org switcher. */\n const orgNames = useMemo(() => {\n const names: Record<string, string> = {}\n for (const workspace of viewerOrgs ?? []) names[workspace.id] = workspace.name\n return names\n }, [viewerOrgs])\n\n const heldOptions = useMemo(\n () => ({ buyerUid: uid ? [{ value: uid, label: 'You' }] : [] }),\n [uid],\n )\n const mineOptions = useMemo(\n () => ({\n buyerOrgId: [\n ...(orgId ? [{ value: orgId, label: 'This workspace' }] : []),\n ...(viewerOrgs ?? [])\n .filter((workspace) => workspace.id !== orgId)\n .map((workspace) => ({ value: workspace.id, label: workspace.name })),\n { value: EVERY_WORKSPACE, label: 'Every workspace you belong to' },\n ],\n }),\n [orgId, viewerOrgs],\n )\n const heldFilter = useListGridFilter({ selectFields: ['buyerUid'] })\n const mineFilter = useListGridFilter({ selectFields: ['buyerOrgId'] })\n const heldLookup = useListingNameLookup(heldFilter.searchWords)\n const mineLookup = useListingNameLookup(mineFilter.searchWords)\n\n /*\n * Both lists read `marketplacePurchases`, which is buyer/org/seller-gated —\n * the org clause landed with AGL-2331 for exactly this list. Held at null\n * until the org and uid resolve: a rules-shaped LIST is evaluated against\n * the QUERY, so a sentinel value is a guaranteed denial retried on the\n * refusal cadence (AGL-1440), not an empty list. A search whose listing\n * lookup is still out holds too, and one that named no listing is answered\n * with no rows rather than an `in []` Firestore refuses.\n *\n * A refunded purchase is not a licence (AGL-1546) and must not be listed as\n * one — the whole point of this panel is deciding whether to buy, and a row\n * that says \"you own this\" when the install route answers 402 is worse than\n * no row. `refundedAt == null` is on both queries (`licenceBase`).\n */\n const purchases = useMemo(\n () => collection(firestore, 'marketplacePurchases'),\n [firestore],\n )\n const heldRead = useListQuery<any>({\n collection:\n orgId && !heldLookup.pending && heldLookup.ids?.length !== 0\n ? purchases\n : null,\n declaration: HELD_LICENCE_QUERY,\n request: {\n clauses: heldFilter.clauses,\n base: orgId\n ? licenceBase({ path: 'buyerOrgId', value: orgId }, heldLookup.ids)\n : [],\n },\n deps: [firestore, orgId, heldLookup.pending, (heldLookup.ids ?? []).join(',')],\n idField: '$id',\n })\n const mineRead = useListQuery<any>({\n collection:\n uid && !mineLookup.pending && mineLookup.ids?.length !== 0 ? purchases : null,\n declaration: MINE_LICENCE_QUERY,\n request: {\n clauses: mineLicenceClauses(mineFilter.clauses),\n base: uid ? licenceBase({ path: 'buyerUid', value: uid }, mineLookup.ids) : [],\n },\n deps: [firestore, uid, mineLookup.pending, (mineLookup.ids ?? []).join(',')],\n idField: '$id',\n })\n const heldNamedNone = heldLookup.ids?.length === 0\n const mineNamedNone = mineLookup.ids?.length === 0\n const heldRows = useMemo(\n () => (heldNamedNone ? [] : heldRead.rows),\n [heldNamedNone, heldRead.rows],\n )\n const mineRows = useMemo(\n () => (mineNamedNone ? [] : mineRead.rows),\n [mineNamedNone, mineRead.rows],\n )\n\n /**\n * Listing id → display name, read by id for the rows on screen. It was a\n * window of the first two hundred listings, past which a license printed\n * its listing's raw id.\n */\n const listingNames = useListingNames(\n [...heldRows, ...mineRows].map((row) => String(row.listingId ?? '')),\n )\n\n const held = useMemo(\n () =>\n heldRows.map((row: any) => ({\n ...row,\n boughtBy: row.buyerUid === uid ? 'You' : 'A colleague',\n })),\n [heldRows, uid],\n )\n const mine = mineRows\n\n const heldColumns = useMemo<GridColDef[]>(\n () =>\n listFilterGridColumns(\n [\n {\n field: 'listingId',\n headerName: 'Listing',\n flex: 1,\n minWidth: 220,\n valueGetter: (_value, row) =>\n listingNames[String(row.listingId ?? '')] ?? String(row.listingId ?? ''),\n renderCell: ({ row, value }) => (\n <AppLink href={listingPath(basePath, String(row.listingId ?? ''))}>\n {value}\n </AppLink>\n ),\n },\n {\n field: 'buyerUid',\n headerName: 'Bought by',\n width: 150,\n // Drawn as who it was, whatever the select's one choice is.\n renderCell: ({ row }) => row.boughtBy,\n },\n {\n field: 'amountCents',\n headerName: 'Paid',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 120,\n // What the licence cost before tax, which is what a reader compares.\n valueGetter: (_value, row) =>\n (Number(row.amountCents ?? 0) - Number(row.taxCents ?? 0)) / 100,\n valueFormatter: (value: number) => `$${value.toFixed(2)}`,\n },\n ],\n HELD_LICENCE_QUERY.fields,\n heldOptions,\n HELD_FILTER_HEADERS,\n ),\n [listingNames, basePath, heldOptions],\n )\n const mineColumns = useMemo<GridColDef[]>(\n () =>\n listFilterGridColumns(\n [\n {\n field: 'listingId',\n headerName: 'Listing',\n flex: 1,\n minWidth: 220,\n valueGetter: (_value, row) =>\n listingNames[String(row.listingId ?? '')] ?? String(row.listingId ?? ''),\n },\n {\n field: 'buyerOrgId',\n headerName: 'Licensed to',\n flex: 1,\n minWidth: 220,\n valueGetter: (_value, row) => {\n const licensedOrg = String(row.buyerOrgId ?? '')\n if (!licensedOrg) return 'Every workspace you belong to'\n return licensedOrg === orgId\n ? 'This workspace'\n : (orgNames[licensedOrg] ?? licensedOrg)\n },\n renderCell: ({ row, value }) => {\n const licensedOrg = String(row.buyerOrgId ?? '')\n // A purchase made before AGL-2331 named no organization, so it is\n // not reinterpreted as belonging to one — it keeps entitling this\n // buyer everywhere, exactly as it did when they paid for it. Saying\n // \"every workspace\" rather than guessing an org is the whole reason\n // nobody loses access here.\n if (!licensedOrg) return <Chip size=\"small\" label={value} />\n return licensedOrg === orgId ? (\n <Chip size=\"small\" color=\"primary\" label={value} />\n ) : (\n <Chip size=\"small\" variant=\"outlined\" label={value} />\n )\n },\n },\n ],\n MINE_LICENCE_QUERY.fields,\n mineOptions,\n MINE_FILTER_HEADERS,\n ),\n [listingNames, orgId, orgNames, mineOptions],\n )\n\n /**\n * \"You own nothing here\" is a claim about this workspace's purchases, and a\n * refused read supports no such claim (AGL-1066). Both queries are ALSO\n * held at `null` until the org and the uid resolve, which the hook reports\n * as `loading` — so an unresolved scope can never reach the zero-state\n * either. `serverDenied` is folded in beside `status`: a listen the server\n * has refused past its retry budget keeps painting whatever the local cache\n * holds, and `status` alone still reads `success` for it.\n */\n const outcome = (read: {\n status: string\n serverDenied: boolean\n }): ReadOutcome =>\n read.status === 'error' || read.serverDenied === true\n ? 'unavailable'\n : read.status === 'success'\n ? 'loaded'\n : 'loading'\n\n const filtering = (filter: ListGridFilter) =>\n filter.clauses.length > 0 || filter.searchWords.length > 0\n\n return (\n <Stack spacing={3}>\n <Alert severity=\"info\">\n {'A marketplace purchase licenses one organization. Any member with ' +\n 'install permission can install what this workspace owns — and a ' +\n 'second workspace needs its own license.'}\n </Alert>\n\n {held.length === 0 && heldRead.page === 0 && !filtering(heldFilter) ? (\n <EmptyState\n read={outcome(heldRead)}\n subject=\"this workspace’s licenses\"\n iconPath={ICON_VARIANT_COMPONENT.path}\n title={'This workspace holds no licenses'}\n description={\n 'Anything bought for this workspace shows up here, whoever on ' +\n 'the team paid for it.'\n }\n />\n ) : (\n <CardDisplay\n header={'This workspace'}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#how-installs-work-the-buyer-side',\n excerpt:\n 'What this workspace owns — installable by any member with ' +\n 'install permission, whoever on the team paid for it.',\n })}\n subheader={\n 'Installable by any member with install permission, whoever ' +\n 'on the team bought it'\n }\n contentGutterX\n contentGutterY\n >\n <LicenceTable\n label=\"Licenses this workspace holds\"\n declaration={HELD_LICENCE_QUERY}\n headers={HELD_FILTER_HEADERS}\n options={heldOptions}\n gridFilter={heldFilter}\n lookup={heldLookup}\n read={heldRead}\n rows={held}\n columns={heldColumns}\n />\n </CardDisplay>\n )}\n\n {mine.length === 0 && mineRead.page === 0 && !filtering(mineFilter) ? (\n <EmptyState\n read={outcome(mineRead)}\n subject=\"your purchases\"\n iconPath={ICON_VARIANT_COMPONENT.path}\n title={'You have not bought anything yet'}\n description={\n 'Your own marketplace receipts appear here, across every ' +\n 'workspace you belong to.'\n }\n />\n ) : (\n <CardDisplay\n header={'Bought by you'}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#how-installs-work-the-buyer-side',\n excerpt:\n 'Your own purchases across every workspace you belong to — a ' +\n 'purchase licenses one organization, so this says which.',\n })}\n subheader={\n 'Your own receipts, and which workspace each license landed in'\n }\n contentGutterX\n contentGutterY\n >\n <LicenceTable\n label=\"Licenses you bought\"\n declaration={MINE_LICENCE_QUERY}\n headers={MINE_FILTER_HEADERS}\n options={mineOptions}\n gridFilter={mineFilter}\n lookup={mineLookup}\n read={mineRead}\n rows={mine}\n columns={mineColumns}\n />\n </CardDisplay>\n )}\n </Stack>\n )\n}\n\n/**\n * One license grid: its clause chips, what the query refused or said, the\n * grid (filtered and searched by the query, not by itself) and its pager.\n */\nfunction LicenceTable(props: {\n label: string\n declaration: ListQueryDeclaration\n headers: Readonly<Record<string, string>>\n options: Readonly<Record<string, readonly ListFilterOption[]>>\n gridFilter: ListGridFilter\n lookup: ReturnType<typeof useListingNameLookup>\n read: UseListQueryResult<any>\n rows: any[]\n columns: GridColDef[]\n}): ReactNode {\n const {\n label,\n declaration,\n headers,\n options,\n gridFilter,\n lookup,\n read,\n rows,\n columns,\n } = props\n const refused = listQueryRefusals([...lookup.refused, ...read.plan.refused], {\n fields: declaration.fields,\n headers,\n options,\n })\n return (\n <Stack spacing={1}>\n <ListFilterChips\n fields={declaration.fields}\n headers={headers}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={options}\n />\n <ListQueryNotices refused={refused} notices={lookup.notices} />\n <ListTable\n aria-label={label}\n rows={rows}\n columns={columns}\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n // The query's order is the purchases' own; the grid does not re-sort a page.\n sortingMode=\"server\"\n disableColumnSorting\n // `ListPagination` below pages the query.\n hideFooter\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No licenses match these filters\"\n />\n <ListPagination\n page={read.page}\n pageSize={read.pageSize}\n rowCount={rows.length}\n hasMore={read.hasMore}\n onPageChange={read.setPage}\n onPageSizeChange={read.setPageSize}\n />\n </Stack>\n )\n}\n\nexport default OrgLicencesPanel\n"],"names":["ICON_VARIANT_COMPONENT","AppLink","CardDisplay","useFirestore","useUser","useListQuery","Alert","Chip","Stack","ListFilterChips","ListPagination","ListQueryNotices","listQueryRefusals","ListTable","listFilterGridColumns","useListGridFilter","collection","useMemo","pluginDocsHelp","TABLE_ROW_HEIGHT","listingPath","EVERY_WORKSPACE","HELD_LICENCE_QUERY","licenceBase","MINE_LICENCE_QUERY","mineLicenceClauses","useListingNameLookup","useListingNames","EmptyState","HELD_FILTER_HEADERS","buyerUid","MINE_FILTER_HEADERS","buyerOrgId","OrgLicencesPanel","orgId","basePath","viewerOrgs","heldLookup","mineLookup","firestore","data","user","uid","orgNames","names","workspace","id","name","heldOptions","value","label","mineOptions","filter","map","heldFilter","selectFields","mineFilter","searchWords","purchases","heldRead","pending","ids","length","declaration","request","clauses","base","path","deps","join","idField","mineRead","heldNamedNone","mineNamedNone","heldRows","rows","mineRows","listingNames","row","String","listingId","held","boughtBy","mine","heldColumns","field","headerName","flex","minWidth","valueGetter","_value","renderCell","href","width","type","align","headerAlign","Number","amountCents","taxCents","valueFormatter","toFixed","fields","mineColumns","licensedOrg","size","color","variant","outcome","read","status","serverDenied","filtering","spacing","severity","page","subject","iconPath","title","description","header","help","anchor","excerpt","subheader","contentGutterX","contentGutterY","LicenceTable","headers","options","gridFilter","lookup","columns","props","refused","plan","onChange","setClauses","notices","aria-label","filterMode","filterModel","onFilterModelChange","quickFilter","sortingMode","disableColumnSorting","hideFooter","rowHeight","noRowsLabel","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize"],"mappings":"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"}