@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.
- package/package.json +15 -11
- package/src/lib/components/edit-listing-dialog.component.d.ts +20 -0
- package/src/lib/components/edit-listing-dialog.component.js +51 -0
- package/src/lib/components/edit-listing-dialog.component.js.map +1 -0
- package/src/lib/components/listing-detail-editor.component.d.ts +24 -0
- package/src/lib/components/listing-detail-editor.component.js +457 -0
- package/src/lib/components/listing-detail-editor.component.js.map +1 -0
- package/src/lib/components/listing-detail-page.component.d.ts +28 -0
- package/src/lib/components/listing-detail-page.component.js +151 -0
- package/src/lib/components/listing-detail-page.component.js.map +1 -0
- package/src/lib/components/listing-review-status.component.d.ts +67 -0
- package/src/lib/components/listing-review-status.component.js +301 -0
- package/src/lib/components/listing-review-status.component.js.map +1 -0
- package/src/lib/components/listing-verification-request.component.d.ts +40 -0
- package/src/lib/components/listing-verification-request.component.js +170 -0
- package/src/lib/components/listing-verification-request.component.js.map +1 -0
- package/src/lib/components/marketplace-hub.component.d.ts +43 -0
- package/src/lib/components/marketplace-hub.component.js +244 -0
- package/src/lib/components/marketplace-hub.component.js.map +1 -0
- package/src/lib/components/marketplace-reports-page.component.d.ts +23 -0
- package/src/lib/components/marketplace-reports-page.component.js +324 -0
- package/src/lib/components/marketplace-reports-page.component.js.map +1 -0
- package/src/lib/components/org-licences-panel.component.d.ts +73 -0
- package/src/lib/components/org-licences-panel.component.js +318 -0
- package/src/lib/components/org-licences-panel.component.js.map +1 -0
- package/src/lib/components/org-publish-panel.component.d.ts +45 -0
- package/src/lib/components/org-publish-panel.component.js +402 -0
- package/src/lib/components/org-publish-panel.component.js.map +1 -0
- package/src/lib/components/org-seller-panel.component.d.ts +28 -0
- package/src/lib/components/org-seller-panel.component.js +956 -0
- package/src/lib/components/org-seller-panel.component.js.map +1 -0
- package/src/lib/components/plugin-review-detail.component.d.ts +16 -0
- package/src/lib/components/plugin-review-detail.component.js +1438 -0
- package/src/lib/components/plugin-review-detail.component.js.map +1 -0
- package/src/lib/components/plugin-reviews-queue.component.d.ts +13 -0
- package/src/lib/components/plugin-reviews-queue.component.js +385 -0
- package/src/lib/components/plugin-reviews-queue.component.js.map +1 -0
- package/src/lib/components/plugin-reviews-surface.component.d.ts +19 -0
- package/src/lib/components/plugin-reviews-surface.component.js +46 -0
- package/src/lib/components/plugin-reviews-surface.component.js.map +1 -0
- package/src/lib/components/publish-plugin-form.component.d.ts +61 -0
- package/src/lib/components/publish-plugin-form.component.js +857 -0
- package/src/lib/components/publish-plugin-form.component.js.map +1 -0
- package/src/lib/components/publish-plugin-page.component.d.ts +29 -0
- package/src/lib/components/publish-plugin-page.component.js +92 -0
- package/src/lib/components/publish-plugin-page.component.js.map +1 -0
- package/src/lib/components/publisher-profile-page.component.d.ts +13 -0
- package/src/lib/components/publisher-profile-page.component.js +261 -0
- package/src/lib/components/publisher-profile-page.component.js.map +1 -0
- package/src/lib/constants/listing-media.d.ts +28 -0
- package/src/lib/constants/listing-media.js +28 -0
- package/src/lib/constants/listing-media.js.map +1 -0
- package/src/lib/model/listing-social-card.d.ts +94 -0
- package/src/lib/model/listing-social-card.js +177 -0
- package/src/lib/model/listing-social-card.js.map +1 -0
- package/src/lib/model/marketplace-paths.d.ts +54 -0
- package/src/lib/model/marketplace-paths.js +59 -0
- package/src/lib/model/marketplace-paths.js.map +1 -0
- package/src/lib/model/payout-readiness.d.ts +81 -0
- package/src/lib/model/payout-readiness.js +52 -0
- package/src/lib/model/payout-readiness.js.map +1 -0
- package/src/lib/model/plugin-rejection-categories.d.ts +66 -0
- package/src/lib/model/plugin-rejection-categories.js +124 -0
- package/src/lib/model/plugin-rejection-categories.js.map +1 -0
- package/src/lib/model/plugin-review-checklist.d.ts +62 -0
- package/src/lib/model/plugin-review-checklist.js +131 -0
- package/src/lib/model/plugin-review-checklist.js.map +1 -0
- package/src/lib/model/plugin-review-status.d.ts +104 -0
- package/src/lib/model/plugin-review-status.js +126 -0
- package/src/lib/model/plugin-review-status.js.map +1 -0
- package/src/lib/model/seller-ledger-totals.d.ts +100 -0
- package/src/lib/model/seller-ledger-totals.js +96 -0
- package/src/lib/model/seller-ledger-totals.js.map +1 -0
- package/src/lib/plugin.js +170 -40
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/admin-reports.d.ts +18 -0
- package/src/lib/server/admin-reports.js +280 -0
- package/src/lib/server/admin-reports.js.map +1 -0
- package/src/lib/server/admin-reviews.d.ts +18 -0
- package/src/lib/server/admin-reviews.js +1341 -0
- package/src/lib/server/admin-reviews.js.map +1 -0
- package/src/lib/server/listing-social-card.server.d.ts +24 -0
- package/src/lib/server/listing-social-card.server.js +65 -0
- package/src/lib/server/listing-social-card.server.js.map +1 -0
- package/src/lib/server/publisher-review-email.d.ts +54 -0
- package/src/lib/server/publisher-review-email.js +85 -0
- package/src/lib/server/publisher-review-email.js.map +1 -0
- package/src/lib/server/revoke-cache-drop.d.ts +59 -0
- package/src/lib/server/revoke-cache-drop.js +74 -0
- package/src/lib/server/revoke-cache-drop.js.map +1 -0
- package/src/lib/server.js +47 -0
- 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;
|