@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,23 @@
1
+ /**
2
+ * The marketplace report queue (AGL-2310).
3
+ *
4
+ * The report button on a listing has told users their report was filed since
5
+ * it shipped. `marketplaceReports` was written and read by NOTHING — no
6
+ * surface, no cron, no collection group — so every report was stored,
7
+ * acknowledged and unreachable, and `status` never moved off `open` because
8
+ * no queue displayed it.
9
+ *
10
+ * The page is deliberately the `abuseReports` queue in miniature: a list, a
11
+ * status transition, a note required to close, and an `adminAudit` row per
12
+ * transition. Two vocabularies for the same act is how one of them stops
13
+ * being maintained.
14
+ *
15
+ * The REASON is the row. Everything else — the listing, the publisher, the
16
+ * date — is context for it, which is why it is rendered in full rather than
17
+ * truncated into a column, and why it is never redacted by staff tier.
18
+ */
19
+ export declare function MarketplaceReportsPage(): import("react").JSX.Element;
20
+ export declare namespace MarketplaceReportsPage {
21
+ var displayName: string;
22
+ }
23
+ export default MarketplaceReportsPage;
@@ -0,0 +1,324 @@
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 { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
19
+ import { PageHeaderHelp, pluginDocsHelp } from "@aglyn/aglyn";
20
+ import { CardDisplay } from "@aglyn/shared-ui-jsx";
21
+ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
22
+ import { Alert, Button, Chip, LinearProgress, MenuItem, Stack, TextField, Typography } from "@mui/material";
23
+ import { useCallback, useEffect, useState } from "react";
24
+ import { useUser } from "@aglyn/tenant-feature-instance";
25
+ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
26
+ const STATUS_COLOR = {
27
+ open: 'warning',
28
+ reviewing: 'info',
29
+ actioned: 'success',
30
+ dismissed: 'default'
31
+ };
32
+ const when = (ms)=>ms ? new Date(ms).toLocaleString() : '—';
33
+ /**
34
+ * The marketplace report queue (AGL-2310).
35
+ *
36
+ * The report button on a listing has told users their report was filed since
37
+ * it shipped. `marketplaceReports` was written and read by NOTHING — no
38
+ * surface, no cron, no collection group — so every report was stored,
39
+ * acknowledged and unreachable, and `status` never moved off `open` because
40
+ * no queue displayed it.
41
+ *
42
+ * The page is deliberately the `abuseReports` queue in miniature: a list, a
43
+ * status transition, a note required to close, and an `adminAudit` row per
44
+ * transition. Two vocabularies for the same act is how one of them stops
45
+ * being maintained.
46
+ *
47
+ * The REASON is the row. Everything else — the listing, the publisher, the
48
+ * date — is context for it, which is why it is rendered in full rather than
49
+ * truncated into a column, and why it is never redacted by staff tier.
50
+ */ export function MarketplaceReportsPage() {
51
+ const { data: user } = useUser();
52
+ const { enqueueSnackbar } = useSnackbar();
53
+ const [reports, setReports] = useState(null);
54
+ const [identityVisible, setIdentityVisible] = useState(false);
55
+ const [statusFilter, setStatusFilter] = useState('open');
56
+ const [error, setError] = useState(null);
57
+ const [loading, setLoading] = useState(false);
58
+ const [busyId, setBusyId] = useState(null);
59
+ const [notes, setNotes] = useState({});
60
+ const [epoch, setEpoch] = useState(0);
61
+ useEffect(()=>{
62
+ let active = true;
63
+ void (async ()=>{
64
+ /*
65
+ * The staff claim is the SHELL's now (AGL-3080). This page used to
66
+ * hold `isStaff !== true` here, because it owned its own route and a
67
+ * request fired from the claim's loading `null` answers a question
68
+ * nobody asked. The generic staff route renders every plugin page
69
+ * inside `StaffOnly`, which renders NOTHING while the claim resolves
70
+ * and 404s a non-holder — so being mounted at all is the claim, and a
71
+ * second check here would be a copy free to disagree with it.
72
+ */ if (!user) return;
73
+ setLoading(true);
74
+ setError(null);
75
+ try {
76
+ const response = await authorizedFetch(user, `/api/marketplace/admin/reports?status=${encodeURIComponent(statusFilter)}`);
77
+ const body = await response.json().catch(()=>null);
78
+ if (!active) return;
79
+ if (!response.ok) {
80
+ var _ref;
81
+ setError((_ref = body == null ? void 0 : body.error) != null ? _ref : 'Report lookup failed');
82
+ } else {
83
+ var _ref1;
84
+ setReports((_ref1 = body == null ? void 0 : body.reports) != null ? _ref1 : []);
85
+ setIdentityVisible(Boolean(body == null ? void 0 : body.identityVisible));
86
+ }
87
+ } catch (unused) {
88
+ if (active) setError('Report lookup failed');
89
+ } finally{
90
+ if (active) setLoading(false);
91
+ }
92
+ })();
93
+ return ()=>{
94
+ active = false;
95
+ };
96
+ }, [
97
+ user,
98
+ statusFilter,
99
+ epoch
100
+ ]);
101
+ const transition = useCallback(async (report, status)=>{
102
+ setBusyId(report.id);
103
+ try {
104
+ var _notes_report_id;
105
+ const response = await authorizedFetch(user, '/api/marketplace/admin/reports', {
106
+ method: 'POST',
107
+ headers: {
108
+ 'Content-Type': 'application/json'
109
+ },
110
+ body: JSON.stringify({
111
+ id: report.id,
112
+ status,
113
+ resolution: (_notes_report_id = notes[report.id]) != null ? _notes_report_id : ''
114
+ })
115
+ });
116
+ const payload = await response.json().catch(()=>({}));
117
+ if (!response.ok) {
118
+ var _ref;
119
+ // The server's own sentence, not a generic failure: the one refusal
120
+ // this route issues ("say what you did") is instruction, and
121
+ // replacing it with "Update failed" would leave the reviewer
122
+ // guessing at a rule the server already stated.
123
+ enqueueSnackbar((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Update failed', {
124
+ variant: 'warning',
125
+ allowDuplicate: true
126
+ });
127
+ return;
128
+ }
129
+ enqueueSnackbar(`Marked ${status}`, {
130
+ variant: 'success',
131
+ persist: false
132
+ });
133
+ setEpoch((value)=>value + 1);
134
+ } catch (unused) {
135
+ enqueueSnackbar('An error has occurred', {
136
+ variant: 'error'
137
+ });
138
+ } finally{
139
+ setBusyId(null);
140
+ }
141
+ }, [
142
+ user,
143
+ notes,
144
+ enqueueSnackbar
145
+ ]);
146
+ return /*#__PURE__*/ _jsxs(_Fragment, {
147
+ children: [
148
+ /*#__PURE__*/ _jsx(PageHeaderHelp, {
149
+ topic: "abuseReports",
150
+ anchor: "#statuses"
151
+ }),
152
+ /*#__PURE__*/ _jsxs(Stack, {
153
+ spacing: 3,
154
+ children: [
155
+ loading && /*#__PURE__*/ _jsx(LinearProgress, {}),
156
+ error && /*#__PURE__*/ _jsx(Alert, {
157
+ severity: "error",
158
+ children: error
159
+ }),
160
+ /*#__PURE__*/ _jsxs(CardDisplay, {
161
+ header: 'Reported listings and reviews',
162
+ help: pluginDocsHelp('abuseReports', {
163
+ excerpt: 'Reports users filed against a marketplace listing or a ' + 'review, with the reason each one gave. Closing one needs ' + 'a note, and every change is audited.'
164
+ }),
165
+ contentGutterX: true,
166
+ contentGutterY: true,
167
+ HeaderProps: {
168
+ action: /*#__PURE__*/ _jsxs(TextField, {
169
+ select: true,
170
+ size: "small",
171
+ value: statusFilter,
172
+ onChange: (event)=>{
173
+ setReports(null);
174
+ setStatusFilter(event.target.value);
175
+ },
176
+ children: [
177
+ /*#__PURE__*/ _jsx(MenuItem, {
178
+ value: "open",
179
+ children: 'Open'
180
+ }),
181
+ /*#__PURE__*/ _jsx(MenuItem, {
182
+ value: "reviewing",
183
+ children: 'Reviewing'
184
+ }),
185
+ /*#__PURE__*/ _jsx(MenuItem, {
186
+ value: "actioned",
187
+ children: 'Actioned'
188
+ }),
189
+ /*#__PURE__*/ _jsx(MenuItem, {
190
+ value: "dismissed",
191
+ children: 'Dismissed'
192
+ }),
193
+ /*#__PURE__*/ _jsx(MenuItem, {
194
+ value: "",
195
+ children: 'Everything'
196
+ })
197
+ ]
198
+ })
199
+ },
200
+ children: [
201
+ !identityVisible ? /*#__PURE__*/ _jsx(Alert, {
202
+ severity: "info",
203
+ sx: {
204
+ mb: 2
205
+ },
206
+ children: 'Reporter accounts are hidden at your access level. Every ' + 'report still shows whether there is somebody behind it.'
207
+ }) : null,
208
+ reports === null ? /*#__PURE__*/ _jsx(Typography, {
209
+ variant: "body2",
210
+ color: "text.secondary",
211
+ children: loading ? 'Reading reports…' : ''
212
+ }) : reports.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
213
+ variant: "body2",
214
+ color: "text.secondary",
215
+ children: 'Nothing in this state.'
216
+ }) : /*#__PURE__*/ _jsx(Stack, {
217
+ spacing: 3,
218
+ children: reports.map((report)=>{
219
+ var _STATUS_COLOR_report_status, _notes_report_id;
220
+ return /*#__PURE__*/ _jsxs(Stack, {
221
+ spacing: 1,
222
+ children: [
223
+ /*#__PURE__*/ _jsxs(Stack, {
224
+ direction: "row",
225
+ sx: {
226
+ flexWrap: 'wrap',
227
+ gap: 1,
228
+ alignItems: 'center'
229
+ },
230
+ children: [
231
+ /*#__PURE__*/ _jsx(Chip, {
232
+ size: "small",
233
+ color: (_STATUS_COLOR_report_status = STATUS_COLOR[report.status]) != null ? _STATUS_COLOR_report_status : 'default',
234
+ label: report.status
235
+ }),
236
+ /*#__PURE__*/ _jsx(Chip, {
237
+ size: "small",
238
+ variant: "outlined",
239
+ label: report.targetType
240
+ }),
241
+ /*#__PURE__*/ _jsx(Typography, {
242
+ variant: "subtitle2",
243
+ children: report.listingName || report.listingId || report.id
244
+ })
245
+ ]
246
+ }),
247
+ /*#__PURE__*/ _jsx(Typography, {
248
+ variant: "body2",
249
+ children: report.reason || '(no reason given)'
250
+ }),
251
+ /*#__PURE__*/ _jsx(Typography, {
252
+ variant: "caption",
253
+ color: "text.secondary",
254
+ children: [
255
+ report.publisherOrgId ? `publisher ${report.publisherOrgId}` : null,
256
+ // A REDACTED reporter and an ANONYMOUS one are
257
+ // different facts: only the second means there is
258
+ // nobody to follow up with.
259
+ report.reporterUid ? `reporter ${report.reporterUid}` : report.reporterKnown ? 'reporter hidden' : 'no reporter recorded',
260
+ `filed ${when(report.createdAtMs)}`,
261
+ report.resolvedByEmail ? `closed by ${report.resolvedByEmail}` : null
262
+ ].filter(Boolean).join(' · ')
263
+ }),
264
+ report.resolution ? /*#__PURE__*/ _jsx(Alert, {
265
+ severity: "success",
266
+ children: report.resolution
267
+ }) : null,
268
+ /*#__PURE__*/ _jsxs(Stack, {
269
+ direction: "row",
270
+ sx: {
271
+ flexWrap: 'wrap',
272
+ gap: 1,
273
+ alignItems: 'center'
274
+ },
275
+ children: [
276
+ /*#__PURE__*/ _jsx(TextField, {
277
+ size: "small",
278
+ placeholder: "What did you do, and why?",
279
+ value: (_notes_report_id = notes[report.id]) != null ? _notes_report_id : '',
280
+ onChange: (event)=>setNotes((previous)=>_extends({}, previous, {
281
+ [report.id]: event.target.value
282
+ })),
283
+ sx: {
284
+ flex: 1,
285
+ minWidth: 260
286
+ }
287
+ }),
288
+ /*#__PURE__*/ _jsx(Button, {
289
+ size: "small",
290
+ disabled: busyId === report.id,
291
+ onClick: ()=>void transition(report, 'reviewing'),
292
+ children: 'Reviewing'
293
+ }),
294
+ /*#__PURE__*/ _jsx(Button, {
295
+ size: "small",
296
+ color: "success",
297
+ disabled: busyId === report.id,
298
+ onClick: ()=>void transition(report, 'actioned'),
299
+ children: 'Actioned'
300
+ }),
301
+ /*#__PURE__*/ _jsx(Button, {
302
+ size: "small",
303
+ color: "warning",
304
+ disabled: busyId === report.id,
305
+ onClick: ()=>void transition(report, 'dismissed'),
306
+ children: 'Dismiss'
307
+ })
308
+ ]
309
+ })
310
+ ]
311
+ }, report.id);
312
+ })
313
+ })
314
+ ]
315
+ })
316
+ ]
317
+ })
318
+ ]
319
+ });
320
+ }
321
+ MarketplaceReportsPage.displayName = 'MarketplaceReportsPage';
322
+ export default MarketplaceReportsPage;
323
+
324
+ //# sourceMappingURL=marketplace-reports-page.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/marketplace-reports-page.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_SYMBOL_FLAG } from '@aglyn/shared-data-enums'\nimport { PageHeaderHelp, pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n LinearProgress,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\n\ninterface MarketplaceReportRow {\n id: string\n status: string\n targetType: string\n listingId: string | null\n listingName: string | null\n publisherOrgId: string | null\n reviewUid: string | null\n reason: string | null\n reporterKnown: boolean\n reporterUid: string | null\n resolution: string | null\n resolvedByEmail: string | null\n createdAtMs: number | null\n updatedAtMs: number | null\n}\n\nconst STATUS_COLOR: Record<string, 'warning' | 'info' | 'success' | 'default'> =\n {\n open: 'warning',\n reviewing: 'info',\n actioned: 'success',\n dismissed: 'default',\n }\n\nconst when = (ms: number | null) =>\n ms ? new Date(ms).toLocaleString() : '—'\n\n/**\n * The marketplace report queue (AGL-2310).\n *\n * The report button on a listing has told users their report was filed since\n * it shipped. `marketplaceReports` was written and read by NOTHING — no\n * surface, no cron, no collection group — so every report was stored,\n * acknowledged and unreachable, and `status` never moved off `open` because\n * no queue displayed it.\n *\n * The page is deliberately the `abuseReports` queue in miniature: a list, a\n * status transition, a note required to close, and an `adminAudit` row per\n * transition. Two vocabularies for the same act is how one of them stops\n * being maintained.\n *\n * The REASON is the row. Everything else — the listing, the publisher, the\n * date — is context for it, which is why it is rendered in full rather than\n * truncated into a column, and why it is never redacted by staff tier.\n */\nexport function MarketplaceReportsPage() {\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [reports, setReports] = useState<MarketplaceReportRow[] | null>(null)\n const [identityVisible, setIdentityVisible] = useState(false)\n const [statusFilter, setStatusFilter] = useState('open')\n const [error, setError] = useState<string | null>(null)\n const [loading, setLoading] = useState(false)\n const [busyId, setBusyId] = useState<string | null>(null)\n const [notes, setNotes] = useState<Record<string, string>>({})\n const [epoch, setEpoch] = useState(0)\n\n useEffect(() => {\n let active = true\n void (async () => {\n /*\n * The staff claim is the SHELL's now (AGL-3080). This page used to\n * hold `isStaff !== true` here, because it owned its own route and a\n * request fired from the claim's loading `null` answers a question\n * nobody asked. The generic staff route renders every plugin page\n * inside `StaffOnly`, which renders NOTHING while the claim resolves\n * and 404s a non-holder — so being mounted at all is the claim, and a\n * second check here would be a copy free to disagree with it.\n */\n if (!user) return\n setLoading(true)\n setError(null)\n try {\n const response = await authorizedFetch(\n user,\n `/api/marketplace/admin/reports?status=${encodeURIComponent(statusFilter)}`,\n )\n const body = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n setError(body?.error ?? 'Report lookup failed')\n } else {\n setReports((body?.reports ?? []) as MarketplaceReportRow[])\n setIdentityVisible(Boolean(body?.identityVisible))\n }\n } catch {\n if (active) setError('Report lookup failed')\n } finally {\n if (active) setLoading(false)\n }\n })()\n return () => {\n active = false\n }\n }, [user, statusFilter, epoch])\n\n const transition = useCallback(\n async (report: MarketplaceReportRow, status: string) => {\n setBusyId(report.id)\n try {\n const response = await authorizedFetch(\n user,\n '/api/marketplace/admin/reports',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n id: report.id,\n status,\n resolution: notes[report.id] ?? '',\n }),\n },\n )\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n // The server's own sentence, not a generic failure: the one refusal\n // this route issues (\"say what you did\") is instruction, and\n // replacing it with \"Update failed\" would leave the reviewer\n // guessing at a rule the server already stated.\n enqueueSnackbar(payload?.error ?? 'Update failed', {\n variant: 'warning',\n allowDuplicate: true,\n })\n return\n }\n enqueueSnackbar(`Marked ${status}`, { variant: 'success', persist: false })\n setEpoch((value) => value + 1)\n } catch {\n enqueueSnackbar('An error has occurred', { variant: 'error' })\n } finally {\n setBusyId(null)\n }\n },\n [user, notes, enqueueSnackbar],\n )\n\n return (\n <>\n {/*\n The abuse-queue topic, deliberately: this is the same triage job on a\n different surface, and a second docs page describing the same four\n statuses is a second page to forget to update.\n\n ANCHORED, though, and published rather than declared. The abuse\n queue's own page opens the same topic bare, and two help icons landing\n in exactly the same place are interchangeable — which is the thing\n `docs-help-destinations` exists to catch. A `staffPages` entry carries\n one `docsTopic` and no anchor, so `#statuses` — the four states and\n what closing one means, which is what a reader arriving from HERE\n needs — comes up through the header seam instead.\n */}\n <PageHeaderHelp topic=\"abuseReports\" anchor=\"#statuses\" />\n <Stack spacing={3}>\n {loading && <LinearProgress />}\n {error && <Alert severity=\"error\">{error}</Alert>}\n <CardDisplay\n header={'Reported listings and reviews'}\n help={pluginDocsHelp('abuseReports', {\n excerpt:\n 'Reports users filed against a marketplace listing or a ' +\n 'review, with the reason each one gave. Closing one needs ' +\n 'a note, and every change is audited.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <TextField\n select\n size=\"small\"\n value={statusFilter}\n onChange={(event) => {\n setReports(null)\n setStatusFilter(event.target.value)\n }}\n >\n <MenuItem value=\"open\">{'Open'}</MenuItem>\n <MenuItem value=\"reviewing\">{'Reviewing'}</MenuItem>\n <MenuItem value=\"actioned\">{'Actioned'}</MenuItem>\n <MenuItem value=\"dismissed\">{'Dismissed'}</MenuItem>\n <MenuItem value=\"\">{'Everything'}</MenuItem>\n </TextField>\n ),\n }}\n >\n {!identityVisible ? (\n <Alert severity=\"info\" sx={{ mb: 2 }}>\n {'Reporter accounts are hidden at your access level. Every ' +\n 'report still shows whether there is somebody behind it.'}\n </Alert>\n ) : null}\n {reports === null ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {loading ? 'Reading reports…' : ''}\n </Typography>\n ) : reports.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Nothing in this state.'}\n </Typography>\n ) : (\n <Stack spacing={3}>\n {reports.map((report) => (\n <Stack key={report.id} spacing={1}>\n <Stack\n direction=\"row\"\n sx={{ flexWrap: 'wrap', gap: 1, alignItems: 'center' }}\n >\n <Chip\n size=\"small\"\n color={STATUS_COLOR[report.status] ?? 'default'}\n label={report.status}\n />\n <Chip size=\"small\" variant=\"outlined\" label={report.targetType} />\n <Typography variant=\"subtitle2\">\n {report.listingName || report.listingId || report.id}\n </Typography>\n </Stack>\n {/* THE REASON, in full. This is the whole content of a\n report; a queue that summarised it would be the\n original silence with an extra click in it. */}\n <Typography variant=\"body2\">\n {report.reason || '(no reason given)'}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {[\n report.publisherOrgId\n ? `publisher ${report.publisherOrgId}`\n : null,\n // A REDACTED reporter and an ANONYMOUS one are\n // different facts: only the second means there is\n // nobody to follow up with.\n report.reporterUid\n ? `reporter ${report.reporterUid}`\n : report.reporterKnown\n ? 'reporter hidden'\n : 'no reporter recorded',\n `filed ${when(report.createdAtMs)}`,\n report.resolvedByEmail\n ? `closed by ${report.resolvedByEmail}`\n : null,\n ]\n .filter(Boolean)\n .join(' · ')}\n </Typography>\n {report.resolution ? (\n <Alert severity=\"success\">{report.resolution}</Alert>\n ) : null}\n <Stack\n direction=\"row\"\n sx={{ flexWrap: 'wrap', gap: 1, alignItems: 'center' }}\n >\n <TextField\n size=\"small\"\n placeholder=\"What did you do, and why?\"\n value={notes[report.id] ?? ''}\n onChange={(event) =>\n setNotes((previous) => ({\n ...previous,\n [report.id]: event.target.value,\n }))\n }\n sx={{ flex: 1, minWidth: 260 }}\n />\n <Button\n size=\"small\"\n disabled={busyId === report.id}\n onClick={() => void transition(report, 'reviewing')}\n >\n {'Reviewing'}\n </Button>\n <Button\n size=\"small\"\n color=\"success\"\n disabled={busyId === report.id}\n onClick={() => void transition(report, 'actioned')}\n >\n {'Actioned'}\n </Button>\n <Button\n size=\"small\"\n color=\"warning\"\n disabled={busyId === report.id}\n onClick={() => void transition(report, 'dismissed')}\n >\n {'Dismiss'}\n </Button>\n </Stack>\n </Stack>\n ))}\n </Stack>\n )}\n </CardDisplay>\n </Stack>\n </>\n )\n}\nMarketplaceReportsPage.displayName = 'MarketplaceReportsPage'\n\nexport default MarketplaceReportsPage\n"],"names":["PageHeaderHelp","pluginDocsHelp","CardDisplay","useSnackbar","Alert","Button","Chip","LinearProgress","MenuItem","Stack","TextField","Typography","useCallback","useEffect","useState","useUser","authorizedFetch","STATUS_COLOR","open","reviewing","actioned","dismissed","when","ms","Date","toLocaleString","MarketplaceReportsPage","data","user","enqueueSnackbar","reports","setReports","identityVisible","setIdentityVisible","statusFilter","setStatusFilter","error","setError","loading","setLoading","busyId","setBusyId","notes","setNotes","epoch","setEpoch","active","response","encodeURIComponent","body","json","catch","ok","Boolean","transition","report","status","id","method","headers","JSON","stringify","resolution","payload","variant","allowDuplicate","persist","value","topic","anchor","spacing","severity","header","help","excerpt","contentGutterX","contentGutterY","HeaderProps","action","select","size","onChange","event","target","sx","mb","color","length","map","direction","flexWrap","gap","alignItems","label","targetType","listingName","listingId","reason","publisherOrgId","reporterUid","reporterKnown","createdAtMs","resolvedByEmail","filter","join","placeholder","previous","flex","minWidth","disabled","onClick","displayName"],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * WHICH WORKSPACE HOLDS A LICENCE (AGL-2331).
3
+ *
4
+ * A marketplace purchase licenses the installing ORGANIZATION, not the person
5
+ * who paid. That is the model the Publisher Agreement already publishes and
6
+ * the model the install routes now enforce — and it is unusable without a
7
+ * surface, because the two questions it creates have no answer anywhere else
8
+ * in the console:
9
+ *
10
+ * "Does THIS workspace own that component, or was it the other client's?"
11
+ * "I bought this once — which workspace did the licence land in?"
12
+ *
13
+ * An agency is the population this matters to most, and an agency is exactly
14
+ * the population that cannot answer either question from memory. Before this
15
+ * panel the only signal was a Buy button, and a Buy button is the same
16
+ * whether you own nothing or own three of them in other workspaces.
17
+ *
18
+ * TWO LISTS, deliberately, because they are two different facts:
19
+ *
20
+ * `This workspace` is the org's inventory — what any member with install
21
+ * rights may install here, including licences a colleague bought and
22
+ * licences bought by someone who has since left. It is the list that
23
+ * decides whether a Buy is needed.
24
+ *
25
+ * `Bought by you` is the buyer's own receipt trail across every workspace
26
+ * they belong to. It is what turns "I already bought this" into "…for
27
+ * Northwind, and this is Contoso".
28
+ *
29
+ * Both read `marketplacePurchases`, which is buyer/org/seller-gated — the org
30
+ * clause landed with AGL-2331 for exactly this list. Held at null until the
31
+ * org and uid resolve: a rules-shaped LIST is evaluated against the QUERY, so
32
+ * a sentinel value is a guaranteed denial retried on the refusal cadence
33
+ * (AGL-1440), not an empty list.
34
+ *
35
+ * ## Presentation (AGL-2486)
36
+ *
37
+ * Each list is a card, like every sibling Marketplace tab — it shipped as two
38
+ * bare `subtitle1` headings with a loose sentence under each, which on a page
39
+ * built from cards reads as an unfinished tab rather than an empty one.
40
+ *
41
+ * More than cosmetic: the sentences became `EmptyState`s, so they are now
42
+ * GATED on the read having succeeded (AGL-1066). "You have not bought
43
+ * anything" is a claim about someone's purchase history, and a refused or
44
+ * unfinished read supports no such claim — on a tab whose whole purpose is
45
+ * answering "do we already own this?", a zero-state produced by a dead
46
+ * session is an invitation to buy something twice. Both queries are held at
47
+ * `null` until the scope resolves, which reads as `loading` and can never
48
+ * reach the zero-state; a listen the server has REFUSED keeps painting from
49
+ * cache with `status: 'success'`, which is why `serverDenied` is checked
50
+ * alongside it rather than trusting the status alone.
51
+ */
52
+ export declare function OrgLicencesPanel({ orgId, basePath, viewerOrgs, }: {
53
+ orgId?: string | null;
54
+ /**
55
+ * Where the marketplace hub is mounted under this organization, from the
56
+ * shell (AGL-3080) — each row links to its listing beneath it. It replaced
57
+ * an org slug and the console's route table, which a plugin may not
58
+ * import.
59
+ */
60
+ basePath: string;
61
+ /**
62
+ * Every workspace the reader belongs to, from the shell, so the "Licensed
63
+ * to" column can name the one a purchase landed in instead of printing its
64
+ * id. A purchase licenses an ORGANIZATION (AGL-2331), which is the whole
65
+ * subject of this panel, and the reader's other workspaces are the answer
66
+ * to half of it.
67
+ */
68
+ viewerOrgs?: readonly {
69
+ id: string;
70
+ name: string;
71
+ }[];
72
+ }): import("react").JSX.Element;
73
+ export default OrgLicencesPanel;