@aglyn/plugins-marketplace 1.0.0-beta.291 → 1.0.0-beta.293

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 (95) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/listing-detail-editor.component.js +32 -10
  3. package/src/lib/components/listing-detail-editor.component.js.map +1 -1
  4. package/src/lib/components/listing-verification-request.component.js +32 -11
  5. package/src/lib/components/listing-verification-request.component.js.map +1 -1
  6. package/src/lib/components/org-seller-panel.component.js +121 -143
  7. package/src/lib/components/org-seller-panel.component.js.map +1 -1
  8. package/src/lib/components/publish-artifact-dialog.component.js +115 -115
  9. package/src/lib/components/publish-artifact-dialog.component.js.map +1 -1
  10. package/src/lib/components/publish-plugin-form.component.js +32 -36
  11. package/src/lib/components/publish-plugin-form.component.js.map +1 -1
  12. package/src/lib/components/publisher-agreement-checkbox.component.d.ts +56 -0
  13. package/src/lib/components/publisher-agreement-checkbox.component.js +121 -0
  14. package/src/lib/components/publisher-agreement-checkbox.component.js.map +1 -0
  15. package/src/lib/components/publisher-agreement-confirm-dialog.component.d.ts +45 -0
  16. package/src/lib/components/publisher-agreement-confirm-dialog.component.js +109 -0
  17. package/src/lib/components/publisher-agreement-confirm-dialog.component.js.map +1 -0
  18. package/src/lib/components/publisher-agreement-summary.component.d.ts +4 -3
  19. package/src/lib/components/publisher-agreement-summary.component.js +4 -3
  20. package/src/lib/components/publisher-agreement-summary.component.js.map +1 -1
  21. package/src/lib/components/starter-picker-shelf.component.d.ts +29 -0
  22. package/src/lib/components/starter-picker-shelf.component.js +317 -0
  23. package/src/lib/components/starter-picker-shelf.component.js.map +1 -0
  24. package/src/lib/declarations.server.js +2 -1
  25. package/src/lib/declarations.server.js.map +1 -1
  26. package/src/lib/hooks/use-starter-purchase.d.ts +31 -0
  27. package/src/lib/hooks/use-starter-purchase.js +101 -0
  28. package/src/lib/hooks/use-starter-purchase.js.map +1 -0
  29. package/src/lib/hooks/use-starter-search.d.ts +42 -0
  30. package/src/lib/hooks/use-starter-search.js +137 -0
  31. package/src/lib/hooks/use-starter-search.js.map +1 -0
  32. package/src/lib/model/listing-visibility.d.ts +17 -9
  33. package/src/lib/model/listing-visibility.js +18 -32
  34. package/src/lib/model/listing-visibility.js.map +1 -1
  35. package/src/lib/model/publisher-agreement.d.ts +105 -8
  36. package/src/lib/model/publisher-agreement.js +131 -24
  37. package/src/lib/model/publisher-agreement.js.map +1 -1
  38. package/src/lib/model/site-starter-listing.js +4 -1
  39. package/src/lib/model/site-starter-listing.js.map +1 -1
  40. package/src/lib/model/starter-query.d.ts +159 -0
  41. package/src/lib/model/starter-query.js +267 -0
  42. package/src/lib/model/starter-query.js.map +1 -0
  43. package/src/lib/plugin.js +12 -0
  44. package/src/lib/plugin.js.map +1 -1
  45. package/src/lib/server/checkout.js +45 -12
  46. package/src/lib/server/checkout.js.map +1 -1
  47. package/src/lib/server/preview-image.js +38 -8
  48. package/src/lib/server/preview-image.js.map +1 -1
  49. package/src/lib/server/publish-dataset-schema.js +27 -2
  50. package/src/lib/server/publish-dataset-schema.js.map +1 -1
  51. package/src/lib/server/publish-email-starter.js +27 -2
  52. package/src/lib/server/publish-email-starter.js.map +1 -1
  53. package/src/lib/server/publish-email-template.js +27 -2
  54. package/src/lib/server/publish-email-template.js.map +1 -1
  55. package/src/lib/server/publish-layout.js +27 -2
  56. package/src/lib/server/publish-layout.js.map +1 -1
  57. package/src/lib/server/publish-plugin.js +120 -14
  58. package/src/lib/server/publish-plugin.js.map +1 -1
  59. package/src/lib/server/publish-preconditions.d.ts +13 -9
  60. package/src/lib/server/publish-preconditions.js +19 -45
  61. package/src/lib/server/publish-preconditions.js.map +1 -1
  62. package/src/lib/server/publish-starter.js +27 -2
  63. package/src/lib/server/publish-starter.js.map +1 -1
  64. package/src/lib/server/publish-template.js +27 -2
  65. package/src/lib/server/publish-template.js.map +1 -1
  66. package/src/lib/server/publish-theme.js +27 -2
  67. package/src/lib/server/publish-theme.js.map +1 -1
  68. package/src/lib/server/publish.js +27 -2
  69. package/src/lib/server/publish.js.map +1 -1
  70. package/src/lib/server/publisher-agreement-acceptance.d.ts +49 -0
  71. package/src/lib/server/publisher-agreement-acceptance.js +97 -0
  72. package/src/lib/server/publisher-agreement-acceptance.js.map +1 -0
  73. package/src/lib/server/publisher-profile-save.js +46 -97
  74. package/src/lib/server/publisher-profile-save.js.map +1 -1
  75. package/src/lib/server/site-starter-source.js +97 -25
  76. package/src/lib/server/site-starter-source.js.map +1 -1
  77. package/src/lib/server/starters.d.ts +31 -0
  78. package/src/lib/server/starters.js +170 -0
  79. package/src/lib/server/starters.js.map +1 -0
  80. package/src/lib/server/verification-request.js +33 -8
  81. package/src/lib/server/verification-request.js.map +1 -1
  82. package/src/lib/server.js +14 -0
  83. package/src/lib/server.js.map +1 -1
  84. package/src/lib/components/publisher-agreement-client.d.ts +0 -48
  85. package/src/lib/components/publisher-agreement-client.js +0 -93
  86. package/src/lib/components/publisher-agreement-client.js.map +0 -1
  87. package/src/lib/components/publisher-agreement-dialog.component.d.ts +0 -47
  88. package/src/lib/components/publisher-agreement-dialog.component.js +0 -222
  89. package/src/lib/components/publisher-agreement-dialog.component.js.map +0 -1
  90. package/src/lib/components/use-publisher-agreement-gate.d.ts +0 -58
  91. package/src/lib/components/use-publisher-agreement-gate.js +0 -166
  92. package/src/lib/components/use-publisher-agreement-gate.js.map +0 -1
  93. package/src/lib/model/publisher-agreement-refusal.d.ts +0 -46
  94. package/src/lib/model/publisher-agreement-refusal.js +0 -41
  95. package/src/lib/model/publisher-agreement-refusal.js.map +0 -1
@@ -22,7 +22,7 @@ import { useUser } from "@aglyn/tenant-feature-instance";
22
22
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
23
23
  import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField, Typography } from "@mui/material";
24
24
  import { useCallback, useEffect, useMemo, useState } from "react";
25
- import usePublisherAgreementGate from "./use-publisher-agreement-gate.js";
25
+ import { PublisherAgreementCheckbox, usePublisherAgreementConsent } from "./publisher-agreement-checkbox.component.js";
26
26
  /**
27
27
  * WHERE EACH KIND GOES, which is the knowledge that used to sit in the app
28
28
  * (AGL-3080).
@@ -133,7 +133,7 @@ import usePublisherAgreementGate from "./use-publisher-agreement-gate.js";
133
133
  * with no widget on that zone leaves the offer out entirely, which is why
134
134
  * this may safely draw nothing for a kind it does not sell.
135
135
  */ export function PublishArtifactDialog({ artifact, onClose }) {
136
- var _ref, _ref1, _ref2, _ref3, _marketplacePriceCostNote;
136
+ var _ref, _ref1, _ref2, _marketplacePriceCostNote;
137
137
  // Memoized because it is a dependency of the publish callback: rebuilt
138
138
  // every render, it would rebuild that callback every render too.
139
139
  const target = useMemo(()=>artifact ? publishRequestFor(artifact) : null, [
@@ -146,13 +146,10 @@ import usePublisherAgreementGate from "./use-publisher-agreement-gate.js";
146
146
  const [category, setCategory] = useState('');
147
147
  const [price, setPrice] = useState('');
148
148
  const [busy, setBusy] = useState(false);
149
- // The publisher agreement (AGL-3407): offered as the dialog opens when the
150
- // org's acceptance is missing or stale, and presented again over the form
151
- // if the publish is refused for it — accepting sends the same publish.
152
- const { send: sendGated, dialog: agreementDialog } = usePublisherAgreementGate({
153
- orgId: (_ref = artifact == null ? void 0 : artifact.orgId) != null ? _ref : null,
154
- offer: Boolean(target)
155
- });
149
+ // The publisher agreement, agreed to on this publish (AGL-4105): the box
150
+ // above the button, unticked for every artifact the dialog opens on.
151
+ const agreement = usePublisherAgreementConsent();
152
+ const { reset: resetAgreement } = agreement;
156
153
  useEffect(()=>{
157
154
  var _artifact_displayName, _artifact_description;
158
155
  if (!artifact) return;
@@ -160,31 +157,29 @@ import usePublisherAgreementGate from "./use-publisher-agreement-gate.js";
160
157
  setDescription((_artifact_description = artifact.description) != null ? _artifact_description : '');
161
158
  setCategory('');
162
159
  setPrice('');
160
+ resetAgreement();
163
161
  }, [
164
- artifact
162
+ artifact,
163
+ resetAgreement
165
164
  ]);
166
165
  const handlePublish = useCallback(async ()=>{
167
- if (!target || !name.trim() || busy) return;
166
+ if (!target || !name.trim() || busy || !agreement.consent) return;
168
167
  setBusy(true);
169
168
  try {
170
- // Serialized once, so a retry after accepting the agreement sends the
171
- // very bytes the first attempt did.
172
- const body = JSON.stringify(_extends({}, target.payload, {
173
- displayName: name.trim(),
174
- description: description.trim(),
175
- category: category.trim(),
176
- priceUsd: Number(price) || 0
177
- }));
178
- const { response, payload, declined } = await sendGated(()=>authorizedFetch(user, `/api/${target.endpoint}`, {
179
- method: 'POST',
180
- headers: {
181
- 'Content-Type': 'application/json'
182
- },
183
- body
184
- }));
185
- // The agreement was shown and closed; it said why, and the form stays
186
- // as it was for when they are ready.
187
- if (declined) return;
169
+ const response = await authorizedFetch(user, `/api/${target.endpoint}`, {
170
+ method: 'POST',
171
+ headers: {
172
+ 'Content-Type': 'application/json'
173
+ },
174
+ body: JSON.stringify(_extends({}, target.payload, {
175
+ displayName: name.trim(),
176
+ description: description.trim(),
177
+ category: category.trim(),
178
+ priceUsd: Number(price) || 0,
179
+ agreement: agreement.consent
180
+ }))
181
+ });
182
+ const payload = await response.json().catch(()=>({}));
188
183
  if (!response.ok) {
189
184
  var _ref;
190
185
  // 412 means a setup step is missing (publisher profile, payouts) —
@@ -198,6 +193,8 @@ import usePublisherAgreementGate from "./use-publisher-agreement-gate.js";
198
193
  variant: 'success',
199
194
  persist: false
200
195
  });
196
+ // Each publish is its own agreement: the next one ticks the box again.
197
+ resetAgreement();
201
198
  onClose();
202
199
  } catch (error) {
203
200
  console.error(error);
@@ -218,7 +215,8 @@ import usePublisherAgreementGate from "./use-publisher-agreement-gate.js";
218
215
  user,
219
216
  enqueueSnackbar,
220
217
  onClose,
221
- sendGated
218
+ agreement.consent,
219
+ resetAgreement
222
220
  ]);
223
221
  if (artifact && !target) {
224
222
  return /*#__PURE__*/ _jsxs(Dialog, {
@@ -247,99 +245,101 @@ import usePublisherAgreementGate from "./use-publisher-agreement-gate.js";
247
245
  ]
248
246
  });
249
247
  }
250
- return /*#__PURE__*/ _jsxs(_Fragment, {
251
- children: [
252
- /*#__PURE__*/ _jsxs(Dialog, {
253
- open: !!target,
254
- onClose: busy ? undefined : onClose,
255
- maxWidth: "xs",
256
- fullWidth: true,
257
- children: [
258
- /*#__PURE__*/ _jsx(DialogTitle, {
259
- children: `Publish ${(_ref1 = target == null ? void 0 : target.noun) != null ? _ref1 : 'artifact'}`
260
- }),
261
- /*#__PURE__*/ _jsx(DialogContent, {
262
- children: /*#__PURE__*/ _jsxs(Stack, {
263
- spacing: 2,
264
- sx: {
265
- pt: 1
266
- },
267
- children: [
268
- /*#__PURE__*/ _jsx(Typography, {
269
- variant: "body2",
270
- color: "text.secondary",
271
- children: `Publishes the current published version of this ` + `${(_ref2 = target == null ? void 0 : target.noun) != null ? _ref2 : 'artifact'} so other organizations can ` + 'install it. Your site is unaffected.'
272
- }),
273
- /*#__PURE__*/ _jsx(TextField, {
274
- autoFocus: true,
275
- size: "small",
276
- label: "Listing name",
277
- value: name,
278
- onChange: (event)=>setName(event.target.value),
279
- disabled: busy,
280
- fullWidth: true
281
- }),
282
- /*#__PURE__*/ _jsx(TextField, {
283
- size: "small",
284
- label: "Description",
285
- value: description,
286
- onChange: (event)=>setDescription(event.target.value),
287
- disabled: busy,
288
- multiline: true,
289
- minRows: 2,
290
- fullWidth: true
291
- }),
292
- /*#__PURE__*/ _jsx(TextField, {
293
- size: "small",
294
- label: "Category",
295
- placeholder: (_ref3 = target == null ? void 0 : target.categoryPlaceholder) != null ? _ref3 : 'e.g. Marketing, Docs',
296
- value: category,
297
- onChange: (event)=>setCategory(event.target.value),
298
- disabled: busy,
299
- fullWidth: true
300
- }),
301
- /*#__PURE__*/ _jsx(TextField, {
302
- size: "small",
303
- label: "Price (USD)",
304
- placeholder: "0 = free",
305
- // The minimum price (AGL-2343). Marketplace checkout is a
306
- // destination charge, so Stripe's fee comes out of the PLATFORM's
307
- // balance while the seller is transferred a fixed share — at $1 the
308
- // fee is larger than the whole platform cut. The publish route
309
- // refuses anything under the floor, so the field says so before the
310
- // publisher gets there and the button holds.
311
- error: isBelowMarketplacePriceFloor(price),
312
- helperText: (_marketplacePriceCostNote = marketplacePriceCostNote(price)) != null ? _marketplacePriceCostNote : marketplacePriceFloorHint('Paid listings need payouts set up on your marketplace profile.'),
313
- value: price,
314
- onChange: (event)=>setPrice(event.target.value.replace(/[^0-9]/g, '')),
315
- disabled: busy,
316
- fullWidth: true
317
- })
318
- ]
319
- })
320
- }),
321
- /*#__PURE__*/ _jsxs(DialogActions, {
248
+ return /*#__PURE__*/ _jsx(_Fragment, {
249
+ children: /*#__PURE__*/ _jsxs(Dialog, {
250
+ open: !!target,
251
+ onClose: busy ? undefined : onClose,
252
+ maxWidth: "xs",
253
+ fullWidth: true,
254
+ children: [
255
+ /*#__PURE__*/ _jsx(DialogTitle, {
256
+ children: `Publish ${(_ref = target == null ? void 0 : target.noun) != null ? _ref : 'artifact'}`
257
+ }),
258
+ /*#__PURE__*/ _jsx(DialogContent, {
259
+ children: /*#__PURE__*/ _jsxs(Stack, {
260
+ spacing: 2,
261
+ sx: {
262
+ pt: 1
263
+ },
322
264
  children: [
323
- /*#__PURE__*/ _jsx(Button, {
265
+ /*#__PURE__*/ _jsx(Typography, {
266
+ variant: "body2",
267
+ color: "text.secondary",
268
+ children: `Publishes the current published version of this ` + `${(_ref1 = target == null ? void 0 : target.noun) != null ? _ref1 : 'artifact'} so other organizations can ` + 'install it. Your site is unaffected.'
269
+ }),
270
+ /*#__PURE__*/ _jsx(TextField, {
271
+ autoFocus: true,
324
272
  size: "small",
325
- onClick: onClose,
273
+ label: "Listing name",
274
+ value: name,
275
+ onChange: (event)=>setName(event.target.value),
326
276
  disabled: busy,
327
- children: 'Cancel'
277
+ fullWidth: true
328
278
  }),
329
- /*#__PURE__*/ _jsx(Button, {
279
+ /*#__PURE__*/ _jsx(TextField, {
330
280
  size: "small",
331
- variant: "contained",
332
- color: "primary",
333
- disabled: busy || !name.trim() || isBelowMarketplacePriceFloor(price),
334
- onClick: ()=>void handlePublish(),
335
- children: busy ? 'Publishing…' : 'Publish'
281
+ label: "Description",
282
+ value: description,
283
+ onChange: (event)=>setDescription(event.target.value),
284
+ disabled: busy,
285
+ multiline: true,
286
+ minRows: 2,
287
+ fullWidth: true
288
+ }),
289
+ /*#__PURE__*/ _jsx(TextField, {
290
+ size: "small",
291
+ label: "Category",
292
+ placeholder: (_ref2 = target == null ? void 0 : target.categoryPlaceholder) != null ? _ref2 : 'e.g. Marketing, Docs',
293
+ value: category,
294
+ onChange: (event)=>setCategory(event.target.value),
295
+ disabled: busy,
296
+ fullWidth: true
297
+ }),
298
+ /*#__PURE__*/ _jsx(TextField, {
299
+ size: "small",
300
+ label: "Price (USD)",
301
+ placeholder: "0 = free",
302
+ // The minimum price (AGL-2343). Marketplace checkout is a
303
+ // destination charge, so Stripe's fee comes out of the PLATFORM's
304
+ // balance while the seller is transferred a fixed share — at $1 the
305
+ // fee is larger than the whole platform cut. The publish route
306
+ // refuses anything under the floor, so the field says so before the
307
+ // publisher gets there and the button holds.
308
+ error: isBelowMarketplacePriceFloor(price),
309
+ helperText: (_marketplacePriceCostNote = marketplacePriceCostNote(price)) != null ? _marketplacePriceCostNote : marketplacePriceFloorHint('Paid listings need payouts set up on your marketplace profile.'),
310
+ value: price,
311
+ onChange: (event)=>setPrice(event.target.value.replace(/[^0-9]/g, '')),
312
+ disabled: busy,
313
+ fullWidth: true
314
+ }),
315
+ /*#__PURE__*/ _jsx(PublisherAgreementCheckbox, {
316
+ checked: agreement.agreed,
317
+ onChange: agreement.setAgreed,
318
+ disabled: busy
336
319
  })
337
320
  ]
338
321
  })
339
- ]
340
- }),
341
- agreementDialog
342
- ]
322
+ }),
323
+ /*#__PURE__*/ _jsxs(DialogActions, {
324
+ children: [
325
+ /*#__PURE__*/ _jsx(Button, {
326
+ size: "small",
327
+ onClick: onClose,
328
+ disabled: busy,
329
+ children: 'Cancel'
330
+ }),
331
+ /*#__PURE__*/ _jsx(Button, {
332
+ size: "small",
333
+ variant: "contained",
334
+ color: "primary",
335
+ disabled: busy || !name.trim() || isBelowMarketplacePriceFloor(price) || !agreement.agreed,
336
+ onClick: ()=>void handlePublish(),
337
+ children: busy ? 'Publishing…' : 'Publish'
338
+ })
339
+ ]
340
+ })
341
+ ]
342
+ })
343
343
  });
344
344
  }
345
345
  PublishArtifactDialog.displayName = 'PublishArtifactDialog';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/publish-artifact-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 {\n isBelowMarketplacePriceFloor,\n marketplacePriceCostNote,\n marketplacePriceFloorHint,\n} from '../model/listing-price-floor'\nimport type {\n ConsoleArtifactPublishZoneProps,\n ConsolePublishableArtifact,\n} from '@aglyn/aglyn'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport usePublisherAgreementGate from './use-publisher-agreement-gate'\n\n/** One kind's publish route, and the words a person reads about it. */\ninterface PublishRequest {\n /** Plugin API path, e.g. `marketplace/publish-layout`. */\n endpoint: string\n /** Extra body fields identifying what is being published. */\n payload: Record<string, unknown>\n /** Noun used in copy: \"layout\", \"component\", … */\n noun: string\n categoryPlaceholder?: string\n}\n\n/**\n * WHERE EACH KIND GOES, which is the knowledge that used to sit in the app\n * (AGL-3080).\n *\n * A console page hands over what it has — a kind, the scope holding it, the\n * document — and this is the only place that turns it into a request. The\n * pages that offer publishing named the endpoint, the payload key and the\n * noun themselves, which made them files that could not compile without a\n * marketplace and could not be right without being kept in step with one.\n *\n * ⚠️ A KIND THIS DOES NOT KNOW IS SAID OUT LOUD, not drawn as nothing. It\n * means a console offering to publish something this marketplace does not\n * sell — the two lists have gone out of step — and the person has already\n * clicked. Drawing nothing would make the control look broken with no way to\n * tell what happened, and guessing an endpoint from the kind would post to a\n * route that does not exist and report the 404 to the publisher.\n *\n * The control itself is a separate matter: a page only offers the publish\n * when this zone has a widget at all, so a workspace with no marketplace\n * never gets here.\n */\nfunction publishRequestFor(\n artifact: ConsolePublishableArtifact,\n): PublishRequest | null {\n const hostId = artifact.hostId ?? ''\n const orgId = artifact.orgId ?? ''\n const artifactId = artifact.artifactId ?? ''\n switch (artifact.kind) {\n case 'layout':\n return {\n endpoint: 'marketplace/publish-layout',\n payload: { hostId, layoutId: artifactId },\n noun: 'layout',\n categoryPlaceholder: 'e.g. Marketing, Docs, Storefront',\n }\n case 'component':\n return {\n endpoint: 'marketplace/publish',\n payload: { hostId, componentId: artifactId },\n noun: 'component',\n categoryPlaceholder: 'e.g. Hero, Footer, Pricing',\n }\n case 'site':\n return {\n endpoint: 'marketplace/publish-template',\n payload: { hostId },\n noun: 'site template',\n categoryPlaceholder: 'e.g. Portfolio, Restaurant',\n }\n case 'theme':\n return {\n endpoint: 'marketplace/publish-theme',\n payload: { hostId },\n noun: 'theme',\n }\n // Datasets are org-scoped, so this route takes orgId rather than hostId.\n case 'datasetSchema':\n return {\n endpoint: 'marketplace/publish-dataset-schema',\n payload: { orgId, datasetId: artifactId },\n noun: 'dataset schema',\n }\n case 'emailTemplate':\n return {\n endpoint: 'marketplace/publish-email-template',\n payload: { hostId, templateKey: artifactId },\n noun: 'email template',\n }\n case 'emailStarter':\n return {\n endpoint: 'marketplace/publish-email-starter',\n payload: { hostId, screenId: artifactId },\n noun: 'email starter',\n }\n default:\n return null\n }\n}\n\n/**\n * Publish an artifact to the marketplace (AGL-672, moved out of the console\n * app by AGL-3080).\n *\n * The publish form is identical across artifact types — name, description,\n * category, price — and only the endpoint and identifying fields differ, so\n * this takes both rather than being copied per type. Every gate that\n * matters (plan, publisher profile, payouts, host role) lives on the server;\n * this surfaces the server's message rather than trying to predict it.\n *\n * Drawn through the `hostArtifactPublish` zone, so the page that offers the\n * publish keeps the control that opens it and never imports this. A page\n * with no widget on that zone leaves the offer out entirely, which is why\n * this may safely draw nothing for a kind it does not sell.\n */\nexport function PublishArtifactDialog({\n artifact,\n onClose,\n}: ConsoleArtifactPublishZoneProps) {\n // Memoized because it is a dependency of the publish callback: rebuilt\n // every render, it would rebuild that callback every render too.\n const target = useMemo(\n () => (artifact ? publishRequestFor(artifact) : null),\n [artifact],\n )\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [name, setName] = useState('')\n const [description, setDescription] = useState('')\n const [category, setCategory] = useState('')\n const [price, setPrice] = useState('')\n const [busy, setBusy] = useState(false)\n // The publisher agreement (AGL-3407): offered as the dialog opens when the\n // org's acceptance is missing or stale, and presented again over the form\n // if the publish is refused for it — accepting sends the same publish.\n const { send: sendGated, dialog: agreementDialog } =\n usePublisherAgreementGate({\n orgId: artifact?.orgId ?? null,\n offer: Boolean(target),\n })\n\n useEffect(() => {\n if (!artifact) return\n setName(artifact.displayName ?? '')\n setDescription(artifact.description ?? '')\n setCategory('')\n setPrice('')\n }, [artifact])\n\n const handlePublish = useCallback(async () => {\n if (!target || !name.trim() || busy) return\n setBusy(true)\n try {\n // Serialized once, so a retry after accepting the agreement sends the\n // very bytes the first attempt did.\n const body = JSON.stringify({\n ...target.payload,\n displayName: name.trim(),\n description: description.trim(),\n category: category.trim(),\n priceUsd: Number(price) || 0,\n })\n const { response, payload, declined } = await sendGated(() =>\n authorizedFetch(user, `/api/${target.endpoint}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n }),\n )\n // The agreement was shown and closed; it said why, and the form stays\n // as it was for when they are ready.\n if (declined) return\n if (!response.ok) {\n // 412 means a setup step is missing (publisher profile, payouts) —\n // actionable, so it reads as a warning rather than an error.\n return void enqueueSnackbar(payload?.error ?? 'Publish failed', {\n variant: response.status === 412 ? 'warning' : 'error',\n allowDuplicate: true,\n })\n }\n enqueueSnackbar(`Published v${payload.version} to the marketplace`, {\n variant: 'success',\n persist: false,\n })\n onClose()\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [\n target,\n name,\n description,\n category,\n price,\n busy,\n user,\n enqueueSnackbar,\n onClose,\n sendGated,\n ])\n\n if (artifact && !target) {\n return (\n <Dialog open onClose={onClose} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{'Nothing publishes this'}</DialogTitle>\n <DialogContent>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`This marketplace does not publish a ${artifact.kind}, so there ` +\n 'is nowhere to send it. Nothing was changed.'}\n </Typography>\n </DialogContent>\n <DialogActions>\n <Button size=\"small\" onClick={onClose}>\n {'Close'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n }\n\n return (\n <>\n <Dialog\n open={!!target}\n onClose={busy ? undefined : onClose}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{`Publish ${target?.noun ?? 'artifact'}`}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Publishes the current published version of this ` +\n `${target?.noun ?? 'artifact'} so other organizations can ` +\n 'install it. Your site is unaffected.'}\n </Typography>\n <TextField\n autoFocus\n size=\"small\"\n label=\"Listing name\"\n value={name}\n onChange={(event) => setName(event.target.value)}\n disabled={busy}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Description\"\n value={description}\n onChange={(event) => setDescription(event.target.value)}\n disabled={busy}\n multiline\n minRows={2}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Category\"\n placeholder={\n target?.categoryPlaceholder ?? 'e.g. Marketing, Docs'\n }\n value={category}\n onChange={(event) => setCategory(event.target.value)}\n disabled={busy}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Price (USD)\"\n placeholder=\"0 = free\"\n // The minimum price (AGL-2343). Marketplace checkout is a\n // destination charge, so Stripe's fee comes out of the PLATFORM's\n // balance while the seller is transferred a fixed share — at $1 the\n // fee is larger than the whole platform cut. The publish route\n // refuses anything under the floor, so the field says so before the\n // publisher gets there and the button holds.\n error={isBelowMarketplacePriceFloor(price)}\n helperText={\n marketplacePriceCostNote(price) ??\n marketplacePriceFloorHint(\n 'Paid listings need payouts set up on your marketplace profile.',\n )\n }\n value={price}\n onChange={(event) =>\n setPrice(event.target.value.replace(/[^0-9]/g, ''))\n }\n disabled={busy}\n fullWidth\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button size=\"small\" onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n disabled={\n busy || !name.trim() || isBelowMarketplacePriceFloor(price)\n }\n onClick={() => void handlePublish()}\n >\n {busy ? 'Publishing…' : 'Publish'}\n </Button>\n </DialogActions>\n </Dialog>\n {agreementDialog}\n </>\n )\n}\n\nPublishArtifactDialog.displayName = 'PublishArtifactDialog'\n\nexport default PublishArtifactDialog\n"],"names":["isBelowMarketplacePriceFloor","marketplacePriceCostNote","marketplacePriceFloorHint","useSnackbar","useUser","authorizedFetch","Button","Dialog","DialogActions","DialogContent","DialogTitle","Stack","TextField","Typography","useCallback","useEffect","useMemo","useState","usePublisherAgreementGate","publishRequestFor","artifact","hostId","orgId","artifactId","kind","endpoint","payload","layoutId","noun","categoryPlaceholder","componentId","datasetId","templateKey","screenId","PublishArtifactDialog","onClose","target","data","user","enqueueSnackbar","name","setName","description","setDescription","category","setCategory","price","setPrice","busy","setBusy","send","sendGated","dialog","agreementDialog","offer","Boolean","displayName","handlePublish","trim","body","JSON","stringify","priceUsd","Number","response","declined","method","headers","ok","error","variant","status","allowDuplicate","version","persist","console","open","maxWidth","fullWidth","color","size","onClick","undefined","spacing","sx","pt","autoFocus","label","value","onChange","event","disabled","multiline","minRows","placeholder","helperText","replace"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SACEA,4BAA4B,EAC5BC,wBAAwB,EACxBC,yBAAyB,QACpB,kCAA8B;AAKrC,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SAASC,eAAe,QAAQ,2CAA0C;AAC1E,SACEC,MAAM,EACNC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,WAAW,EACXC,KAAK,EACLC,SAAS,EACTC,UAAU,QACL,gBAAe;AACtB,SAASC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACjE,OAAOC,+BAA+B,oCAAgC;AAatE;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,SAASC,kBACPC,QAAoC;QAErBA,kBACDA,iBACKA;IAFnB,MAAMC,UAASD,mBAAAA,SAASC,MAAM,YAAfD,mBAAmB;IAClC,MAAME,SAAQF,kBAAAA,SAASE,KAAK,YAAdF,kBAAkB;IAChC,MAAMG,cAAaH,uBAAAA,SAASG,UAAU,YAAnBH,uBAAuB;IAC1C,OAAQA,SAASI,IAAI;QACnB,KAAK;YACH,OAAO;gBACLC,UAAU;gBACVC,SAAS;oBAAEL;oBAAQM,UAAUJ;gBAAW;gBACxCK,MAAM;gBACNC,qBAAqB;YACvB;QACF,KAAK;YACH,OAAO;gBACLJ,UAAU;gBACVC,SAAS;oBAAEL;oBAAQS,aAAaP;gBAAW;gBAC3CK,MAAM;gBACNC,qBAAqB;YACvB;QACF,KAAK;YACH,OAAO;gBACLJ,UAAU;gBACVC,SAAS;oBAAEL;gBAAO;gBAClBO,MAAM;gBACNC,qBAAqB;YACvB;QACF,KAAK;YACH,OAAO;gBACLJ,UAAU;gBACVC,SAAS;oBAAEL;gBAAO;gBAClBO,MAAM;YACR;QACF,yEAAyE;QACzE,KAAK;YACH,OAAO;gBACLH,UAAU;gBACVC,SAAS;oBAAEJ;oBAAOS,WAAWR;gBAAW;gBACxCK,MAAM;YACR;QACF,KAAK;YACH,OAAO;gBACLH,UAAU;gBACVC,SAAS;oBAAEL;oBAAQW,aAAaT;gBAAW;gBAC3CK,MAAM;YACR;QACF,KAAK;YACH,OAAO;gBACLH,UAAU;gBACVC,SAAS;oBAAEL;oBAAQY,UAAUV;gBAAW;gBACxCK,MAAM;YACR;QACF;YACE,OAAO;IACX;AACF;AAEA;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASM,sBAAsB,EACpCd,QAAQ,EACRe,OAAO,EACyB;mCAsKlBlC;IArKd,uEAAuE;IACvE,iEAAiE;IACjE,MAAMmC,SAASpB,QACb,IAAOI,WAAWD,kBAAkBC,YAAY,MAChD;QAACA;KAAS;IAEZ,MAAM,EAAEiB,MAAMC,IAAI,EAAE,GAAGlC;IACvB,MAAM,EAAEmC,eAAe,EAAE,GAAGpC;IAC5B,MAAM,CAACqC,MAAMC,QAAQ,GAAGxB,SAAS;IACjC,MAAM,CAACyB,aAAaC,eAAe,GAAG1B,SAAS;IAC/C,MAAM,CAAC2B,UAAUC,YAAY,GAAG5B,SAAS;IACzC,MAAM,CAAC6B,OAAOC,SAAS,GAAG9B,SAAS;IACnC,MAAM,CAAC+B,MAAMC,QAAQ,GAAGhC,SAAS;IACjC,2EAA2E;IAC3E,0EAA0E;IAC1E,uEAAuE;IACvE,MAAM,EAAEiC,MAAMC,SAAS,EAAEC,QAAQC,eAAe,EAAE,GAChDnC,0BAA0B;QACxBI,KAAK,UAAEF,4BAAAA,SAAUE,KAAK,mBAAI;QAC1BgC,OAAOC,QAAQnB;IACjB;IAEFrB,UAAU;YAEAK,uBACOA;QAFf,IAAI,CAACA,UAAU;QACfqB,SAAQrB,wBAAAA,SAASoC,WAAW,YAApBpC,wBAAwB;QAChCuB,gBAAevB,wBAAAA,SAASsB,WAAW,YAApBtB,wBAAwB;QACvCyB,YAAY;QACZE,SAAS;IACX,GAAG;QAAC3B;KAAS;IAEb,MAAMqC,gBAAgB3C,YAAY;QAChC,IAAI,CAACsB,UAAU,CAACI,KAAKkB,IAAI,MAAMV,MAAM;QACrCC,QAAQ;QACR,IAAI;YACF,sEAAsE;YACtE,oCAAoC;YACpC,MAAMU,OAAOC,KAAKC,SAAS,CAAC,aACvBzB,OAAOV,OAAO;gBACjB8B,aAAahB,KAAKkB,IAAI;gBACtBhB,aAAaA,YAAYgB,IAAI;gBAC7Bd,UAAUA,SAASc,IAAI;gBACvBI,UAAUC,OAAOjB,UAAU;;YAE7B,MAAM,EAAEkB,QAAQ,EAAEtC,OAAO,EAAEuC,QAAQ,EAAE,GAAG,MAAMd,UAAU,IACtD9C,gBAAgBiC,MAAM,CAAC,KAAK,EAAEF,OAAOX,QAAQ,EAAE,EAAE;oBAC/CyC,QAAQ;oBACRC,SAAS;wBAAE,gBAAgB;oBAAmB;oBAC9CR;gBACF;YAEF,sEAAsE;YACtE,qCAAqC;YACrC,IAAIM,UAAU;YACd,IAAI,CAACD,SAASI,EAAE,EAAE;;gBAChB,mEAAmE;gBACnE,6DAA6D;gBAC7D,OAAO,KAAK7B,wBAAgBb,2BAAAA,QAAS2C,KAAK,mBAAI,kBAAkB;oBAC9DC,SAASN,SAASO,MAAM,KAAK,MAAM,YAAY;oBAC/CC,gBAAgB;gBAClB;YACF;YACAjC,gBAAgB,CAAC,WAAW,EAAEb,QAAQ+C,OAAO,CAAC,mBAAmB,CAAC,EAAE;gBAClEH,SAAS;gBACTI,SAAS;YACX;YACAvC;QACF,EAAE,OAAOkC,OAAO;YACdM,QAAQN,KAAK,CAACA;YACd9B,gBAAgB,yBAAyB;gBACvC+B,SAAS;gBACTE,gBAAgB;YAClB;QACF,SAAU;YACRvB,QAAQ;QACV;IACF,GAAG;QACDb;QACAI;QACAE;QACAE;QACAE;QACAE;QACAV;QACAC;QACAJ;QACAgB;KACD;IAED,IAAI/B,YAAY,CAACgB,QAAQ;QACvB,qBACE,MAAC7B;YAAOqE,IAAI;YAACzC,SAASA;YAAS0C,UAAS;YAAKC,SAAS;;8BACpD,KAACpE;8BAAa;;8BACd,KAACD;8BACC,cAAA,KAACI;wBAAWyD,SAAQ;wBAAQS,OAAM;kCAC/B,CAAC,oCAAoC,EAAE3D,SAASI,IAAI,CAAC,WAAW,CAAC,GAChE;;;8BAGN,KAAChB;8BACC,cAAA,KAACF;wBAAO0E,MAAK;wBAAQC,SAAS9C;kCAC3B;;;;;IAKX;IAEA,qBACE;;0BACE,MAAC5B;gBACCqE,MAAM,CAAC,CAACxC;gBACRD,SAASa,OAAOkC,YAAY/C;gBAC5B0C,UAAS;gBACTC,SAAS;;kCAET,KAACpE;kCAAa,CAAC,QAAQ,WAAE0B,0BAAAA,OAAQR,IAAI,oBAAI,YAAY;;kCACrD,KAACnB;kCACC,cAAA,MAACE;4BAAMwE,SAAS;4BAAGC,IAAI;gCAAEC,IAAI;4BAAE;;8CAC7B,KAACxE;oCAAWyD,SAAQ;oCAAQS,OAAM;8CAC/B,CAAC,gDAAgD,CAAC,GACjD,YAAG3C,0BAAAA,OAAQR,IAAI,oBAAI,WAAW,4BAA4B,CAAC,GAC3D;;8CAEJ,KAAChB;oCACC0E,SAAS;oCACTN,MAAK;oCACLO,OAAM;oCACNC,OAAOhD;oCACPiD,UAAU,CAACC,QAAUjD,QAAQiD,MAAMtD,MAAM,CAACoD,KAAK;oCAC/CG,UAAU3C;oCACV8B,SAAS;;8CAEX,KAAClE;oCACCoE,MAAK;oCACLO,OAAM;oCACNC,OAAO9C;oCACP+C,UAAU,CAACC,QAAU/C,eAAe+C,MAAMtD,MAAM,CAACoD,KAAK;oCACtDG,UAAU3C;oCACV4C,SAAS;oCACTC,SAAS;oCACTf,SAAS;;8CAEX,KAAClE;oCACCoE,MAAK;oCACLO,OAAM;oCACNO,WAAW,WACT1D,0BAAAA,OAAQP,mBAAmB,oBAAI;oCAEjC2D,OAAO5C;oCACP6C,UAAU,CAACC,QAAU7C,YAAY6C,MAAMtD,MAAM,CAACoD,KAAK;oCACnDG,UAAU3C;oCACV8B,SAAS;;8CAEX,KAAClE;oCACCoE,MAAK;oCACLO,OAAM;oCACNO,aAAY;oCACZ,0DAA0D;oCAC1D,kEAAkE;oCAClE,oEAAoE;oCACpE,+DAA+D;oCAC/D,oEAAoE;oCACpE,6CAA6C;oCAC7CzB,OAAOrE,6BAA6B8C;oCACpCiD,UAAU,GACR9F,4BAAAA,yBAAyB6C,kBAAzB7C,4BACAC,0BACE;oCAGJsF,OAAO1C;oCACP2C,UAAU,CAACC,QACT3C,SAAS2C,MAAMtD,MAAM,CAACoD,KAAK,CAACQ,OAAO,CAAC,WAAW;oCAEjDL,UAAU3C;oCACV8B,SAAS;;;;;kCAIf,MAACtE;;0CACC,KAACF;gCAAO0E,MAAK;gCAAQC,SAAS9C;gCAASwD,UAAU3C;0CAC9C;;0CAEH,KAAC1C;gCACC0E,MAAK;gCACLV,SAAQ;gCACRS,OAAM;gCACNY,UACE3C,QAAQ,CAACR,KAAKkB,IAAI,MAAM1D,6BAA6B8C;gCAEvDmC,SAAS,IAAM,KAAKxB;0CAEnBT,OAAO,gBAAgB;;;;;;YAI7BK;;;AAGP;AAEAnB,sBAAsBsB,WAAW,GAAG;AAEpC,eAAetB,sBAAqB"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/publish-artifact-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 {\n isBelowMarketplacePriceFloor,\n marketplacePriceCostNote,\n marketplacePriceFloorHint,\n} from '../model/listing-price-floor'\nimport type {\n ConsoleArtifactPublishZoneProps,\n ConsolePublishableArtifact,\n} from '@aglyn/aglyn'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport {\n PublisherAgreementCheckbox,\n usePublisherAgreementConsent,\n} from './publisher-agreement-checkbox.component'\n\n/** One kind's publish route, and the words a person reads about it. */\ninterface PublishRequest {\n /** Plugin API path, e.g. `marketplace/publish-layout`. */\n endpoint: string\n /** Extra body fields identifying what is being published. */\n payload: Record<string, unknown>\n /** Noun used in copy: \"layout\", \"component\", … */\n noun: string\n categoryPlaceholder?: string\n}\n\n/**\n * WHERE EACH KIND GOES, which is the knowledge that used to sit in the app\n * (AGL-3080).\n *\n * A console page hands over what it has — a kind, the scope holding it, the\n * document — and this is the only place that turns it into a request. The\n * pages that offer publishing named the endpoint, the payload key and the\n * noun themselves, which made them files that could not compile without a\n * marketplace and could not be right without being kept in step with one.\n *\n * ⚠️ A KIND THIS DOES NOT KNOW IS SAID OUT LOUD, not drawn as nothing. It\n * means a console offering to publish something this marketplace does not\n * sell — the two lists have gone out of step — and the person has already\n * clicked. Drawing nothing would make the control look broken with no way to\n * tell what happened, and guessing an endpoint from the kind would post to a\n * route that does not exist and report the 404 to the publisher.\n *\n * The control itself is a separate matter: a page only offers the publish\n * when this zone has a widget at all, so a workspace with no marketplace\n * never gets here.\n */\nfunction publishRequestFor(\n artifact: ConsolePublishableArtifact,\n): PublishRequest | null {\n const hostId = artifact.hostId ?? ''\n const orgId = artifact.orgId ?? ''\n const artifactId = artifact.artifactId ?? ''\n switch (artifact.kind) {\n case 'layout':\n return {\n endpoint: 'marketplace/publish-layout',\n payload: { hostId, layoutId: artifactId },\n noun: 'layout',\n categoryPlaceholder: 'e.g. Marketing, Docs, Storefront',\n }\n case 'component':\n return {\n endpoint: 'marketplace/publish',\n payload: { hostId, componentId: artifactId },\n noun: 'component',\n categoryPlaceholder: 'e.g. Hero, Footer, Pricing',\n }\n case 'site':\n return {\n endpoint: 'marketplace/publish-template',\n payload: { hostId },\n noun: 'site template',\n categoryPlaceholder: 'e.g. Portfolio, Restaurant',\n }\n case 'theme':\n return {\n endpoint: 'marketplace/publish-theme',\n payload: { hostId },\n noun: 'theme',\n }\n // Datasets are org-scoped, so this route takes orgId rather than hostId.\n case 'datasetSchema':\n return {\n endpoint: 'marketplace/publish-dataset-schema',\n payload: { orgId, datasetId: artifactId },\n noun: 'dataset schema',\n }\n case 'emailTemplate':\n return {\n endpoint: 'marketplace/publish-email-template',\n payload: { hostId, templateKey: artifactId },\n noun: 'email template',\n }\n case 'emailStarter':\n return {\n endpoint: 'marketplace/publish-email-starter',\n payload: { hostId, screenId: artifactId },\n noun: 'email starter',\n }\n default:\n return null\n }\n}\n\n/**\n * Publish an artifact to the marketplace (AGL-672, moved out of the console\n * app by AGL-3080).\n *\n * The publish form is identical across artifact types — name, description,\n * category, price — and only the endpoint and identifying fields differ, so\n * this takes both rather than being copied per type. Every gate that\n * matters (plan, publisher profile, payouts, host role) lives on the server;\n * this surfaces the server's message rather than trying to predict it.\n *\n * Drawn through the `hostArtifactPublish` zone, so the page that offers the\n * publish keeps the control that opens it and never imports this. A page\n * with no widget on that zone leaves the offer out entirely, which is why\n * this may safely draw nothing for a kind it does not sell.\n */\nexport function PublishArtifactDialog({\n artifact,\n onClose,\n}: ConsoleArtifactPublishZoneProps) {\n // Memoized because it is a dependency of the publish callback: rebuilt\n // every render, it would rebuild that callback every render too.\n const target = useMemo(\n () => (artifact ? publishRequestFor(artifact) : null),\n [artifact],\n )\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [name, setName] = useState('')\n const [description, setDescription] = useState('')\n const [category, setCategory] = useState('')\n const [price, setPrice] = useState('')\n const [busy, setBusy] = useState(false)\n // The publisher agreement, agreed to on this publish (AGL-4105): the box\n // above the button, unticked for every artifact the dialog opens on.\n const agreement = usePublisherAgreementConsent()\n const { reset: resetAgreement } = agreement\n\n useEffect(() => {\n if (!artifact) return\n setName(artifact.displayName ?? '')\n setDescription(artifact.description ?? '')\n setCategory('')\n setPrice('')\n resetAgreement()\n }, [artifact, resetAgreement])\n\n const handlePublish = useCallback(async () => {\n if (!target || !name.trim() || busy || !agreement.consent) return\n setBusy(true)\n try {\n const response = await authorizedFetch(user, `/api/${target.endpoint}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...target.payload,\n displayName: name.trim(),\n description: description.trim(),\n category: category.trim(),\n priceUsd: Number(price) || 0,\n agreement: agreement.consent,\n }),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n // 412 means a setup step is missing (publisher profile, payouts) —\n // actionable, so it reads as a warning rather than an error.\n return void enqueueSnackbar(payload?.error ?? 'Publish failed', {\n variant: response.status === 412 ? 'warning' : 'error',\n allowDuplicate: true,\n })\n }\n enqueueSnackbar(`Published v${payload.version} to the marketplace`, {\n variant: 'success',\n persist: false,\n })\n // Each publish is its own agreement: the next one ticks the box again.\n resetAgreement()\n onClose()\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [\n target,\n name,\n description,\n category,\n price,\n busy,\n user,\n enqueueSnackbar,\n onClose,\n agreement.consent,\n resetAgreement,\n ])\n\n if (artifact && !target) {\n return (\n <Dialog open onClose={onClose} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{'Nothing publishes this'}</DialogTitle>\n <DialogContent>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`This marketplace does not publish a ${artifact.kind}, so there ` +\n 'is nowhere to send it. Nothing was changed.'}\n </Typography>\n </DialogContent>\n <DialogActions>\n <Button size=\"small\" onClick={onClose}>\n {'Close'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n }\n\n return (\n <>\n <Dialog\n open={!!target}\n onClose={busy ? undefined : onClose}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{`Publish ${target?.noun ?? 'artifact'}`}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Publishes the current published version of this ` +\n `${target?.noun ?? 'artifact'} so other organizations can ` +\n 'install it. Your site is unaffected.'}\n </Typography>\n <TextField\n autoFocus\n size=\"small\"\n label=\"Listing name\"\n value={name}\n onChange={(event) => setName(event.target.value)}\n disabled={busy}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Description\"\n value={description}\n onChange={(event) => setDescription(event.target.value)}\n disabled={busy}\n multiline\n minRows={2}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Category\"\n placeholder={\n target?.categoryPlaceholder ?? 'e.g. Marketing, Docs'\n }\n value={category}\n onChange={(event) => setCategory(event.target.value)}\n disabled={busy}\n fullWidth\n />\n <TextField\n size=\"small\"\n label=\"Price (USD)\"\n placeholder=\"0 = free\"\n // The minimum price (AGL-2343). Marketplace checkout is a\n // destination charge, so Stripe's fee comes out of the PLATFORM's\n // balance while the seller is transferred a fixed share — at $1 the\n // fee is larger than the whole platform cut. The publish route\n // refuses anything under the floor, so the field says so before the\n // publisher gets there and the button holds.\n error={isBelowMarketplacePriceFloor(price)}\n helperText={\n marketplacePriceCostNote(price) ??\n marketplacePriceFloorHint(\n 'Paid listings need payouts set up on your marketplace profile.',\n )\n }\n value={price}\n onChange={(event) =>\n setPrice(event.target.value.replace(/[^0-9]/g, ''))\n }\n disabled={busy}\n fullWidth\n />\n {/* Directly above Publish (AGL-4105), which waits for it. */}\n <PublisherAgreementCheckbox\n checked={agreement.agreed}\n onChange={agreement.setAgreed}\n disabled={busy}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button size=\"small\" onClick={onClose} disabled={busy}>\n {'Cancel'}\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n disabled={\n busy ||\n !name.trim() ||\n isBelowMarketplacePriceFloor(price) ||\n !agreement.agreed\n }\n onClick={() => void handlePublish()}\n >\n {busy ? 'Publishing…' : 'Publish'}\n </Button>\n </DialogActions>\n </Dialog>\n </>\n )\n}\n\nPublishArtifactDialog.displayName = 'PublishArtifactDialog'\n\nexport default PublishArtifactDialog\n"],"names":["isBelowMarketplacePriceFloor","marketplacePriceCostNote","marketplacePriceFloorHint","useSnackbar","useUser","authorizedFetch","Button","Dialog","DialogActions","DialogContent","DialogTitle","Stack","TextField","Typography","useCallback","useEffect","useMemo","useState","PublisherAgreementCheckbox","usePublisherAgreementConsent","publishRequestFor","artifact","hostId","orgId","artifactId","kind","endpoint","payload","layoutId","noun","categoryPlaceholder","componentId","datasetId","templateKey","screenId","PublishArtifactDialog","onClose","target","data","user","enqueueSnackbar","name","setName","description","setDescription","category","setCategory","price","setPrice","busy","setBusy","agreement","reset","resetAgreement","displayName","handlePublish","trim","consent","response","method","headers","body","JSON","stringify","priceUsd","Number","json","catch","ok","error","variant","status","allowDuplicate","version","persist","console","open","maxWidth","fullWidth","color","size","onClick","undefined","spacing","sx","pt","autoFocus","label","value","onChange","event","disabled","multiline","minRows","placeholder","helperText","replace","checked","agreed","setAgreed"],"mappings":"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"}
@@ -30,7 +30,7 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
30
30
  import { inheritedMediaAlt } from "@aglyn/aglyn/app-utils/media-metadata";
31
31
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
32
32
  import { listingPath } from "../model/marketplace-paths.js";
33
- import usePublisherAgreementGate from "./use-publisher-agreement-gate.js";
33
+ import { PublisherAgreementCheckbox, usePublisherAgreementConsent } from "./publisher-agreement-checkbox.component.js";
34
34
  // Mirrors the server taxonomy; the server re-validates (the console cannot
35
35
  // import the marketplace plugin's model — scope:app may not reach aglyn:addons).
36
36
  const LISTING_CATEGORIES = [
@@ -230,15 +230,11 @@ const draftKey = (orgId, listingId)=>`aglyn.publish-plugin.draft.${orgId}${listi
230
230
  // Normalized on arrival so a plain string from anywhere else still reads.
231
231
  const [problems, setProblems] = useState([]);
232
232
  const [missingAttestations, setMissingAttestations] = useState([]);
233
+ // The publisher agreement, agreed to on this publish (AGL-4105): the box
234
+ // above the button. Never part of the draft — a draft restored after a
235
+ // reload asks again, as every publish does.
236
+ const agreement = usePublisherAgreementConsent();
233
237
  const [agreementProblem, setAgreementProblem] = useState('');
234
- // The publisher agreement (AGL-3407), asked for HERE rather than on the
235
- // Publisher Profile tab: offered as the form opens when the org's
236
- // acceptance is missing or stale, and presented again if the publish is
237
- // refused for it — accepting sends the same bundle and fields again.
238
- const agreement = usePublisherAgreementGate({
239
- orgId,
240
- offer: true
241
- });
242
238
  const [fieldErrors, setFieldErrors] = useState({});
243
239
  const set = (key)=>(value)=>setDraft((current)=>_extends({}, current, {
244
240
  [key]: value
@@ -339,6 +335,7 @@ const draftKey = (orgId, listingId)=>`aglyn.publish-plugin.draft.${orgId}${listi
339
335
  setMissingAttestations([]);
340
336
  setAgreementProblem('');
341
337
  setFieldErrors({});
338
+ if (!agreement.consent) return;
342
339
  if (!bundleFile) {
343
340
  setFieldErrors({
344
341
  bundle: 'Choose a plugin bundle (.js) to upload.'
@@ -356,8 +353,6 @@ const draftKey = (orgId, listingId)=>`aglyn.publish-plugin.draft.${orgId}${listi
356
353
  try {
357
354
  var _manifest_name, _ref, _ref1;
358
355
  const bundle = await fileToBase64(bundleFile);
359
- // Serialized once: a retry after the agreement is accepted sends the
360
- // bytes this attempt did, not whatever the form holds by then.
361
356
  const body = JSON.stringify(_extends({
362
357
  orgId,
363
358
  bundle,
@@ -378,14 +373,17 @@ const draftKey = (orgId, listingId)=>`aglyn.publish-plugin.draft.${orgId}${listi
378
373
  license: draft.license.trim()
379
374
  } : {}, draft.repositoryUrl.trim() ? {
380
375
  repositoryUrl: draft.repositoryUrl.trim()
381
- } : {}));
382
- const { response, payload, declined } = await agreement.send(()=>authorizedFetch(user, '/api/marketplace/publish-plugin', {
383
- method: 'POST',
384
- headers: {
385
- 'Content-Type': 'application/json'
386
- },
387
- body
388
- }));
376
+ } : {}, {
377
+ agreement: agreement.consent
378
+ }));
379
+ const response = await authorizedFetch(user, '/api/marketplace/publish-plugin', {
380
+ method: 'POST',
381
+ headers: {
382
+ 'Content-Type': 'application/json'
383
+ },
384
+ body
385
+ });
386
+ const payload = await response.json().catch(()=>({}));
389
387
  if (!response.ok) {
390
388
  var _ref2;
391
389
  if (Array.isArray(payload == null ? void 0 : payload.problems)) {
@@ -403,10 +401,9 @@ const draftKey = (orgId, listingId)=>`aglyn.publish-plugin.draft.${orgId}${listi
403
401
  if (Array.isArray(payload == null ? void 0 : payload.missingAttestations)) {
404
402
  setMissingAttestations(payload.missingAttestations);
405
403
  }
406
- // The agreement was presented and closed without accepting
407
- // (AGL-3407). Held as an alert with the way back to it, not a toast:
408
- // the dialog already said why, and the form keeps everything.
409
- if (declined) {
404
+ // Refused for the agreement (AGL-4105): said under the box it is
405
+ // about, not in a toast, and the form keeps everything.
406
+ if (payload == null ? void 0 : payload.agreement) {
410
407
  var _payload_error;
411
408
  return void setAgreementProblem(String((_payload_error = payload.error) != null ? _payload_error : ''));
412
409
  }
@@ -430,6 +427,7 @@ const draftKey = (orgId, listingId)=>`aglyn.publish-plugin.draft.${orgId}${listi
430
427
  // opened from, which said nothing about what had just happened; the
431
428
  // listing shows the version now queued for review.
432
429
  clearDraft();
430
+ agreement.reset();
433
431
  enqueueSnackbar(`Published v${(_ref = payload == null ? void 0 : payload.version) != null ? _ref : '1'} — it is queued for review`, {
434
432
  variant: 'success'
435
433
  });
@@ -449,7 +447,7 @@ const draftKey = (orgId, listingId)=>`aglyn.publish-plugin.draft.${orgId}${listi
449
447
  // minimum off a 400. A private plugin has no price at all — its field is
450
448
  // disabled and sends 0 — so it can never be blocked by this.
451
449
  const priceBelowFloor = draft.visibility !== 'private' && isBelowMarketplacePriceFloor(draft.priceUsd);
452
- const blocked = busy || !bundleFile || unattested.length > 0 || unfilledSubjects.length > 0 || repositoryInvalid || manifestIdMismatch || priceBelowFloor;
450
+ const blocked = busy || !bundleFile || unattested.length > 0 || unfilledSubjects.length > 0 || repositoryInvalid || manifestIdMismatch || priceBelowFloor || !agreement.agreed;
453
451
  return /*#__PURE__*/ _jsxs(Stack, {
454
452
  spacing: 2,
455
453
  children: [
@@ -483,17 +481,6 @@ const draftKey = (orgId, listingId)=>`aglyn.publish-plugin.draft.${orgId}${listi
483
481
  onClose: ()=>setDraftRestored(false),
484
482
  children: 'Picked up where you left off. Your bundle and manifest file are ' + 'not saved — choose them again before publishing.'
485
483
  }) : null,
486
- agreementProblem ? /*#__PURE__*/ _jsx(Alert, {
487
- severity: "warning",
488
- action: /*#__PURE__*/ _jsx(Button, {
489
- color: "inherit",
490
- size: "small",
491
- onClick: agreement.review,
492
- children: 'Review the agreement'
493
- }),
494
- children: agreementProblem
495
- }) : null,
496
- agreement.dialog,
497
484
  /*#__PURE__*/ _jsx(CardDisplay, {
498
485
  header: isUpdate ? 'The new bundle and manifest' : 'Bundle and manifest',
499
486
  help: pluginDocsHelp('publisherHandbook', {
@@ -841,6 +828,15 @@ const draftKey = (orgId, listingId)=>`aglyn.publish-plugin.draft.${orgId}${listi
841
828
  ]
842
829
  })
843
830
  }),
831
+ /*#__PURE__*/ _jsx(PublisherAgreementCheckbox, {
832
+ checked: agreement.agreed,
833
+ onChange: (checked)=>{
834
+ agreement.setAgreed(checked);
835
+ setAgreementProblem('');
836
+ },
837
+ disabled: busy,
838
+ error: agreementProblem || null
839
+ }),
844
840
  /*#__PURE__*/ _jsxs(Stack, {
845
841
  direction: "row",
846
842
  spacing: 1,
@@ -853,7 +849,7 @@ const draftKey = (orgId, listingId)=>`aglyn.publish-plugin.draft.${orgId}${listi
853
849
  color: "primary",
854
850
  onClick: ()=>void submit(),
855
851
  disabled: blocked,
856
- children: busy ? 'Publishing…' : !bundleFile ? 'Choose a bundle' : unattested.length ? `Confirm ${unattested.length} more` : unfilledSubjects.length || repositoryInvalid ? 'Add a repository URL' : isUpdate ? 'Publish new version' : draft.visibility === 'private' ? 'Publish privately' : 'Publish plugin'
852
+ children: busy ? 'Publishing…' : !bundleFile ? 'Choose a bundle' : unattested.length ? `Confirm ${unattested.length} more` : unfilledSubjects.length || repositoryInvalid ? 'Add a repository URL' : !agreement.agreed ? 'Agree to publish' : isUpdate ? 'Publish new version' : draft.visibility === 'private' ? 'Publish privately' : 'Publish plugin'
857
853
  }),
858
854
  /*#__PURE__*/ _jsx(Button, {
859
855
  disabled: busy,