@aglyn/plugins-marketplace 1.0.0-beta.149 → 1.0.0-beta.151

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 (92) hide show
  1. package/package.json +15 -11
  2. package/src/lib/components/edit-listing-dialog.component.d.ts +20 -0
  3. package/src/lib/components/edit-listing-dialog.component.js +51 -0
  4. package/src/lib/components/edit-listing-dialog.component.js.map +1 -0
  5. package/src/lib/components/listing-detail-editor.component.d.ts +24 -0
  6. package/src/lib/components/listing-detail-editor.component.js +457 -0
  7. package/src/lib/components/listing-detail-editor.component.js.map +1 -0
  8. package/src/lib/components/listing-detail-page.component.d.ts +28 -0
  9. package/src/lib/components/listing-detail-page.component.js +151 -0
  10. package/src/lib/components/listing-detail-page.component.js.map +1 -0
  11. package/src/lib/components/listing-review-status.component.d.ts +67 -0
  12. package/src/lib/components/listing-review-status.component.js +301 -0
  13. package/src/lib/components/listing-review-status.component.js.map +1 -0
  14. package/src/lib/components/listing-verification-request.component.d.ts +40 -0
  15. package/src/lib/components/listing-verification-request.component.js +170 -0
  16. package/src/lib/components/listing-verification-request.component.js.map +1 -0
  17. package/src/lib/components/marketplace-hub.component.d.ts +43 -0
  18. package/src/lib/components/marketplace-hub.component.js +244 -0
  19. package/src/lib/components/marketplace-hub.component.js.map +1 -0
  20. package/src/lib/components/marketplace-reports-page.component.d.ts +23 -0
  21. package/src/lib/components/marketplace-reports-page.component.js +324 -0
  22. package/src/lib/components/marketplace-reports-page.component.js.map +1 -0
  23. package/src/lib/components/org-licences-panel.component.d.ts +73 -0
  24. package/src/lib/components/org-licences-panel.component.js +318 -0
  25. package/src/lib/components/org-licences-panel.component.js.map +1 -0
  26. package/src/lib/components/org-publish-panel.component.d.ts +45 -0
  27. package/src/lib/components/org-publish-panel.component.js +402 -0
  28. package/src/lib/components/org-publish-panel.component.js.map +1 -0
  29. package/src/lib/components/org-seller-panel.component.d.ts +28 -0
  30. package/src/lib/components/org-seller-panel.component.js +956 -0
  31. package/src/lib/components/org-seller-panel.component.js.map +1 -0
  32. package/src/lib/components/plugin-review-detail.component.d.ts +16 -0
  33. package/src/lib/components/plugin-review-detail.component.js +1438 -0
  34. package/src/lib/components/plugin-review-detail.component.js.map +1 -0
  35. package/src/lib/components/plugin-reviews-queue.component.d.ts +13 -0
  36. package/src/lib/components/plugin-reviews-queue.component.js +385 -0
  37. package/src/lib/components/plugin-reviews-queue.component.js.map +1 -0
  38. package/src/lib/components/plugin-reviews-surface.component.d.ts +19 -0
  39. package/src/lib/components/plugin-reviews-surface.component.js +46 -0
  40. package/src/lib/components/plugin-reviews-surface.component.js.map +1 -0
  41. package/src/lib/components/publish-plugin-form.component.d.ts +61 -0
  42. package/src/lib/components/publish-plugin-form.component.js +857 -0
  43. package/src/lib/components/publish-plugin-form.component.js.map +1 -0
  44. package/src/lib/components/publish-plugin-page.component.d.ts +29 -0
  45. package/src/lib/components/publish-plugin-page.component.js +92 -0
  46. package/src/lib/components/publish-plugin-page.component.js.map +1 -0
  47. package/src/lib/components/publisher-profile-page.component.d.ts +13 -0
  48. package/src/lib/components/publisher-profile-page.component.js +261 -0
  49. package/src/lib/components/publisher-profile-page.component.js.map +1 -0
  50. package/src/lib/constants/listing-media.d.ts +28 -0
  51. package/src/lib/constants/listing-media.js +28 -0
  52. package/src/lib/constants/listing-media.js.map +1 -0
  53. package/src/lib/model/listing-social-card.d.ts +94 -0
  54. package/src/lib/model/listing-social-card.js +177 -0
  55. package/src/lib/model/listing-social-card.js.map +1 -0
  56. package/src/lib/model/marketplace-paths.d.ts +54 -0
  57. package/src/lib/model/marketplace-paths.js +59 -0
  58. package/src/lib/model/marketplace-paths.js.map +1 -0
  59. package/src/lib/model/payout-readiness.d.ts +81 -0
  60. package/src/lib/model/payout-readiness.js +52 -0
  61. package/src/lib/model/payout-readiness.js.map +1 -0
  62. package/src/lib/model/plugin-rejection-categories.d.ts +66 -0
  63. package/src/lib/model/plugin-rejection-categories.js +124 -0
  64. package/src/lib/model/plugin-rejection-categories.js.map +1 -0
  65. package/src/lib/model/plugin-review-checklist.d.ts +62 -0
  66. package/src/lib/model/plugin-review-checklist.js +131 -0
  67. package/src/lib/model/plugin-review-checklist.js.map +1 -0
  68. package/src/lib/model/plugin-review-status.d.ts +104 -0
  69. package/src/lib/model/plugin-review-status.js +126 -0
  70. package/src/lib/model/plugin-review-status.js.map +1 -0
  71. package/src/lib/model/seller-ledger-totals.d.ts +100 -0
  72. package/src/lib/model/seller-ledger-totals.js +96 -0
  73. package/src/lib/model/seller-ledger-totals.js.map +1 -0
  74. package/src/lib/plugin.js +170 -40
  75. package/src/lib/plugin.js.map +1 -1
  76. package/src/lib/server/admin-reports.d.ts +18 -0
  77. package/src/lib/server/admin-reports.js +280 -0
  78. package/src/lib/server/admin-reports.js.map +1 -0
  79. package/src/lib/server/admin-reviews.d.ts +18 -0
  80. package/src/lib/server/admin-reviews.js +1341 -0
  81. package/src/lib/server/admin-reviews.js.map +1 -0
  82. package/src/lib/server/listing-social-card.server.d.ts +24 -0
  83. package/src/lib/server/listing-social-card.server.js +65 -0
  84. package/src/lib/server/listing-social-card.server.js.map +1 -0
  85. package/src/lib/server/publisher-review-email.d.ts +54 -0
  86. package/src/lib/server/publisher-review-email.js +85 -0
  87. package/src/lib/server/publisher-review-email.js.map +1 -0
  88. package/src/lib/server/revoke-cache-drop.d.ts +59 -0
  89. package/src/lib/server/revoke-cache-drop.js +74 -0
  90. package/src/lib/server/revoke-cache-drop.js.map +1 -0
  91. package/src/lib/server.js +47 -0
  92. package/src/lib/server.js.map +1 -1
@@ -0,0 +1,318 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
+ import { ICON_VARIANT_COMPONENT } from "@aglyn/shared-data-enums";
19
+ import { AppLink, CardDisplay } from "@aglyn/shared-ui-jsx";
20
+ import { useFirestore, useFirestoreCollection, useUser } from "@aglyn/tenant-feature-instance";
21
+ import { Alert, Chip, Stack } from "@mui/material";
22
+ import { ListTable } from "@aglyn/shared-ui-jsx/components/list-table.component";
23
+ import { collection, documentId, limit, orderBy, query, where } from "firebase/firestore";
24
+ import { useMemo } from "react";
25
+ import { pluginDocsHelp } from "@aglyn/aglyn";
26
+ import { TABLE_ROW_HEIGHT } from "@aglyn/shared-ui-jsx/const/table-pagination";
27
+ import { listingPath } from "../model/marketplace-paths.js";
28
+ import EmptyState from "@aglyn/shared-ui-jsx/components/read-gated-empty-state.component";
29
+ /**
30
+ * WHICH WORKSPACE HOLDS A LICENCE (AGL-2331).
31
+ *
32
+ * A marketplace purchase licenses the installing ORGANIZATION, not the person
33
+ * who paid. That is the model the Publisher Agreement already publishes and
34
+ * the model the install routes now enforce — and it is unusable without a
35
+ * surface, because the two questions it creates have no answer anywhere else
36
+ * in the console:
37
+ *
38
+ * "Does THIS workspace own that component, or was it the other client's?"
39
+ * "I bought this once — which workspace did the licence land in?"
40
+ *
41
+ * An agency is the population this matters to most, and an agency is exactly
42
+ * the population that cannot answer either question from memory. Before this
43
+ * panel the only signal was a Buy button, and a Buy button is the same
44
+ * whether you own nothing or own three of them in other workspaces.
45
+ *
46
+ * TWO LISTS, deliberately, because they are two different facts:
47
+ *
48
+ * `This workspace` is the org's inventory — what any member with install
49
+ * rights may install here, including licences a colleague bought and
50
+ * licences bought by someone who has since left. It is the list that
51
+ * decides whether a Buy is needed.
52
+ *
53
+ * `Bought by you` is the buyer's own receipt trail across every workspace
54
+ * they belong to. It is what turns "I already bought this" into "…for
55
+ * Northwind, and this is Contoso".
56
+ *
57
+ * Both read `marketplacePurchases`, which is buyer/org/seller-gated — the org
58
+ * clause landed with AGL-2331 for exactly this list. Held at null until the
59
+ * org and uid resolve: a rules-shaped LIST is evaluated against the QUERY, so
60
+ * a sentinel value is a guaranteed denial retried on the refusal cadence
61
+ * (AGL-1440), not an empty list.
62
+ *
63
+ * ## Presentation (AGL-2486)
64
+ *
65
+ * Each list is a card, like every sibling Marketplace tab — it shipped as two
66
+ * bare `subtitle1` headings with a loose sentence under each, which on a page
67
+ * built from cards reads as an unfinished tab rather than an empty one.
68
+ *
69
+ * More than cosmetic: the sentences became `EmptyState`s, so they are now
70
+ * GATED on the read having succeeded (AGL-1066). "You have not bought
71
+ * anything" is a claim about someone's purchase history, and a refused or
72
+ * unfinished read supports no such claim — on a tab whose whole purpose is
73
+ * answering "do we already own this?", a zero-state produced by a dead
74
+ * session is an invitation to buy something twice. Both queries are held at
75
+ * `null` until the scope resolves, which reads as `loading` and can never
76
+ * reach the zero-state; a listen the server has REFUSED keeps painting from
77
+ * cache with `status: 'success'`, which is why `serverDenied` is checked
78
+ * alongside it rather than trusting the status alone.
79
+ */ export function OrgLicencesPanel({ orgId, basePath, viewerOrgs }) {
80
+ const firestore = useFirestore();
81
+ const { data: user } = useUser();
82
+ const uid = user == null ? void 0 : user.uid;
83
+ /** The licences this workspace holds — whoever in it did the buying. */ const orgLicencesRead = useFirestoreCollection(()=>orgId ? query(collection(firestore, 'marketplacePurchases'), where('buyerOrgId', '==', orgId)) : null, [
84
+ firestore,
85
+ orgId
86
+ ], {
87
+ idField: '$id'
88
+ });
89
+ /** Everything this person has ever bought, in any workspace. */ const myPurchasesRead = useFirestoreCollection(()=>uid ? query(collection(firestore, 'marketplacePurchases'), where('buyerUid', '==', uid)) : null, [
90
+ firestore,
91
+ uid
92
+ ], {
93
+ idField: '$id'
94
+ });
95
+ /**
96
+ * Listing id → display name.
97
+ *
98
+ * One subscription rather than a read per row: `marketplaceListings` is
99
+ * world-readable (it is the catalogue), the browse grid on the sibling tab
100
+ * already listens to it, and a per-row hook would change the hook count
101
+ * between renders as licences arrive.
102
+ */ const { data: listings } = useFirestoreCollection(/*
103
+ * ORDERED, so the window is a reachable 200 rather than a sample.
104
+ *
105
+ * A capped read with no `orderBy` is answered in document-id order
106
+ * anyway, so naming it changes no row — what it changes is that the
107
+ * ordering is a decision rather than an accident, and that the obvious
108
+ * next edit is caught: `orderBy('displayName')` would drop every listing
109
+ * saved without one out of the map, and a licence whose listing is
110
+ * missing from the map is a row that names itself by raw id.
111
+ *
112
+ * Ordering on the document NAME, which is what a listing id already is
113
+ * here, keeps the map's keys and the walk in the same space. Past 200
114
+ * listings a licence still renders — the fallback below prints the
115
+ * listing id — so the cap degrades the label and never the row.
116
+ */ ()=>query(collection(firestore, 'marketplaceListings'), orderBy(documentId()), limit(200)), [
117
+ firestore
118
+ ], {
119
+ idField: '$id'
120
+ });
121
+ const listingNames = useMemo(()=>{
122
+ const names = {};
123
+ for (const listing of listings != null ? listings : []){
124
+ var _listing_displayName;
125
+ names[listing.$id] = String((_listing_displayName = listing.displayName) != null ? _listing_displayName : listing.$id);
126
+ }
127
+ return names;
128
+ }, [
129
+ listings
130
+ ]);
131
+ /** Workspace id → the name the member already sees in the org switcher. */ const orgNames = useMemo(()=>{
132
+ const names = {};
133
+ for (const workspace of viewerOrgs != null ? viewerOrgs : [])names[workspace.id] = workspace.name;
134
+ return names;
135
+ }, [
136
+ viewerOrgs
137
+ ]);
138
+ /**
139
+ * A refunded purchase is not a licence (AGL-1546) and must not be listed as
140
+ * one — the whole point of this panel is deciding whether to buy, and a row
141
+ * that says "you own this" when the install route answers 402 is worse than
142
+ * no row.
143
+ */ const live = (rows)=>(rows != null ? rows : []).filter((row)=>!(row == null ? void 0 : row.refundedAt));
144
+ const held = live(orgLicencesRead.data);
145
+ const mine = live(myPurchasesRead.data);
146
+ /*==========================================
147
+ * BOTH LISTS ARE WHOLE, so the grid pages them and its counts are TOTALS.
148
+ *
149
+ * Neither query is capped: each reads one workspace's purchases or one
150
+ * person's, whole. So the card holds the entire list it is describing, the
151
+ * refund filter above has already run over all of it, and the grid's
152
+ * footer counts the collection's real size rather than a window's length —
153
+ * the one case where a client page can state a total without qualifying it.
154
+ *
155
+ * Server-paging either one would break that. `refundedAt` is filtered after
156
+ * reading, so a ten-document page arrives holding anywhere from zero to ten
157
+ * licences, and an agency deciding whether to buy a component again is
158
+ * exactly the reader who must not be shown a short page as a complete
159
+ * answer.
160
+ *=========================================*/ const heldColumns = useMemo(()=>[
161
+ {
162
+ field: 'listingId',
163
+ headerName: 'Listing',
164
+ flex: 1,
165
+ minWidth: 220,
166
+ valueGetter: (_value, row)=>{
167
+ var _listingNames_String, _row_listingId, _row_listingId1;
168
+ return (_listingNames_String = listingNames[String((_row_listingId = row.listingId) != null ? _row_listingId : '')]) != null ? _listingNames_String : String((_row_listingId1 = row.listingId) != null ? _row_listingId1 : '');
169
+ },
170
+ renderCell: ({ row, value })=>{
171
+ var _row_listingId;
172
+ return /*#__PURE__*/ _jsx(AppLink, {
173
+ href: listingPath(basePath, String((_row_listingId = row.listingId) != null ? _row_listingId : '')),
174
+ children: value
175
+ });
176
+ }
177
+ },
178
+ {
179
+ field: 'buyerUid',
180
+ headerName: 'Bought by',
181
+ width: 150,
182
+ valueGetter: (_value, row)=>row.buyerUid === uid ? 'You' : 'A colleague'
183
+ },
184
+ {
185
+ field: 'amountCents',
186
+ headerName: 'Paid',
187
+ type: 'number',
188
+ align: 'right',
189
+ headerAlign: 'right',
190
+ width: 120,
191
+ // What the licence cost before tax, which is what a reader compares.
192
+ valueGetter: (_value, row)=>{
193
+ var _row_amountCents, _row_taxCents;
194
+ return (Number((_row_amountCents = row.amountCents) != null ? _row_amountCents : 0) - Number((_row_taxCents = row.taxCents) != null ? _row_taxCents : 0)) / 100;
195
+ },
196
+ valueFormatter: (value)=>`$${value.toFixed(2)}`
197
+ }
198
+ ], [
199
+ listingNames,
200
+ basePath,
201
+ uid
202
+ ]);
203
+ const mineColumns = useMemo(()=>[
204
+ {
205
+ field: 'listingId',
206
+ headerName: 'Listing',
207
+ flex: 1,
208
+ minWidth: 220,
209
+ valueGetter: (_value, row)=>{
210
+ var _listingNames_String, _row_listingId, _row_listingId1;
211
+ return (_listingNames_String = listingNames[String((_row_listingId = row.listingId) != null ? _row_listingId : '')]) != null ? _listingNames_String : String((_row_listingId1 = row.listingId) != null ? _row_listingId1 : '');
212
+ }
213
+ },
214
+ {
215
+ field: 'buyerOrgId',
216
+ headerName: 'Licensed to',
217
+ flex: 1,
218
+ minWidth: 220,
219
+ valueGetter: (_value, row)=>{
220
+ var _row_buyerOrgId, _orgNames_licensedOrg;
221
+ const licensedOrg = String((_row_buyerOrgId = row.buyerOrgId) != null ? _row_buyerOrgId : '');
222
+ if (!licensedOrg) return 'Every workspace you belong to';
223
+ return licensedOrg === orgId ? 'This workspace' : (_orgNames_licensedOrg = orgNames[licensedOrg]) != null ? _orgNames_licensedOrg : licensedOrg;
224
+ },
225
+ renderCell: ({ row, value })=>{
226
+ var _row_buyerOrgId;
227
+ const licensedOrg = String((_row_buyerOrgId = row.buyerOrgId) != null ? _row_buyerOrgId : '');
228
+ // A purchase made before AGL-2331 named no organization, so it is
229
+ // not reinterpreted as belonging to one — it keeps entitling this
230
+ // buyer everywhere, exactly as it did when they paid for it. Saying
231
+ // "every workspace" rather than guessing an org is the whole reason
232
+ // nobody loses access here.
233
+ if (!licensedOrg) return /*#__PURE__*/ _jsx(Chip, {
234
+ size: "small",
235
+ label: value
236
+ });
237
+ return licensedOrg === orgId ? /*#__PURE__*/ _jsx(Chip, {
238
+ size: "small",
239
+ color: "primary",
240
+ label: value
241
+ }) : /*#__PURE__*/ _jsx(Chip, {
242
+ size: "small",
243
+ variant: "outlined",
244
+ label: value
245
+ });
246
+ }
247
+ }
248
+ ], [
249
+ listingNames,
250
+ orgId,
251
+ orgNames
252
+ ]);
253
+ /**
254
+ * "You own nothing here" is a claim about this workspace's purchases, and a
255
+ * refused read supports no such claim (AGL-1066). Both queries are ALSO
256
+ * held at `null` until the org and the uid resolve, which the hook reports
257
+ * as `loading` — so an unresolved scope can never reach the zero-state
258
+ * either. `serverDenied` is folded in beside `status`: a listen the server
259
+ * has refused past its retry budget keeps painting whatever the local cache
260
+ * holds, and `status` alone still reads `success` for it.
261
+ */ const outcome = (read)=>read.status === 'error' || read.serverDenied === true ? 'unavailable' : read.status === 'success' ? 'loaded' : 'loading';
262
+ return /*#__PURE__*/ _jsxs(Stack, {
263
+ spacing: 3,
264
+ children: [
265
+ /*#__PURE__*/ _jsx(Alert, {
266
+ severity: "info",
267
+ 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.'
268
+ }),
269
+ held.length === 0 ? /*#__PURE__*/ _jsx(EmptyState, {
270
+ read: outcome(orgLicencesRead),
271
+ subject: "this workspace’s licenses",
272
+ iconPath: ICON_VARIANT_COMPONENT.path,
273
+ title: 'This workspace holds no licenses',
274
+ description: 'Anything bought for this workspace shows up here, whoever on ' + 'the team paid for it.'
275
+ }) : /*#__PURE__*/ _jsx(CardDisplay, {
276
+ header: 'This workspace',
277
+ help: pluginDocsHelp('publisherHandbook', {
278
+ anchor: '#how-installs-work-the-buyer-side',
279
+ excerpt: 'What this workspace owns — installable by any member with ' + 'install permission, whoever on the team paid for it.'
280
+ }),
281
+ subheader: 'Installable by any member with install permission, whoever ' + 'on the team bought it',
282
+ contentGutterX: true,
283
+ contentGutterY: true,
284
+ children: /*#__PURE__*/ _jsx(ListTable, {
285
+ "aria-label": "Licenses this workspace holds",
286
+ rows: held,
287
+ columns: heldColumns,
288
+ rowHeight: TABLE_ROW_HEIGHT
289
+ })
290
+ }),
291
+ mine.length === 0 ? /*#__PURE__*/ _jsx(EmptyState, {
292
+ read: outcome(myPurchasesRead),
293
+ subject: "your purchases",
294
+ iconPath: ICON_VARIANT_COMPONENT.path,
295
+ title: 'You have not bought anything yet',
296
+ description: 'Your own marketplace receipts appear here, across every ' + 'workspace you belong to.'
297
+ }) : /*#__PURE__*/ _jsx(CardDisplay, {
298
+ header: 'Bought by you',
299
+ help: pluginDocsHelp('publisherHandbook', {
300
+ anchor: '#how-installs-work-the-buyer-side',
301
+ excerpt: 'Your own purchases across every workspace you belong to — a ' + 'purchase licenses one organization, so this says which.'
302
+ }),
303
+ subheader: 'Your own receipts, and which workspace each license landed in',
304
+ contentGutterX: true,
305
+ contentGutterY: true,
306
+ children: /*#__PURE__*/ _jsx(ListTable, {
307
+ "aria-label": "Licenses you bought",
308
+ rows: mine,
309
+ columns: mineColumns,
310
+ rowHeight: TABLE_ROW_HEIGHT
311
+ })
312
+ })
313
+ ]
314
+ });
315
+ }
316
+ export default OrgLicencesPanel;
317
+
318
+ //# sourceMappingURL=org-licences-panel.component.js.map
@@ -0,0 +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":"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"}
@@ -0,0 +1,45 @@
1
+ import { resolveMarketplaceFeePct } from '@aglyn/aglyn';
2
+ /**
3
+ * Org-level publish (AGL-776): pick a source site, then a component, a layout,
4
+ * or the whole site (as a template), and publish it under the org's publisher
5
+ * identity. This panel's whole job is choosing WHAT: it names the kind, the
6
+ * scope that holds it and the document, and hands that to the
7
+ * `hostArtifactPublish` zone (AGL-3080), whose widget owns the name/price
8
+ * form, the route and the POST. Plugins publish from their own bundle upload,
9
+ * not here.
10
+ *
11
+ * The per-site publish buttons (Components/Layouts/Setup pages) stay as
12
+ * in-context shortcuts; this is the one place that spans every site.
13
+ */
14
+ export declare function OrgPublishPanel({ orgId, hosts, basePath, billingPath, org, orgReady, }: {
15
+ orgId: string;
16
+ hosts: ReadonlyArray<{
17
+ id: string;
18
+ label: string;
19
+ }>;
20
+ /**
21
+ * Where the marketplace hub is mounted under this organization, from the
22
+ * shell (AGL-3080). The publish-a-plugin page hangs beneath it.
23
+ */
24
+ basePath: string;
25
+ /**
26
+ * Where this organization manages its plan, from the shell's org mount.
27
+ * Absent on a deployment that bills nobody, and the fee notice then says
28
+ * the rate without offering a way to change it.
29
+ */
30
+ billingPath?: string;
31
+ /**
32
+ * The org billing doc the shell already loaded, and whether it has
33
+ * settled. Read here rather than through the console's `useCurrentOrg`:
34
+ * the fee shown is a claim about this publisher's plan, and
35
+ * `resolveMarketplaceFeePct(undefined)` answers the FREE rate — so a
36
+ * number rendered during load accuses a paying publisher of the higher
37
+ * cut.
38
+ */
39
+ org?: Parameters<typeof resolveMarketplaceFeePct>[0];
40
+ orgReady: boolean;
41
+ }): import("react").JSX.Element;
42
+ export declare namespace OrgPublishPanel {
43
+ var displayName: string;
44
+ }
45
+ export default OrgPublishPanel;