@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,181 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
19
+ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
20
+ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField, Typography } from "@mui/material";
21
+ import { useCallback, useState } from "react";
22
+ import { useUser } from "@aglyn/tenant-feature-instance";
23
+ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
24
+ /** Mirrors `MAX_REASON_LENGTH` in `server/report.ts`. */ const MAX_REASON_LENGTH = 1000;
25
+ /**
26
+ * "Report" — the only way anything reaches the staff abuse queue (AGL-658).
27
+ *
28
+ * WHY THIS EXISTS. `POST marketplace/report` has been registered, rule-complete
29
+ * and unreachable: it verifies the reporter's ID token, derives a deterministic
30
+ * doc id so one account cannot inflate a queue by reporting twice, resolves the
31
+ * listing name and publisher org, and writes `marketplaceReports/{id}`.
32
+ * `/admin/marketplace-reports` reads that collection and can triage it. Nothing
33
+ * in the product ever POSTed to the route, so the queue could never receive a
34
+ * single report and the whole pipeline was staff-side machinery attached to
35
+ * nothing.
36
+ *
37
+ * Pre-publication review is plugin-only by design — a template is inert until
38
+ * installed — so after-the-fact reporting is the entire safety net for every
39
+ * other artifact type. It was not connected.
40
+ *
41
+ * SIGNED-IN ONLY, and said rather than hidden. The route answers 401 without a
42
+ * bearer token; a button that silently did nothing would teach a reporter that
43
+ * reporting does not work, which is worse than the absence it replaces.
44
+ */ export function ReportTarget(props) {
45
+ const { listingId, reviewUid, label } = props;
46
+ const { data: user } = useUser();
47
+ const { enqueueSnackbar } = useSnackbar();
48
+ const [open, setOpen] = useState(false);
49
+ const [reason, setReason] = useState('');
50
+ const [busy, setBusy] = useState(false);
51
+ const submit = useCallback(async ()=>{
52
+ const trimmed = reason.trim();
53
+ // The server refuses an empty reason with a 400; refusing here as well
54
+ // keeps a reporter from losing what they typed to a round trip.
55
+ if (!trimmed) return;
56
+ setBusy(true);
57
+ try {
58
+ const response = await authorizedFetch(user, '/api/marketplace/report', {
59
+ method: 'POST',
60
+ headers: {
61
+ 'Content-Type': 'application/json'
62
+ },
63
+ body: JSON.stringify(_extends({
64
+ listingId
65
+ }, reviewUid ? {
66
+ reviewUid
67
+ } : {}, {
68
+ reason: trimmed
69
+ }))
70
+ });
71
+ const payload = await response.json().catch(()=>({}));
72
+ if (!response.ok) {
73
+ return void enqueueSnackbar((payload == null ? void 0 : payload.error) || 'Could not send that report', {
74
+ variant: 'error',
75
+ persist: false
76
+ });
77
+ }
78
+ setOpen(false);
79
+ setReason('');
80
+ // Says what happens next. "Reported" alone leaves someone wondering
81
+ // whether anybody will look, and the honest answer — a human reads it —
82
+ // is the reason to file one at all.
83
+ enqueueSnackbar('Thanks — staff will review this report', {
84
+ variant: 'success',
85
+ persist: false
86
+ });
87
+ } catch (unused) {
88
+ enqueueSnackbar('Could not send that report', {
89
+ variant: 'error',
90
+ persist: false
91
+ });
92
+ } finally{
93
+ setBusy(false);
94
+ }
95
+ }, [
96
+ enqueueSnackbar,
97
+ listingId,
98
+ reason,
99
+ reviewUid,
100
+ user
101
+ ]);
102
+ return /*#__PURE__*/ _jsxs(_Fragment, {
103
+ children: [
104
+ /*#__PURE__*/ _jsx(Button, {
105
+ size: "small",
106
+ color: "inherit",
107
+ sx: {
108
+ alignSelf: 'flex-start'
109
+ },
110
+ onClick: ()=>setOpen(true),
111
+ children: 'Report'
112
+ }),
113
+ /*#__PURE__*/ _jsxs(Dialog, {
114
+ open: open,
115
+ onClose: ()=>setOpen(false),
116
+ maxWidth: "xs",
117
+ fullWidth: true,
118
+ children: [
119
+ /*#__PURE__*/ _jsx(DialogTitle, {
120
+ children: `Report ${label}`
121
+ }),
122
+ /*#__PURE__*/ _jsx(DialogContent, {
123
+ dividers: true,
124
+ children: /*#__PURE__*/ _jsxs(Stack, {
125
+ spacing: 1.5,
126
+ children: [
127
+ /*#__PURE__*/ _jsx(Typography, {
128
+ variant: "body2",
129
+ color: "text.secondary",
130
+ children: 'Tell us what is wrong with this ' + (reviewUid ? 'review' : 'listing') + '. A staff member reads every report.'
131
+ }),
132
+ /*#__PURE__*/ _jsx(TextField, {
133
+ autoFocus: true,
134
+ multiline: true,
135
+ minRows: 3,
136
+ size: "small",
137
+ label: "Reason",
138
+ value: reason,
139
+ onChange: (event)=>setReason(event.target.value.slice(0, MAX_REASON_LENGTH)),
140
+ slotProps: {
141
+ htmlInput: {
142
+ maxLength: MAX_REASON_LENGTH
143
+ }
144
+ }
145
+ }),
146
+ user ? null : /*#__PURE__*/ _jsx(Typography, {
147
+ variant: "caption",
148
+ color: "warning.main",
149
+ children: 'Sign in to send a report.'
150
+ }),
151
+ /*#__PURE__*/ _jsx(Typography, {
152
+ variant: "caption",
153
+ color: "text.secondary",
154
+ children: 'Reporting again updates your report rather than adding ' + 'another one.'
155
+ })
156
+ ]
157
+ })
158
+ }),
159
+ /*#__PURE__*/ _jsxs(DialogActions, {
160
+ children: [
161
+ /*#__PURE__*/ _jsx(Button, {
162
+ onClick: ()=>setOpen(false),
163
+ children: 'Cancel'
164
+ }),
165
+ /*#__PURE__*/ _jsx(Button, {
166
+ variant: "contained",
167
+ disabled: busy || !reason.trim() || !user,
168
+ onClick: ()=>void submit(),
169
+ children: 'Send report'
170
+ })
171
+ ]
172
+ })
173
+ ]
174
+ })
175
+ ]
176
+ });
177
+ }
178
+ ReportTarget.displayName = 'ReportTarget';
179
+ export default ReportTarget;
180
+
181
+ //# sourceMappingURL=report-target.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/report-target.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 { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\n\n/** Mirrors `MAX_REASON_LENGTH` in `server/report.ts`. */\nconst MAX_REASON_LENGTH = 1000\n\nexport interface ReportTargetProps {\n listingId: string\n /**\n * Reporting a REVIEW rather than the listing itself. Omitted, the report is\n * filed against the listing — the same fork `report.ts` takes to set\n * `targetType`.\n */\n reviewUid?: string\n /** What the reporter is looking at, for the dialog copy. */\n label: string\n}\n\n/**\n * \"Report\" — the only way anything reaches the staff abuse queue (AGL-658).\n *\n * WHY THIS EXISTS. `POST marketplace/report` has been registered, rule-complete\n * and unreachable: it verifies the reporter's ID token, derives a deterministic\n * doc id so one account cannot inflate a queue by reporting twice, resolves the\n * listing name and publisher org, and writes `marketplaceReports/{id}`.\n * `/admin/marketplace-reports` reads that collection and can triage it. Nothing\n * in the product ever POSTed to the route, so the queue could never receive a\n * single report and the whole pipeline was staff-side machinery attached to\n * nothing.\n *\n * Pre-publication review is plugin-only by design — a template is inert until\n * installed — so after-the-fact reporting is the entire safety net for every\n * other artifact type. It was not connected.\n *\n * SIGNED-IN ONLY, and said rather than hidden. The route answers 401 without a\n * bearer token; a button that silently did nothing would teach a reporter that\n * reporting does not work, which is worse than the absence it replaces.\n */\nexport function ReportTarget(props: ReportTargetProps) {\n const { listingId, reviewUid, label } = props\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [open, setOpen] = useState(false)\n const [reason, setReason] = useState('')\n const [busy, setBusy] = useState(false)\n\n const submit = useCallback(async () => {\n const trimmed = reason.trim()\n // The server refuses an empty reason with a 400; refusing here as well\n // keeps a reporter from losing what they typed to a round trip.\n if (!trimmed) return\n setBusy(true)\n try {\n const response = await authorizedFetch(user, '/api/marketplace/report', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n listingId,\n ...(reviewUid ? { reviewUid } : {}),\n reason: trimmed,\n }),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n return void enqueueSnackbar(\n payload?.error || 'Could not send that report',\n { variant: 'error', persist: false },\n )\n }\n setOpen(false)\n setReason('')\n // Says what happens next. \"Reported\" alone leaves someone wondering\n // whether anybody will look, and the honest answer — a human reads it —\n // is the reason to file one at all.\n enqueueSnackbar('Thanks — staff will review this report', {\n variant: 'success',\n persist: false,\n })\n } catch {\n enqueueSnackbar('Could not send that report', {\n variant: 'error',\n persist: false,\n })\n } finally {\n setBusy(false)\n }\n }, [enqueueSnackbar, listingId, reason, reviewUid, user])\n\n return (\n <>\n <Button\n size=\"small\"\n color=\"inherit\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => setOpen(true)}\n >\n {'Report'}\n </Button>\n <Dialog\n open={open}\n onClose={() => setOpen(false)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{`Report ${label}`}</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Tell us what is wrong with this ' +\n (reviewUid ? 'review' : 'listing') +\n '. A staff member reads every report.'}\n </Typography>\n <TextField\n autoFocus\n multiline\n minRows={3}\n size=\"small\"\n label=\"Reason\"\n value={reason}\n onChange={(event) =>\n setReason(event.target.value.slice(0, MAX_REASON_LENGTH))\n }\n slotProps={{ htmlInput: { maxLength: MAX_REASON_LENGTH } }}\n />\n {/* The route requires a verified token. Saying so beats a 401\n the reporter cannot act on. */}\n {user ? null : (\n <Typography variant=\"caption\" color=\"warning.main\">\n {'Sign in to send a report.'}\n </Typography>\n )}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Reporting again updates your report rather than adding ' +\n 'another one.'}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setOpen(false)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n disabled={busy || !reason.trim() || !user}\n onClick={() => void submit()}\n >\n {'Send report'}\n </Button>\n </DialogActions>\n </Dialog>\n </>\n )\n}\n\nReportTarget.displayName = 'ReportTarget'\n\nexport default ReportTarget\n"],"names":["useSnackbar","Button","Dialog","DialogActions","DialogContent","DialogTitle","Stack","TextField","Typography","useCallback","useState","useUser","authorizedFetch","MAX_REASON_LENGTH","ReportTarget","props","listingId","reviewUid","label","data","user","enqueueSnackbar","open","setOpen","reason","setReason","busy","setBusy","submit","trimmed","trim","response","method","headers","body","JSON","stringify","payload","json","catch","ok","error","variant","persist","size","color","sx","alignSelf","onClick","onClose","maxWidth","fullWidth","dividers","spacing","autoFocus","multiline","minRows","value","onChange","event","target","slice","slotProps","htmlInput","maxLength","disabled","displayName"],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { UninstallTarget } from '../model/marketplace';
2
+ export interface UninstallImpactDialogProps {
3
+ open: boolean;
4
+ listing: {
5
+ $id?: string;
6
+ displayName?: string;
7
+ pluginId?: string;
8
+ } | null;
9
+ /** Sites this uninstall touches — see `resolveUninstallTargets`. */
10
+ targets: readonly UninstallTarget[];
11
+ scope: 'org' | 'host';
12
+ onCancel: () => void;
13
+ onConfirm: () => void | Promise<unknown>;
14
+ }
15
+ /**
16
+ * What an uninstall breaks, before it happens (AGL-1027).
17
+ *
18
+ * Uninstalling was a single click with no confirmation and no statement of
19
+ * consequence — the pin went away and every page placing the plugin stopped
20
+ * rendering it, on live sites, silently. A generic "are you sure?" would not
21
+ * have fixed that: a confirmation that only asks whether you are sure trains
22
+ * people to click through it. This one names what stops working, and scales its
23
+ * friction to the finding:
24
+ *
25
+ * * nothing placed anywhere → a plain confirm, because there is nothing to
26
+ * weigh and adding ceremony to a harmless action is how ceremony stops
27
+ * being read;
28
+ * * published pages affected → a typed acknowledgement, because the number is
29
+ * the point and typing it is the only way to be sure it was seen.
30
+ *
31
+ * It also has to say when uninstalling changes NOTHING visible: removing a host
32
+ * pin while an org pin still covers the site swaps which pointer is in use, and
33
+ * a dialog that implied the plugin was about to stop working would be wrong in
34
+ * the direction that stops people trusting it.
35
+ */
36
+ export declare function UninstallImpactDialog(props: UninstallImpactDialogProps): import("react").JSX.Element;
37
+ export declare namespace UninstallImpactDialog {
38
+ var displayName: string;
39
+ }
40
+ export default UninstallImpactDialog;
@@ -0,0 +1,244 @@
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 { useUser } from "@aglyn/tenant-feature-instance";
19
+ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
20
+ import { Alert, Box, Button, Chip, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField, Typography } from "@mui/material";
21
+ import { useCallback, useEffect, useState } from "react";
22
+ /** The word people expect to type when a confirmation is worth slowing down. */ const ACKNOWLEDGEMENT = 'UNINSTALL';
23
+ const PLACEMENT_NOUNS = {
24
+ screen: 'page',
25
+ layout: 'layout',
26
+ component: 'component'
27
+ };
28
+ /**
29
+ * What an uninstall breaks, before it happens (AGL-1027).
30
+ *
31
+ * Uninstalling was a single click with no confirmation and no statement of
32
+ * consequence — the pin went away and every page placing the plugin stopped
33
+ * rendering it, on live sites, silently. A generic "are you sure?" would not
34
+ * have fixed that: a confirmation that only asks whether you are sure trains
35
+ * people to click through it. This one names what stops working, and scales its
36
+ * friction to the finding:
37
+ *
38
+ * * nothing placed anywhere → a plain confirm, because there is nothing to
39
+ * weigh and adding ceremony to a harmless action is how ceremony stops
40
+ * being read;
41
+ * * published pages affected → a typed acknowledgement, because the number is
42
+ * the point and typing it is the only way to be sure it was seen.
43
+ *
44
+ * It also has to say when uninstalling changes NOTHING visible: removing a host
45
+ * pin while an org pin still covers the site swaps which pointer is in use, and
46
+ * a dialog that implied the plugin was about to stop working would be wrong in
47
+ * the direction that stops people trusting it.
48
+ */ export function UninstallImpactDialog(props) {
49
+ var _ref, _ref1, _ref2, _ref3;
50
+ const { open, listing, targets, scope, onCancel, onConfirm } = props;
51
+ const { data: user } = useUser();
52
+ const [impact, setImpact] = useState(null);
53
+ const [failed, setFailed] = useState(false);
54
+ const [typed, setTyped] = useState('');
55
+ const [busy, setBusy] = useState(false);
56
+ const pluginId = (listing == null ? void 0 : listing.pluginId) ? String(listing.pluginId) : '';
57
+ const losing = targets.filter((target)=>!target.stillCovered);
58
+ const covered = targets.filter((target)=>target.stillCovered);
59
+ const targetKey = targets.map((target)=>target.hostId).join(',');
60
+ useEffect(()=>{
61
+ if (!open) {
62
+ setImpact(null);
63
+ setFailed(false);
64
+ setTyped('');
65
+ return;
66
+ }
67
+ // Nothing to scan: either the plugin has no manifest id to match nodes on,
68
+ // or every site keeps it. Both are answerable without a request.
69
+ if (!pluginId || !losing.length) {
70
+ setImpact(null);
71
+ return;
72
+ }
73
+ let active = true;
74
+ setFailed(false);
75
+ void (async ()=>{
76
+ try {
77
+ const response = await authorizedFetch(user, '/api/hosts/plugin-impact', {
78
+ method: 'POST',
79
+ headers: {
80
+ 'Content-Type': 'application/json'
81
+ },
82
+ body: JSON.stringify({
83
+ pluginId,
84
+ hostIds: targets.map((target)=>target.hostId),
85
+ stillCoveredHostIds: covered.map((target)=>target.hostId)
86
+ })
87
+ });
88
+ const payload = await response.json().catch(()=>null);
89
+ if (!active) return;
90
+ if (!response.ok || !payload) return void setFailed(true);
91
+ setImpact(payload);
92
+ } catch (unused) {
93
+ if (active) setFailed(true);
94
+ }
95
+ })();
96
+ return ()=>{
97
+ active = false;
98
+ };
99
+ // eslint-disable-next-line react-hooks/exhaustive-deps
100
+ }, [
101
+ open,
102
+ pluginId,
103
+ targetKey,
104
+ user
105
+ ]);
106
+ const confirm = useCallback(async ()=>{
107
+ setBusy(true);
108
+ try {
109
+ await onConfirm();
110
+ } finally{
111
+ setBusy(false);
112
+ }
113
+ }, [
114
+ onConfirm
115
+ ]);
116
+ const name = (_ref = listing == null ? void 0 : listing.displayName) != null ? _ref : 'this plugin';
117
+ const scanning = Boolean(pluginId) && losing.length > 0 && !impact && !failed;
118
+ const affected = (_ref1 = impact == null ? void 0 : impact.affectedScreens) != null ? _ref1 : 0;
119
+ /**
120
+ * The scan could not speak, so it must not be read as "nothing breaks".
121
+ * A failed check is a reason for MORE friction, not less — the one thing it
122
+ * rules out is confidence.
123
+ */ const unknown = failed || !pluginId && losing.length > 0;
124
+ const needsAcknowledgement = affected > 0 || unknown;
125
+ const canConfirm = !busy && !scanning && (!needsAcknowledgement || typed.trim().toUpperCase() === ACKNOWLEDGEMENT);
126
+ return /*#__PURE__*/ _jsxs(Dialog, {
127
+ open: open,
128
+ onClose: ()=>!busy && onCancel(),
129
+ maxWidth: "sm",
130
+ fullWidth: true,
131
+ children: [
132
+ /*#__PURE__*/ _jsx(DialogTitle, {
133
+ children: scope === 'org' ? `Uninstall ${name} across the organization?` : `Remove ${name} from this site?`
134
+ }),
135
+ /*#__PURE__*/ _jsx(DialogContent, {
136
+ dividers: true,
137
+ children: /*#__PURE__*/ _jsxs(Stack, {
138
+ spacing: 2,
139
+ children: [
140
+ scanning ? /*#__PURE__*/ _jsxs(Stack, {
141
+ direction: "row",
142
+ spacing: 1,
143
+ sx: {
144
+ alignItems: 'center'
145
+ },
146
+ children: [
147
+ /*#__PURE__*/ _jsx(CircularProgress, {
148
+ size: 16
149
+ }),
150
+ /*#__PURE__*/ _jsx(Typography, {
151
+ variant: "body2",
152
+ color: "text.secondary",
153
+ children: 'Checking what this would break…'
154
+ })
155
+ ]
156
+ }) : null,
157
+ unknown && !scanning ? /*#__PURE__*/ _jsx(Alert, {
158
+ severity: "warning",
159
+ children: pluginId ? 'The impact check could not run, so this cannot say what ' + 'would stop working. Nothing here means "nothing breaks".' : 'This listing records no plugin id, so its placements cannot ' + 'be found. Pages using it may stop rendering it.'
160
+ }) : null,
161
+ !scanning && !unknown && affected > 0 ? /*#__PURE__*/ _jsx(Alert, {
162
+ severity: "error",
163
+ children: `Removes ${name} from ${(_ref2 = impact == null ? void 0 : impact.losingSites) != null ? _ref2 : losing.length} ` + `site${((_ref3 = impact == null ? void 0 : impact.losingSites) != null ? _ref3 : losing.length) === 1 ? '' : 's'}; ` + `${(impact == null ? void 0 : impact.truncated) ? 'at least ' : ''}${affected} published ` + `page${affected === 1 ? '' : 's'} ` + `place${affected === 1 ? 's' : ''} it and will stop ` + 'rendering it.'
164
+ }) : null,
165
+ !scanning && !unknown && affected === 0 && losing.length ? /*#__PURE__*/ _jsx(Alert, {
166
+ severity: "info",
167
+ children: 'No published page places this plugin, so nothing visitors can ' + 'see changes.'
168
+ }) : null,
169
+ covered.length ? // The case that would otherwise read as a lie.
170
+ /*#__PURE__*/ _jsx(Alert, {
171
+ severity: "info",
172
+ children: covered.length === targets.length ? 'The plugin keeps running on ' + (covered.length === 1 ? 'this site' : `all ${covered.length} of these sites`) + ', because another install still covers ' + (covered.length === 1 ? 'it' : 'them') + '. Only which version is in use changes.' : `${covered.length} of these sites keep the plugin — ` + `${covered.map((target)=>target.label).join(', ')} — because another install still covers them.`
173
+ }) : null,
174
+ impact == null ? void 0 : impact.sites.filter((site)=>!site.stillCovered && site.placements.length).map((site)=>/*#__PURE__*/ _jsxs(Box, {
175
+ children: [
176
+ /*#__PURE__*/ _jsx(Typography, {
177
+ variant: "subtitle2",
178
+ children: site.label
179
+ }),
180
+ /*#__PURE__*/ _jsx(Typography, {
181
+ variant: "caption",
182
+ color: "text.secondary",
183
+ children: `${site.affectedScreens} published page` + `${site.affectedScreens === 1 ? '' : 's'} affected`
184
+ }),
185
+ /*#__PURE__*/ _jsxs(Stack, {
186
+ direction: "row",
187
+ spacing: 1,
188
+ sx: {
189
+ flexWrap: 'wrap',
190
+ gap: 1,
191
+ marginTop: 1
192
+ },
193
+ children: [
194
+ site.placements.slice(0, 12).map((placement)=>/*#__PURE__*/ _jsx(Chip, {
195
+ size: "small",
196
+ variant: "outlined",
197
+ label: `${PLACEMENT_NOUNS[placement.type]}: ${placement.name}` + (placement.count > 1 ? ` ×${placement.count}` : '')
198
+ }, `${placement.type}:${placement.id}`)),
199
+ site.placements.length > 12 ? /*#__PURE__*/ _jsx(Chip, {
200
+ size: "small",
201
+ label: `+${site.placements.length - 12} more`
202
+ }) : null
203
+ ]
204
+ })
205
+ ]
206
+ }, site.hostId)),
207
+ !scanning && losing.length ? /*#__PURE__*/ _jsx(Typography, {
208
+ variant: "body2",
209
+ color: "text.secondary",
210
+ children: 'Uninstalling removes the install only. The plugin’s settings ' + 'and any data it stored are kept, and installing it again ' + 'restores the pages that place it.'
211
+ }) : null,
212
+ needsAcknowledgement && !scanning ? /*#__PURE__*/ _jsx(TextField, {
213
+ size: "small",
214
+ label: `Type ${ACKNOWLEDGEMENT} to confirm`,
215
+ value: typed,
216
+ onChange: (event)=>setTyped(event.target.value),
217
+ autoComplete: "off"
218
+ }) : null
219
+ ]
220
+ })
221
+ }),
222
+ /*#__PURE__*/ _jsxs(DialogActions, {
223
+ children: [
224
+ /*#__PURE__*/ _jsx(Button, {
225
+ disabled: busy,
226
+ onClick: onCancel,
227
+ children: 'Cancel'
228
+ }),
229
+ /*#__PURE__*/ _jsx(Button, {
230
+ color: "error",
231
+ variant: "contained",
232
+ disabled: !canConfirm,
233
+ onClick: ()=>void confirm(),
234
+ children: scope === 'org' ? 'Uninstall org-wide' : 'Remove'
235
+ })
236
+ ]
237
+ })
238
+ ]
239
+ });
240
+ }
241
+ UninstallImpactDialog.displayName = 'UninstallImpactDialog';
242
+ export default UninstallImpactDialog;
243
+
244
+ //# sourceMappingURL=uninstall-impact-dialog.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/uninstall-impact-dialog.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 { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport type { UninstallTarget } from '../model/marketplace'\n\n/** Mirrors the `/api/hosts/plugin-impact` response. */\ninterface ImpactSite {\n hostId: string\n label: string\n stillCovered: boolean\n placements: Array<{\n type: 'screen' | 'layout' | 'component'\n id: string\n name: string\n count: number\n }>\n affectedScreens: number\n truncated: boolean\n}\ninterface Impact {\n sites: ImpactSite[]\n losingSites: number\n placements: number\n affectedScreens: number\n truncated: boolean\n dataSurvives: boolean\n}\n\n/** The word people expect to type when a confirmation is worth slowing down. */\nconst ACKNOWLEDGEMENT = 'UNINSTALL'\n\nconst PLACEMENT_NOUNS: Record<ImpactSite['placements'][number]['type'], string> =\n {\n screen: 'page',\n layout: 'layout',\n component: 'component',\n }\n\nexport interface UninstallImpactDialogProps {\n open: boolean\n listing: { $id?: string; displayName?: string; pluginId?: string } | null\n /** Sites this uninstall touches — see `resolveUninstallTargets`. */\n targets: readonly UninstallTarget[]\n scope: 'org' | 'host'\n onCancel: () => void\n onConfirm: () => void | Promise<unknown>\n}\n\n/**\n * What an uninstall breaks, before it happens (AGL-1027).\n *\n * Uninstalling was a single click with no confirmation and no statement of\n * consequence — the pin went away and every page placing the plugin stopped\n * rendering it, on live sites, silently. A generic \"are you sure?\" would not\n * have fixed that: a confirmation that only asks whether you are sure trains\n * people to click through it. This one names what stops working, and scales its\n * friction to the finding:\n *\n * * nothing placed anywhere → a plain confirm, because there is nothing to\n * weigh and adding ceremony to a harmless action is how ceremony stops\n * being read;\n * * published pages affected → a typed acknowledgement, because the number is\n * the point and typing it is the only way to be sure it was seen.\n *\n * It also has to say when uninstalling changes NOTHING visible: removing a host\n * pin while an org pin still covers the site swaps which pointer is in use, and\n * a dialog that implied the plugin was about to stop working would be wrong in\n * the direction that stops people trusting it.\n */\nexport function UninstallImpactDialog(props: UninstallImpactDialogProps) {\n const { open, listing, targets, scope, onCancel, onConfirm } = props\n const { data: user } = useUser()\n const [impact, setImpact] = useState<Impact | null>(null)\n const [failed, setFailed] = useState(false)\n const [typed, setTyped] = useState('')\n const [busy, setBusy] = useState(false)\n\n const pluginId = listing?.pluginId ? String(listing.pluginId) : ''\n const losing = targets.filter((target) => !target.stillCovered)\n const covered = targets.filter((target) => target.stillCovered)\n const targetKey = targets.map((target) => target.hostId).join(',')\n\n useEffect(() => {\n if (!open) {\n setImpact(null)\n setFailed(false)\n setTyped('')\n return\n }\n // Nothing to scan: either the plugin has no manifest id to match nodes on,\n // or every site keeps it. Both are answerable without a request.\n if (!pluginId || !losing.length) {\n setImpact(null)\n return\n }\n let active = true\n setFailed(false)\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n '/api/hosts/plugin-impact',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n pluginId,\n hostIds: targets.map((target) => target.hostId),\n stillCoveredHostIds: covered.map((target) => target.hostId),\n }),\n },\n )\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok || !payload) return void setFailed(true)\n setImpact(payload as Impact)\n } catch {\n if (active) setFailed(true)\n }\n })()\n return () => {\n active = false\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, pluginId, targetKey, user])\n\n const confirm = useCallback(async () => {\n setBusy(true)\n try {\n await onConfirm()\n } finally {\n setBusy(false)\n }\n }, [onConfirm])\n\n const name = listing?.displayName ?? 'this plugin'\n const scanning = Boolean(pluginId) && losing.length > 0 && !impact && !failed\n const affected = impact?.affectedScreens ?? 0\n /**\n * The scan could not speak, so it must not be read as \"nothing breaks\".\n * A failed check is a reason for MORE friction, not less — the one thing it\n * rules out is confidence.\n */\n const unknown = failed || (!pluginId && losing.length > 0)\n const needsAcknowledgement = affected > 0 || unknown\n const canConfirm =\n !busy &&\n !scanning &&\n (!needsAcknowledgement || typed.trim().toUpperCase() === ACKNOWLEDGEMENT)\n\n return (\n <Dialog open={open} onClose={() => !busy && onCancel()} maxWidth=\"sm\" fullWidth>\n <DialogTitle>\n {scope === 'org'\n ? `Uninstall ${name} across the organization?`\n : `Remove ${name} from this site?`}\n </DialogTitle>\n <DialogContent dividers>\n <Stack spacing={2}>\n {scanning ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={16} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Checking what this would break…'}\n </Typography>\n </Stack>\n ) : null}\n\n {unknown && !scanning ? (\n <Alert severity=\"warning\">\n {pluginId\n ? 'The impact check could not run, so this cannot say what ' +\n 'would stop working. Nothing here means \"nothing breaks\".'\n : 'This listing records no plugin id, so its placements cannot ' +\n 'be found. Pages using it may stop rendering it.'}\n </Alert>\n ) : null}\n\n {!scanning && !unknown && affected > 0 ? (\n <Alert severity=\"error\">\n {`Removes ${name} from ${impact?.losingSites ?? losing.length} ` +\n `site${(impact?.losingSites ?? losing.length) === 1 ? '' : 's'}; ` +\n `${impact?.truncated ? 'at least ' : ''}${affected} published ` +\n `page${affected === 1 ? '' : 's'} ` +\n `place${affected === 1 ? 's' : ''} it and will stop ` +\n 'rendering it.'}\n </Alert>\n ) : null}\n\n {!scanning && !unknown && affected === 0 && losing.length ? (\n <Alert severity=\"info\">\n {'No published page places this plugin, so nothing visitors can ' +\n 'see changes.'}\n </Alert>\n ) : null}\n\n {covered.length ? (\n // The case that would otherwise read as a lie.\n <Alert severity=\"info\">\n {covered.length === targets.length\n ? 'The plugin keeps running on ' +\n (covered.length === 1\n ? 'this site'\n : `all ${covered.length} of these sites`) +\n ', because another install still covers ' +\n (covered.length === 1 ? 'it' : 'them') +\n '. Only which version is in use changes.'\n : `${covered.length} of these sites keep the plugin — ` +\n `${covered\n .map((target) => target.label)\n .join(', ')} — because another install still covers them.`}\n </Alert>\n ) : null}\n\n {impact?.sites\n .filter((site) => !site.stillCovered && site.placements.length)\n .map((site) => (\n <Box key={site.hostId}>\n <Typography variant=\"subtitle2\">{site.label}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${site.affectedScreens} published page` +\n `${site.affectedScreens === 1 ? '' : 's'} affected`}\n </Typography>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ flexWrap: 'wrap', gap: 1, marginTop: 1 }}\n >\n {site.placements.slice(0, 12).map((placement) => (\n <Chip\n key={`${placement.type}:${placement.id}`}\n size=\"small\"\n variant=\"outlined\"\n label={\n `${PLACEMENT_NOUNS[placement.type]}: ${placement.name}` +\n (placement.count > 1 ? ` ×${placement.count}` : '')\n }\n />\n ))}\n {site.placements.length > 12 ? (\n <Chip\n size=\"small\"\n label={`+${site.placements.length - 12} more`}\n />\n ) : null}\n </Stack>\n </Box>\n ))}\n\n {!scanning && losing.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Uninstalling removes the install only. The plugin’s settings ' +\n 'and any data it stored are kept, and installing it again ' +\n 'restores the pages that place it.'}\n </Typography>\n ) : null}\n\n {needsAcknowledgement && !scanning ? (\n <TextField\n size=\"small\"\n label={`Type ${ACKNOWLEDGEMENT} to confirm`}\n value={typed}\n onChange={(event) => setTyped(event.target.value)}\n autoComplete=\"off\"\n />\n ) : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button disabled={busy} onClick={onCancel}>\n {'Cancel'}\n </Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n disabled={!canConfirm}\n onClick={() => void confirm()}\n >\n {scope === 'org' ? 'Uninstall org-wide' : 'Remove'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nUninstallImpactDialog.displayName = 'UninstallImpactDialog'\n\nexport default UninstallImpactDialog\n"],"names":["useUser","authorizedFetch","Alert","Box","Button","Chip","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Stack","TextField","Typography","useCallback","useEffect","useState","ACKNOWLEDGEMENT","PLACEMENT_NOUNS","screen","layout","component","UninstallImpactDialog","props","open","listing","targets","scope","onCancel","onConfirm","data","user","impact","setImpact","failed","setFailed","typed","setTyped","busy","setBusy","pluginId","String","losing","filter","target","stillCovered","covered","targetKey","map","hostId","join","length","active","response","method","headers","body","JSON","stringify","hostIds","stillCoveredHostIds","payload","json","catch","ok","confirm","name","displayName","scanning","Boolean","affected","affectedScreens","unknown","needsAcknowledgement","canConfirm","trim","toUpperCase","onClose","maxWidth","fullWidth","dividers","spacing","direction","sx","alignItems","size","variant","color","severity","losingSites","truncated","label","sites","site","placements","flexWrap","gap","marginTop","slice","placement","type","count","id","value","onChange","event","autoComplete","disabled","onClick"],"mappings":"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"}
@@ -0,0 +1,8 @@
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
+ */
8
+ export declare const BUNDLE_ID = "marketplace";
@@ -0,0 +1,9 @@
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
+ */ export const BUNDLE_ID = 'marketplace';
8
+
9
+ //# sourceMappingURL=bundle-common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/constants/bundle-common.ts"],"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 */\n\nexport const BUNDLE_ID = 'marketplace'\n"],"names":["BUNDLE_ID"],"mappings":"AAAA;;;;;;CAMC,GAED,OAAO,MAAMA,YAAY,cAAa"}
@@ -0,0 +1,41 @@
1
+ import type { ArtifactChange } from '../model';
2
+ /** The server's answer to "what would this update do to my copy?" (AGL-1018). */
3
+ export interface ArtifactUpdatePreview {
4
+ artifactType: string;
5
+ installedVersion: string | null;
6
+ availableVersion: string | null;
7
+ mergeable: boolean;
8
+ reason?: string;
9
+ safe: ArtifactChange[];
10
+ kept: ArtifactChange[];
11
+ conflicts: ArtifactChange[];
12
+ unchanged: number;
13
+ identical: boolean;
14
+ schema?: {
15
+ added: string[];
16
+ removed: string[];
17
+ retyped: string[];
18
+ additiveOnly: boolean;
19
+ recordCount: number;
20
+ };
21
+ }
22
+ /**
23
+ * Preview-then-apply for a copied artifact (AGL-1018).
24
+ *
25
+ * Deliberately separate from `useMarketplaceActions`: install writes the
26
+ * publisher's version, and an update reconciles it with a copy that has
27
+ * diverged. Sharing one call would put the destructive path one forgotten
28
+ * argument away from the safe one.
29
+ */
30
+ export declare function useArtifactUpdate(hostId: string): {
31
+ preview: ArtifactUpdatePreview;
32
+ loading: boolean;
33
+ loadPreview: (listingId: string) => Promise<ArtifactUpdatePreview>;
34
+ applyUpdate: (listingId: string, options: {
35
+ mode: "merge" | "copy";
36
+ takePaths?: string[];
37
+ confirmDestructive?: boolean;
38
+ }) => Promise<boolean>;
39
+ setPreview: import("react").Dispatch<import("react").SetStateAction<ArtifactUpdatePreview>>;
40
+ };
41
+ export default useArtifactUpdate;