@aglyn/plugins-marketplace 1.0.0-beta.292 → 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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-marketplace",
3
- "version": "1.0.0-beta.292",
3
+ "version": "1.0.0-beta.293",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,19 +25,19 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.292",
29
- "@aglyn/aglyn-markdown-editor": "1.0.0-beta.292",
30
- "@aglyn/shared-data-enums": "1.0.0-beta.292",
31
- "@aglyn/shared-data-mdi": "1.0.0-beta.292",
32
- "@aglyn/shared-ui-jsx": "1.0.0-beta.292",
33
- "@aglyn/shared-ui-next": "1.0.0-beta.292",
34
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.292",
35
- "@aglyn/shared-util-email": "1.0.0-beta.292",
36
- "@aglyn/shared-util-http": "1.0.0-beta.292",
37
- "@aglyn/shared-util-timestamp": "1.0.0-beta.292",
38
- "@aglyn/tenant-data-admin": "1.0.0-beta.292",
39
- "@aglyn/tenant-feature-instance": "1.0.0-beta.292",
40
- "@aglyn/tenant-runtime": "1.0.0-beta.292",
28
+ "@aglyn/aglyn": "1.0.0-beta.293",
29
+ "@aglyn/aglyn-markdown-editor": "1.0.0-beta.293",
30
+ "@aglyn/shared-data-enums": "1.0.0-beta.293",
31
+ "@aglyn/shared-data-mdi": "1.0.0-beta.293",
32
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.293",
33
+ "@aglyn/shared-ui-next": "1.0.0-beta.293",
34
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.293",
35
+ "@aglyn/shared-util-email": "1.0.0-beta.293",
36
+ "@aglyn/shared-util-http": "1.0.0-beta.293",
37
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.293",
38
+ "@aglyn/tenant-data-admin": "1.0.0-beta.293",
39
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.293",
40
+ "@aglyn/tenant-runtime": "1.0.0-beta.293",
41
41
  "@swc/helpers": "0.5.23"
42
42
  },
43
43
  "peerDependencies": {
@@ -28,6 +28,7 @@ import { useEffect, useRef, useState } from "react";
28
28
  import { LISTING_LOGO_HINT } from "../constants/listing-media.js";
29
29
  import { formatMediaRef, pluginDocsHelp, useMediaPicker } from "@aglyn/aglyn";
30
30
  import { MarkdownField } from "@aglyn/aglyn-markdown-editor";
31
+ import { PublisherAgreementCheckbox, usePublisherAgreementConsent } from "./publisher-agreement-checkbox.component.js";
31
32
  // Mirrors the server's fixed taxonomy (marketplace plugin model), which the
32
33
  // console can't import across the aglyn:addons boundary. The server
33
34
  // re-validates, so drift fails the save rather than corrupting a listing.
@@ -60,6 +61,8 @@ const MAX_SCREENSHOTS = 6;
60
61
  const { enqueueSnackbar } = useSnackbar();
61
62
  const editorRef = useRef(null);
62
63
  const [busy, setBusy] = useState(false);
64
+ // The publisher agreement, agreed to on this save (AGL-4105).
65
+ const agreement = usePublisherAgreementConsent();
63
66
  const [values, setValues] = useState({
64
67
  displayName: '',
65
68
  description: '',
@@ -157,23 +160,37 @@ const MAX_SCREENSHOTS = 6;
157
160
  ]);
158
161
  };
159
162
  const save = async ()=>{
163
+ if (!agreement.consent) return;
160
164
  setBusy(true);
161
165
  try {
162
166
  const headers = {
163
167
  'Content-Type': 'application/json'
164
168
  };
165
169
  // Preview image isn't a content field, so it has its own endpoint
166
- // (AGL-863). Only touch it when it actually changed.
170
+ // (AGL-863). Only touch it when it actually changed. Both requests carry
171
+ // the one agreement this save was made with (AGL-4105), and a refused
172
+ // preview stops the save rather than saving the rest without it.
167
173
  if (values.previewImageUrl !== originalPreview.current) {
168
- await authorizedFetch(user, '/api/marketplace/preview-image', {
174
+ const preview = await authorizedFetch(user, '/api/marketplace/preview-image', {
169
175
  method: values.previewImageUrl ? 'POST' : 'DELETE',
170
176
  headers,
171
177
  body: JSON.stringify(_extends({
172
178
  listingId
173
179
  }, values.previewImageUrl ? {
174
180
  url: values.previewImageUrl
175
- } : {}))
181
+ } : {}, {
182
+ agreement: agreement.consent
183
+ }))
176
184
  });
185
+ if (!preview.ok) {
186
+ var _ref;
187
+ const refused = await preview.json().catch(()=>({}));
188
+ return void enqueueSnackbar((_ref = refused == null ? void 0 : refused.error) != null ? _ref : 'Update failed', {
189
+ variant: 'error',
190
+ allowDuplicate: true
191
+ });
192
+ }
193
+ originalPreview.current = values.previewImageUrl;
177
194
  }
178
195
  const response = await authorizedFetch(user, '/api/marketplace/publish-plugin', {
179
196
  method: 'POST',
@@ -191,13 +208,14 @@ const MAX_SCREENSHOTS = 6;
191
208
  categories: values.category ? [
192
209
  values.category
193
210
  ] : [],
194
- screenshots
211
+ screenshots,
212
+ agreement: agreement.consent
195
213
  })
196
214
  });
197
215
  const payload = await response.json().catch(()=>({}));
198
216
  if (!response.ok) {
199
- var _ref;
200
- return void enqueueSnackbar((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Update failed', {
217
+ var _ref1;
218
+ return void enqueueSnackbar((_ref1 = payload == null ? void 0 : payload.error) != null ? _ref1 : 'Update failed', {
201
219
  variant: 'error',
202
220
  allowDuplicate: true
203
221
  });
@@ -206,6 +224,8 @@ const MAX_SCREENSHOTS = 6;
206
224
  variant: 'success',
207
225
  persist: false
208
226
  });
227
+ // Each save is its own agreement: the next one ticks the box again.
228
+ agreement.reset();
209
229
  onDone();
210
230
  } catch (error) {
211
231
  console.error(error);
@@ -426,17 +446,19 @@ const MAX_SCREENSHOTS = 6;
426
446
  })
427
447
  ]
428
448
  }),
449
+ /*#__PURE__*/ _jsx(PublisherAgreementCheckbox, {
450
+ checked: agreement.agreed,
451
+ onChange: agreement.setAgreed,
452
+ disabled: busy
453
+ }),
429
454
  /*#__PURE__*/ _jsxs(Stack, {
430
455
  direction: "row",
431
456
  spacing: 1,
432
- sx: {
433
- pt: 1
434
- },
435
457
  children: [
436
458
  /*#__PURE__*/ _jsx(Button, {
437
459
  variant: "contained",
438
460
  color: "primary",
439
- disabled: busy || !values.displayName.trim(),
461
+ disabled: busy || !values.displayName.trim() || !agreement.agreed,
440
462
  onClick: ()=>void save(),
441
463
  children: busy ? 'Saving…' : 'Save listing'
442
464
  }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/listing-detail-editor.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 { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\n// The stored preview may be a `media:` reference (AGL-1424), which is not a\n// URL. Resolved where it is DISPLAYED, never in `values`.\nimport { resolveMediaSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport { inheritedMediaAlt } from '@aglyn/aglyn/app-utils/media-metadata'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Box,\n Button,\n Divider,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useRef, useState } from 'react'\nimport { LISTING_LOGO_HINT } from '../constants/listing-media'\nimport { formatMediaRef, pluginDocsHelp, useMediaPicker } from '@aglyn/aglyn'\nimport {\n MarkdownField,\n type MarkdownFieldHandle,\n} from '@aglyn/aglyn-markdown-editor'\n\n// Mirrors the server's fixed taxonomy (marketplace plugin model), which the\n// console can't import across the aglyn:addons boundary. The server\n// re-validates, so drift fails the save rather than corrupting a listing.\nconst LISTING_CATEGORIES = [\n 'analytics',\n 'automation',\n 'commerce',\n 'communication',\n 'content',\n 'design',\n 'forms',\n 'integrations',\n 'marketing',\n 'productivity',\n 'seo',\n 'security',\n] as const\nconst MAX_SCREENSHOTS = 6\n\n/** Which image field / target the DAM picker is currently filling. */\ntype PickTarget = 'preview' | 'logo' | 'screenshot' | 'body' | null\n\nexport interface ListingDetailEditorProps {\n orgId: string\n listingId: string\n listing: Record<string, any>\n /** Firebase user, for the ID token on the update requests. */\n user: unknown\n /** Leave edit mode (Cancel or after a successful save). */\n onDone: () => void\n}\n\n/**\n * Full-page listing editor (AGL-869): the owner edits the whole detail page,\n * not a cramped sidebar card. The body uses the same WYSIWYG markdown editor\n * as blog content (`MarkdownVisualEditor`), and every image — preview, logo,\n * screenshots, and inline body images — picks from the shared DAM.\n *\n * Content saves through the marketplace plugin's HTTP API (`update-listing`);\n * the preview image goes through its dedicated endpoint since it isn't a\n * content field. Both are merges, so an unchanged field is left untouched.\n */\nexport function ListingDetailEditor(props: ListingDetailEditorProps) {\n const { orgId, listingId, listing, user, onDone } = props\n const { enqueueSnackbar } = useSnackbar()\n const editorRef = useRef<MarkdownFieldHandle | null>(null)\n const [busy, setBusy] = useState(false)\n\n const [values, setValues] = useState({\n displayName: '',\n description: '',\n previewImageUrl: '',\n logoUrl: '',\n homepageUrl: '',\n repositoryUrl: '',\n license: '',\n category: '',\n })\n const [readme, setReadme] = useState('')\n const [screenshots, setScreenshots] = useState<string[]>([])\n const originalPreview = useRef<string>('')\n\n useEffect(() => {\n setValues({\n displayName: listing?.displayName ?? '',\n description: listing?.description ?? '',\n previewImageUrl: listing?.previewImageUrl ?? '',\n logoUrl: listing?.logoUrl ?? '',\n homepageUrl: listing?.homepageUrl ?? '',\n repositoryUrl: listing?.repositoryUrl ?? '',\n license: listing?.license ?? '',\n category: listing?.categories?.[0] ?? listing?.category ?? '',\n })\n setReadme(listing?.readme ?? '')\n setScreenshots(\n Array.isArray(listing?.screenshots) ? listing.screenshots : [],\n )\n originalPreview.current = listing?.previewImageUrl ?? ''\n }, [listing?.$id])\n\n const set = (key: keyof typeof values) => (event: any) =>\n setValues((current) => ({ ...current, [key]: event.target.value }))\n\n /*\n * The console's media library, opened through the picker context rather\n * than by mounting its dialog (AGL-3080). The library is coupled to the\n * org and the session, which a plugin has no way to reach, and the console\n * provides `pickMedia` around every plugin page for exactly this — so the\n * `pickTarget` state that only existed to keep one dialog open for four\n * buttons goes with it: the target is now the argument.\n *\n * The URL comes back already resolved (CDN path over the raw download URL,\n * AGL-1215), which is what `mediaSrc` was doing here.\n */\n const { pickMedia } = useMediaPicker()\n const onPickMedia = async (target: Exclude<PickTarget, null>) => {\n const media = await pickMedia?.({ kind: 'image' })\n if (!media) return\n const url = media.url\n if (!url) return\n if (target === 'preview') setValues((c) => ({ ...c, previewImageUrl: url }))\n else if (target === 'logo') setValues((c) => ({ ...c, logoUrl: url }))\n else if (target === 'body') {\n // The README BODY takes a reference; the three image FIELDS above keep\n // `mediaSrc` (AGL-1705). Not an inconsistency — a field is one string\n // with known readers and its own AGL-1701 validator, while the body is\n // markdown that AGL-1686 taught every renderer to resolve.\n //\n // The absolute form was justified by `mediaSrc`'s own docstring — it\n // prefixes the origin because the markdown \"renders elsewhere\". That\n // was checked rather than assumed, and it is not true. The renderer\n // `MarketplaceListingContent` is registered through\n // `registerConsoleExtension`; its `marketplaceListing` slot has exactly\n // one render site, the console's `/[orgSlug]/marketplace/[listingId]`\n // route; and `apps/tenant` never imports the marketplace plugin at all.\n // The body reaches no other origin by another door either — the social\n // card builds `og:image` from `previewImageUrl`/`logoUrl`, never from\n // the README.\n //\n // So a site-relative resolve lands on the console origin, which serves\n // `/api/media/cdn/[...path]` — the very route the absolute URL pointed\n // at. Same bytes over the same route, minus a baked-in origin and a\n // baked-in route shape that cannot be changed without a migration.\n const src = formatMediaRef(media.mediaScope, media.mediaId) ?? url\n // The alt was a hardcoded '' (AGL-1896). This surface offers no alt\n // prompt at all, so the asset's own alt was the only description that\n // could ever have reached the row — and a markdown-lite image row's\n // alt cannot be edited after insertion, so '' was permanent. Still ''\n // for an asset nobody has described; never a fabricated one.\n editorRef.current?.insertImage(\n inheritedMediaAlt({ assetAlt: media.alt }) ?? '',\n src,\n )\n } else if (target === 'screenshot')\n setScreenshots((current) =>\n current.length >= MAX_SCREENSHOTS || current.includes(url)\n ? current\n : [...current, url],\n )\n }\n\n const save = async () => {\n setBusy(true)\n try {\n const headers = { 'Content-Type': 'application/json' }\n // Preview image isn't a content field, so it has its own endpoint\n // (AGL-863). Only touch it when it actually changed.\n if (values.previewImageUrl !== originalPreview.current) {\n await authorizedFetch(user, '/api/marketplace/preview-image', {\n method: values.previewImageUrl ? 'POST' : 'DELETE',\n headers,\n body: JSON.stringify({\n listingId,\n ...(values.previewImageUrl ? { url: values.previewImageUrl } : {}),\n }),\n })\n }\n const response = await authorizedFetch(\n user,\n '/api/marketplace/publish-plugin',\n {\n method: 'POST',\n headers,\n body: JSON.stringify({\n action: 'update-listing',\n listingId,\n displayName: values.displayName,\n description: values.description,\n readme,\n logoUrl: values.logoUrl,\n homepageUrl: values.homepageUrl,\n repositoryUrl: values.repositoryUrl,\n license: values.license,\n categories: values.category ? [values.category] : [],\n screenshots,\n }),\n },\n )\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n return void enqueueSnackbar(payload?.error ?? 'Update failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n enqueueSnackbar('Listing updated', { variant: 'success', persist: false })\n onDone()\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\n return (\n <CardDisplay\n header={'Edit listing'}\n help={pluginDocsHelp('publisherHandbook', { anchor: '#edit-listing' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Everything here shows on the public listing page. The body is ' +\n 'rich text — use the toolbar, and add images from your media ' +\n 'library.'}\n </Typography>\n\n <TextField\n label=\"Listing name\"\n value={values.displayName}\n onChange={set('displayName')}\n size=\"small\"\n fullWidth\n />\n <TextField\n label=\"Description\"\n helperText=\"One line, shown on every browse card\"\n value={values.description}\n onChange={set('description')}\n size=\"small\"\n multiline\n minRows={2}\n fullWidth\n />\n\n {/* Preview image — the hero shown on cards and at the top of detail. */}\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{'Preview image'}</Typography>\n {/* Resolved for DISPLAY only (AGL-1424). `values.previewImageUrl`\n stays the stored form — it is what the save POSTs, and writing\n the resolved URL back would undo the reference. */}\n {resolveMediaSrc(values.previewImageUrl) ? (\n <Box\n component=\"img\"\n src={resolveMediaSrc(values.previewImageUrl)}\n alt=\"Preview\"\n sx={{\n width: '100%',\n maxHeight: 220,\n objectFit: 'cover',\n borderRadius: 1,\n border: 1,\n borderColor: 'divider',\n }}\n />\n ) : null}\n <Stack direction=\"row\" spacing={1}>\n <Button size=\"small\" onClick={() => onPickMedia('preview')}>\n {values.previewImageUrl ? 'Replace' : 'Choose from library'}\n </Button>\n {values.previewImageUrl ? (\n <Button\n size=\"small\"\n color=\"error\"\n onClick={() =>\n setValues((c) => ({ ...c, previewImageUrl: '' }))\n }\n >\n {'Remove'}\n </Button>\n ) : null}\n </Stack>\n </Stack>\n\n <Divider />\n\n {/* Body — the WYSIWYG markdown editor, same as blog content, and\n since AGL-1080 the same component the publish form uses. */}\n <MarkdownField\n label=\"About\"\n value={readme}\n onChange={setReadme}\n onPickImageFromMedia={() => onPickMedia('body')}\n editorRef={(handle) => {\n editorRef.current = handle\n }}\n />\n\n <Divider />\n\n {/* Screenshots gallery. */}\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">\n {`Screenshots (${screenshots.length}/${MAX_SCREENSHOTS})`}\n </Typography>\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap' }}>\n {screenshots.map((url, index) => (\n <Box key={url} sx={{ position: 'relative' }}>\n <Box\n component=\"img\"\n src={url}\n alt={`Screenshot ${index + 1}`}\n sx={{\n height: 72,\n borderRadius: 1,\n border: 1,\n borderColor: 'divider',\n }}\n />\n <Button\n size=\"small\"\n color=\"error\"\n onClick={() =>\n setScreenshots((current) =>\n current.filter((entry) => entry !== url),\n )\n }\n >\n {'Remove'}\n </Button>\n </Box>\n ))}\n </Stack>\n {screenshots.length < MAX_SCREENSHOTS ? (\n <Box>\n <Button size=\"small\" onClick={() => onPickMedia('screenshot')}>\n {'Add from library'}\n </Button>\n </Box>\n ) : null}\n </Stack>\n\n <Divider />\n\n {/* Logo + metadata. */}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'flex-start' }}>\n <TextField\n label=\"Logo URL (https)\"\n value={values.logoUrl}\n onChange={set('logoUrl')}\n size=\"small\"\n sx={{ flex: 1 }}\n helperText={LISTING_LOGO_HINT}\n />\n <Button\n size=\"small\"\n onClick={() => onPickMedia('logo')}\n sx={{ mt: 0.5 }}\n >\n {'Library'}\n </Button>\n </Stack>\n <TextField\n label=\"Homepage (https)\"\n value={values.homepageUrl}\n onChange={set('homepageUrl')}\n size=\"small\"\n fullWidth\n />\n <TextField\n label=\"Repository (https)\"\n value={values.repositoryUrl}\n onChange={set('repositoryUrl')}\n size=\"small\"\n fullWidth\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"License\"\n value={values.license}\n onChange={set('license')}\n size=\"small\"\n sx={{ flex: 1 }}\n />\n <TextField\n select\n label=\"Category\"\n value={values.category}\n onChange={set('category')}\n size=\"small\"\n sx={{ flex: 1 }}\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {LISTING_CATEGORIES.map((entry) => (\n <MenuItem key={entry} value={entry}>\n {entry}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n\n <Stack direction=\"row\" spacing={1} sx={{ pt: 1 }}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={busy || !values.displayName.trim()}\n onClick={() => void save()}\n >\n {busy ? 'Saving…' : 'Save listing'}\n </Button>\n <Button disabled={busy} onClick={onDone}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n\n </CardDisplay>\n )\n}\n\nListingDetailEditor.displayName = 'ListingDetailEditor'\n\nexport default ListingDetailEditor\n"],"names":["CardDisplay","useSnackbar","resolveMediaSrc","inheritedMediaAlt","authorizedFetch","Box","Button","Divider","MenuItem","Stack","TextField","Typography","useEffect","useRef","useState","LISTING_LOGO_HINT","formatMediaRef","pluginDocsHelp","useMediaPicker","MarkdownField","LISTING_CATEGORIES","MAX_SCREENSHOTS","ListingDetailEditor","props","orgId","listingId","listing","user","onDone","enqueueSnackbar","editorRef","busy","setBusy","values","setValues","displayName","description","previewImageUrl","logoUrl","homepageUrl","repositoryUrl","license","category","readme","setReadme","screenshots","setScreenshots","originalPreview","categories","Array","isArray","current","$id","set","key","event","target","value","pickMedia","onPickMedia","media","kind","url","c","src","mediaScope","mediaId","insertImage","assetAlt","alt","length","includes","save","headers","method","body","JSON","stringify","response","action","payload","json","catch","ok","error","variant","allowDuplicate","persist","console","header","help","anchor","contentGutterX","contentGutterY","spacing","color","label","onChange","size","fullWidth","helperText","multiline","minRows","component","sx","width","maxHeight","objectFit","borderRadius","border","borderColor","direction","onClick","onPickImageFromMedia","handle","useFlexGap","flexWrap","map","index","position","height","filter","entry","alignItems","flex","mt","select","pt","disabled","trim"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/listing-detail-editor.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 { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\n// The stored preview may be a `media:` reference (AGL-1424), which is not a\n// URL. Resolved where it is DISPLAYED, never in `values`.\nimport { resolveMediaSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport { inheritedMediaAlt } from '@aglyn/aglyn/app-utils/media-metadata'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Box,\n Button,\n Divider,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useRef, useState } from 'react'\nimport { LISTING_LOGO_HINT } from '../constants/listing-media'\nimport { formatMediaRef, pluginDocsHelp, useMediaPicker } from '@aglyn/aglyn'\nimport {\n MarkdownField,\n type MarkdownFieldHandle,\n} from '@aglyn/aglyn-markdown-editor'\nimport {\n PublisherAgreementCheckbox,\n usePublisherAgreementConsent,\n} from './publisher-agreement-checkbox.component'\n\n// Mirrors the server's fixed taxonomy (marketplace plugin model), which the\n// console can't import across the aglyn:addons boundary. The server\n// re-validates, so drift fails the save rather than corrupting a listing.\nconst LISTING_CATEGORIES = [\n 'analytics',\n 'automation',\n 'commerce',\n 'communication',\n 'content',\n 'design',\n 'forms',\n 'integrations',\n 'marketing',\n 'productivity',\n 'seo',\n 'security',\n] as const\nconst MAX_SCREENSHOTS = 6\n\n/** Which image field / target the DAM picker is currently filling. */\ntype PickTarget = 'preview' | 'logo' | 'screenshot' | 'body' | null\n\nexport interface ListingDetailEditorProps {\n orgId: string\n listingId: string\n listing: Record<string, any>\n /** Firebase user, for the ID token on the update requests. */\n user: unknown\n /** Leave edit mode (Cancel or after a successful save). */\n onDone: () => void\n}\n\n/**\n * Full-page listing editor (AGL-869): the owner edits the whole detail page,\n * not a cramped sidebar card. The body uses the same WYSIWYG markdown editor\n * as blog content (`MarkdownVisualEditor`), and every image — preview, logo,\n * screenshots, and inline body images — picks from the shared DAM.\n *\n * Content saves through the marketplace plugin's HTTP API (`update-listing`);\n * the preview image goes through its dedicated endpoint since it isn't a\n * content field. Both are merges, so an unchanged field is left untouched.\n */\nexport function ListingDetailEditor(props: ListingDetailEditorProps) {\n const { orgId, listingId, listing, user, onDone } = props\n const { enqueueSnackbar } = useSnackbar()\n const editorRef = useRef<MarkdownFieldHandle | null>(null)\n const [busy, setBusy] = useState(false)\n // The publisher agreement, agreed to on this save (AGL-4105).\n const agreement = usePublisherAgreementConsent()\n\n const [values, setValues] = useState({\n displayName: '',\n description: '',\n previewImageUrl: '',\n logoUrl: '',\n homepageUrl: '',\n repositoryUrl: '',\n license: '',\n category: '',\n })\n const [readme, setReadme] = useState('')\n const [screenshots, setScreenshots] = useState<string[]>([])\n const originalPreview = useRef<string>('')\n\n useEffect(() => {\n setValues({\n displayName: listing?.displayName ?? '',\n description: listing?.description ?? '',\n previewImageUrl: listing?.previewImageUrl ?? '',\n logoUrl: listing?.logoUrl ?? '',\n homepageUrl: listing?.homepageUrl ?? '',\n repositoryUrl: listing?.repositoryUrl ?? '',\n license: listing?.license ?? '',\n category: listing?.categories?.[0] ?? listing?.category ?? '',\n })\n setReadme(listing?.readme ?? '')\n setScreenshots(\n Array.isArray(listing?.screenshots) ? listing.screenshots : [],\n )\n originalPreview.current = listing?.previewImageUrl ?? ''\n }, [listing?.$id])\n\n const set = (key: keyof typeof values) => (event: any) =>\n setValues((current) => ({ ...current, [key]: event.target.value }))\n\n /*\n * The console's media library, opened through the picker context rather\n * than by mounting its dialog (AGL-3080). The library is coupled to the\n * org and the session, which a plugin has no way to reach, and the console\n * provides `pickMedia` around every plugin page for exactly this — so the\n * `pickTarget` state that only existed to keep one dialog open for four\n * buttons goes with it: the target is now the argument.\n *\n * The URL comes back already resolved (CDN path over the raw download URL,\n * AGL-1215), which is what `mediaSrc` was doing here.\n */\n const { pickMedia } = useMediaPicker()\n const onPickMedia = async (target: Exclude<PickTarget, null>) => {\n const media = await pickMedia?.({ kind: 'image' })\n if (!media) return\n const url = media.url\n if (!url) return\n if (target === 'preview') setValues((c) => ({ ...c, previewImageUrl: url }))\n else if (target === 'logo') setValues((c) => ({ ...c, logoUrl: url }))\n else if (target === 'body') {\n // The README BODY takes a reference; the three image FIELDS above keep\n // `mediaSrc` (AGL-1705). Not an inconsistency — a field is one string\n // with known readers and its own AGL-1701 validator, while the body is\n // markdown that AGL-1686 taught every renderer to resolve.\n //\n // The absolute form was justified by `mediaSrc`'s own docstring — it\n // prefixes the origin because the markdown \"renders elsewhere\". That\n // was checked rather than assumed, and it is not true. The renderer\n // `MarketplaceListingContent` is registered through\n // `registerConsoleExtension`; its `marketplaceListing` slot has exactly\n // one render site, the console's `/[orgSlug]/marketplace/[listingId]`\n // route; and `apps/tenant` never imports the marketplace plugin at all.\n // The body reaches no other origin by another door either — the social\n // card builds `og:image` from `previewImageUrl`/`logoUrl`, never from\n // the README.\n //\n // So a site-relative resolve lands on the console origin, which serves\n // `/api/media/cdn/[...path]` — the very route the absolute URL pointed\n // at. Same bytes over the same route, minus a baked-in origin and a\n // baked-in route shape that cannot be changed without a migration.\n const src = formatMediaRef(media.mediaScope, media.mediaId) ?? url\n // The alt was a hardcoded '' (AGL-1896). This surface offers no alt\n // prompt at all, so the asset's own alt was the only description that\n // could ever have reached the row — and a markdown-lite image row's\n // alt cannot be edited after insertion, so '' was permanent. Still ''\n // for an asset nobody has described; never a fabricated one.\n editorRef.current?.insertImage(\n inheritedMediaAlt({ assetAlt: media.alt }) ?? '',\n src,\n )\n } else if (target === 'screenshot')\n setScreenshots((current) =>\n current.length >= MAX_SCREENSHOTS || current.includes(url)\n ? current\n : [...current, url],\n )\n }\n\n const save = async () => {\n if (!agreement.consent) return\n setBusy(true)\n try {\n const headers = { 'Content-Type': 'application/json' }\n // Preview image isn't a content field, so it has its own endpoint\n // (AGL-863). Only touch it when it actually changed. Both requests carry\n // the one agreement this save was made with (AGL-4105), and a refused\n // preview stops the save rather than saving the rest without it.\n if (values.previewImageUrl !== originalPreview.current) {\n const preview = await authorizedFetch(user, '/api/marketplace/preview-image', {\n method: values.previewImageUrl ? 'POST' : 'DELETE',\n headers,\n body: JSON.stringify({\n listingId,\n ...(values.previewImageUrl ? { url: values.previewImageUrl } : {}),\n agreement: agreement.consent,\n }),\n })\n if (!preview.ok) {\n const refused = await preview.json().catch(() => ({}))\n return void enqueueSnackbar(refused?.error ?? 'Update failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n originalPreview.current = values.previewImageUrl\n }\n const response = await authorizedFetch(\n user,\n '/api/marketplace/publish-plugin',\n {\n method: 'POST',\n headers,\n body: JSON.stringify({\n action: 'update-listing',\n listingId,\n displayName: values.displayName,\n description: values.description,\n readme,\n logoUrl: values.logoUrl,\n homepageUrl: values.homepageUrl,\n repositoryUrl: values.repositoryUrl,\n license: values.license,\n categories: values.category ? [values.category] : [],\n screenshots,\n agreement: agreement.consent,\n }),\n },\n )\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n return void enqueueSnackbar(payload?.error ?? 'Update failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n enqueueSnackbar('Listing updated', { variant: 'success', persist: false })\n // Each save is its own agreement: the next one ticks the box again.\n agreement.reset()\n onDone()\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\n return (\n <CardDisplay\n header={'Edit listing'}\n help={pluginDocsHelp('publisherHandbook', { anchor: '#edit-listing' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Everything here shows on the public listing page. The body is ' +\n 'rich text — use the toolbar, and add images from your media ' +\n 'library.'}\n </Typography>\n\n <TextField\n label=\"Listing name\"\n value={values.displayName}\n onChange={set('displayName')}\n size=\"small\"\n fullWidth\n />\n <TextField\n label=\"Description\"\n helperText=\"One line, shown on every browse card\"\n value={values.description}\n onChange={set('description')}\n size=\"small\"\n multiline\n minRows={2}\n fullWidth\n />\n\n {/* Preview image — the hero shown on cards and at the top of detail. */}\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{'Preview image'}</Typography>\n {/* Resolved for DISPLAY only (AGL-1424). `values.previewImageUrl`\n stays the stored form — it is what the save POSTs, and writing\n the resolved URL back would undo the reference. */}\n {resolveMediaSrc(values.previewImageUrl) ? (\n <Box\n component=\"img\"\n src={resolveMediaSrc(values.previewImageUrl)}\n alt=\"Preview\"\n sx={{\n width: '100%',\n maxHeight: 220,\n objectFit: 'cover',\n borderRadius: 1,\n border: 1,\n borderColor: 'divider',\n }}\n />\n ) : null}\n <Stack direction=\"row\" spacing={1}>\n <Button size=\"small\" onClick={() => onPickMedia('preview')}>\n {values.previewImageUrl ? 'Replace' : 'Choose from library'}\n </Button>\n {values.previewImageUrl ? (\n <Button\n size=\"small\"\n color=\"error\"\n onClick={() =>\n setValues((c) => ({ ...c, previewImageUrl: '' }))\n }\n >\n {'Remove'}\n </Button>\n ) : null}\n </Stack>\n </Stack>\n\n <Divider />\n\n {/* Body — the WYSIWYG markdown editor, same as blog content, and\n since AGL-1080 the same component the publish form uses. */}\n <MarkdownField\n label=\"About\"\n value={readme}\n onChange={setReadme}\n onPickImageFromMedia={() => onPickMedia('body')}\n editorRef={(handle) => {\n editorRef.current = handle\n }}\n />\n\n <Divider />\n\n {/* Screenshots gallery. */}\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">\n {`Screenshots (${screenshots.length}/${MAX_SCREENSHOTS})`}\n </Typography>\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap' }}>\n {screenshots.map((url, index) => (\n <Box key={url} sx={{ position: 'relative' }}>\n <Box\n component=\"img\"\n src={url}\n alt={`Screenshot ${index + 1}`}\n sx={{\n height: 72,\n borderRadius: 1,\n border: 1,\n borderColor: 'divider',\n }}\n />\n <Button\n size=\"small\"\n color=\"error\"\n onClick={() =>\n setScreenshots((current) =>\n current.filter((entry) => entry !== url),\n )\n }\n >\n {'Remove'}\n </Button>\n </Box>\n ))}\n </Stack>\n {screenshots.length < MAX_SCREENSHOTS ? (\n <Box>\n <Button size=\"small\" onClick={() => onPickMedia('screenshot')}>\n {'Add from library'}\n </Button>\n </Box>\n ) : null}\n </Stack>\n\n <Divider />\n\n {/* Logo + metadata. */}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'flex-start' }}>\n <TextField\n label=\"Logo URL (https)\"\n value={values.logoUrl}\n onChange={set('logoUrl')}\n size=\"small\"\n sx={{ flex: 1 }}\n helperText={LISTING_LOGO_HINT}\n />\n <Button\n size=\"small\"\n onClick={() => onPickMedia('logo')}\n sx={{ mt: 0.5 }}\n >\n {'Library'}\n </Button>\n </Stack>\n <TextField\n label=\"Homepage (https)\"\n value={values.homepageUrl}\n onChange={set('homepageUrl')}\n size=\"small\"\n fullWidth\n />\n <TextField\n label=\"Repository (https)\"\n value={values.repositoryUrl}\n onChange={set('repositoryUrl')}\n size=\"small\"\n fullWidth\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"License\"\n value={values.license}\n onChange={set('license')}\n size=\"small\"\n sx={{ flex: 1 }}\n />\n <TextField\n select\n label=\"Category\"\n value={values.category}\n onChange={set('category')}\n size=\"small\"\n sx={{ flex: 1 }}\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {LISTING_CATEGORIES.map((entry) => (\n <MenuItem key={entry} value={entry}>\n {entry}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n\n {/* Directly above Save listing (AGL-4105), which waits for it. */}\n <PublisherAgreementCheckbox\n checked={agreement.agreed}\n onChange={agreement.setAgreed}\n disabled={busy}\n />\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={busy || !values.displayName.trim() || !agreement.agreed}\n onClick={() => void save()}\n >\n {busy ? 'Saving…' : 'Save listing'}\n </Button>\n <Button disabled={busy} onClick={onDone}>\n {'Cancel'}\n </Button>\n </Stack>\n </Stack>\n\n </CardDisplay>\n )\n}\n\nListingDetailEditor.displayName = 'ListingDetailEditor'\n\nexport default ListingDetailEditor\n"],"names":["CardDisplay","useSnackbar","resolveMediaSrc","inheritedMediaAlt","authorizedFetch","Box","Button","Divider","MenuItem","Stack","TextField","Typography","useEffect","useRef","useState","LISTING_LOGO_HINT","formatMediaRef","pluginDocsHelp","useMediaPicker","MarkdownField","PublisherAgreementCheckbox","usePublisherAgreementConsent","LISTING_CATEGORIES","MAX_SCREENSHOTS","ListingDetailEditor","props","orgId","listingId","listing","user","onDone","enqueueSnackbar","editorRef","busy","setBusy","agreement","values","setValues","displayName","description","previewImageUrl","logoUrl","homepageUrl","repositoryUrl","license","category","readme","setReadme","screenshots","setScreenshots","originalPreview","categories","Array","isArray","current","$id","set","key","event","target","value","pickMedia","onPickMedia","media","kind","url","c","src","mediaScope","mediaId","insertImage","assetAlt","alt","length","includes","save","consent","headers","preview","method","body","JSON","stringify","ok","refused","json","catch","error","variant","allowDuplicate","response","action","payload","persist","reset","console","header","help","anchor","contentGutterX","contentGutterY","spacing","color","label","onChange","size","fullWidth","helperText","multiline","minRows","component","sx","width","maxHeight","objectFit","borderRadius","border","borderColor","direction","onClick","onPickImageFromMedia","handle","useFlexGap","flexWrap","map","index","position","height","filter","entry","alignItems","flex","mt","select","checked","agreed","setAgreed","disabled","trim"],"mappings":"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"}
@@ -22,6 +22,7 @@ import { timestampMs, VERIFICATION_DECLINE_COOLDOWN_DAYS, verificationRequestBlo
22
22
  import { PLATFORM_BRAND_NAME } from "@aglyn/aglyn/app-utils/platform-brand";
23
23
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
24
24
  import { pluginDocsHelp } from "@aglyn/aglyn";
25
+ import { PublisherAgreementCheckbox, usePublisherAgreementConsent } from "./publisher-agreement-checkbox.component.js";
25
26
  /** What the badge actually claims, said once, in the place someone asks for
26
27
  * it — so the request is informed rather than a guess at what it buys.
27
28
  *
@@ -53,6 +54,9 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
53
54
  // Optimistically reflects the action just taken, so the card does not sit
54
55
  // unchanged while the listing snapshot catches up.
55
56
  const [justDid, setJustDid] = useState(null);
57
+ // The publisher agreement, agreed to on this save (AGL-4105): asking for
58
+ // the badge and taking the ask back are publisher saves like any other.
59
+ const agreement = usePublisherAgreementConsent();
56
60
  const request = listing == null ? void 0 : listing.verificationRequest;
57
61
  const state = justDid != null ? justDid : request == null ? void 0 : request.state;
58
62
  const block = verificationRequestBlock({
@@ -61,6 +65,7 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
61
65
  nowMs: Date.now()
62
66
  });
63
67
  const post = async (action)=>{
68
+ if (!agreement.consent) return;
64
69
  setBusy(true);
65
70
  setError(null);
66
71
  try {
@@ -71,7 +76,8 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
71
76
  },
72
77
  body: JSON.stringify({
73
78
  listingId,
74
- action
79
+ action,
80
+ agreement: agreement.consent
75
81
  })
76
82
  });
77
83
  const data = await response.json().catch(()=>({}));
@@ -83,6 +89,7 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
83
89
  return;
84
90
  }
85
91
  setJustDid(action === 'request' ? 'pending' : 'withdrawn');
92
+ agreement.reset();
86
93
  } catch (unused) {
87
94
  setError('That did not work. Try again.');
88
95
  } finally{
@@ -122,12 +129,17 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
122
129
  severity: "info",
123
130
  children: 'Your request is with the review team. Your plugin stays ' + 'listed and installable while they look.'
124
131
  }),
132
+ /*#__PURE__*/ _jsx(PublisherAgreementCheckbox, {
133
+ checked: agreement.agreed,
134
+ onChange: agreement.setAgreed,
135
+ disabled: busy
136
+ }),
125
137
  /*#__PURE__*/ _jsx(Stack, {
126
138
  direction: "row",
127
139
  children: /*#__PURE__*/ _jsx(Button, {
128
140
  variant: "outlined",
129
141
  color: "inherit",
130
- disabled: busy,
142
+ disabled: busy || !agreement.agreed,
131
143
  onClick: ()=>void post('withdraw'),
132
144
  children: 'Withdraw request'
133
145
  })
@@ -149,15 +161,24 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
149
161
  })
150
162
  ]
151
163
  })
152
- }) : /*#__PURE__*/ _jsx(Stack, {
153
- direction: "row",
154
- children: /*#__PURE__*/ _jsx(Button, {
155
- variant: "contained",
156
- color: "primary",
157
- disabled: busy || block !== null,
158
- onClick: ()=>void post('request'),
159
- children: 'Request verification'
160
- })
164
+ }) : /*#__PURE__*/ _jsxs(_Fragment, {
165
+ children: [
166
+ /*#__PURE__*/ _jsx(PublisherAgreementCheckbox, {
167
+ checked: agreement.agreed,
168
+ onChange: agreement.setAgreed,
169
+ disabled: busy || block !== null
170
+ }),
171
+ /*#__PURE__*/ _jsx(Stack, {
172
+ direction: "row",
173
+ children: /*#__PURE__*/ _jsx(Button, {
174
+ variant: "contained",
175
+ color: "primary",
176
+ disabled: busy || block !== null || !agreement.agreed,
177
+ onClick: ()=>void post('request'),
178
+ children: 'Request verification'
179
+ })
180
+ })
181
+ ]
161
182
  }),
162
183
  error ? /*#__PURE__*/ _jsx(Alert, {
163
184
  severity: "error",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/listing-verification-request.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\n'use client'\n\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Alert, Button, Stack, Typography } from '@mui/material'\nimport { useState } from 'react'\nimport {\n timestampMs,\n VERIFICATION_DECLINE_COOLDOWN_DAYS,\n verificationRequestBlock,\n type VerifiableListing,\n} from '../model/listing-verification'\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface ListingVerificationRequestProps {\n listingId: string\n listing: VerifiableListing | null | undefined\n /** The org the viewer is acting as. */\n viewerOrgId: string | null | undefined\n user: unknown\n /** Verified is a plugin-listing claim; other artifact types never ask. */\n isPlugin: boolean\n}\n\n/** What the badge actually claims, said once, in the place someone asks for\n * it — so the request is informed rather than a guess at what it buys.\n *\n * `PLATFORM_BRAND_NAME` and NOT the org's resolved `productName` (AGL-2351).\n * The sentence names WHO DID THE VERIFYING, and that is whoever operates this\n * deployment's marketplace — the reviewers who read the listing. Substituting\n * the viewing org's brand would make a white-label agency's console assert\n * that the agency vetted this publisher, which nobody at the agency did. The\n * marketplace is a platform-relationship surface: one catalog, one review\n * queue, shared by every org on the deployment. On a self-host install the\n * operator's own staff really are the humans in this sentence, and the env var\n * renames them with the rest of the product.\n */\nconst WHAT_VERIFIED_CLAIMS =\n `Verified says a human at ${PLATFORM_BRAND_NAME} confirmed who you are, ` +\n 'and that this listing describes what the code does. It is a claim about ' +\n 'you as a publisher, so it survives a version bump. It is not a security ' +\n 'guarantee, and it does not review the bytes of any particular release — ' +\n 'that is what Reviewed means, and it is earned per version.'\n\n/**\n * The publisher's door to the Verified badge (AGL-1217).\n *\n * Before this there was none: verification was reviewer-initiated only, so a\n * publisher who wanted the badge had no affordance and no way to learn that\n * asking was even possible.\n *\n * Eligibility comes from `verificationRequestBlock` — the same function the\n * server enforces. That sharing is deliberate: a rule the button knows and\n * the server does not is decoration, and a rule the server knows and the\n * button does not is a button that fails with no explanation.\n */\nexport function ListingVerificationRequest(\n props: ListingVerificationRequestProps,\n) {\n const { listingId, listing, viewerOrgId, user, isPlugin } = props\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n // Optimistically reflects the action just taken, so the card does not sit\n // unchanged while the listing snapshot catches up.\n const [justDid, setJustDid] = useState<'pending' | 'withdrawn' | null>(null)\n\n const request = listing?.verificationRequest\n const state = justDid ?? request?.state\n const block = verificationRequestBlock({\n listing,\n viewerOrgId,\n nowMs: Date.now(),\n })\n\n const post = async (action: 'request' | 'withdraw') => {\n setBusy(true)\n setError(null)\n try {\n const response = await authorizedFetch(\n user,\n '/api/marketplace/verification-request',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ listingId, action }),\n },\n )\n const data = (await response.json().catch(() => ({}))) as {\n error?: string\n }\n if (!response.ok) {\n // The server's own words. It re-checks the same policy, so when it\n // refuses it knows something this component's snapshot did not.\n setError(data.error ?? 'That did not work. Try again.')\n return\n }\n setJustDid(action === 'request' ? 'pending' : 'withdrawn')\n } catch {\n setError('That did not work. Try again.')\n } finally {\n setBusy(false)\n }\n }\n\n if (!isPlugin) return null\n // Not the publisher, or already carrying the badge: nothing to offer. The\n // badge itself is rendered on the listing body, so saying so here would be\n // repeating it in a card about asking.\n if (block === 'not-publisher' || block === 'already-verified') return null\n // A listing still in the queue has a review card above already explaining\n // where it stands. A second card saying \"and you cannot ask for a badge\n // yet\" adds noise to a publisher who has not asked for anything.\n if (block === 'not-listed' && state !== 'pending') return null\n\n const declinedAt = timestampMs(request?.decidedAt)\n const canAskAgain =\n declinedAt === null\n ? null\n : new Date(\n declinedAt + VERIFICATION_DECLINE_COOLDOWN_DAYS * 86_400_000,\n ).toLocaleDateString()\n\n return (\n <CardDisplay\n header={'Verified publisher'}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#asking-to-be-verified',\n excerpt:\n 'When you can ask, what happens while it waits, and what a decline ' +\n 'does and does not mean.',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {WHAT_VERIFIED_CLAIMS}\n </Typography>\n\n {state === 'pending' ? (\n <>\n <Alert severity=\"info\">\n {'Your request is with the review team. Your plugin stays ' +\n 'listed and installable while they look.'}\n </Alert>\n <Stack direction=\"row\">\n <Button\n variant=\"outlined\"\n color=\"inherit\"\n disabled={busy}\n onClick={() => void post('withdraw')}\n >\n {'Withdraw request'}\n </Button>\n </Stack>\n </>\n ) : block === 'cooling-down' ? (\n <Alert severity=\"warning\">\n <Stack spacing={1}>\n <Typography variant=\"body2\">\n {request?.declineReason\n ? `Declined: ${request.declineReason}`\n : 'A previous request was declined.'}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {canAskAgain\n ? `You can ask again from ${canAskAgain}.`\n : `You can ask again ${VERIFICATION_DECLINE_COOLDOWN_DAYS} days after the decision.`}\n </Typography>\n </Stack>\n </Alert>\n ) : (\n <Stack direction=\"row\">\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={busy || block !== null}\n onClick={() => void post('request')}\n >\n {'Request verification'}\n </Button>\n </Stack>\n )}\n\n {error ? <Alert severity=\"error\">{error}</Alert> : null}\n </Stack>\n </CardDisplay>\n )\n}\n\nexport default ListingVerificationRequest\n"],"names":["CardDisplay","Alert","Button","Stack","Typography","useState","timestampMs","VERIFICATION_DECLINE_COOLDOWN_DAYS","verificationRequestBlock","PLATFORM_BRAND_NAME","authorizedFetch","pluginDocsHelp","WHAT_VERIFIED_CLAIMS","ListingVerificationRequest","props","listingId","listing","viewerOrgId","user","isPlugin","busy","setBusy","error","setError","justDid","setJustDid","request","verificationRequest","state","block","nowMs","Date","now","post","action","response","method","headers","body","JSON","stringify","data","json","catch","ok","declinedAt","decidedAt","canAskAgain","toLocaleDateString","header","help","anchor","excerpt","contentGutterX","contentGutterY","spacing","variant","color","severity","direction","disabled","onClick","declineReason"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/listing-verification-request.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\n'use client'\n\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Alert, Button, Stack, Typography } from '@mui/material'\nimport { useState } from 'react'\nimport {\n timestampMs,\n VERIFICATION_DECLINE_COOLDOWN_DAYS,\n verificationRequestBlock,\n type VerifiableListing,\n} from '../model/listing-verification'\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport {\n PublisherAgreementCheckbox,\n usePublisherAgreementConsent,\n} from './publisher-agreement-checkbox.component'\n\nexport interface ListingVerificationRequestProps {\n listingId: string\n listing: VerifiableListing | null | undefined\n /** The org the viewer is acting as. */\n viewerOrgId: string | null | undefined\n user: unknown\n /** Verified is a plugin-listing claim; other artifact types never ask. */\n isPlugin: boolean\n}\n\n/** What the badge actually claims, said once, in the place someone asks for\n * it — so the request is informed rather than a guess at what it buys.\n *\n * `PLATFORM_BRAND_NAME` and NOT the org's resolved `productName` (AGL-2351).\n * The sentence names WHO DID THE VERIFYING, and that is whoever operates this\n * deployment's marketplace — the reviewers who read the listing. Substituting\n * the viewing org's brand would make a white-label agency's console assert\n * that the agency vetted this publisher, which nobody at the agency did. The\n * marketplace is a platform-relationship surface: one catalog, one review\n * queue, shared by every org on the deployment. On a self-host install the\n * operator's own staff really are the humans in this sentence, and the env var\n * renames them with the rest of the product.\n */\nconst WHAT_VERIFIED_CLAIMS =\n `Verified says a human at ${PLATFORM_BRAND_NAME} confirmed who you are, ` +\n 'and that this listing describes what the code does. It is a claim about ' +\n 'you as a publisher, so it survives a version bump. It is not a security ' +\n 'guarantee, and it does not review the bytes of any particular release — ' +\n 'that is what Reviewed means, and it is earned per version.'\n\n/**\n * The publisher's door to the Verified badge (AGL-1217).\n *\n * Before this there was none: verification was reviewer-initiated only, so a\n * publisher who wanted the badge had no affordance and no way to learn that\n * asking was even possible.\n *\n * Eligibility comes from `verificationRequestBlock` — the same function the\n * server enforces. That sharing is deliberate: a rule the button knows and\n * the server does not is decoration, and a rule the server knows and the\n * button does not is a button that fails with no explanation.\n */\nexport function ListingVerificationRequest(\n props: ListingVerificationRequestProps,\n) {\n const { listingId, listing, viewerOrgId, user, isPlugin } = props\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n // Optimistically reflects the action just taken, so the card does not sit\n // unchanged while the listing snapshot catches up.\n const [justDid, setJustDid] = useState<'pending' | 'withdrawn' | null>(null)\n // The publisher agreement, agreed to on this save (AGL-4105): asking for\n // the badge and taking the ask back are publisher saves like any other.\n const agreement = usePublisherAgreementConsent()\n\n const request = listing?.verificationRequest\n const state = justDid ?? request?.state\n const block = verificationRequestBlock({\n listing,\n viewerOrgId,\n nowMs: Date.now(),\n })\n\n const post = async (action: 'request' | 'withdraw') => {\n if (!agreement.consent) return\n setBusy(true)\n setError(null)\n try {\n const response = await authorizedFetch(\n user,\n '/api/marketplace/verification-request',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ listingId, action, agreement: agreement.consent }),\n },\n )\n const data = (await response.json().catch(() => ({}))) as {\n error?: string\n }\n if (!response.ok) {\n // The server's own words. It re-checks the same policy, so when it\n // refuses it knows something this component's snapshot did not.\n setError(data.error ?? 'That did not work. Try again.')\n return\n }\n setJustDid(action === 'request' ? 'pending' : 'withdrawn')\n agreement.reset()\n } catch {\n setError('That did not work. Try again.')\n } finally {\n setBusy(false)\n }\n }\n\n if (!isPlugin) return null\n // Not the publisher, or already carrying the badge: nothing to offer. The\n // badge itself is rendered on the listing body, so saying so here would be\n // repeating it in a card about asking.\n if (block === 'not-publisher' || block === 'already-verified') return null\n // A listing still in the queue has a review card above already explaining\n // where it stands. A second card saying \"and you cannot ask for a badge\n // yet\" adds noise to a publisher who has not asked for anything.\n if (block === 'not-listed' && state !== 'pending') return null\n\n const declinedAt = timestampMs(request?.decidedAt)\n const canAskAgain =\n declinedAt === null\n ? null\n : new Date(\n declinedAt + VERIFICATION_DECLINE_COOLDOWN_DAYS * 86_400_000,\n ).toLocaleDateString()\n\n return (\n <CardDisplay\n header={'Verified publisher'}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#asking-to-be-verified',\n excerpt:\n 'When you can ask, what happens while it waits, and what a decline ' +\n 'does and does not mean.',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {WHAT_VERIFIED_CLAIMS}\n </Typography>\n\n {state === 'pending' ? (\n <>\n <Alert severity=\"info\">\n {'Your request is with the review team. Your plugin stays ' +\n 'listed and installable while they look.'}\n </Alert>\n <PublisherAgreementCheckbox\n checked={agreement.agreed}\n onChange={agreement.setAgreed}\n disabled={busy}\n />\n <Stack direction=\"row\">\n <Button\n variant=\"outlined\"\n color=\"inherit\"\n disabled={busy || !agreement.agreed}\n onClick={() => void post('withdraw')}\n >\n {'Withdraw request'}\n </Button>\n </Stack>\n </>\n ) : block === 'cooling-down' ? (\n <Alert severity=\"warning\">\n <Stack spacing={1}>\n <Typography variant=\"body2\">\n {request?.declineReason\n ? `Declined: ${request.declineReason}`\n : 'A previous request was declined.'}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {canAskAgain\n ? `You can ask again from ${canAskAgain}.`\n : `You can ask again ${VERIFICATION_DECLINE_COOLDOWN_DAYS} days after the decision.`}\n </Typography>\n </Stack>\n </Alert>\n ) : (\n <>\n <PublisherAgreementCheckbox\n checked={agreement.agreed}\n onChange={agreement.setAgreed}\n disabled={busy || block !== null}\n />\n <Stack direction=\"row\">\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={busy || block !== null || !agreement.agreed}\n onClick={() => void post('request')}\n >\n {'Request verification'}\n </Button>\n </Stack>\n </>\n )}\n\n {error ? <Alert severity=\"error\">{error}</Alert> : null}\n </Stack>\n </CardDisplay>\n )\n}\n\nexport default ListingVerificationRequest\n"],"names":["CardDisplay","Alert","Button","Stack","Typography","useState","timestampMs","VERIFICATION_DECLINE_COOLDOWN_DAYS","verificationRequestBlock","PLATFORM_BRAND_NAME","authorizedFetch","pluginDocsHelp","PublisherAgreementCheckbox","usePublisherAgreementConsent","WHAT_VERIFIED_CLAIMS","ListingVerificationRequest","props","listingId","listing","viewerOrgId","user","isPlugin","busy","setBusy","error","setError","justDid","setJustDid","agreement","request","verificationRequest","state","block","nowMs","Date","now","post","action","consent","response","method","headers","body","JSON","stringify","data","json","catch","ok","reset","declinedAt","decidedAt","canAskAgain","toLocaleDateString","header","help","anchor","excerpt","contentGutterX","contentGutterY","spacing","variant","color","severity","checked","agreed","onChange","setAgreed","disabled","direction","onClick","declineReason"],"mappings":"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"}