@aglyn/plugins-marketplace 1.0.0-beta.188 → 1.0.0-beta.190
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-marketplace",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.190",
|
|
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.
|
|
29
|
-
"@aglyn/aglyn-markdown-editor": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-ui-next": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
38
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
39
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
40
|
-
"@aglyn/tenant-runtime": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.190",
|
|
29
|
+
"@aglyn/aglyn-markdown-editor": "1.0.0-beta.190",
|
|
30
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.190",
|
|
31
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.190",
|
|
32
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.190",
|
|
33
|
+
"@aglyn/shared-ui-next": "1.0.0-beta.190",
|
|
34
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.190",
|
|
35
|
+
"@aglyn/shared-util-email": "1.0.0-beta.190",
|
|
36
|
+
"@aglyn/shared-util-http": "1.0.0-beta.190",
|
|
37
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.190",
|
|
38
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.190",
|
|
39
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.190",
|
|
40
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.190",
|
|
41
41
|
"@swc/helpers": "0.5.23"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
@@ -14,18 +14,59 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ 'use client';
|
|
17
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
18
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
18
19
|
import { ICON_VARIANT_COMPONENT } from "@aglyn/shared-data-enums";
|
|
19
20
|
import { AppLink, CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
20
21
|
import { useFirestore, useFirestoreCollection, useUser } from "@aglyn/tenant-feature-instance";
|
|
21
22
|
import { Alert, Chip, Stack } from "@mui/material";
|
|
23
|
+
import ListFilterChips from "@aglyn/shared-ui-jsx/components/list-filter-chips.component";
|
|
22
24
|
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";
|
|
23
27
|
import { collection, documentId, limit, orderBy, query, where } from "firebase/firestore";
|
|
24
28
|
import { useMemo } from "react";
|
|
25
29
|
import { pluginDocsHelp } from "@aglyn/aglyn";
|
|
26
30
|
import { TABLE_ROW_HEIGHT } from "@aglyn/shared-ui-jsx/const/table-pagination";
|
|
27
31
|
import { listingPath } from "../model/marketplace-paths.js";
|
|
28
32
|
import EmptyState from "@aglyn/shared-ui-jsx/components/read-gated-empty-state.component";
|
|
33
|
+
/*
|
|
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',
|
|
44
|
+
buyerUid: 'Bought by'
|
|
45
|
+
};
|
|
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
|
+
const MINE_FILTER_HEADERS = {
|
|
63
|
+
listingId: 'Listing',
|
|
64
|
+
buyerOrgId: 'Licensed to'
|
|
65
|
+
};
|
|
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
|
+
];
|
|
29
70
|
/**
|
|
30
71
|
* WHICH WORKSPACE HOLDS A LICENCE (AGL-2331).
|
|
31
72
|
*
|
|
@@ -141,8 +182,62 @@ import EmptyState from "@aglyn/shared-ui-jsx/components/read-gated-empty-state.c
|
|
|
141
182
|
* that says "you own this" when the install route answers 402 is worse than
|
|
142
183
|
* no row.
|
|
143
184
|
*/ const live = (rows)=>(rows != null ? rows : []).filter((row)=>!(row == null ? void 0 : row.refundedAt));
|
|
144
|
-
const held = live(orgLicencesRead.data)
|
|
145
|
-
|
|
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
|
+
});
|
|
191
|
+
}), [
|
|
192
|
+
orgLicencesRead.data,
|
|
193
|
+
listingNames,
|
|
194
|
+
uid
|
|
195
|
+
]);
|
|
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
|
+
const mineOptions = useMemo(()=>({
|
|
207
|
+
buyerOrgId: [
|
|
208
|
+
...orgId ? [
|
|
209
|
+
{
|
|
210
|
+
value: orgId,
|
|
211
|
+
label: 'This workspace'
|
|
212
|
+
}
|
|
213
|
+
] : [],
|
|
214
|
+
...(viewerOrgs != null ? viewerOrgs : []).filter((workspace)=>workspace.id !== orgId).map((workspace)=>({
|
|
215
|
+
value: workspace.id,
|
|
216
|
+
label: workspace.name
|
|
217
|
+
})),
|
|
218
|
+
{
|
|
219
|
+
value: EVERY_WORKSPACE,
|
|
220
|
+
label: 'Every workspace you belong to'
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
}), [
|
|
224
|
+
orgId,
|
|
225
|
+
viewerOrgs
|
|
226
|
+
]);
|
|
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
|
|
233
|
+
});
|
|
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
|
|
240
|
+
});
|
|
146
241
|
/*==========================================
|
|
147
242
|
* BOTH LISTS ARE WHOLE, so the grid pages them and its counts are TOTALS.
|
|
148
243
|
*
|
|
@@ -179,7 +274,8 @@ import EmptyState from "@aglyn/shared-ui-jsx/components/read-gated-empty-state.c
|
|
|
179
274
|
field: 'buyerUid',
|
|
180
275
|
headerName: 'Bought by',
|
|
181
276
|
width: 150,
|
|
182
|
-
|
|
277
|
+
// The select's value; the grid shows its label, You or A colleague.
|
|
278
|
+
valueGetter: (_value, row)=>row.boughtBy
|
|
183
279
|
},
|
|
184
280
|
{
|
|
185
281
|
field: 'amountCents',
|
|
@@ -197,8 +293,7 @@ import EmptyState from "@aglyn/shared-ui-jsx/components/read-gated-empty-state.c
|
|
|
197
293
|
}
|
|
198
294
|
], [
|
|
199
295
|
listingNames,
|
|
200
|
-
basePath
|
|
201
|
-
uid
|
|
296
|
+
basePath
|
|
202
297
|
]);
|
|
203
298
|
const mineColumns = useMemo(()=>[
|
|
204
299
|
{
|
|
@@ -281,11 +376,19 @@ import EmptyState from "@aglyn/shared-ui-jsx/components/read-gated-empty-state.c
|
|
|
281
376
|
subheader: 'Installable by any member with install permission, whoever ' + 'on the team bought it',
|
|
282
377
|
contentGutterX: true,
|
|
283
378
|
contentGutterY: true,
|
|
284
|
-
children: /*#__PURE__*/
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
+
]
|
|
289
392
|
})
|
|
290
393
|
}),
|
|
291
394
|
mine.length === 0 ? /*#__PURE__*/ _jsx(EmptyState, {
|
|
@@ -303,11 +406,19 @@ import EmptyState from "@aglyn/shared-ui-jsx/components/read-gated-empty-state.c
|
|
|
303
406
|
subheader: 'Your own receipts, and which workspace each license landed in',
|
|
304
407
|
contentGutterX: true,
|
|
305
408
|
contentGutterY: true,
|
|
306
|
-
children: /*#__PURE__*/
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
+
]
|
|
311
422
|
})
|
|
312
423
|
})
|
|
313
424
|
]
|
|
@@ -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 { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\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 * 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) =>\n (rows ?? []).filter((row) => !row?.refundedAt)\n\n const held = live(orgLicencesRead.data)\n const mine = live(myPurchasesRead.data)\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 valueGetter: (_value, row) => (row.buyerUid === uid ? 'You' : 'A colleague'),\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, uid],\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 <ListTable\n aria-label=\"Licenses this workspace holds\"\n rows={held}\n columns={heldColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n />\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 <ListTable\n aria-label=\"Licenses you bought\"\n rows={mine}\n columns={mineColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n />\n </CardDisplay>\n )}\n </Stack>\n )\n}\n\nexport default OrgLicencesPanel\n"],"names":["ICON_VARIANT_COMPONENT","AppLink","CardDisplay","useFirestore","useFirestoreCollection","useUser","Alert","Chip","Stack","ListTable","collection","documentId","limit","orderBy","query","where","useMemo","pluginDocsHelp","TABLE_ROW_HEIGHT","listingPath","EmptyState","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","mine","heldColumns","field","headerName","flex","minWidth","valueGetter","_value","listingId","renderCell","value","href","width","buyerUid","type","align","headerAlign","Number","amountCents","taxCents","valueFormatter","toFixed","mineColumns","licensedOrg","buyerOrgId","size","label","color","variant","outcome","read","status","serverDenied","spacing","severity","length","subject","iconPath","path","title","description","header","help","anchor","excerpt","subheader","contentGutterX","contentGutterY","aria-label","columns","rowHeight"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,sBAAsB,QAAQ,2BAA0B;AACjE,SAASC,OAAO,EAAEC,WAAW,QAAQ,uBAAsB;AAC3D,SACEC,YAAY,EACZC,sBAAsB,EACtBC,OAAO,QACF,iCAAgC;AACvC,SAASC,KAAK,EAAEC,IAAI,EAAEC,KAAK,QAAQ,gBAAe;AAClD,SAASC,SAAS,QAAQ,uDAAsD;AAEhF,SACEC,UAAU,EACVC,UAAU,EACVC,KAAK,EACLC,OAAO,EACPC,KAAK,EACLC,KAAK,QACA,qBAAoB;AAC3B,SAASC,OAAO,QAAQ,QAAO;AAC/B,SAASC,cAAc,QAAQ,eAAc;AAC7C,SAASC,gBAAgB,QAAQ,8CAA6C;AAC9E,SAASC,WAAW,QAAQ,gCAA4B;AACxD,OAAOC,gBAAgB,mEAAkE;AAGzF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkDC,GACD,OAAO,SAASC,iBAAiB,EAC/BC,KAAK,EACLC,QAAQ,EACRC,UAAU,EAkBX;IACC,MAAMC,YAAYtB;IAClB,MAAM,EAAEuB,MAAMC,IAAI,EAAE,GAAGtB;IACvB,MAAMuB,MAAOD,wBAAD,AAACA,KAA8CC,GAAG;IAE9D,sEAAsE,GACtE,MAAMC,kBAAkBzB,uBACtB,IACEkB,QACIR,MACEJ,WAAWe,WAAW,yBACtBV,MAAM,cAAc,MAAMO,UAE5B,MACN;QAACG;QAAWH;KAAM,EAClB;QAAEQ,SAAS;IAAM;IAEnB,8DAA8D,GAC9D,MAAMC,kBAAkB3B,uBACtB,IACEwB,MACId,MACEJ,WAAWe,WAAW,yBACtBV,MAAM,YAAY,MAAMa,QAE1B,MACN;QAACH;QAAWG;KAAI,EAChB;QAAEE,SAAS;IAAM;IAGnB;;;;;;;GAOC,GACD,MAAM,EAAEJ,MAAMM,QAAQ,EAAE,GAAG5B,uBACzB;;;;;;;;;;;;;;KAcC,GACD,IACEU,MACEJ,WAAWe,WAAW,wBACtBZ,QAAQF,eACRC,MAAM,OAEV;QAACa;KAAU,EACX;QAAEK,SAAS;IAAM;IAEnB,MAAMG,eAAejB,QAAQ;QAC3B,MAAMkB,QAAgC,CAAC;QACvC,KAAK,MAAMC,WAAWH,mBAAAA,WAAY,EAAE,CAAE;gBACRG;YAA5BD,KAAK,CAACC,QAAQC,GAAG,CAAC,GAAGC,QAAOF,uBAAAA,QAAQG,WAAW,YAAnBH,uBAAuBA,QAAQC,GAAG;QAChE;QACA,OAAOF;IACT,GAAG;QAACF;KAAS;IAEb,yEAAyE,GACzE,MAAMO,WAAWvB,QAAQ;QACvB,MAAMkB,QAAgC,CAAC;QACvC,KAAK,MAAMM,aAAahB,qBAAAA,aAAc,EAAE,CAAEU,KAAK,CAACM,UAAUC,EAAE,CAAC,GAAGD,UAAUE,IAAI;QAC9E,OAAOR;IACT,GAAG;QAACV;KAAW;IAEf;;;;;GAKC,GACD,MAAMmB,OAAO,CAACC,OACZ,CAACA,eAAAA,OAAQ,EAAE,EAAEC,MAAM,CAAC,CAACC,MAAQ,EAACA,uBAAAA,IAAKC,UAAU;IAE/C,MAAMC,OAAOL,KAAKd,gBAAgBH,IAAI;IACtC,MAAMuB,OAAON,KAAKZ,gBAAgBL,IAAI;IAEtC;;;;;;;;;;;;;;6CAc2C,GAC3C,MAAMwB,cAAclC,QAClB,IAAM;YACJ;gBACEmC,OAAO;gBACPC,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQV;wBACpBb,sBAAoBa,gBAAgCA;4BAApDb,uBAAAA,YAAY,CAACI,QAAOS,iBAAAA,IAAIW,SAAS,YAAbX,iBAAiB,IAAI,YAAzCb,uBAA6CI,QAAOS,kBAAAA,IAAIW,SAAS,YAAbX,kBAAiB;;gBACvEY,YAAY,CAAC,EAAEZ,GAAG,EAAEa,KAAK,EAAE;wBACmBb;yCAA5C,KAAC7C;wBAAQ2D,MAAMzC,YAAYI,UAAUc,QAAOS,iBAAAA,IAAIW,SAAS,YAAbX,iBAAiB;kCAC1Da;;;YAGP;YACA;gBACER,OAAO;gBACPC,YAAY;gBACZS,OAAO;gBACPN,aAAa,CAACC,QAAQV,MAASA,IAAIgB,QAAQ,KAAKlC,MAAM,QAAQ;YAChE;YACA;gBACEuB,OAAO;gBACPC,YAAY;gBACZW,MAAM;gBACNC,OAAO;gBACPC,aAAa;gBACbJ,OAAO;gBACP,qEAAqE;gBACrEN,aAAa,CAACC,QAAQV;wBACZA,kBAA+BA;2BAAvC,AAACoB,CAAAA,QAAOpB,mBAAAA,IAAIqB,WAAW,YAAfrB,mBAAmB,KAAKoB,QAAOpB,gBAAAA,IAAIsB,QAAQ,YAAZtB,gBAAgB,EAAC,IAAK;;gBAC/DuB,gBAAgB,CAACV,QAAkB,CAAC,CAAC,EAAEA,MAAMW,OAAO,CAAC,IAAI;YAC3D;SACD,EACD;QAACrC;QAAcV;QAAUK;KAAI;IAE/B,MAAM2C,cAAcvD,QAClB,IAAM;YACJ;gBACEmC,OAAO;gBACPC,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQV;wBACpBb,sBAAoBa,gBAAgCA;4BAApDb,uBAAAA,YAAY,CAACI,QAAOS,iBAAAA,IAAIW,SAAS,YAAbX,iBAAiB,IAAI,YAAzCb,uBAA6CI,QAAOS,kBAAAA,IAAIW,SAAS,YAAbX,kBAAiB;;YACzE;YACA;gBACEK,OAAO;gBACPC,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,aAAa,CAACC,QAAQV;wBACOA,iBAItBP;oBAJL,MAAMiC,cAAcnC,QAAOS,kBAAAA,IAAI2B,UAAU,YAAd3B,kBAAkB;oBAC7C,IAAI,CAAC0B,aAAa,OAAO;oBACzB,OAAOA,gBAAgBlD,QACnB,oBACCiB,wBAAAA,QAAQ,CAACiC,YAAY,YAArBjC,wBAAyBiC;gBAChC;gBACAd,YAAY,CAAC,EAAEZ,GAAG,EAAEa,KAAK,EAAE;wBACEb;oBAA3B,MAAM0B,cAAcnC,QAAOS,kBAAAA,IAAI2B,UAAU,YAAd3B,kBAAkB;oBAC7C,kEAAkE;oBAClE,kEAAkE;oBAClE,oEAAoE;oBACpE,oEAAoE;oBACpE,4BAA4B;oBAC5B,IAAI,CAAC0B,aAAa,qBAAO,KAACjE;wBAAKmE,MAAK;wBAAQC,OAAOhB;;oBACnD,OAAOa,gBAAgBlD,sBACrB,KAACf;wBAAKmE,MAAK;wBAAQE,OAAM;wBAAUD,OAAOhB;uCAE1C,KAACpD;wBAAKmE,MAAK;wBAAQG,SAAQ;wBAAWF,OAAOhB;;gBAEjD;YACF;SACD,EACD;QAAC1B;QAAcX;QAAOiB;KAAS;IAGjC;;;;;;;;GAQC,GACD,MAAMuC,UAAU,CAACC,OAIfA,KAAKC,MAAM,KAAK,WAAWD,KAAKE,YAAY,KAAK,OAC7C,gBACAF,KAAKC,MAAM,KAAK,YACd,WACA;IAER,qBACE,MAACxE;QAAM0E,SAAS;;0BACd,KAAC5E;gBAAM6E,UAAS;0BACb,uEACC,qEACA;;YAGHnC,KAAKoC,MAAM,KAAK,kBACf,KAAChE;gBACC2D,MAAMD,QAAQjD;gBACdwD,SAAQ;gBACRC,UAAUtF,uBAAuBuF,IAAI;gBACrCC,OAAO;gBACPC,aACE,kEACA;+BAIJ,KAACvF;gBACCwF,QAAQ;gBACRC,MAAM1E,eAAe,qBAAqB;oBACxC2E,QAAQ;oBACRC,SACE,+DACA;gBACJ;gBACAC,WACE,gEACA;gBAEFC,cAAc;gBACdC,cAAc;0BAEd,cAAA,KAACvF;oBACCwF,cAAW;oBACXrD,MAAMI;oBACNkD,SAAShD;oBACTiD,WAAWjF;;;YAKhB+B,KAAKmC,MAAM,KAAK,kBACf,KAAChE;gBACC2D,MAAMD,QAAQ/C;gBACdsD,SAAQ;gBACRC,UAAUtF,uBAAuBuF,IAAI;gBACrCC,OAAO;gBACPC,aACE,6DACA;+BAIJ,KAACvF;gBACCwF,QAAQ;gBACRC,MAAM1E,eAAe,qBAAqB;oBACxC2E,QAAQ;oBACRC,SACE,iEACA;gBACJ;gBACAC,WACE;gBAEFC,cAAc;gBACdC,cAAc;0BAEd,cAAA,KAACvF;oBACCwF,cAAW;oBACXrD,MAAMK;oBACNiD,SAAS3B;oBACT4B,WAAWjF;;;;;AAMvB;AAEA,eAAeG,iBAAgB"}
|
|
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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"}
|