@aglyn/plugins-marketplace 1.0.0-beta.199 → 1.0.0-beta.201
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +14 -14
- package/src/lib/components/marketplace-browse.component.js +104 -169
- package/src/lib/components/marketplace-browse.component.js.map +1 -1
- package/src/lib/components/marketplace-reports-page.component.js +9 -0
- package/src/lib/components/marketplace-reports-page.component.js.map +1 -1
- package/src/lib/components/org-licences-panel.component.js +212 -170
- package/src/lib/components/org-licences-panel.component.js.map +1 -1
- package/src/lib/components/org-seller-panel.component.js +23 -5
- package/src/lib/components/org-seller-panel.component.js.map +1 -1
- package/src/lib/components/plugin-reviews-queue.component.d.ts +9 -0
- package/src/lib/components/plugin-reviews-queue.component.js +190 -114
- package/src/lib/components/plugin-reviews-queue.component.js.map +1 -1
- package/src/lib/hooks/use-listing-name-lookup.d.ts +34 -0
- package/src/lib/hooks/use-listing-name-lookup.js +131 -0
- package/src/lib/hooks/use-listing-name-lookup.js.map +1 -0
- package/src/lib/model/listing-query.d.ts +140 -0
- package/src/lib/model/listing-query.js +393 -0
- package/src/lib/model/listing-query.js.map +1 -0
- package/src/lib/model/marketplace.d.ts +11 -0
- package/src/lib/model/marketplace.js.map +1 -1
- package/src/lib/server/admin-reports.js +30 -12
- package/src/lib/server/admin-reports.js.map +1 -1
- package/src/lib/server/admin-reviews.js +180 -128
- package/src/lib/server/admin-reviews.js.map +1 -1
- package/src/lib/server/admin-trust.js +2 -1
- package/src/lib/server/admin-trust.js.map +1 -1
- package/src/lib/server/billing-webhook.js +13 -1
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/checkout.js +3 -1
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/install-email-starter.js +9 -2
- package/src/lib/server/install-email-starter.js.map +1 -1
- package/src/lib/server/install-layout.js +17 -5
- package/src/lib/server/install-layout.js.map +1 -1
- package/src/lib/server/install-template.js +15 -5
- package/src/lib/server/install-template.js.map +1 -1
- package/src/lib/server/install.js +10 -2
- package/src/lib/server/install.js.map +1 -1
- package/src/lib/server/listing-query-fields.d.ts +38 -0
- package/src/lib/server/listing-query-fields.js +50 -0
- package/src/lib/server/listing-query-fields.js.map +1 -0
- package/src/lib/server/publish-dataset-schema.js +6 -2
- package/src/lib/server/publish-dataset-schema.js.map +1 -1
- package/src/lib/server/publish-email-starter.js +6 -2
- package/src/lib/server/publish-email-starter.js.map +1 -1
- package/src/lib/server/publish-email-template.js +6 -2
- package/src/lib/server/publish-email-template.js.map +1 -1
- package/src/lib/server/publish-layout.js +6 -2
- package/src/lib/server/publish-layout.js.map +1 -1
- package/src/lib/server/publish-plugin.js +75 -7
- package/src/lib/server/publish-plugin.js.map +1 -1
- package/src/lib/server/publish-template.js +6 -2
- package/src/lib/server/publish-template.js.map +1 -1
- package/src/lib/server/publish-theme.js +6 -2
- package/src/lib/server/publish-theme.js.map +1 -1
- package/src/lib/server/publish.js +6 -2
- package/src/lib/server/publish.js.map +1 -1
- package/src/lib/server/update-artifact.js +18 -4
- package/src/lib/server/update-artifact.js.map +1 -1
|
@@ -14,15 +14,19 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ 'use client';
|
|
17
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
18
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
18
19
|
import { PageHeaderHelp } from "@aglyn/aglyn";
|
|
19
20
|
import { AppLink, CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
21
|
+
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
22
|
+
import ListQueryNotices, { listQueryRefusals } from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
|
|
20
23
|
import { Alert, Chip, MenuItem, Skeleton, Stack, TextField, Typography } from "@mui/material";
|
|
21
24
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
22
25
|
import { useUser } from "@aglyn/tenant-feature-instance";
|
|
23
26
|
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
24
27
|
import { pluginDocsHelp } from "@aglyn/aglyn";
|
|
25
28
|
import { reviewStatusMeaning } from "../model/plugin-review-status.js";
|
|
29
|
+
import { REVIEW_QUEUE_QUERY } from "../model/listing-query.js";
|
|
26
30
|
const STATUS_FILTERS = [
|
|
27
31
|
{
|
|
28
32
|
value: 'all',
|
|
@@ -49,6 +53,25 @@ const STATUS_FILTERS = [
|
|
|
49
53
|
label: 'Taken down'
|
|
50
54
|
}
|
|
51
55
|
];
|
|
56
|
+
/** How the queue's clauses read in a refusal. */ const QUEUE_HEADERS = {
|
|
57
|
+
reviewStatus: 'Status',
|
|
58
|
+
takenDown: 'Taken down'
|
|
59
|
+
};
|
|
60
|
+
const QUEUE_OPTIONS = {
|
|
61
|
+
reviewStatus: STATUS_FILTERS.filter((option)=>option.value !== 'all' && option.value !== 'hidden')
|
|
62
|
+
};
|
|
63
|
+
/** Rows per page of each section. */ const QUEUE_PAGE = 50;
|
|
64
|
+
/** How long the search box waits for typing to stop before it asks again. */ const SEARCH_SETTLE_MS = 300;
|
|
65
|
+
const NO_MORE = {
|
|
66
|
+
queue: false,
|
|
67
|
+
listed: false,
|
|
68
|
+
verification: false
|
|
69
|
+
};
|
|
70
|
+
const FIRST_PAGES = {
|
|
71
|
+
queue: 0,
|
|
72
|
+
listed: 0,
|
|
73
|
+
verification: 0
|
|
74
|
+
};
|
|
52
75
|
/**
|
|
53
76
|
* Staff marketplace review index (AGL-961).
|
|
54
77
|
*
|
|
@@ -57,6 +80,15 @@ const STATUS_FILTERS = [
|
|
|
57
80
|
* on the detail page. The previous version stacked all of it inline, which
|
|
58
81
|
* meant the most destructive controls in the platform sat as same-weight
|
|
59
82
|
* text buttons in a wall of caption text.
|
|
83
|
+
*
|
|
84
|
+
* The search box and the Status select are served by the route's QUERY
|
|
85
|
+
* (AGL-3321): each section asks Firestore for its rows with the status and
|
|
86
|
+
* the searched name word on it, so a plugin past the first hundred is found.
|
|
87
|
+
* They used to be matched here over the hundred the route had read. Search
|
|
88
|
+
* reads the listing name; a refusal or a one-word notice from the plan is
|
|
89
|
+
* shown above the sections. Each section pages: the route reads every section
|
|
90
|
+
* out to the furthest page asked plus one row, which is how a section knows
|
|
91
|
+
* there is a next page (the window `usePagedCollection` reads, over a route).
|
|
60
92
|
*/ export function PluginReviewsQueue({ basePath }) {
|
|
61
93
|
const { data: user } = useUser();
|
|
62
94
|
const [queue, setQueue] = useState([]);
|
|
@@ -66,26 +98,57 @@ const STATUS_FILTERS = [
|
|
|
66
98
|
const [loaded, setLoaded] = useState(false);
|
|
67
99
|
const [loadError, setLoadError] = useState(null);
|
|
68
100
|
const [search, setSearch] = useState('');
|
|
101
|
+
const [asked, setAsked] = useState('');
|
|
69
102
|
const [status, setStatus] = useState('all');
|
|
103
|
+
const [pages, setPages] = useState(FIRST_PAGES);
|
|
104
|
+
/** Every section is read out to the furthest page any of them is on. */ const limit = QUEUE_PAGE * (Math.max(...Object.values(pages)) + 1);
|
|
105
|
+
const [more, setMore] = useState(NO_MORE);
|
|
106
|
+
const [refused, setRefused] = useState([]);
|
|
107
|
+
const [notices, setNotices] = useState([]);
|
|
108
|
+
// The search is asked once typing settles, not per keystroke.
|
|
109
|
+
useEffect(()=>{
|
|
110
|
+
const timer = setTimeout(()=>setAsked(search.trim()), SEARCH_SETTLE_MS);
|
|
111
|
+
return ()=>clearTimeout(timer);
|
|
112
|
+
}, [
|
|
113
|
+
search
|
|
114
|
+
]);
|
|
115
|
+
// A new question starts from the first page.
|
|
116
|
+
useEffect(()=>{
|
|
117
|
+
setPages(FIRST_PAGES);
|
|
118
|
+
}, [
|
|
119
|
+
asked,
|
|
120
|
+
status
|
|
121
|
+
]);
|
|
70
122
|
const refresh = useCallback(async ()=>{
|
|
71
|
-
const
|
|
123
|
+
const params = new URLSearchParams();
|
|
124
|
+
if (asked) params.set('q', asked);
|
|
125
|
+
if (status !== 'all') params.set('status', status);
|
|
126
|
+
params.set('limit', String(limit));
|
|
127
|
+
const suffix = params.toString();
|
|
128
|
+
const response = await authorizedFetch(user, `/api/marketplace/admin/reviews${suffix ? `?${suffix}` : ''}`);
|
|
72
129
|
const payload = await response.json().catch(()=>({}));
|
|
73
130
|
if (response.ok) {
|
|
74
|
-
var _ref, _ref1, _ref2, _ref3;
|
|
131
|
+
var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6;
|
|
75
132
|
setLoadError(null);
|
|
76
133
|
setQueue((_ref = payload == null ? void 0 : payload.queue) != null ? _ref : []);
|
|
77
134
|
setListed((_ref1 = payload == null ? void 0 : payload.listed) != null ? _ref1 : []);
|
|
78
135
|
setVerificationRequests((_ref2 = payload == null ? void 0 : payload.verificationRequests) != null ? _ref2 : []);
|
|
79
136
|
setPublishers((_ref3 = payload == null ? void 0 : payload.publishers) != null ? _ref3 : {});
|
|
137
|
+
setMore(_extends({}, NO_MORE, (_ref4 = payload == null ? void 0 : payload.more) != null ? _ref4 : {}));
|
|
138
|
+
setRefused((_ref5 = payload == null ? void 0 : payload.refused) != null ? _ref5 : []);
|
|
139
|
+
setNotices((_ref6 = payload == null ? void 0 : payload.notices) != null ? _ref6 : []);
|
|
80
140
|
} else {
|
|
81
|
-
var
|
|
141
|
+
var _ref7;
|
|
82
142
|
// Named, not swallowed. An empty queue and a queue that could not be
|
|
83
143
|
// read look identical, and only one of them means there is no work.
|
|
84
|
-
setLoadError((
|
|
144
|
+
setLoadError((_ref7 = payload == null ? void 0 : payload.error) != null ? _ref7 : `Loading the queue failed (${response.status})`);
|
|
85
145
|
}
|
|
86
146
|
setLoaded(true);
|
|
87
147
|
}, [
|
|
88
|
-
user
|
|
148
|
+
user,
|
|
149
|
+
asked,
|
|
150
|
+
status,
|
|
151
|
+
limit
|
|
89
152
|
]);
|
|
90
153
|
useEffect(()=>{
|
|
91
154
|
if (user) void refresh();
|
|
@@ -99,37 +162,37 @@ const STATUS_FILTERS = [
|
|
|
99
162
|
}, [
|
|
100
163
|
publishers
|
|
101
164
|
]);
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const visibleQueue = useMemo(()=>queue.filter(matches), [
|
|
121
|
-
queue,
|
|
122
|
-
matches
|
|
123
|
-
]);
|
|
124
|
-
const visibleListed = useMemo(()=>listed.filter(matches), [
|
|
125
|
-
listed,
|
|
126
|
-
matches
|
|
165
|
+
// Every section is already what the query answered: nothing is matched
|
|
166
|
+
// here, only the page on screen sliced out of the window read.
|
|
167
|
+
const pageOf = (section, rows)=>rows.slice(pages[section] * QUEUE_PAGE, (pages[section] + 1) * QUEUE_PAGE);
|
|
168
|
+
const visibleQueue = pageOf('queue', queue);
|
|
169
|
+
const visibleListed = pageOf('listed', listed);
|
|
170
|
+
const visibleVerification = pageOf('verification', verificationRequests);
|
|
171
|
+
const total = {
|
|
172
|
+
queue: queue.length,
|
|
173
|
+
listed: listed.length,
|
|
174
|
+
verification: verificationRequests.length
|
|
175
|
+
};
|
|
176
|
+
const filtering = asked.length > 0 || status !== 'all';
|
|
177
|
+
const refusals = useMemo(()=>listQueryRefusals(refused, {
|
|
178
|
+
fields: REVIEW_QUEUE_QUERY.fields,
|
|
179
|
+
headers: QUEUE_HEADERS,
|
|
180
|
+
options: QUEUE_OPTIONS
|
|
181
|
+
}), [
|
|
182
|
+
refused
|
|
127
183
|
]);
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
184
|
+
const pager = (section, shown)=>{
|
|
185
|
+
const hasMore = total[section] > (pages[section] + 1) * QUEUE_PAGE || more[section];
|
|
186
|
+
return pages[section] > 0 || hasMore ? /*#__PURE__*/ _jsx(ListPagination, {
|
|
187
|
+
page: pages[section],
|
|
188
|
+
pageSize: QUEUE_PAGE,
|
|
189
|
+
rowCount: shown,
|
|
190
|
+
hasMore: hasMore,
|
|
191
|
+
onPageChange: (page)=>setPages((previous)=>_extends({}, previous, {
|
|
192
|
+
[section]: page
|
|
193
|
+
}))
|
|
194
|
+
}) : null;
|
|
195
|
+
};
|
|
133
196
|
const row = (key, listingId, name, profileId, chips, caption)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
134
197
|
spacing: 0.25,
|
|
135
198
|
sx: {
|
|
@@ -186,7 +249,7 @@ const STATUS_FILTERS = [
|
|
|
186
249
|
children: [
|
|
187
250
|
/*#__PURE__*/ _jsx(TextField, {
|
|
188
251
|
size: "small",
|
|
189
|
-
placeholder: "Search
|
|
252
|
+
placeholder: "Search plugin names",
|
|
190
253
|
value: search,
|
|
191
254
|
onChange: (event)=>setSearch(event.target.value),
|
|
192
255
|
sx: {
|
|
@@ -212,6 +275,10 @@ const STATUS_FILTERS = [
|
|
|
212
275
|
severity: "error",
|
|
213
276
|
children: loadError
|
|
214
277
|
}) : null,
|
|
278
|
+
/*#__PURE__*/ _jsx(ListQueryNotices, {
|
|
279
|
+
refused: refusals,
|
|
280
|
+
notices: notices
|
|
281
|
+
}),
|
|
215
282
|
!loaded ? /*#__PURE__*/ _jsxs(Stack, {
|
|
216
283
|
spacing: 2,
|
|
217
284
|
children: [
|
|
@@ -227,7 +294,7 @@ const STATUS_FILTERS = [
|
|
|
227
294
|
}) : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
228
295
|
children: [
|
|
229
296
|
visibleVerification.length ? /*#__PURE__*/ _jsxs(CardDisplay, {
|
|
230
|
-
header: `Verification requested (${
|
|
297
|
+
header: `Verification requested (${total.verification}${more.verification ? '+' : ''})`,
|
|
231
298
|
help: pluginDocsHelp('publisherHandbook', {
|
|
232
299
|
anchor: '#asking-to-be-verified',
|
|
233
300
|
excerpt: 'Publishers who asked to be VERIFIED — a claim about who they are, ' + 'separate from reviewing any one version.'
|
|
@@ -244,31 +311,34 @@ const STATUS_FILTERS = [
|
|
|
244
311
|
},
|
|
245
312
|
children: 'These plugins are already live. The publisher has ' + 'asked us to vouch for who they are — granting adds ' + 'the badge and changes nothing about installability.'
|
|
246
313
|
}),
|
|
247
|
-
/*#__PURE__*/
|
|
248
|
-
children:
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
314
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
315
|
+
children: [
|
|
316
|
+
visibleVerification.map((entry)=>row(`verify-${entry.listingId}`, entry.listingId, entry.displayName, entry.profileId, /*#__PURE__*/ _jsxs(_Fragment, {
|
|
317
|
+
children: [
|
|
318
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
319
|
+
size: "small",
|
|
320
|
+
color: "info",
|
|
321
|
+
label: "Verification requested"
|
|
322
|
+
}),
|
|
323
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
324
|
+
size: "small",
|
|
325
|
+
color: reviewStatusMeaning(entry.reviewStatus).color,
|
|
326
|
+
label: reviewStatusMeaning(entry.reviewStatus).label
|
|
327
|
+
}),
|
|
328
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
329
|
+
size: "small",
|
|
330
|
+
variant: "outlined",
|
|
331
|
+
label: `v${entry.version || '—'}`
|
|
332
|
+
})
|
|
333
|
+
]
|
|
334
|
+
}), 'Awaiting a verification decision')),
|
|
335
|
+
pager('verification', visibleVerification.length)
|
|
336
|
+
]
|
|
267
337
|
})
|
|
268
338
|
]
|
|
269
339
|
}) : null,
|
|
270
340
|
/*#__PURE__*/ _jsxs(CardDisplay, {
|
|
271
|
-
header: `Awaiting review (${
|
|
341
|
+
header: `Awaiting review (${total.queue}${more.queue ? '+' : ''})`,
|
|
272
342
|
help: pluginDocsHelp('manifestAndEnvs', {
|
|
273
343
|
anchor: '#review--trust-lifecycle',
|
|
274
344
|
excerpt: 'Submissions waiting on a staff verdict. Open one to read its manifest, verifier findings and act.'
|
|
@@ -285,31 +355,34 @@ const STATUS_FILTERS = [
|
|
|
285
355
|
},
|
|
286
356
|
children: 'Submitted and In review are not installable by anyone.'
|
|
287
357
|
}),
|
|
288
|
-
visibleQueue.length ? /*#__PURE__*/
|
|
289
|
-
children:
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
358
|
+
visibleQueue.length ? /*#__PURE__*/ _jsxs(Stack, {
|
|
359
|
+
children: [
|
|
360
|
+
visibleQueue.map((entry)=>row(entry.listingId, entry.listingId, entry.displayName, entry.profileId, /*#__PURE__*/ _jsxs(_Fragment, {
|
|
361
|
+
children: [
|
|
362
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
363
|
+
size: "small",
|
|
364
|
+
color: reviewStatusMeaning(entry.reviewStatus).color,
|
|
365
|
+
label: reviewStatusMeaning(entry.reviewStatus).label
|
|
366
|
+
}),
|
|
367
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
368
|
+
size: "small",
|
|
369
|
+
variant: "outlined",
|
|
370
|
+
label: `v${entry.version || '—'}`
|
|
371
|
+
}),
|
|
372
|
+
entry.private ? /*#__PURE__*/ _jsx(Chip, {
|
|
373
|
+
size: "small",
|
|
374
|
+
variant: "outlined",
|
|
375
|
+
label: "Private"
|
|
376
|
+
}) : null,
|
|
377
|
+
entry.license || entry.private ? null : /*#__PURE__*/ _jsx(Chip, {
|
|
378
|
+
size: "small",
|
|
379
|
+
color: "warning",
|
|
380
|
+
label: "No license"
|
|
381
|
+
})
|
|
382
|
+
]
|
|
383
|
+
}), entry.priceUsd > 0 ? `$${entry.priceUsd}` : 'Free')),
|
|
384
|
+
pager('queue', visibleQueue.length)
|
|
385
|
+
]
|
|
313
386
|
}) : /*#__PURE__*/ _jsx(Alert, {
|
|
314
387
|
severity: filtering ? 'info' : 'success',
|
|
315
388
|
children: filtering ? 'No submissions match this filter.' : 'No plugin submissions waiting for review.'
|
|
@@ -317,7 +390,7 @@ const STATUS_FILTERS = [
|
|
|
317
390
|
]
|
|
318
391
|
}),
|
|
319
392
|
/*#__PURE__*/ _jsxs(CardDisplay, {
|
|
320
|
-
header: `Listed plugins (${
|
|
393
|
+
header: `Listed plugins (${total.listed}${more.listed ? '+' : ''})`,
|
|
321
394
|
help: pluginDocsHelp('publisherHandbook', {
|
|
322
395
|
anchor: '#review-what-happens-after-you-publish',
|
|
323
396
|
excerpt: 'Listings already installable. Listing is the step that makes a ' + 'version reachable from Browse.'
|
|
@@ -337,36 +410,39 @@ const STATUS_FILTERS = [
|
|
|
337
410
|
// it would misdescribe.
|
|
338
411
|
'Rows marked Private are the exception: approved and ' + 'live, but only for the org that published them.'
|
|
339
412
|
}),
|
|
340
|
-
visibleListed.length ? /*#__PURE__*/
|
|
341
|
-
children:
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
413
|
+
visibleListed.length ? /*#__PURE__*/ _jsxs(Stack, {
|
|
414
|
+
children: [
|
|
415
|
+
visibleListed.map((entry)=>row(entry.listingId, entry.listingId, entry.displayName, entry.profileId, /*#__PURE__*/ _jsxs(_Fragment, {
|
|
416
|
+
children: [
|
|
417
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
418
|
+
size: "small",
|
|
419
|
+
color: reviewStatusMeaning(entry.reviewStatus).color,
|
|
420
|
+
label: reviewStatusMeaning(entry.reviewStatus).label
|
|
421
|
+
}),
|
|
422
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
423
|
+
size: "small",
|
|
424
|
+
variant: "outlined",
|
|
425
|
+
label: `v${entry.latestVersion || '—'}`
|
|
426
|
+
}),
|
|
427
|
+
entry.private ? /*#__PURE__*/ _jsx(Chip, {
|
|
428
|
+
size: "small",
|
|
429
|
+
variant: "outlined",
|
|
430
|
+
label: "Private"
|
|
431
|
+
}) : null,
|
|
432
|
+
entry.realmVersions ? /*#__PURE__*/ _jsx(Chip, {
|
|
433
|
+
size: "small",
|
|
434
|
+
color: "success",
|
|
435
|
+
label: `${entry.realmVersions} realm-trusted`
|
|
436
|
+
}) : null,
|
|
437
|
+
entry.hidden ? /*#__PURE__*/ _jsx(Chip, {
|
|
438
|
+
size: "small",
|
|
439
|
+
color: "error",
|
|
440
|
+
label: "Taken down"
|
|
441
|
+
}) : null
|
|
442
|
+
]
|
|
443
|
+
}), `${entry.versionCount} version${entry.versionCount === 1 ? '' : 's'}${entry.hidden && entry.hiddenReason ? ` · ${entry.hiddenReason}` : ''}`)),
|
|
444
|
+
pager('listed', visibleListed.length)
|
|
445
|
+
]
|
|
370
446
|
}) : /*#__PURE__*/ _jsx(Alert, {
|
|
371
447
|
severity: "info",
|
|
372
448
|
children: filtering ? 'No listed plugins match this filter.' : 'No listed plugins yet.'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/plugin-reviews-queue.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 } from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n Alert,\n Chip,\n MenuItem,\n Skeleton,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { reviewStatusMeaning } from '../model/plugin-review-status'\n\ninterface QueueRow {\n listingId: string\n displayName: string\n description: string\n license: string\n categories: string[]\n profileId: string\n reviewStatus: string\n priceUsd: number\n version: string\n hidden: boolean\n /** Private plugin (AGL-968): reviewed identically, never listed. */\n private: boolean\n /** The publisher's standing ask for the Verified badge (AGL-1217). */\n verificationRequest?: { state?: string; requestedAt?: unknown } | null\n}\n\ninterface ListedRow {\n listingId: string\n displayName: string\n reviewStatus: string\n profileId: string\n latestVersion: string\n hidden: boolean\n hiddenReason: string\n realmVersions: number\n versionCount: number\n private: boolean\n}\n\nconst STATUS_FILTERS = [\n { value: 'all', label: 'All statuses' },\n { value: 'submitted', label: 'Submitted' },\n { value: 'in_review', label: 'In review' },\n { value: 'listed', label: 'Listed' },\n { value: 'verified', label: 'Verified' },\n { value: 'hidden', label: 'Taken down' },\n]\n\n/**\n * Staff marketplace review index (AGL-961).\n *\n * Scanning surface only: rows carry just enough to pick the right listing,\n * and every consequential action — verdicts, realm trust, takedown — lives\n * on the detail page. The previous version stacked all of it inline, which\n * meant the most destructive controls in the platform sat as same-weight\n * text buttons in a wall of caption text.\n */\nexport function PluginReviewsQueue({ basePath }: { basePath: string }) {\n const { data: user } = useUser()\n const [queue, setQueue] = useState<QueueRow[]>([])\n const [listed, setListed] = useState<ListedRow[]>([])\n const [verificationRequests, setVerificationRequests] = useState<QueueRow[]>(\n [],\n )\n const [publishers, setPublishers] = useState<Record<string, string>>({})\n const [loaded, setLoaded] = useState(false)\n const [loadError, setLoadError] = useState<string | null>(null)\n const [search, setSearch] = useState('')\n const [status, setStatus] = useState('all')\n\n const refresh = useCallback(async () => {\n const response = await authorizedFetch(user, '/api/marketplace/admin/reviews')\n const payload = await response.json().catch(() => ({}))\n if (response.ok) {\n setLoadError(null)\n setQueue(payload?.queue ?? [])\n setListed(payload?.listed ?? [])\n setVerificationRequests(payload?.verificationRequests ?? [])\n setPublishers(payload?.publishers ?? {})\n } else {\n // Named, not swallowed. An empty queue and a queue that could not be\n // read look identical, and only one of them means there is no work.\n setLoadError(\n payload?.error ?? `Loading the queue failed (${response.status})`,\n )\n }\n setLoaded(true)\n }, [user])\n\n useEffect(() => {\n if (user) void refresh()\n }, [user, refresh])\n\n const publisherName = useCallback(\n (profileId: string) => publishers[profileId] ?? profileId,\n [publishers],\n )\n\n // One predicate for both sections, so a search never means two things.\n const matches = useCallback(\n (row: { displayName: string; listingId: string; profileId: string; reviewStatus: string; hidden: boolean }) => {\n const term = search.trim().toLowerCase()\n if (\n term &&\n ![row.displayName, row.listingId, publisherName(row.profileId)].some(\n (field) => String(field).toLowerCase().includes(term),\n )\n ) {\n return false\n }\n if (status === 'all') return true\n if (status === 'hidden') return row.hidden\n return row.reviewStatus === status\n },\n [search, status, publisherName],\n )\n\n const visibleQueue = useMemo(() => queue.filter(matches), [queue, matches])\n const visibleListed = useMemo(() => listed.filter(matches), [listed, matches])\n const visibleVerification = useMemo(\n () => verificationRequests.filter(matches),\n [verificationRequests, matches],\n )\n const filtering = search.trim().length > 0 || status !== 'all'\n\n const row = (\n key: string,\n listingId: string,\n name: string,\n profileId: string,\n chips: React.ReactNode,\n caption: string,\n ) => (\n <Stack\n key={key}\n spacing={0.25}\n sx={{\n py: 1,\n borderBottom: 1,\n borderColor: 'divider',\n '&:last-of-type': { borderBottom: 0 },\n }}\n >\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <AppLink href={`${basePath}/${encodeURIComponent(listingId)}`}>\n <Typography variant=\"subtitle2\" component=\"span\">\n {name}\n </Typography>\n </AppLink>\n {chips}\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${publisherName(profileId)} · ${caption}`}\n </Typography>\n </Stack>\n )\n\n return (\n <>\n {/*\n The staff console's own topic, anchored. A `staffPages` entry carries\n one `docsTopic` and no anchor, and the staff console page is long\n enough that landing at the top of it is the AGL-2200 shape.\n */}\n <PageHeaderHelp topic=\"staffConsole\" anchor=\"#plugin-reviews\" />\n {/*\n The staff gate is the shell's: the generic staff route renders every\n plugin page inside `StaffOnly`, which renders nothing while the claim\n resolves and 404s a non-holder. Without it a non-staff visitor was\n told \"No plugin submissions waiting for review\" — the queue fetch\n 403s and the empty result reads as good news rather than a refusal\n (AGL-760).\n */}\n <Stack spacing={3}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <TextField\n size=\"small\"\n placeholder=\"Search name, publisher or listing id\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n sx={{ minWidth: 320 }}\n />\n <TextField\n size=\"small\"\n select\n value={status}\n onChange={(event) => setStatus(event.target.value)}\n sx={{ minWidth: 180 }}\n >\n {STATUS_FILTERS.map((option) => (\n <MenuItem key={option.value} value={option.value}>\n {option.label}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n\n {loadError ? (\n <Alert severity=\"error\">{loadError}</Alert>\n ) : null}\n\n {!loaded ? (\n <Stack spacing={2}>\n <Skeleton variant=\"rounded\" height={140} />\n <Skeleton variant=\"rounded\" height={200} />\n </Stack>\n ) : (\n <>\n {/* Its own card, above the review queue (AGL-1217). A\n verification request is a different question from\n \"have these bytes been read\" — it is about the\n publisher, and it is answered by a different action.\n Folded into Awaiting review it would be invisible: a\n listing whose bytes are already approved does not\n appear there at all. Hidden when empty, so it costs\n nothing on the common day. */}\n {visibleVerification.length ? (\n <CardDisplay\n header={`Verification requested (${visibleVerification.length})`}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#asking-to-be-verified',\n excerpt:\n 'Publishers who asked to be VERIFIED — a claim about who they are, ' +\n 'separate from reviewing any one version.',\n })}\n contentGutterX\n contentGutterY\n >\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mb: 1 }}\n >\n {'These plugins are already live. The publisher has ' +\n 'asked us to vouch for who they are — granting adds ' +\n 'the badge and changes nothing about installability.'}\n </Typography>\n <Stack>\n {visibleVerification.map((entry) =>\n row(\n `verify-${entry.listingId}`,\n entry.listingId,\n entry.displayName,\n entry.profileId,\n <>\n <Chip\n size=\"small\"\n color=\"info\"\n label=\"Verification requested\"\n />\n <Chip\n size=\"small\"\n color={reviewStatusMeaning(entry.reviewStatus).color}\n label={reviewStatusMeaning(entry.reviewStatus).label}\n />\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={`v${entry.version || '—'}`}\n />\n </>,\n 'Awaiting a verification decision',\n ),\n )}\n </Stack>\n </CardDisplay>\n ) : null}\n <CardDisplay\n header={`Awaiting review (${visibleQueue.length})`}\n help={pluginDocsHelp('manifestAndEnvs', {\n anchor: '#review--trust-lifecycle',\n excerpt:\n 'Submissions waiting on a staff verdict. Open one to read its manifest, verifier findings and act.',\n })}\n contentGutterX\n contentGutterY\n >\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mb: 1 }}\n >\n {'Submitted and In review are not installable by anyone.'}\n </Typography>\n {visibleQueue.length ? (\n <Stack>\n {visibleQueue.map((entry) =>\n row(\n entry.listingId,\n entry.listingId,\n entry.displayName,\n entry.profileId,\n <>\n <Chip\n size=\"small\"\n color={reviewStatusMeaning(entry.reviewStatus).color}\n label={reviewStatusMeaning(entry.reviewStatus).label}\n />\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={`v${entry.version || '—'}`}\n />\n {/* Same bar, smaller audience (AGL-968/995).\n Worth flagging so a reviewer knows the\n blast radius is one workspace — never so\n they review it more loosely. */}\n {entry.private ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Private\"\n />\n ) : null}\n {/* A private plugin has no marketplace listing\n page, so \"no license\" is not a finding. */}\n {entry.license || entry.private ? null : (\n <Chip\n size=\"small\"\n color=\"warning\"\n label=\"No license\"\n />\n )}\n </>,\n entry.priceUsd > 0 ? `$${entry.priceUsd}` : 'Free',\n ),\n )}\n </Stack>\n ) : (\n <Alert severity={filtering ? 'info' : 'success'}>\n {filtering\n ? 'No submissions match this filter.'\n : 'No plugin submissions waiting for review.'}\n </Alert>\n )}\n </CardDisplay>\n\n <CardDisplay\n header={`Listed plugins (${visibleListed.length})`}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#review-what-happens-after-you-publish',\n excerpt:\n 'Listings already installable. Listing is the step that makes a ' +\n 'version reachable from Browse.',\n })}\n contentGutterX\n contentGutterY\n >\n {/* Listed vs Verified is the distinction reviewers get\n wrong (AGL-966): both are LIVE. Verified only adds a\n badge — it does not change who can install. */}\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mb: 1 }}\n >\n {'Both states are live — installable by every workspace. ' +\n 'Verified additionally carries the reviewed badge on ' +\n 'its listing page; it does not change installability. ' +\n // The sentence above is false for a private row\n // (AGL-968/995), and it sits directly over the rows\n // it would misdescribe.\n 'Rows marked Private are the exception: approved and ' +\n 'live, but only for the org that published them.'}\n </Typography>\n {visibleListed.length ? (\n <Stack>\n {visibleListed.map((entry) =>\n row(\n entry.listingId,\n entry.listingId,\n entry.displayName,\n entry.profileId,\n <>\n <Chip\n size=\"small\"\n color={reviewStatusMeaning(entry.reviewStatus).color}\n label={reviewStatusMeaning(entry.reviewStatus).label}\n />\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={`v${entry.latestVersion || '—'}`}\n />\n {entry.private ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Private\"\n />\n ) : null}\n {entry.realmVersions ? (\n <Chip\n size=\"small\"\n color=\"success\"\n label={`${entry.realmVersions} realm-trusted`}\n />\n ) : null}\n {entry.hidden ? (\n <Chip\n size=\"small\"\n color=\"error\"\n label=\"Taken down\"\n />\n ) : null}\n </>,\n `${entry.versionCount} version${\n entry.versionCount === 1 ? '' : 's'\n }${entry.hidden && entry.hiddenReason ? ` · ${entry.hiddenReason}` : ''}`,\n ),\n )}\n </Stack>\n ) : (\n <Alert severity=\"info\">\n {filtering\n ? 'No listed plugins match this filter.'\n : 'No listed plugins yet.'}\n </Alert>\n )}\n </CardDisplay>\n </>\n )}\n </Stack>\n </>\n )\n}\n\nexport default PluginReviewsQueue\n"],"names":["PageHeaderHelp","AppLink","CardDisplay","Alert","Chip","MenuItem","Skeleton","Stack","TextField","Typography","useCallback","useEffect","useMemo","useState","useUser","authorizedFetch","pluginDocsHelp","reviewStatusMeaning","STATUS_FILTERS","value","label","PluginReviewsQueue","basePath","data","user","queue","setQueue","listed","setListed","verificationRequests","setVerificationRequests","publishers","setPublishers","loaded","setLoaded","loadError","setLoadError","search","setSearch","status","setStatus","refresh","response","payload","json","catch","ok","error","publisherName","profileId","matches","row","term","trim","toLowerCase","displayName","listingId","some","field","String","includes","hidden","reviewStatus","visibleQueue","filter","visibleListed","visibleVerification","filtering","length","key","name","chips","caption","spacing","sx","py","borderBottom","borderColor","direction","alignItems","flexWrap","href","encodeURIComponent","variant","component","color","topic","anchor","size","placeholder","onChange","event","target","minWidth","select","map","option","severity","height","header","help","excerpt","contentGutterX","contentGutterY","display","mb","entry","version","private","license","priceUsd","latestVersion","realmVersions","versionCount","hiddenReason"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/plugin-reviews-queue.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 { PageHeaderHelp } from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport type { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport {\n Alert,\n Chip,\n MenuItem,\n Skeleton,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { reviewStatusMeaning } from '../model/plugin-review-status'\nimport {\n REVIEW_QUEUE_QUERY,\n type ReviewQueueSection,\n} from '../model/listing-query'\n\ninterface QueueRow {\n listingId: string\n displayName: string\n description: string\n license: string\n categories: string[]\n profileId: string\n reviewStatus: string\n priceUsd: number\n version: string\n hidden: boolean\n /** Private plugin (AGL-968): reviewed identically, never listed. */\n private: boolean\n /** The publisher's standing ask for the Verified badge (AGL-1217). */\n verificationRequest?: { state?: string; requestedAt?: unknown } | null\n}\n\ninterface ListedRow {\n listingId: string\n displayName: string\n reviewStatus: string\n profileId: string\n latestVersion: string\n hidden: boolean\n hiddenReason: string\n realmVersions: number\n versionCount: number\n private: boolean\n}\n\nconst STATUS_FILTERS = [\n { value: 'all', label: 'All statuses' },\n { value: 'submitted', label: 'Submitted' },\n { value: 'in_review', label: 'In review' },\n { value: 'listed', label: 'Listed' },\n { value: 'verified', label: 'Verified' },\n { value: 'hidden', label: 'Taken down' },\n]\n\n/** How the queue's clauses read in a refusal. */\nconst QUEUE_HEADERS: Readonly<Record<string, string>> = {\n reviewStatus: 'Status',\n takenDown: 'Taken down',\n}\nconst QUEUE_OPTIONS = {\n reviewStatus: STATUS_FILTERS.filter(\n (option) => option.value !== 'all' && option.value !== 'hidden',\n ),\n}\n\n/** Rows per page of each section. */\nconst QUEUE_PAGE = 50\n/** How long the search box waits for typing to stop before it asks again. */\nconst SEARCH_SETTLE_MS = 300\n\nconst NO_MORE: Record<ReviewQueueSection, boolean> = {\n queue: false,\n listed: false,\n verification: false,\n}\nconst FIRST_PAGES: Record<ReviewQueueSection, number> = {\n queue: 0,\n listed: 0,\n verification: 0,\n}\n\n/**\n * Staff marketplace review index (AGL-961).\n *\n * Scanning surface only: rows carry just enough to pick the right listing,\n * and every consequential action — verdicts, realm trust, takedown — lives\n * on the detail page. The previous version stacked all of it inline, which\n * meant the most destructive controls in the platform sat as same-weight\n * text buttons in a wall of caption text.\n *\n * The search box and the Status select are served by the route's QUERY\n * (AGL-3321): each section asks Firestore for its rows with the status and\n * the searched name word on it, so a plugin past the first hundred is found.\n * They used to be matched here over the hundred the route had read. Search\n * reads the listing name; a refusal or a one-word notice from the plan is\n * shown above the sections. Each section pages: the route reads every section\n * out to the furthest page asked plus one row, which is how a section knows\n * there is a next page (the window `usePagedCollection` reads, over a route).\n */\nexport function PluginReviewsQueue({ basePath }: { basePath: string }) {\n const { data: user } = useUser()\n const [queue, setQueue] = useState<QueueRow[]>([])\n const [listed, setListed] = useState<ListedRow[]>([])\n const [verificationRequests, setVerificationRequests] = useState<QueueRow[]>(\n [],\n )\n const [publishers, setPublishers] = useState<Record<string, string>>({})\n const [loaded, setLoaded] = useState(false)\n const [loadError, setLoadError] = useState<string | null>(null)\n const [search, setSearch] = useState('')\n const [asked, setAsked] = useState('')\n const [status, setStatus] = useState('all')\n const [pages, setPages] =\n useState<Record<ReviewQueueSection, number>>(FIRST_PAGES)\n /** Every section is read out to the furthest page any of them is on. */\n const limit = QUEUE_PAGE * (Math.max(...Object.values(pages)) + 1)\n const [more, setMore] = useState<Record<ReviewQueueSection, boolean>>(NO_MORE)\n const [refused, setRefused] = useState<\n Array<{ clause: ListFilterClause | 'search'; reason: string }>\n >([])\n const [notices, setNotices] = useState<string[]>([])\n\n // The search is asked once typing settles, not per keystroke.\n useEffect(() => {\n const timer = setTimeout(() => setAsked(search.trim()), SEARCH_SETTLE_MS)\n return () => clearTimeout(timer)\n }, [search])\n // A new question starts from the first page.\n useEffect(() => {\n setPages(FIRST_PAGES)\n }, [asked, status])\n\n const refresh = useCallback(async () => {\n const params = new URLSearchParams()\n if (asked) params.set('q', asked)\n if (status !== 'all') params.set('status', status)\n params.set('limit', String(limit))\n const suffix = params.toString()\n const response = await authorizedFetch(\n user,\n `/api/marketplace/admin/reviews${suffix ? `?${suffix}` : ''}`,\n )\n const payload = await response.json().catch(() => ({}))\n if (response.ok) {\n setLoadError(null)\n setQueue(payload?.queue ?? [])\n setListed(payload?.listed ?? [])\n setVerificationRequests(payload?.verificationRequests ?? [])\n setPublishers(payload?.publishers ?? {})\n setMore({ ...NO_MORE, ...(payload?.more ?? {}) })\n setRefused(payload?.refused ?? [])\n setNotices(payload?.notices ?? [])\n } else {\n // Named, not swallowed. An empty queue and a queue that could not be\n // read look identical, and only one of them means there is no work.\n setLoadError(\n payload?.error ?? `Loading the queue failed (${response.status})`,\n )\n }\n setLoaded(true)\n }, [user, asked, status, limit])\n\n useEffect(() => {\n if (user) void refresh()\n }, [user, refresh])\n\n const publisherName = useCallback(\n (profileId: string) => publishers[profileId] ?? profileId,\n [publishers],\n )\n\n // Every section is already what the query answered: nothing is matched\n // here, only the page on screen sliced out of the window read.\n const pageOf = <T,>(section: ReviewQueueSection, rows: T[]): T[] =>\n rows.slice(pages[section] * QUEUE_PAGE, (pages[section] + 1) * QUEUE_PAGE)\n const visibleQueue = pageOf('queue', queue)\n const visibleListed = pageOf('listed', listed)\n const visibleVerification = pageOf('verification', verificationRequests)\n const total: Record<ReviewQueueSection, number> = {\n queue: queue.length,\n listed: listed.length,\n verification: verificationRequests.length,\n }\n const filtering = asked.length > 0 || status !== 'all'\n const refusals = useMemo(\n () =>\n listQueryRefusals(refused, {\n fields: REVIEW_QUEUE_QUERY.fields,\n headers: QUEUE_HEADERS,\n options: QUEUE_OPTIONS,\n }),\n [refused],\n )\n const pager = (section: ReviewQueueSection, shown: number) => {\n const hasMore =\n total[section] > (pages[section] + 1) * QUEUE_PAGE || more[section]\n return pages[section] > 0 || hasMore ? (\n <ListPagination\n page={pages[section]}\n pageSize={QUEUE_PAGE}\n rowCount={shown}\n hasMore={hasMore}\n onPageChange={(page) =>\n setPages((previous) => ({ ...previous, [section]: page }))\n }\n />\n ) : null\n }\n\n const row = (\n key: string,\n listingId: string,\n name: string,\n profileId: string,\n chips: React.ReactNode,\n caption: string,\n ) => (\n <Stack\n key={key}\n spacing={0.25}\n sx={{\n py: 1,\n borderBottom: 1,\n borderColor: 'divider',\n '&:last-of-type': { borderBottom: 0 },\n }}\n >\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <AppLink href={`${basePath}/${encodeURIComponent(listingId)}`}>\n <Typography variant=\"subtitle2\" component=\"span\">\n {name}\n </Typography>\n </AppLink>\n {chips}\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${publisherName(profileId)} · ${caption}`}\n </Typography>\n </Stack>\n )\n\n return (\n <>\n {/*\n The staff console's own topic, anchored. A `staffPages` entry carries\n one `docsTopic` and no anchor, and the staff console page is long\n enough that landing at the top of it is the AGL-2200 shape.\n */}\n <PageHeaderHelp topic=\"staffConsole\" anchor=\"#plugin-reviews\" />\n {/*\n The staff gate is the shell's: the generic staff route renders every\n plugin page inside `StaffOnly`, which renders nothing while the claim\n resolves and 404s a non-holder. Without it a non-staff visitor was\n told \"No plugin submissions waiting for review\" — the queue fetch\n 403s and the empty result reads as good news rather than a refusal\n (AGL-760).\n */}\n <Stack spacing={3}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <TextField\n size=\"small\"\n placeholder=\"Search plugin names\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n sx={{ minWidth: 320 }}\n />\n <TextField\n size=\"small\"\n select\n value={status}\n onChange={(event) => setStatus(event.target.value)}\n sx={{ minWidth: 180 }}\n >\n {STATUS_FILTERS.map((option) => (\n <MenuItem key={option.value} value={option.value}>\n {option.label}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n\n {loadError ? (\n <Alert severity=\"error\">{loadError}</Alert>\n ) : null}\n <ListQueryNotices refused={refusals} notices={notices} />\n\n {!loaded ? (\n <Stack spacing={2}>\n <Skeleton variant=\"rounded\" height={140} />\n <Skeleton variant=\"rounded\" height={200} />\n </Stack>\n ) : (\n <>\n {/* Its own card, above the review queue (AGL-1217). A\n verification request is a different question from\n \"have these bytes been read\" — it is about the\n publisher, and it is answered by a different action.\n Folded into Awaiting review it would be invisible: a\n listing whose bytes are already approved does not\n appear there at all. Hidden when empty, so it costs\n nothing on the common day. */}\n {visibleVerification.length ? (\n <CardDisplay\n header={`Verification requested (${total.verification}${more.verification ? '+' : ''})`}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#asking-to-be-verified',\n excerpt:\n 'Publishers who asked to be VERIFIED — a claim about who they are, ' +\n 'separate from reviewing any one version.',\n })}\n contentGutterX\n contentGutterY\n >\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mb: 1 }}\n >\n {'These plugins are already live. The publisher has ' +\n 'asked us to vouch for who they are — granting adds ' +\n 'the badge and changes nothing about installability.'}\n </Typography>\n <Stack>\n {visibleVerification.map((entry) =>\n row(\n `verify-${entry.listingId}`,\n entry.listingId,\n entry.displayName,\n entry.profileId,\n <>\n <Chip\n size=\"small\"\n color=\"info\"\n label=\"Verification requested\"\n />\n <Chip\n size=\"small\"\n color={reviewStatusMeaning(entry.reviewStatus).color}\n label={reviewStatusMeaning(entry.reviewStatus).label}\n />\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={`v${entry.version || '—'}`}\n />\n </>,\n 'Awaiting a verification decision',\n ),\n )}\n {pager('verification', visibleVerification.length)}\n </Stack>\n </CardDisplay>\n ) : null}\n <CardDisplay\n header={`Awaiting review (${total.queue}${more.queue ? '+' : ''})`}\n help={pluginDocsHelp('manifestAndEnvs', {\n anchor: '#review--trust-lifecycle',\n excerpt:\n 'Submissions waiting on a staff verdict. Open one to read its manifest, verifier findings and act.',\n })}\n contentGutterX\n contentGutterY\n >\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mb: 1 }}\n >\n {'Submitted and In review are not installable by anyone.'}\n </Typography>\n {visibleQueue.length ? (\n <Stack>\n {visibleQueue.map((entry) =>\n row(\n entry.listingId,\n entry.listingId,\n entry.displayName,\n entry.profileId,\n <>\n <Chip\n size=\"small\"\n color={reviewStatusMeaning(entry.reviewStatus).color}\n label={reviewStatusMeaning(entry.reviewStatus).label}\n />\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={`v${entry.version || '—'}`}\n />\n {/* Same bar, smaller audience (AGL-968/995).\n Worth flagging so a reviewer knows the\n blast radius is one workspace — never so\n they review it more loosely. */}\n {entry.private ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Private\"\n />\n ) : null}\n {/* A private plugin has no marketplace listing\n page, so \"no license\" is not a finding. */}\n {entry.license || entry.private ? null : (\n <Chip\n size=\"small\"\n color=\"warning\"\n label=\"No license\"\n />\n )}\n </>,\n entry.priceUsd > 0 ? `$${entry.priceUsd}` : 'Free',\n ),\n )}\n {pager('queue', visibleQueue.length)}\n </Stack>\n ) : (\n <Alert severity={filtering ? 'info' : 'success'}>\n {filtering\n ? 'No submissions match this filter.'\n : 'No plugin submissions waiting for review.'}\n </Alert>\n )}\n </CardDisplay>\n\n <CardDisplay\n header={`Listed plugins (${total.listed}${more.listed ? '+' : ''})`}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#review-what-happens-after-you-publish',\n excerpt:\n 'Listings already installable. Listing is the step that makes a ' +\n 'version reachable from Browse.',\n })}\n contentGutterX\n contentGutterY\n >\n {/* Listed vs Verified is the distinction reviewers get\n wrong (AGL-966): both are LIVE. Verified only adds a\n badge — it does not change who can install. */}\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mb: 1 }}\n >\n {'Both states are live — installable by every workspace. ' +\n 'Verified additionally carries the reviewed badge on ' +\n 'its listing page; it does not change installability. ' +\n // The sentence above is false for a private row\n // (AGL-968/995), and it sits directly over the rows\n // it would misdescribe.\n 'Rows marked Private are the exception: approved and ' +\n 'live, but only for the org that published them.'}\n </Typography>\n {visibleListed.length ? (\n <Stack>\n {visibleListed.map((entry) =>\n row(\n entry.listingId,\n entry.listingId,\n entry.displayName,\n entry.profileId,\n <>\n <Chip\n size=\"small\"\n color={reviewStatusMeaning(entry.reviewStatus).color}\n label={reviewStatusMeaning(entry.reviewStatus).label}\n />\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label={`v${entry.latestVersion || '—'}`}\n />\n {entry.private ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Private\"\n />\n ) : null}\n {entry.realmVersions ? (\n <Chip\n size=\"small\"\n color=\"success\"\n label={`${entry.realmVersions} realm-trusted`}\n />\n ) : null}\n {entry.hidden ? (\n <Chip\n size=\"small\"\n color=\"error\"\n label=\"Taken down\"\n />\n ) : null}\n </>,\n `${entry.versionCount} version${\n entry.versionCount === 1 ? '' : 's'\n }${entry.hidden && entry.hiddenReason ? ` · ${entry.hiddenReason}` : ''}`,\n ),\n )}\n {pager('listed', visibleListed.length)}\n </Stack>\n ) : (\n <Alert severity=\"info\">\n {filtering\n ? 'No listed plugins match this filter.'\n : 'No listed plugins yet.'}\n </Alert>\n )}\n </CardDisplay>\n </>\n )}\n </Stack>\n </>\n )\n}\n\nexport default PluginReviewsQueue\n"],"names":["PageHeaderHelp","AppLink","CardDisplay","ListPagination","ListQueryNotices","listQueryRefusals","Alert","Chip","MenuItem","Skeleton","Stack","TextField","Typography","useCallback","useEffect","useMemo","useState","useUser","authorizedFetch","pluginDocsHelp","reviewStatusMeaning","REVIEW_QUEUE_QUERY","STATUS_FILTERS","value","label","QUEUE_HEADERS","reviewStatus","takenDown","QUEUE_OPTIONS","filter","option","QUEUE_PAGE","SEARCH_SETTLE_MS","NO_MORE","queue","listed","verification","FIRST_PAGES","PluginReviewsQueue","basePath","data","user","setQueue","setListed","verificationRequests","setVerificationRequests","publishers","setPublishers","loaded","setLoaded","loadError","setLoadError","search","setSearch","asked","setAsked","status","setStatus","pages","setPages","limit","Math","max","Object","values","more","setMore","refused","setRefused","notices","setNotices","timer","setTimeout","trim","clearTimeout","refresh","params","URLSearchParams","set","String","suffix","toString","response","payload","json","catch","ok","error","publisherName","profileId","pageOf","section","rows","slice","visibleQueue","visibleListed","visibleVerification","total","length","filtering","refusals","fields","headers","options","pager","shown","hasMore","page","pageSize","rowCount","onPageChange","previous","row","key","listingId","name","chips","caption","spacing","sx","py","borderBottom","borderColor","direction","alignItems","flexWrap","href","encodeURIComponent","variant","component","color","topic","anchor","size","placeholder","onChange","event","target","minWidth","select","map","severity","height","header","help","excerpt","contentGutterX","contentGutterY","display","mb","entry","displayName","version","private","license","priceUsd","latestVersion","realmVersions","hidden","versionCount","hiddenReason"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ListQueryRefusal } from '@aglyn/shared-ui-jsx/const/list-query-plan';
|
|
2
|
+
export interface ListingNameLookup {
|
|
3
|
+
/**
|
|
4
|
+
* The listings a search names, for a `listingId in [...]` clause; `null`
|
|
5
|
+
* when nothing is searched, so the list is not narrowed at all.
|
|
6
|
+
*/
|
|
7
|
+
ids: string[] | null;
|
|
8
|
+
/** Still asking which listings the word names. */
|
|
9
|
+
pending: boolean;
|
|
10
|
+
/** The search, refused — too many listings share the word. */
|
|
11
|
+
refused: ListQueryRefusal[];
|
|
12
|
+
/** Said about what was searched, e.g. one word of two. */
|
|
13
|
+
notices: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The listings whose NAME holds a typed word (AGL-3321).
|
|
17
|
+
*
|
|
18
|
+
* A license row names its listing by id, and the name lives on the listing,
|
|
19
|
+
* where the publisher can change it. So a license search asks the listings
|
|
20
|
+
* first — `nameTokens array-contains` the word, the key every listing writer
|
|
21
|
+
* stamps — and the license query then asks for those listing ids. Nothing is
|
|
22
|
+
* matched over rows already loaded.
|
|
23
|
+
*
|
|
24
|
+
* Firestore takes at most thirty values in one `in`, so a word naming more
|
|
25
|
+
* than thirty listings is refused by name rather than answered with thirty of
|
|
26
|
+
* them.
|
|
27
|
+
*/
|
|
28
|
+
export declare function useListingNameLookup(words: readonly string[]): ListingNameLookup;
|
|
29
|
+
/**
|
|
30
|
+
* Listing id → display name, for the rows on screen: read BY ID, thirty at a
|
|
31
|
+
* time, so a license always names its listing however large the catalog is.
|
|
32
|
+
* A listing that cannot be read keeps its id as its label.
|
|
33
|
+
*/
|
|
34
|
+
export declare function useListingNames(ids: readonly string[]): Record<string, string>;
|