@aglyn/plugins-marketplace 1.0.0-beta.143

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 (165) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +51 -0
  4. package/src/index.d.ts +19 -0
  5. package/src/index.js +20 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/artifact-update-dialog.component.d.ts +27 -0
  8. package/src/lib/components/artifact-update-dialog.component.js +244 -0
  9. package/src/lib/components/artifact-update-dialog.component.js.map +1 -0
  10. package/src/lib/components/host-plugins-card.component.d.ts +23 -0
  11. package/src/lib/components/host-plugins-card.component.js +566 -0
  12. package/src/lib/components/host-plugins-card.component.js.map +1 -0
  13. package/src/lib/components/listing-content.component.d.ts +57 -0
  14. package/src/lib/components/listing-content.component.js +1880 -0
  15. package/src/lib/components/listing-content.component.js.map +1 -0
  16. package/src/lib/components/listing-image.component.d.ts +46 -0
  17. package/src/lib/components/listing-image.component.js +38 -0
  18. package/src/lib/components/listing-image.component.js.map +1 -0
  19. package/src/lib/components/listing-reviews.component.d.ts +22 -0
  20. package/src/lib/components/listing-reviews.component.js +419 -0
  21. package/src/lib/components/listing-reviews.component.js.map +1 -0
  22. package/src/lib/components/marketplace-browse.component.d.ts +38 -0
  23. package/src/lib/components/marketplace-browse.component.js +802 -0
  24. package/src/lib/components/marketplace-browse.component.js.map +1 -0
  25. package/src/lib/components/plugin-site-set-panel.component.d.ts +24 -0
  26. package/src/lib/components/plugin-site-set-panel.component.js +137 -0
  27. package/src/lib/components/plugin-site-set-panel.component.js.map +1 -0
  28. package/src/lib/components/plugin-site-set.component.d.ts +43 -0
  29. package/src/lib/components/plugin-site-set.component.js +425 -0
  30. package/src/lib/components/plugin-site-set.component.js.map +1 -0
  31. package/src/lib/components/rating-input.component.d.ts +3 -0
  32. package/src/lib/components/rating-input.component.js +47 -0
  33. package/src/lib/components/rating-input.component.js.map +1 -0
  34. package/src/lib/components/report-target.component.d.ts +36 -0
  35. package/src/lib/components/report-target.component.js +181 -0
  36. package/src/lib/components/report-target.component.js.map +1 -0
  37. package/src/lib/components/uninstall-impact-dialog.component.d.ts +40 -0
  38. package/src/lib/components/uninstall-impact-dialog.component.js +244 -0
  39. package/src/lib/components/uninstall-impact-dialog.component.js.map +1 -0
  40. package/src/lib/constants/bundle-common.d.ts +8 -0
  41. package/src/lib/constants/bundle-common.js +9 -0
  42. package/src/lib/constants/bundle-common.js.map +1 -0
  43. package/src/lib/hooks/use-artifact-update.d.ts +41 -0
  44. package/src/lib/hooks/use-artifact-update.js +169 -0
  45. package/src/lib/hooks/use-artifact-update.js.map +1 -0
  46. package/src/lib/hooks/use-marketplace-actions.d.ts +37 -0
  47. package/src/lib/hooks/use-marketplace-actions.js +523 -0
  48. package/src/lib/hooks/use-marketplace-actions.js.map +1 -0
  49. package/src/lib/model/artifact-merge.d.ts +154 -0
  50. package/src/lib/model/artifact-merge.js +228 -0
  51. package/src/lib/model/artifact-merge.js.map +1 -0
  52. package/src/lib/model/index.d.ts +43 -0
  53. package/src/lib/model/index.js +41 -0
  54. package/src/lib/model/index.js.map +1 -0
  55. package/src/lib/model/marketplace-props.d.ts +57 -0
  56. package/src/lib/model/marketplace-props.js +441 -0
  57. package/src/lib/model/marketplace-props.js.map +1 -0
  58. package/src/lib/model/marketplace.d.ts +833 -0
  59. package/src/lib/model/marketplace.js +929 -0
  60. package/src/lib/model/marketplace.js.map +1 -0
  61. package/src/lib/model/rating-field.d.ts +24 -0
  62. package/src/lib/model/rating-field.js +33 -0
  63. package/src/lib/model/rating-field.js.map +1 -0
  64. package/src/lib/plugin.d.ts +33 -0
  65. package/src/lib/plugin.js +86 -0
  66. package/src/lib/plugin.js.map +1 -0
  67. package/src/lib/server/billing-webhook.d.ts +25 -0
  68. package/src/lib/server/billing-webhook.js +1062 -0
  69. package/src/lib/server/billing-webhook.js.map +1 -0
  70. package/src/lib/server/checkout.d.ts +76 -0
  71. package/src/lib/server/checkout.js +477 -0
  72. package/src/lib/server/checkout.js.map +1 -0
  73. package/src/lib/server/connect.d.ts +24 -0
  74. package/src/lib/server/connect.js +205 -0
  75. package/src/lib/server/connect.js.map +1 -0
  76. package/src/lib/server/install-dataset-schema.d.ts +32 -0
  77. package/src/lib/server/install-dataset-schema.js +255 -0
  78. package/src/lib/server/install-dataset-schema.js.map +1 -0
  79. package/src/lib/server/install-email-starter.d.ts +65 -0
  80. package/src/lib/server/install-email-starter.js +279 -0
  81. package/src/lib/server/install-email-starter.js.map +1 -0
  82. package/src/lib/server/install-email-template.d.ts +34 -0
  83. package/src/lib/server/install-email-template.js +252 -0
  84. package/src/lib/server/install-email-template.js.map +1 -0
  85. package/src/lib/server/install-layout.d.ts +29 -0
  86. package/src/lib/server/install-layout.js +323 -0
  87. package/src/lib/server/install-layout.js.map +1 -0
  88. package/src/lib/server/install-pin-counts.d.ts +173 -0
  89. package/src/lib/server/install-pin-counts.js +248 -0
  90. package/src/lib/server/install-pin-counts.js.map +1 -0
  91. package/src/lib/server/install-plugin.d.ts +18 -0
  92. package/src/lib/server/install-plugin.js +405 -0
  93. package/src/lib/server/install-plugin.js.map +1 -0
  94. package/src/lib/server/install-template.d.ts +37 -0
  95. package/src/lib/server/install-template.js +348 -0
  96. package/src/lib/server/install-template.js.map +1 -0
  97. package/src/lib/server/install-theme.d.ts +42 -0
  98. package/src/lib/server/install-theme.js +313 -0
  99. package/src/lib/server/install-theme.js.map +1 -0
  100. package/src/lib/server/install.d.ts +27 -0
  101. package/src/lib/server/install.js +276 -0
  102. package/src/lib/server/install.js.map +1 -0
  103. package/src/lib/server/listing-versions.d.ts +26 -0
  104. package/src/lib/server/listing-versions.js +378 -0
  105. package/src/lib/server/listing-versions.js.map +1 -0
  106. package/src/lib/server/preview-image.d.ts +37 -0
  107. package/src/lib/server/preview-image.js +216 -0
  108. package/src/lib/server/preview-image.js.map +1 -0
  109. package/src/lib/server/provenance.d.ts +100 -0
  110. package/src/lib/server/provenance.js +118 -0
  111. package/src/lib/server/provenance.js.map +1 -0
  112. package/src/lib/server/publish-dataset-schema.d.ts +31 -0
  113. package/src/lib/server/publish-dataset-schema.js +202 -0
  114. package/src/lib/server/publish-dataset-schema.js.map +1 -0
  115. package/src/lib/server/publish-email-starter.d.ts +44 -0
  116. package/src/lib/server/publish-email-starter.js +231 -0
  117. package/src/lib/server/publish-email-starter.js.map +1 -0
  118. package/src/lib/server/publish-email-template.d.ts +33 -0
  119. package/src/lib/server/publish-email-template.js +211 -0
  120. package/src/lib/server/publish-email-template.js.map +1 -0
  121. package/src/lib/server/publish-layout.d.ts +33 -0
  122. package/src/lib/server/publish-layout.js +243 -0
  123. package/src/lib/server/publish-layout.js.map +1 -0
  124. package/src/lib/server/publish-plugin.d.ts +18 -0
  125. package/src/lib/server/publish-plugin.js +485 -0
  126. package/src/lib/server/publish-plugin.js.map +1 -0
  127. package/src/lib/server/publish-preconditions.d.ts +86 -0
  128. package/src/lib/server/publish-preconditions.js +160 -0
  129. package/src/lib/server/publish-preconditions.js.map +1 -0
  130. package/src/lib/server/publish-template.d.ts +27 -0
  131. package/src/lib/server/publish-template.js +215 -0
  132. package/src/lib/server/publish-template.js.map +1 -0
  133. package/src/lib/server/publish-theme.d.ts +38 -0
  134. package/src/lib/server/publish-theme.js +186 -0
  135. package/src/lib/server/publish-theme.js.map +1 -0
  136. package/src/lib/server/publish.d.ts +26 -0
  137. package/src/lib/server/publish.js +204 -0
  138. package/src/lib/server/publish.js.map +1 -0
  139. package/src/lib/server/publisher-profile-save.d.ts +30 -0
  140. package/src/lib/server/publisher-profile-save.js +226 -0
  141. package/src/lib/server/publisher-profile-save.js.map +1 -0
  142. package/src/lib/server/publisher-profile.d.ts +86 -0
  143. package/src/lib/server/publisher-profile.js +116 -0
  144. package/src/lib/server/publisher-profile.js.map +1 -0
  145. package/src/lib/server/purchase-entitlement.d.ts +83 -0
  146. package/src/lib/server/purchase-entitlement.js +118 -0
  147. package/src/lib/server/purchase-entitlement.js.map +1 -0
  148. package/src/lib/server/report.d.ts +32 -0
  149. package/src/lib/server/report.js +101 -0
  150. package/src/lib/server/report.js.map +1 -0
  151. package/src/lib/server/reviews.d.ts +41 -0
  152. package/src/lib/server/reviews.js +240 -0
  153. package/src/lib/server/reviews.js.map +1 -0
  154. package/src/lib/server/update-artifact.d.ts +19 -0
  155. package/src/lib/server/update-artifact.js +569 -0
  156. package/src/lib/server/update-artifact.js.map +1 -0
  157. package/src/lib/server/verification-request.d.ts +31 -0
  158. package/src/lib/server/verification-request.js +173 -0
  159. package/src/lib/server/verification-request.js.map +1 -0
  160. package/src/lib/server/version-stats.d.ts +202 -0
  161. package/src/lib/server/version-stats.js +305 -0
  162. package/src/lib/server/version-stats.js.map +1 -0
  163. package/src/lib/server.d.ts +30 -0
  164. package/src/lib/server.js +95 -0
  165. package/src/lib/server.js.map +1 -0
@@ -0,0 +1,425 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
18
+ import { MdiIcon } from "@aglyn/shared-ui-jsx";
19
+ import { mdiCheckCircle } from "@aglyn/shared-data-mdi";
20
+ import { Alert, Box, Button, Checkbox, Chip, Dialog, DialogActions, DialogContent, DialogTitle, Divider, FormControlLabel, FormGroup, Stack, Typography } from "@mui/material";
21
+ import { useEffect, useMemo, useState } from "react";
22
+ import { compareArtifactVersions } from "@aglyn/aglyn";
23
+ import { resolveUninstallTargets } from "../model/marketplace.js";
24
+ import UninstallImpactDialog from "./uninstall-impact-dialog.component.js";
25
+ /**
26
+ * The set of sites a plugin is installed on, and the controls to change it
27
+ * (AGL-997), extracted from the listing page so the installation detail page
28
+ * (AGL-1007) shows the same thing rather than a second implementation that
29
+ * drifts.
30
+ *
31
+ * An org-scope install is a SET, not a boolean: an org pin covers every site
32
+ * including ones created later, host pins name specific sites, and a host
33
+ * pin on top of an org pin shadows it for that one site. All three facts
34
+ * have to survive into the UI, which is why this takes the whole summary
35
+ * rather than a scope.
36
+ */ export function PluginSiteSet(props) {
37
+ var _ref, _ref1, _ref2, _ref3;
38
+ const { listing, hosts, orgInstall, latestVersion, installPlan, uninstall, onChanged } = props;
39
+ const [addHostIds, setAddHostIds] = useState([]);
40
+ /**
41
+ * The uninstall awaiting confirmation (AGL-1027). Uninstalling used to be a
42
+ * bare click; nothing said which sites lost the plugin or which published
43
+ * pages stopped rendering it.
44
+ */ const [pendingUninstall, setPendingUninstall] = useState(null);
45
+ const [siteBusy, setSiteBusy] = useState(false);
46
+ /**
47
+ * A site is behind only when its pin is genuinely OLDER (AGL-1017). "Not
48
+ * equal" also fires when a site runs something newer than the offer — a
49
+ * publisher testing their own build, or a version withdrawn after install —
50
+ * and offering to "update" those would quietly move them backwards.
51
+ */ const staleSites = useMemo(()=>orgInstall.sites.filter((site)=>{
52
+ var _compareArtifactVersions;
53
+ return ((_compareArtifactVersions = compareArtifactVersions(site.version, latestVersion)) != null ? _compareArtifactVersions : 0) < 0;
54
+ }), [
55
+ orgInstall.sites,
56
+ latestVersion
57
+ ]);
58
+ /**
59
+ * Update is per-site FIRST (AGL-1017). A version with breaking changes has
60
+ * to be absorbable one site at a time — take it on a quiet site, see what
61
+ * breaks, then decide about the rest — so each site behind the offer gets
62
+ * its own control and the bulk action is the convenience on top.
63
+ *
64
+ * An org pin cannot do that: it is one pointer covering every site, so
65
+ * moving it moves all of them at once. Rather than hide that behind a
66
+ * button that looks per-site, the panel says so and points at the split.
67
+ */ const [pending, setPending] = useState(null);
68
+ const [changelog, setChangelog] = useState(null);
69
+ useEffect(()=>{
70
+ if (!pending || !(listing == null ? void 0 : listing.$id) || !latestVersion) return;
71
+ let active = true;
72
+ setChangelog(null);
73
+ void fetch(`/api/marketplace/listing-versions?listingId=${encodeURIComponent(String(listing.$id))}`).then((response)=>response.ok ? response.json() : {
74
+ versions: []
75
+ }).then((payload)=>{
76
+ var _ref, _ref1;
77
+ if (!active) return;
78
+ const entry = ((_ref = payload == null ? void 0 : payload.versions) != null ? _ref : []).find((version)=>String(version == null ? void 0 : version.version) === String(latestVersion));
79
+ setChangelog((_ref1 = entry == null ? void 0 : entry.changelog) != null ? _ref1 : '');
80
+ }).catch(()=>active && setChangelog(''));
81
+ return ()=>{
82
+ active = false;
83
+ };
84
+ }, [
85
+ pending,
86
+ listing == null ? void 0 : listing.$id,
87
+ latestVersion
88
+ ]);
89
+ const runSiteEdit = async (work)=>{
90
+ setSiteBusy(true);
91
+ try {
92
+ await work();
93
+ } finally{
94
+ setSiteBusy(false);
95
+ setAddHostIds([]);
96
+ onChanged();
97
+ }
98
+ };
99
+ if (!orgInstall.installedAnywhere) return null;
100
+ return /*#__PURE__*/ _jsxs(Stack, {
101
+ spacing: 1.5,
102
+ children: [
103
+ /*#__PURE__*/ _jsxs(Stack, {
104
+ direction: "row",
105
+ spacing: 1,
106
+ sx: {
107
+ alignItems: 'flex-start'
108
+ },
109
+ children: [
110
+ /*#__PURE__*/ _jsx(MdiIcon, {
111
+ path: mdiCheckCircle.path,
112
+ color: "success",
113
+ sx: {
114
+ fontSize: 20,
115
+ mt: '2px'
116
+ }
117
+ }),
118
+ /*#__PURE__*/ _jsx(Typography, {
119
+ variant: "body2",
120
+ children: orgInstall.orgWide ? `Installed for the whole organization ` + `(v${orgInstall.orgVersion}) — every site, ` + 'including ones you add later.' : `Installed on ${orgInstall.sites.length} of ` + `${hosts.length} sites.`
121
+ })
122
+ ]
123
+ }),
124
+ /*#__PURE__*/ _jsx(Stack, {
125
+ spacing: 0.5,
126
+ children: orgInstall.sites.map((site)=>{
127
+ var _site_version;
128
+ return /*#__PURE__*/ _jsxs(Stack, {
129
+ direction: "row",
130
+ spacing: 1,
131
+ sx: {
132
+ alignItems: 'center'
133
+ },
134
+ children: [
135
+ /*#__PURE__*/ _jsx(Typography, {
136
+ variant: "body2",
137
+ sx: {
138
+ flex: 1,
139
+ minWidth: 0
140
+ },
141
+ noWrap: true,
142
+ children: site.label
143
+ }),
144
+ /*#__PURE__*/ _jsx(Typography, {
145
+ variant: "caption",
146
+ color: "text.secondary",
147
+ children: `v${(_site_version = site.version) != null ? _site_version : '—'}`
148
+ }),
149
+ site.shadowed ? /*#__PURE__*/ _jsx(Chip, {
150
+ size: "small",
151
+ variant: "outlined",
152
+ label: "Overrides org"
153
+ }) : null,
154
+ site.pinnedBy === 'host' && staleSites.some((stale)=>stale.hostId === site.hostId) ? /*#__PURE__*/ _jsx(Button, {
155
+ size: "small",
156
+ variant: "outlined",
157
+ color: "primary",
158
+ disabled: siteBusy,
159
+ onClick: ()=>setPending({
160
+ hostIds: [
161
+ site.hostId
162
+ ],
163
+ label: site.label
164
+ }),
165
+ children: `Update to v${latestVersion}`
166
+ }) : null,
167
+ site.pinnedBy === 'host' ? /*#__PURE__*/ _jsx(Button, {
168
+ size: "small",
169
+ color: "inherit",
170
+ disabled: siteBusy,
171
+ onClick: ()=>setPendingUninstall({
172
+ scope: 'host',
173
+ hostId: site.hostId,
174
+ targets: resolveUninstallTargets(orgInstall, 'host', site.hostId)
175
+ }),
176
+ children: 'Remove'
177
+ }) : null
178
+ ]
179
+ }, site.hostId);
180
+ })
181
+ }),
182
+ staleSites.length ? /*#__PURE__*/ _jsxs(Stack, {
183
+ spacing: 1,
184
+ children: [
185
+ orgInstall.orgWide ? // Said out loud rather than discovered afterwards (AGL-1017): an
186
+ // org pin is a single pointer, so there is no half of it to move.
187
+ // A workspace that wants to stage a breaking update has to be on
188
+ // per-site pins, and the way there is the split below.
189
+ /*#__PURE__*/ _jsx(Alert, {
190
+ severity: "info",
191
+ variant: "outlined",
192
+ children: 'This is one organization-wide pin, so updating moves every ' + 'site at once. To take a version on one site first, install ' + 'per site instead.'
193
+ }) : null,
194
+ /*#__PURE__*/ _jsx(Box, {
195
+ children: /*#__PURE__*/ _jsx(Button, {
196
+ size: "small",
197
+ variant: "outlined",
198
+ color: "primary",
199
+ disabled: siteBusy,
200
+ onClick: ()=>setPending({
201
+ hostIds: orgInstall.orgWide ? null : staleSites.map((site)=>site.hostId),
202
+ label: orgInstall.orgWide ? 'every site in this organization' : `${staleSites.length} site${staleSites.length === 1 ? '' : 's'}`
203
+ }),
204
+ children: orgInstall.orgWide ? `Update to v${latestVersion}` : `Update all ${staleSites.length} to v${latestVersion}`
205
+ })
206
+ })
207
+ ]
208
+ }) : null,
209
+ /*#__PURE__*/ _jsxs(Dialog, {
210
+ open: Boolean(pending),
211
+ onClose: ()=>setPending(null),
212
+ maxWidth: "sm",
213
+ fullWidth: true,
214
+ children: [
215
+ /*#__PURE__*/ _jsx(DialogTitle, {
216
+ children: `Update to v${latestVersion}?`
217
+ }),
218
+ /*#__PURE__*/ _jsx(DialogContent, {
219
+ dividers: true,
220
+ children: /*#__PURE__*/ _jsxs(Stack, {
221
+ spacing: 1.5,
222
+ children: [
223
+ /*#__PURE__*/ _jsx(Typography, {
224
+ variant: "body2",
225
+ children: `${(_ref = listing == null ? void 0 : listing.displayName) != null ? _ref : 'This plugin'} moves from the version ` + `running now to v${latestVersion} on ${(_ref1 = pending == null ? void 0 : pending.label) != null ? _ref1 : ''}.`
226
+ }),
227
+ (pending == null ? void 0 : pending.hostIds) && pending.hostIds.length === 1 ? /*#__PURE__*/ _jsx(Typography, {
228
+ variant: "body2",
229
+ color: "text.secondary",
230
+ children: 'Other sites stay on the version they are running.'
231
+ }) : null,
232
+ /*#__PURE__*/ _jsxs(Stack, {
233
+ spacing: 0.5,
234
+ children: [
235
+ /*#__PURE__*/ _jsx(Typography, {
236
+ variant: "subtitle2",
237
+ children: "What's new"
238
+ }),
239
+ changelog === null ? /*#__PURE__*/ _jsx(Typography, {
240
+ variant: "body2",
241
+ color: "text.secondary",
242
+ children: 'Loading the changelog…'
243
+ }) : changelog ? /*#__PURE__*/ _jsx(Typography, {
244
+ variant: "body2",
245
+ sx: {
246
+ whiteSpace: 'pre-wrap'
247
+ },
248
+ children: changelog
249
+ }) : /*#__PURE__*/ _jsx(Typography, {
250
+ variant: "body2",
251
+ color: "text.secondary",
252
+ children: 'The publisher did not describe this version.'
253
+ })
254
+ ]
255
+ }),
256
+ /*#__PURE__*/ _jsx(Typography, {
257
+ variant: "caption",
258
+ color: "text.secondary",
259
+ children: 'Pins only move forward — there is no going back to the ' + 'previous version from here.'
260
+ })
261
+ ]
262
+ })
263
+ }),
264
+ /*#__PURE__*/ _jsxs(DialogActions, {
265
+ children: [
266
+ /*#__PURE__*/ _jsx(Button, {
267
+ color: "inherit",
268
+ onClick: ()=>setPending(null),
269
+ children: 'Cancel'
270
+ }),
271
+ /*#__PURE__*/ _jsx(Button, {
272
+ variant: "contained",
273
+ disabled: siteBusy,
274
+ onClick: ()=>{
275
+ const target = pending;
276
+ setPending(null);
277
+ if (!target) return;
278
+ void runSiteEdit(()=>installPlan(listing, target.hostIds ? target.hostIds.map((hostId)=>({
279
+ scope: 'host',
280
+ hostId
281
+ })) : [
282
+ {
283
+ scope: 'org'
284
+ }
285
+ ], {
286
+ intent: 'update',
287
+ version: latestVersion
288
+ }));
289
+ },
290
+ children: 'Update'
291
+ })
292
+ ]
293
+ })
294
+ ]
295
+ }),
296
+ !orgInstall.orgWide && orgInstall.availableHostIds.length ? /*#__PURE__*/ _jsxs(Stack, {
297
+ spacing: 0.5,
298
+ children: [
299
+ /*#__PURE__*/ _jsx(Typography, {
300
+ variant: "caption",
301
+ color: "text.secondary",
302
+ children: 'Add to more sites'
303
+ }),
304
+ /*#__PURE__*/ _jsx(FormGroup, {
305
+ sx: {
306
+ maxHeight: 200,
307
+ overflowY: 'auto'
308
+ },
309
+ children: orgInstall.availableHostIds.map((hostIdOption)=>{
310
+ var _ref;
311
+ var _hosts_find;
312
+ return /*#__PURE__*/ _jsx(FormControlLabel, {
313
+ control: /*#__PURE__*/ _jsx(Checkbox, {
314
+ size: "small",
315
+ checked: addHostIds.includes(hostIdOption),
316
+ onChange: (event)=>setAddHostIds((current)=>event.target.checked ? [
317
+ ...current,
318
+ hostIdOption
319
+ ] : current.filter((id)=>id !== hostIdOption))
320
+ }),
321
+ label: (_ref = (_hosts_find = hosts.find((host)=>host.id === hostIdOption)) == null ? void 0 : _hosts_find.label) != null ? _ref : hostIdOption,
322
+ slotProps: {
323
+ typography: {
324
+ variant: 'body2'
325
+ }
326
+ }
327
+ }, hostIdOption);
328
+ })
329
+ }),
330
+ /*#__PURE__*/ _jsx(Box, {
331
+ children: /*#__PURE__*/ _jsx(Button, {
332
+ size: "small",
333
+ variant: "outlined",
334
+ color: "primary",
335
+ disabled: siteBusy || !addHostIds.length,
336
+ onClick: ()=>void runSiteEdit(()=>installPlan(listing, addHostIds.map((hostIdOption)=>({
337
+ scope: 'host',
338
+ hostId: hostIdOption
339
+ })))),
340
+ children: 'Add to selected'
341
+ })
342
+ })
343
+ ]
344
+ }) : null,
345
+ /*#__PURE__*/ _jsx(Divider, {}),
346
+ orgInstall.orgWide ? /*#__PURE__*/ _jsxs(_Fragment, {
347
+ children: [
348
+ /*#__PURE__*/ _jsx(Button, {
349
+ fullWidth: true,
350
+ size: "small",
351
+ variant: "outlined",
352
+ color: "primary",
353
+ disabled: siteBusy || !hosts.length,
354
+ onClick: ()=>void runSiteEdit(async ()=>{
355
+ // Pin every site BEFORE dropping the org pin, so a failure
356
+ // part-way leaves the plugin installed everywhere it already
357
+ // was rather than nowhere.
358
+ await installPlan(listing, hosts.map((host)=>({
359
+ scope: 'host',
360
+ hostId: host.id
361
+ })));
362
+ await uninstall(listing, 'org');
363
+ }),
364
+ children: 'Split into per-site installs'
365
+ }),
366
+ /*#__PURE__*/ _jsx(Typography, {
367
+ variant: "caption",
368
+ color: "text.secondary",
369
+ children: 'Pins each site separately so you can update them one at a time. ' + 'Sites you add later will not get this plugin automatically.'
370
+ }),
371
+ /*#__PURE__*/ _jsx(Button, {
372
+ fullWidth: true,
373
+ size: "small",
374
+ color: "inherit",
375
+ disabled: siteBusy,
376
+ onClick: ()=>setPendingUninstall({
377
+ scope: 'org',
378
+ targets: resolveUninstallTargets(orgInstall, 'org')
379
+ }),
380
+ children: 'Uninstall org-wide'
381
+ })
382
+ ]
383
+ }) : /*#__PURE__*/ _jsx(Button, {
384
+ fullWidth: true,
385
+ size: "small",
386
+ variant: "outlined",
387
+ color: "primary",
388
+ disabled: siteBusy,
389
+ onClick: ()=>void runSiteEdit(async ()=>{
390
+ // Promote: add the org pin, then drop the
391
+ // per-site pins it makes redundant. In
392
+ // this order, so a failure part-way
393
+ // leaves the plugin installed everywhere
394
+ // it already was rather than nowhere.
395
+ await installPlan(listing, [
396
+ {
397
+ scope: 'org'
398
+ }
399
+ ]);
400
+ for (const hostIdPinned of orgInstall.hostPinnedIds){
401
+ await uninstall(listing, 'host', hostIdPinned);
402
+ }
403
+ }),
404
+ children: 'Install for the whole organization'
405
+ }),
406
+ /*#__PURE__*/ _jsx(UninstallImpactDialog, {
407
+ open: pendingUninstall !== null,
408
+ listing: listing,
409
+ scope: (_ref2 = pendingUninstall == null ? void 0 : pendingUninstall.scope) != null ? _ref2 : 'host',
410
+ targets: (_ref3 = pendingUninstall == null ? void 0 : pendingUninstall.targets) != null ? _ref3 : [],
411
+ onCancel: ()=>setPendingUninstall(null),
412
+ onConfirm: async ()=>{
413
+ const target = pendingUninstall;
414
+ setPendingUninstall(null);
415
+ if (!target) return;
416
+ await runSiteEdit(()=>uninstall(listing, target.scope, target.hostId));
417
+ }
418
+ })
419
+ ]
420
+ });
421
+ }
422
+ PluginSiteSet.displayName = 'PluginSiteSet';
423
+ export default PluginSiteSet;
424
+
425
+ //# sourceMappingURL=plugin-site-set.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/plugin-site-set.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 { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { mdiCheckCircle } from '@aglyn/shared-data-mdi'\nimport {\n Alert,\n Box,\n Button,\n Checkbox,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Divider,\n FormControlLabel,\n FormGroup,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\nimport { compareArtifactVersions } from '@aglyn/aglyn'\nimport {\n resolveUninstallTargets,\n type OrgInstallSummary,\n type UninstallTarget,\n} from '../model/marketplace'\nimport UninstallImpactDialog from './uninstall-impact-dialog.component'\n\nexport interface PluginSiteSetProps {\n /** The listing/installation these pins belong to. */\n listing: any\n /** Every site in the org, in display order. */\n hosts: ReadonlyArray<{ id: string; label: string }>\n /** The resolved picture — see `resolveOrgInstallSummary`. */\n orgInstall: OrgInstallSummary\n /** Version the pins should be at; a site behind this can be updated. */\n latestVersion?: string | number | null\n /** Install the given concrete steps (org pin and/or named host pins). */\n installPlan: (\n listing: any,\n steps: ReadonlyArray<{ scope: 'org' | 'host'; hostId?: string }>,\n options?: { intent?: 'install' | 'update'; version?: string | number | null },\n ) => Promise<unknown>\n /** Drop a pin — org-wide, or from one named site. */\n uninstall: (\n listing: any,\n scope?: 'org' | 'host',\n hostId?: string,\n ) => Promise<unknown>\n /** Re-read the pins after a write; the caller owns that state. */\n onChanged: () => void\n}\n\n/**\n * The set of sites a plugin is installed on, and the controls to change it\n * (AGL-997), extracted from the listing page so the installation detail page\n * (AGL-1007) shows the same thing rather than a second implementation that\n * drifts.\n *\n * An org-scope install is a SET, not a boolean: an org pin covers every site\n * including ones created later, host pins name specific sites, and a host\n * pin on top of an org pin shadows it for that one site. All three facts\n * have to survive into the UI, which is why this takes the whole summary\n * rather than a scope.\n */\nexport function PluginSiteSet(props: PluginSiteSetProps) {\n const {\n listing,\n hosts,\n orgInstall,\n latestVersion,\n installPlan,\n uninstall,\n onChanged,\n } = props\n const [addHostIds, setAddHostIds] = useState<string[]>([])\n /**\n * The uninstall awaiting confirmation (AGL-1027). Uninstalling used to be a\n * bare click; nothing said which sites lost the plugin or which published\n * pages stopped rendering it.\n */\n const [pendingUninstall, setPendingUninstall] = useState<{\n scope: 'org' | 'host'\n hostId?: string\n targets: UninstallTarget[]\n } | null>(null)\n const [siteBusy, setSiteBusy] = useState(false)\n /**\n * A site is behind only when its pin is genuinely OLDER (AGL-1017). \"Not\n * equal\" also fires when a site runs something newer than the offer — a\n * publisher testing their own build, or a version withdrawn after install —\n * and offering to \"update\" those would quietly move them backwards.\n */\n const staleSites = useMemo(\n () =>\n orgInstall.sites.filter(\n (site) => (compareArtifactVersions(site.version, latestVersion) ?? 0) < 0,\n ),\n [orgInstall.sites, latestVersion],\n )\n /**\n * Update is per-site FIRST (AGL-1017). A version with breaking changes has\n * to be absorbable one site at a time — take it on a quiet site, see what\n * breaks, then decide about the rest — so each site behind the offer gets\n * its own control and the bulk action is the convenience on top.\n *\n * An org pin cannot do that: it is one pointer covering every site, so\n * moving it moves all of them at once. Rather than hide that behind a\n * button that looks per-site, the panel says so and points at the split.\n */\n const [pending, setPending] = useState<\n { hostIds: string[] | null; label: string } | null\n >(null)\n const [changelog, setChangelog] = useState<string | null>(null)\n useEffect(() => {\n if (!pending || !listing?.$id || !latestVersion) return\n let active = true\n setChangelog(null)\n void fetch(\n `/api/marketplace/listing-versions?listingId=${encodeURIComponent(\n String(listing.$id),\n )}`,\n )\n .then((response) => (response.ok ? response.json() : { versions: [] }))\n .then((payload) => {\n if (!active) return\n const entry = (payload?.versions ?? []).find(\n (version: { version?: string }) =>\n String(version?.version) === String(latestVersion),\n )\n setChangelog(entry?.changelog ?? '')\n })\n .catch(() => active && setChangelog(''))\n return () => {\n active = false\n }\n }, [pending, listing?.$id, latestVersion])\n const runSiteEdit = async (work: () => Promise<unknown>) => {\n setSiteBusy(true)\n try {\n await work()\n } finally {\n setSiteBusy(false)\n setAddHostIds([])\n onChanged()\n }\n }\n\n if (!orgInstall.installedAnywhere) return null\n\n return (\n <Stack spacing={1.5}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'flex-start' }}\n >\n <MdiIcon\n path={mdiCheckCircle.path}\n color=\"success\"\n sx={{ fontSize: 20, mt: '2px' }}\n />\n <Typography variant=\"body2\">\n {orgInstall.orgWide\n ? `Installed for the whole organization ` +\n `(v${orgInstall.orgVersion}) — every site, ` +\n 'including ones you add later.'\n : `Installed on ${orgInstall.sites.length} of ` +\n `${hosts.length} sites.`}\n </Typography>\n </Stack>\n\n {/* Name them. A count alone still leaves you\n guessing which sites are running the thing. */}\n <Stack spacing={0.5}>\n {orgInstall.sites.map((site) => (\n <Stack\n key={site.hostId}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Typography\n variant=\"body2\"\n sx={{ flex: 1, minWidth: 0 }}\n noWrap\n >\n {site.label}\n </Typography>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {`v${site.version ?? '—'}`}\n </Typography>\n {site.shadowed ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Overrides org\"\n />\n ) : null}\n {/* Removable only where the site owns its\n pin: dropping a site covered by the org\n pin would mean writing an exclusion the\n loader has no concept of. */}\n {/* This site alone (AGL-1017). Only where the site owns its pin —\n a site covered by the org pin has no pointer of its own to\n move, and pretending otherwise would update every site. */}\n {site.pinnedBy === 'host' &&\n staleSites.some((stale) => stale.hostId === site.hostId) ? (\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n disabled={siteBusy}\n onClick={() =>\n setPending({\n hostIds: [site.hostId],\n label: site.label,\n })\n }\n >\n {`Update to v${latestVersion}`}\n </Button>\n ) : null}\n {site.pinnedBy === 'host' ? (\n <Button\n size=\"small\"\n color=\"inherit\"\n disabled={siteBusy}\n onClick={() =>\n setPendingUninstall({\n scope: 'host',\n hostId: site.hostId,\n targets: resolveUninstallTargets(\n orgInstall,\n 'host',\n site.hostId,\n ),\n })\n }\n >\n {'Remove'}\n </Button>\n ) : null}\n </Stack>\n ))}\n </Stack>\n\n {staleSites.length ? (\n <Stack spacing={1}>\n {orgInstall.orgWide ? (\n // Said out loud rather than discovered afterwards (AGL-1017): an\n // org pin is a single pointer, so there is no half of it to move.\n // A workspace that wants to stage a breaking update has to be on\n // per-site pins, and the way there is the split below.\n <Alert severity=\"info\" variant=\"outlined\">\n {'This is one organization-wide pin, so updating moves every ' +\n 'site at once. To take a version on one site first, install ' +\n 'per site instead.'}\n </Alert>\n ) : null}\n <Box>\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n disabled={siteBusy}\n onClick={() =>\n setPending({\n hostIds: orgInstall.orgWide ? null : staleSites.map((site) => site.hostId),\n label: orgInstall.orgWide\n ? 'every site in this organization'\n : `${staleSites.length} site${staleSites.length === 1 ? '' : 's'}`,\n })\n }\n >\n {orgInstall.orgWide\n ? `Update to v${latestVersion}`\n : `Update all ${staleSites.length} to v${latestVersion}`}\n </Button>\n </Box>\n </Stack>\n ) : null}\n\n {/* What changes, before it changes (AGL-1017). Third-party code moving\n in a workspace deserves more than a spinner: the version pair, the\n publisher's changelog, and which sites are about to take it. */}\n <Dialog\n open={Boolean(pending)}\n onClose={() => setPending(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>{`Update to v${latestVersion}?`}</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={1.5}>\n <Typography variant=\"body2\">\n {`${listing?.displayName ?? 'This plugin'} moves from the version ` +\n `running now to v${latestVersion} on ${pending?.label ?? ''}.`}\n </Typography>\n {pending?.hostIds && pending.hostIds.length === 1 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Other sites stay on the version they are running.'}\n </Typography>\n ) : null}\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{\"What's new\"}</Typography>\n {changelog === null ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading the changelog…'}\n </Typography>\n ) : changelog ? (\n <Typography variant=\"body2\" sx={{ whiteSpace: 'pre-wrap' }}>\n {changelog}\n </Typography>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The publisher did not describe this version.'}\n </Typography>\n )}\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Pins only move forward — there is no going back to the ' +\n 'previous version from here.'}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={() => setPending(null)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={siteBusy}\n onClick={() => {\n const target = pending\n setPending(null)\n if (!target) return\n void runSiteEdit(() =>\n installPlan(\n listing,\n target.hostIds\n ? target.hostIds.map((hostId) => ({\n scope: 'host' as const,\n hostId,\n }))\n : [{ scope: 'org' as const }],\n { intent: 'update', version: latestVersion },\n ),\n )\n }}\n >\n {'Update'}\n </Button>\n </DialogActions>\n </Dialog>\n\n {/* Add sites after the fact. The picker used to\n render only when nothing was installed, which\n froze the set at install time. */}\n {!orgInstall.orgWide &&\n orgInstall.availableHostIds.length ? (\n <Stack spacing={0.5}>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {'Add to more sites'}\n </Typography>\n <FormGroup\n sx={{ maxHeight: 200, overflowY: 'auto' }}\n >\n {orgInstall.availableHostIds.map((hostIdOption) => (\n <FormControlLabel\n key={hostIdOption}\n control={\n <Checkbox\n size=\"small\"\n checked={addHostIds.includes(\n hostIdOption,\n )}\n onChange={(event) =>\n setAddHostIds((current) =>\n event.target.checked\n ? [...current, hostIdOption]\n : current.filter(\n (id) => id !== hostIdOption,\n ),\n )\n }\n />\n }\n label={\n hosts.find(\n (host) => host.id === hostIdOption,\n )?.label ?? hostIdOption\n }\n slotProps={{\n typography: { variant: 'body2' },\n }}\n />\n ))}\n </FormGroup>\n <Box>\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n disabled={siteBusy || !addHostIds.length}\n onClick={() =>\n void runSiteEdit(() =>\n installPlan(\n listing,\n addHostIds.map((hostIdOption) => ({\n scope: 'host' as const,\n hostId: hostIdOption,\n })),\n ),\n )\n }\n >\n {'Add to selected'}\n </Button>\n </Box>\n </Stack>\n ) : null}\n\n <Divider />\n\n {/* Change the shape of the install, not just its\n membership: one org pin covers sites made\n later, which no set of host pins can. */}\n {orgInstall.orgWide ? (\n <>\n {/* The inverse of promote (AGL-1017), and the thing that makes\n per-site updates reachable at all: one org pin cannot be moved\n for one site, so a workspace that needs to stage a breaking\n version has to hold per-site pins first. Sites added later are\n no longer covered automatically — that is the trade, and the\n button says so rather than discovering it weeks later. */}\n <Button\n fullWidth\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n disabled={siteBusy || !hosts.length}\n onClick={() =>\n void runSiteEdit(async () => {\n // Pin every site BEFORE dropping the org pin, so a failure\n // part-way leaves the plugin installed everywhere it already\n // was rather than nowhere.\n await installPlan(\n listing,\n hosts.map((host) => ({ scope: 'host' as const, hostId: host.id })),\n )\n await uninstall(listing, 'org')\n })\n }\n >\n {'Split into per-site installs'}\n </Button>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Pins each site separately so you can update them one at a time. ' +\n 'Sites you add later will not get this plugin automatically.'}\n </Typography>\n <Button\n fullWidth\n size=\"small\"\n color=\"inherit\"\n disabled={siteBusy}\n onClick={() =>\n setPendingUninstall({\n scope: 'org',\n targets: resolveUninstallTargets(orgInstall, 'org'),\n })\n }\n >\n {'Uninstall org-wide'}\n </Button>\n </>\n ) : (\n <Button\n fullWidth\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n disabled={siteBusy}\n onClick={() =>\n void runSiteEdit(async () => {\n // Promote: add the org pin, then drop the\n // per-site pins it makes redundant. In\n // this order, so a failure part-way\n // leaves the plugin installed everywhere\n // it already was rather than nowhere.\n await installPlan(listing, [\n { scope: 'org' },\n ])\n for (const hostIdPinned of orgInstall.hostPinnedIds) {\n await uninstall(\n listing,\n 'host',\n hostIdPinned,\n )\n }\n })\n }\n >\n {'Install for the whole organization'}\n </Button>\n )}\n\n {/* Only the two DIRECT uninstalls are confirmed. The promote and split\n flows above also call `uninstall`, but there the removal is one half of\n a move that leaves the plugin installed everywhere it already was —\n confirming \"this will stop working\" would be false. */}\n <UninstallImpactDialog\n open={pendingUninstall !== null}\n listing={listing}\n scope={pendingUninstall?.scope ?? 'host'}\n targets={pendingUninstall?.targets ?? []}\n onCancel={() => setPendingUninstall(null)}\n onConfirm={async () => {\n const target = pendingUninstall\n setPendingUninstall(null)\n if (!target) return\n await runSiteEdit(() =>\n uninstall(listing, target.scope, target.hostId),\n )\n }}\n />\n </Stack>\n )\n}\nPluginSiteSet.displayName = 'PluginSiteSet'\n\nexport default PluginSiteSet\n"],"names":["MdiIcon","mdiCheckCircle","Alert","Box","Button","Checkbox","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Divider","FormControlLabel","FormGroup","Stack","Typography","useEffect","useMemo","useState","compareArtifactVersions","resolveUninstallTargets","UninstallImpactDialog","PluginSiteSet","props","listing","hosts","orgInstall","latestVersion","installPlan","uninstall","onChanged","addHostIds","setAddHostIds","pendingUninstall","setPendingUninstall","siteBusy","setSiteBusy","staleSites","sites","filter","site","version","pending","setPending","changelog","setChangelog","$id","active","fetch","encodeURIComponent","String","then","response","ok","json","versions","payload","entry","find","catch","runSiteEdit","work","installedAnywhere","spacing","direction","sx","alignItems","path","color","fontSize","mt","variant","orgWide","orgVersion","length","map","flex","minWidth","noWrap","label","shadowed","size","pinnedBy","some","stale","hostId","disabled","onClick","hostIds","scope","targets","severity","open","Boolean","onClose","maxWidth","fullWidth","dividers","displayName","whiteSpace","target","intent","availableHostIds","maxHeight","overflowY","hostIdOption","control","checked","includes","onChange","event","current","id","host","slotProps","typography","hostIdPinned","hostPinnedIds","onCancel","onConfirm"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { CustomFieldInputProps } from '@aglyn/aglyn';
2
+ /** Record-editor input for the `rating` custom field type (AGL-434). */
3
+ export default function RatingInput({ value, onChange, disabled, label, }: CustomFieldInputProps): import("react").JSX.Element;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
+ import { MenuItem, TextField } from "@mui/material";
19
+ /** Record-editor input for the `rating` custom field type (AGL-434). */ export default function RatingInput({ value, onChange, disabled, label }) {
20
+ return /*#__PURE__*/ _jsxs(TextField, {
21
+ select: true,
22
+ size: "small",
23
+ label: label != null ? label : 'Rating',
24
+ value: String(value != null ? value : ''),
25
+ disabled: disabled,
26
+ onChange: (event)=>onChange(event.target.value),
27
+ children: [
28
+ /*#__PURE__*/ _jsx(MenuItem, {
29
+ value: "",
30
+ children: '—'
31
+ }),
32
+ [
33
+ 0,
34
+ 1,
35
+ 2,
36
+ 3,
37
+ 4,
38
+ 5
39
+ ].map((rating)=>/*#__PURE__*/ _jsx(MenuItem, {
40
+ value: String(rating),
41
+ children: '★'.repeat(rating) || '☆ (0)'
42
+ }, rating))
43
+ ]
44
+ });
45
+ }
46
+
47
+ //# sourceMappingURL=rating-input.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/rating-input.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 type { CustomFieldInputProps } from '@aglyn/aglyn'\nimport { MenuItem, TextField } from '@mui/material'\n\n/** Record-editor input for the `rating` custom field type (AGL-434). */\nexport default function RatingInput({\n value,\n onChange,\n disabled,\n label,\n}: CustomFieldInputProps) {\n return (\n <TextField\n select\n size=\"small\"\n label={label ?? 'Rating'}\n value={String(value ?? '')}\n disabled={disabled}\n onChange={(event) => onChange(event.target.value)}\n >\n <MenuItem value=\"\">{'—'}</MenuItem>\n {[0, 1, 2, 3, 4, 5].map((rating) => (\n <MenuItem key={rating} value={String(rating)}>\n {'★'.repeat(rating) || '☆ (0)'}\n </MenuItem>\n ))}\n </TextField>\n )\n}\n"],"names":["MenuItem","TextField","RatingInput","value","onChange","disabled","label","select","size","String","event","target","map","rating","repeat"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAGA,SAASA,QAAQ,EAAEC,SAAS,QAAQ,gBAAe;AAEnD,sEAAsE,GACtE,eAAe,SAASC,YAAY,EAClCC,KAAK,EACLC,QAAQ,EACRC,QAAQ,EACRC,KAAK,EACiB;IACtB,qBACE,MAACL;QACCM,MAAM;QACNC,MAAK;QACLF,KAAK,EAAEA,gBAAAA,QAAS;QAChBH,OAAOM,OAAON,gBAAAA,QAAS;QACvBE,UAAUA;QACVD,UAAU,CAACM,QAAUN,SAASM,MAAMC,MAAM,CAACR,KAAK;;0BAEhD,KAACH;gBAASG,OAAM;0BAAI;;YACnB;gBAAC;gBAAG;gBAAG;gBAAG;gBAAG;gBAAG;aAAE,CAACS,GAAG,CAAC,CAACC,uBACvB,KAACb;oBAAsBG,OAAOM,OAAOI;8BAClC,IAAIC,MAAM,CAACD,WAAW;mBADVA;;;AAMvB"}
@@ -0,0 +1,36 @@
1
+ export interface ReportTargetProps {
2
+ listingId: string;
3
+ /**
4
+ * Reporting a REVIEW rather than the listing itself. Omitted, the report is
5
+ * filed against the listing — the same fork `report.ts` takes to set
6
+ * `targetType`.
7
+ */
8
+ reviewUid?: string;
9
+ /** What the reporter is looking at, for the dialog copy. */
10
+ label: string;
11
+ }
12
+ /**
13
+ * "Report" — the only way anything reaches the staff abuse queue (AGL-658).
14
+ *
15
+ * WHY THIS EXISTS. `POST marketplace/report` has been registered, rule-complete
16
+ * and unreachable: it verifies the reporter's ID token, derives a deterministic
17
+ * doc id so one account cannot inflate a queue by reporting twice, resolves the
18
+ * listing name and publisher org, and writes `marketplaceReports/{id}`.
19
+ * `/admin/marketplace-reports` reads that collection and can triage it. Nothing
20
+ * in the product ever POSTed to the route, so the queue could never receive a
21
+ * single report and the whole pipeline was staff-side machinery attached to
22
+ * nothing.
23
+ *
24
+ * Pre-publication review is plugin-only by design — a template is inert until
25
+ * installed — so after-the-fact reporting is the entire safety net for every
26
+ * other artifact type. It was not connected.
27
+ *
28
+ * SIGNED-IN ONLY, and said rather than hidden. The route answers 401 without a
29
+ * bearer token; a button that silently did nothing would teach a reporter that
30
+ * reporting does not work, which is worse than the absence it replaces.
31
+ */
32
+ export declare function ReportTarget(props: ReportTargetProps): import("react").JSX.Element;
33
+ export declare namespace ReportTarget {
34
+ var displayName: string;
35
+ }
36
+ export default ReportTarget;