@aglyn/plugins-marketplace 1.0.0-beta.149 → 1.0.0-beta.151

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 (92) hide show
  1. package/package.json +15 -11
  2. package/src/lib/components/edit-listing-dialog.component.d.ts +20 -0
  3. package/src/lib/components/edit-listing-dialog.component.js +51 -0
  4. package/src/lib/components/edit-listing-dialog.component.js.map +1 -0
  5. package/src/lib/components/listing-detail-editor.component.d.ts +24 -0
  6. package/src/lib/components/listing-detail-editor.component.js +457 -0
  7. package/src/lib/components/listing-detail-editor.component.js.map +1 -0
  8. package/src/lib/components/listing-detail-page.component.d.ts +28 -0
  9. package/src/lib/components/listing-detail-page.component.js +151 -0
  10. package/src/lib/components/listing-detail-page.component.js.map +1 -0
  11. package/src/lib/components/listing-review-status.component.d.ts +67 -0
  12. package/src/lib/components/listing-review-status.component.js +301 -0
  13. package/src/lib/components/listing-review-status.component.js.map +1 -0
  14. package/src/lib/components/listing-verification-request.component.d.ts +40 -0
  15. package/src/lib/components/listing-verification-request.component.js +170 -0
  16. package/src/lib/components/listing-verification-request.component.js.map +1 -0
  17. package/src/lib/components/marketplace-hub.component.d.ts +43 -0
  18. package/src/lib/components/marketplace-hub.component.js +244 -0
  19. package/src/lib/components/marketplace-hub.component.js.map +1 -0
  20. package/src/lib/components/marketplace-reports-page.component.d.ts +23 -0
  21. package/src/lib/components/marketplace-reports-page.component.js +324 -0
  22. package/src/lib/components/marketplace-reports-page.component.js.map +1 -0
  23. package/src/lib/components/org-licences-panel.component.d.ts +73 -0
  24. package/src/lib/components/org-licences-panel.component.js +318 -0
  25. package/src/lib/components/org-licences-panel.component.js.map +1 -0
  26. package/src/lib/components/org-publish-panel.component.d.ts +45 -0
  27. package/src/lib/components/org-publish-panel.component.js +402 -0
  28. package/src/lib/components/org-publish-panel.component.js.map +1 -0
  29. package/src/lib/components/org-seller-panel.component.d.ts +28 -0
  30. package/src/lib/components/org-seller-panel.component.js +956 -0
  31. package/src/lib/components/org-seller-panel.component.js.map +1 -0
  32. package/src/lib/components/plugin-review-detail.component.d.ts +16 -0
  33. package/src/lib/components/plugin-review-detail.component.js +1438 -0
  34. package/src/lib/components/plugin-review-detail.component.js.map +1 -0
  35. package/src/lib/components/plugin-reviews-queue.component.d.ts +13 -0
  36. package/src/lib/components/plugin-reviews-queue.component.js +385 -0
  37. package/src/lib/components/plugin-reviews-queue.component.js.map +1 -0
  38. package/src/lib/components/plugin-reviews-surface.component.d.ts +19 -0
  39. package/src/lib/components/plugin-reviews-surface.component.js +46 -0
  40. package/src/lib/components/plugin-reviews-surface.component.js.map +1 -0
  41. package/src/lib/components/publish-plugin-form.component.d.ts +61 -0
  42. package/src/lib/components/publish-plugin-form.component.js +857 -0
  43. package/src/lib/components/publish-plugin-form.component.js.map +1 -0
  44. package/src/lib/components/publish-plugin-page.component.d.ts +29 -0
  45. package/src/lib/components/publish-plugin-page.component.js +92 -0
  46. package/src/lib/components/publish-plugin-page.component.js.map +1 -0
  47. package/src/lib/components/publisher-profile-page.component.d.ts +13 -0
  48. package/src/lib/components/publisher-profile-page.component.js +261 -0
  49. package/src/lib/components/publisher-profile-page.component.js.map +1 -0
  50. package/src/lib/constants/listing-media.d.ts +28 -0
  51. package/src/lib/constants/listing-media.js +28 -0
  52. package/src/lib/constants/listing-media.js.map +1 -0
  53. package/src/lib/model/listing-social-card.d.ts +94 -0
  54. package/src/lib/model/listing-social-card.js +177 -0
  55. package/src/lib/model/listing-social-card.js.map +1 -0
  56. package/src/lib/model/marketplace-paths.d.ts +54 -0
  57. package/src/lib/model/marketplace-paths.js +59 -0
  58. package/src/lib/model/marketplace-paths.js.map +1 -0
  59. package/src/lib/model/payout-readiness.d.ts +81 -0
  60. package/src/lib/model/payout-readiness.js +52 -0
  61. package/src/lib/model/payout-readiness.js.map +1 -0
  62. package/src/lib/model/plugin-rejection-categories.d.ts +66 -0
  63. package/src/lib/model/plugin-rejection-categories.js +124 -0
  64. package/src/lib/model/plugin-rejection-categories.js.map +1 -0
  65. package/src/lib/model/plugin-review-checklist.d.ts +62 -0
  66. package/src/lib/model/plugin-review-checklist.js +131 -0
  67. package/src/lib/model/plugin-review-checklist.js.map +1 -0
  68. package/src/lib/model/plugin-review-status.d.ts +104 -0
  69. package/src/lib/model/plugin-review-status.js +126 -0
  70. package/src/lib/model/plugin-review-status.js.map +1 -0
  71. package/src/lib/model/seller-ledger-totals.d.ts +100 -0
  72. package/src/lib/model/seller-ledger-totals.js +96 -0
  73. package/src/lib/model/seller-ledger-totals.js.map +1 -0
  74. package/src/lib/plugin.js +170 -40
  75. package/src/lib/plugin.js.map +1 -1
  76. package/src/lib/server/admin-reports.d.ts +18 -0
  77. package/src/lib/server/admin-reports.js +280 -0
  78. package/src/lib/server/admin-reports.js.map +1 -0
  79. package/src/lib/server/admin-reviews.d.ts +18 -0
  80. package/src/lib/server/admin-reviews.js +1341 -0
  81. package/src/lib/server/admin-reviews.js.map +1 -0
  82. package/src/lib/server/listing-social-card.server.d.ts +24 -0
  83. package/src/lib/server/listing-social-card.server.js +65 -0
  84. package/src/lib/server/listing-social-card.server.js.map +1 -0
  85. package/src/lib/server/publisher-review-email.d.ts +54 -0
  86. package/src/lib/server/publisher-review-email.js +85 -0
  87. package/src/lib/server/publisher-review-email.js.map +1 -0
  88. package/src/lib/server/revoke-cache-drop.d.ts +59 -0
  89. package/src/lib/server/revoke-cache-drop.js +74 -0
  90. package/src/lib/server/revoke-cache-drop.js.map +1 -0
  91. package/src/lib/server.js +47 -0
  92. package/src/lib/server.js.map +1 -1
@@ -0,0 +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', {\n anchor: '#authoring-your-listing',\n excerpt:\n 'What buyers see on the public listing page — name, summary, ' +\n 'rich-text body, and media.',\n })}\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 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","excerpt","contentGutterX","contentGutterY","spacing","color","label","onChange","size","fullWidth","helperText","multiline","minRows","component","sx","width","maxHeight","objectFit","borderRadius","border","borderColor","direction","onClick","onPickImageFromMedia","handle","flexWrap","map","index","position","height","filter","entry","alignItems","flex","mt","select","pt","disabled","trim"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Org marketplace listing detail (AGL-772), at `{marketplace}/{listingId}`.
3
+ *
4
+ * It was a console route until AGL-3080, which is why it used to reach the
5
+ * body through the `marketplaceListing` zone: an app page could not import
6
+ * this plugin. This page IS the plugin now, so it renders the content
7
+ * directly — the zone stays registered for the surfaces that still need it.
8
+ *
9
+ * Install pins validate against a site, so the detail acts through the org's
10
+ * first site until targeting lands (AGL-773).
11
+ */
12
+ export declare function ListingDetailPage(props: {
13
+ listingId: string;
14
+ orgId: string | null;
15
+ basePath: string;
16
+ /** The site an install acts through, and every site for the target picker. */
17
+ actingHost: string;
18
+ hosts: ReadonlyArray<{
19
+ id: string;
20
+ label: string;
21
+ }>;
22
+ permissions?: Record<string, boolean | undefined>;
23
+ orgSlug: string;
24
+ }): import("react").JSX.Element;
25
+ export declare namespace ListingDetailPage {
26
+ var displayName: string;
27
+ }
28
+ export default ListingDetailPage;
@@ -0,0 +1,151 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
18
+ import { PageHeaderActions, PageHeaderHelp, PageHeaderRecord } from "@aglyn/aglyn";
19
+ import { AppLink } from "@aglyn/shared-ui-jsx";
20
+ import { useFirestore, useFirestoreDoc, useUser } from "@aglyn/tenant-feature-instance";
21
+ import { Alert, Button, Stack } from "@mui/material";
22
+ import { doc } from "firebase/firestore";
23
+ import { useMemo, useState } from "react";
24
+ import ListingDetailEditor from "./listing-detail-editor.component.js";
25
+ import ListingReviewStatus from "./listing-review-status.component.js";
26
+ import ListingVerificationRequest from "./listing-verification-request.component.js";
27
+ import { MarketplaceListingContent } from "./listing-content.component.js";
28
+ import { publishPluginPath } from "../model/marketplace-paths.js";
29
+ /**
30
+ * Org marketplace listing detail (AGL-772), at `{marketplace}/{listingId}`.
31
+ *
32
+ * It was a console route until AGL-3080, which is why it used to reach the
33
+ * body through the `marketplaceListing` zone: an app page could not import
34
+ * this plugin. This page IS the plugin now, so it renders the content
35
+ * directly — the zone stays registered for the surfaces that still need it.
36
+ *
37
+ * Install pins validate against a site, so the detail acts through the org's
38
+ * first site until targeting lands (AGL-773).
39
+ */ export function ListingDetailPage(props) {
40
+ var _ref, _ref1;
41
+ const { listingId, orgId, basePath, actingHost, hosts, permissions, orgSlug } = props;
42
+ const firestore = useFirestore();
43
+ const { data: user } = useUser();
44
+ // The listing itself, so the owner can edit the whole page in place
45
+ // (AGL-869) rather than through a cramped sidebar card.
46
+ const { data: listing } = useFirestoreDoc(()=>doc(firestore, 'marketplaceListings', listingId || '-missing-'), [
47
+ firestore,
48
+ listingId
49
+ ], {
50
+ idField: '$id'
51
+ });
52
+ // Listings are org-owned (AGL-652): ownership is an org comparison.
53
+ const isOwner = Boolean(orgId && (listing == null ? void 0 : listing.profileId) === orgId);
54
+ const [editing, setEditing] = useState(false);
55
+ // Name the thing you are looking at (AGL-1000). The page used to announce
56
+ // itself as "Marketplace listing" over a breadcrumb ending in "Listing",
57
+ // which made the largest text on the page the one part identical on every
58
+ // listing. This page already reads the doc for the in-place editor, so the
59
+ // name costs nothing; the surface's declared title stays as the fallback
60
+ // for the beat before it resolves and for a listing that does not exist.
61
+ const listingName = String((_ref = listing == null ? void 0 : listing.displayName) != null ? _ref : '').trim();
62
+ const isPlugin = ((_ref1 = listing == null ? void 0 : listing.artifactType) != null ? _ref1 : listing == null ? void 0 : listing.type) === 'plugin';
63
+ /*
64
+ * The hero's owner actions, memoized (AGL-1005). `PageHeaderActions`
65
+ * publishes upward through an effect keyed on what it is handed, so a node
66
+ * rebuilt every render would republish every render. The dependencies are
67
+ * exactly the four facts the buttons read.
68
+ */ const ownerActions = useMemo(()=>isOwner && !editing ? /*#__PURE__*/ _jsxs(Stack, {
69
+ direction: "row",
70
+ spacing: 1,
71
+ children: [
72
+ isPlugin ? /*#__PURE__*/ _jsx(AppLink, {
73
+ href: publishPluginPath(basePath, listingId),
74
+ children: /*#__PURE__*/ _jsx(Button, {
75
+ variant: "contained",
76
+ color: "primary",
77
+ component: "span",
78
+ children: 'Publish new version'
79
+ })
80
+ }) : null,
81
+ /*#__PURE__*/ _jsx(Button, {
82
+ variant: "outlined",
83
+ color: "primary",
84
+ onClick: ()=>setEditing(true),
85
+ children: 'Edit listing'
86
+ })
87
+ ]
88
+ }) : null, [
89
+ isOwner,
90
+ editing,
91
+ isPlugin,
92
+ basePath,
93
+ listingId
94
+ ]);
95
+ return /*#__PURE__*/ _jsxs(_Fragment, {
96
+ children: [
97
+ /*#__PURE__*/ _jsx(PageHeaderRecord, {
98
+ title: listingName
99
+ }),
100
+ /*#__PURE__*/ _jsx(PageHeaderHelp, {
101
+ topic: "plugins",
102
+ anchor: "#what-the-badges-on-a-listing-mean"
103
+ }),
104
+ /*#__PURE__*/ _jsx(PageHeaderActions, {
105
+ children: ownerActions
106
+ }),
107
+ editing && isOwner && orgId && listing ? /*#__PURE__*/ _jsx(ListingDetailEditor, {
108
+ orgId: orgId,
109
+ listingId: listingId,
110
+ listing: listing,
111
+ user: user,
112
+ onDone: ()=>setEditing(false)
113
+ }) : /*#__PURE__*/ _jsxs(_Fragment, {
114
+ children: [
115
+ isOwner ? /*#__PURE__*/ _jsxs(_Fragment, {
116
+ children: [
117
+ /*#__PURE__*/ _jsx(ListingReviewStatus, {
118
+ listingId: listingId,
119
+ isPlugin: isPlugin
120
+ }),
121
+ /*#__PURE__*/ _jsx(ListingVerificationRequest, {
122
+ listingId: listingId,
123
+ listing: listing,
124
+ viewerOrgId: orgId != null ? orgId : undefined,
125
+ user: user,
126
+ isPlugin: isPlugin
127
+ })
128
+ ]
129
+ }) : null,
130
+ !actingHost ? // Owners can still edit their listing with no site of their own —
131
+ // through the hero's Edit listing action (AGL-1005).
132
+ /*#__PURE__*/ _jsx(Alert, {
133
+ severity: "info",
134
+ children: isOwner ? 'Add a site to your organization to install marketplace ' + 'items. You can still edit this listing above.' : 'Add a site to your organization to view and install ' + 'marketplace items.'
135
+ }) : /*#__PURE__*/ _jsx(MarketplaceListingContent, {
136
+ hostId: actingHost,
137
+ listingId: listingId,
138
+ permissions: permissions,
139
+ orgScoped: true,
140
+ orgSlug: orgSlug,
141
+ hosts: hosts
142
+ })
143
+ ]
144
+ })
145
+ ]
146
+ });
147
+ }
148
+ ListingDetailPage.displayName = 'ListingDetailPage';
149
+ export default ListingDetailPage;
150
+
151
+ //# sourceMappingURL=listing-detail-page.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/listing-detail-page.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 PageHeaderActions,\n PageHeaderHelp,\n PageHeaderRecord,\n} from '@aglyn/aglyn'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { useFirestore, useFirestoreDoc, useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Stack } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useMemo, useState } from 'react'\nimport ListingDetailEditor from './listing-detail-editor.component'\nimport ListingReviewStatus from './listing-review-status.component'\nimport ListingVerificationRequest from './listing-verification-request.component'\nimport { MarketplaceListingContent } from './listing-content.component'\nimport { publishPluginPath } from '../model/marketplace-paths'\n\n/**\n * Org marketplace listing detail (AGL-772), at `{marketplace}/{listingId}`.\n *\n * It was a console route until AGL-3080, which is why it used to reach the\n * body through the `marketplaceListing` zone: an app page could not import\n * this plugin. This page IS the plugin now, so it renders the content\n * directly — the zone stays registered for the surfaces that still need it.\n *\n * Install pins validate against a site, so the detail acts through the org's\n * first site until targeting lands (AGL-773).\n */\nexport function ListingDetailPage(props: {\n listingId: string\n orgId: string | null\n basePath: string\n /** The site an install acts through, and every site for the target picker. */\n actingHost: string\n hosts: ReadonlyArray<{ id: string; label: string }>\n permissions?: Record<string, boolean | undefined>\n orgSlug: string\n}) {\n const { listingId, orgId, basePath, actingHost, hosts, permissions, orgSlug } =\n props\n const firestore = useFirestore()\n const { data: user } = useUser()\n\n // The listing itself, so the owner can edit the whole page in place\n // (AGL-869) rather than through a cramped sidebar card.\n const { data: listing } = useFirestoreDoc<any>(\n () => doc(firestore, 'marketplaceListings', listingId || '-missing-'),\n [firestore, listingId],\n { idField: '$id' },\n )\n // Listings are org-owned (AGL-652): ownership is an org comparison.\n const isOwner = Boolean(orgId && listing?.profileId === orgId)\n const [editing, setEditing] = useState(false)\n\n // Name the thing you are looking at (AGL-1000). The page used to announce\n // itself as \"Marketplace listing\" over a breadcrumb ending in \"Listing\",\n // which made the largest text on the page the one part identical on every\n // listing. This page already reads the doc for the in-place editor, so the\n // name costs nothing; the surface's declared title stays as the fallback\n // for the beat before it resolves and for a listing that does not exist.\n const listingName = String(listing?.displayName ?? '').trim()\n const isPlugin = (listing?.artifactType ?? listing?.type) === 'plugin'\n\n /*\n * The hero's owner actions, memoized (AGL-1005). `PageHeaderActions`\n * publishes upward through an effect keyed on what it is handed, so a node\n * rebuilt every render would republish every render. The dependencies are\n * exactly the four facts the buttons read.\n */\n const ownerActions = useMemo(\n () =>\n isOwner && !editing ? (\n <Stack direction=\"row\" spacing={1}>\n {/* Shipping a new version had no door on the listing itself\n (AGL-1008) — Edit changes metadata and View changes nothing,\n so the only reading left was \"make a second listing\". Same\n publish form, pre-bound to this listing. Plugins only: a\n bundle is the thing a new version ships. */}\n {isPlugin ? (\n <AppLink href={publishPluginPath(basePath, listingId)}>\n <Button variant=\"contained\" color=\"primary\" component=\"span\">\n {'Publish new version'}\n </Button>\n </AppLink>\n ) : null}\n <Button\n variant=\"outlined\"\n color=\"primary\"\n onClick={() => setEditing(true)}\n >\n {'Edit listing'}\n </Button>\n </Stack>\n ) : null,\n [isOwner, editing, isPlugin, basePath, listingId],\n )\n\n return (\n <>\n <PageHeaderRecord title={listingName} />\n <PageHeaderHelp\n topic=\"plugins\"\n anchor=\"#what-the-badges-on-a-listing-mean\"\n />\n {/* Owner action in the hero (AGL-1005), where the chrome already has a\n slot for it. It used to float in a bare right-aligned box above the\n content — unanchored to anything, and it moved depending on whether\n the org had a site yet. */}\n <PageHeaderActions>{ownerActions}</PageHeaderActions>\n {editing && isOwner && orgId && listing ? (\n <ListingDetailEditor\n orgId={orgId}\n listingId={listingId}\n listing={listing}\n user={user}\n onDone={() => setEditing(false)}\n />\n ) : (\n <>\n {/* The publisher's own view of the queue (AGL-1079). Owner-only:\n review state, rejection reasons and what was attested are the\n publisher's business, and a buyer is never offered a version\n this card exists to explain. Above the buyer-facing body,\n because \"what is happening to my submission\" is why an owner\n opened their own listing. */}\n {isOwner ? (\n <>\n <ListingReviewStatus listingId={listingId} isPlugin={isPlugin} />\n {/* The door to the Verified badge (AGL-1217). Below the review\n card on purpose: \"where is my submission\" is the more urgent\n question, and asking for a badge only makes sense once the\n listing is live. */}\n <ListingVerificationRequest\n listingId={listingId}\n listing={listing}\n viewerOrgId={orgId ?? undefined}\n user={user}\n isPlugin={isPlugin}\n />\n </>\n ) : null}\n {!actingHost ? (\n // Owners can still edit their listing with no site of their own —\n // through the hero's Edit listing action (AGL-1005).\n <Alert severity=\"info\">\n {isOwner\n ? 'Add a site to your organization to install marketplace ' +\n 'items. You can still edit this listing above.'\n : 'Add a site to your organization to view and install ' +\n 'marketplace items.'}\n </Alert>\n ) : (\n <MarketplaceListingContent\n hostId={actingHost}\n listingId={listingId}\n permissions={permissions}\n orgScoped\n orgSlug={orgSlug}\n hosts={hosts}\n />\n )}\n </>\n )}\n </>\n )\n}\n\nListingDetailPage.displayName = 'ListingDetailPage'\n\nexport default ListingDetailPage\n"],"names":["PageHeaderActions","PageHeaderHelp","PageHeaderRecord","AppLink","useFirestore","useFirestoreDoc","useUser","Alert","Button","Stack","doc","useMemo","useState","ListingDetailEditor","ListingReviewStatus","ListingVerificationRequest","MarketplaceListingContent","publishPluginPath","ListingDetailPage","props","listingId","orgId","basePath","actingHost","hosts","permissions","orgSlug","firestore","data","user","listing","idField","isOwner","Boolean","profileId","editing","setEditing","listingName","String","displayName","trim","isPlugin","artifactType","type","ownerActions","direction","spacing","href","variant","color","component","onClick","title","topic","anchor","onDone","viewerOrgId","undefined","severity","hostId","orgScoped"],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import { PUBLISHER_ATTESTATION } from '@aglyn/aglyn/app-utils/publisher-attestation';
2
+ export interface PublisherVersion {
3
+ version: string;
4
+ publishedAtMs: number | null;
5
+ sha256: string;
6
+ reviewState: string;
7
+ grandfathered: boolean;
8
+ signed: boolean;
9
+ changelog: string;
10
+ rejectionReason: string;
11
+ /**
12
+ * The platform kill switch, per version (AGL-2328). Independent of
13
+ * `reviewState` and NOT a substitute for it: a version is normally
14
+ * approved first and revoked afterwards, so both are true at once and the
15
+ * card has to say both.
16
+ */
17
+ revoked?: boolean;
18
+ /**
19
+ * Reconciled against the listing totals server-side (AGL-1418), so this
20
+ * card and the listing's own Version history print the same number for the
21
+ * same version instead of two answers to one question.
22
+ */
23
+ activeInstalls: number;
24
+ installCount?: number;
25
+ attestation: string[];
26
+ }
27
+ /**
28
+ * A review state said in words (AGL-1079).
29
+ *
30
+ * `grandfathered` is checked FIRST and deliberately: those versions were
31
+ * published before review was per-version (AGL-966), so they carry no
32
+ * verdict at all. Calling them "pending" would promise a reviewer who is
33
+ * never coming, and calling them "approved" would claim a judgement nobody
34
+ * made.
35
+ */
36
+ declare function reviewLabel(entry: PublisherVersion): {
37
+ label: string;
38
+ color: 'default' | 'success' | 'warning' | 'error' | 'info';
39
+ detail: string;
40
+ };
41
+ export interface ListingReviewStatusProps {
42
+ listingId: string;
43
+ /** Only a plugin listing has a review-per-version story to tell. */
44
+ isPlugin: boolean;
45
+ }
46
+ /**
47
+ * What review is doing with a publisher's versions (AGL-1079).
48
+ *
49
+ * AGL-966's guarantee — a new version waits in the queue while the
50
+ * previously approved one keeps installing — has been true and invisible
51
+ * since the day it shipped. A publisher got an email on the outcome and,
52
+ * in between, a listing row identical to the one they had before they
53
+ * published. This is the page that answers what they submitted, what state
54
+ * it is in, which version their customers are actually on, and why one was
55
+ * sent back.
56
+ *
57
+ * Reads through the publisher-scoped versions endpoint rather than
58
+ * Firestore: `pluginVersions` is server-only, because the docs carry
59
+ * publish internals a client has no business holding.
60
+ */
61
+ export declare function ListingReviewStatus(props: ListingReviewStatusProps): import("react").JSX.Element;
62
+ export declare namespace ListingReviewStatus {
63
+ var displayName: string;
64
+ }
65
+ export default ListingReviewStatus;
66
+ /** Exported for the spec: the words each state is said in. */
67
+ export { reviewLabel, PUBLISHER_ATTESTATION };
@@ -0,0 +1,301 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
+ import { CardDisplay } from "@aglyn/shared-ui-jsx";
19
+ import { Alert, Chip, Divider, Stack, Typography } from "@mui/material";
20
+ import { useEffect, useState } from "react";
21
+ import { useUser } from "@aglyn/tenant-feature-instance";
22
+ import { attestationLabels, PUBLISHER_ATTESTATION, requiredAttestationIds } from "@aglyn/aglyn/app-utils/publisher-attestation";
23
+ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
24
+ import { pluginDocsHelp } from "@aglyn/aglyn";
25
+ /**
26
+ * A review state said in words (AGL-1079).
27
+ *
28
+ * `grandfathered` is checked FIRST and deliberately: those versions were
29
+ * published before review was per-version (AGL-966), so they carry no
30
+ * verdict at all. Calling them "pending" would promise a reviewer who is
31
+ * never coming, and calling them "approved" would claim a judgement nobody
32
+ * made.
33
+ */ function reviewLabel(entry) {
34
+ if (entry.grandfathered) {
35
+ return {
36
+ label: 'Published before review',
37
+ color: 'default',
38
+ detail: 'Published before versions were reviewed individually. It installs, ' + 'but no reviewer ever signed off on these bytes.'
39
+ };
40
+ }
41
+ switch(entry.reviewState){
42
+ case 'approved':
43
+ return {
44
+ label: 'Approved',
45
+ color: 'success',
46
+ detail: 'A reviewer approved these exact bytes.'
47
+ };
48
+ case 'rejected':
49
+ return {
50
+ label: 'Rejected',
51
+ color: 'error',
52
+ detail: 'Not installable. Fix what the reason says and publish a new ' + 'version — these bytes cannot be edited.'
53
+ };
54
+ case 'pending':
55
+ return {
56
+ label: 'In review',
57
+ color: 'warning',
58
+ detail: 'Waiting for a reviewer. Nobody else can install it — but you ' + 'can, on your own sites, which is how you test a version before ' + 'it is approved.'
59
+ };
60
+ default:
61
+ return {
62
+ label: 'Unknown',
63
+ color: 'default',
64
+ detail: 'No review state was recorded for this version.'
65
+ };
66
+ }
67
+ }
68
+ /**
69
+ * What review is doing with a publisher's versions (AGL-1079).
70
+ *
71
+ * AGL-966's guarantee — a new version waits in the queue while the
72
+ * previously approved one keeps installing — has been true and invisible
73
+ * since the day it shipped. A publisher got an email on the outcome and,
74
+ * in between, a listing row identical to the one they had before they
75
+ * published. This is the page that answers what they submitted, what state
76
+ * it is in, which version their customers are actually on, and why one was
77
+ * sent back.
78
+ *
79
+ * Reads through the publisher-scoped versions endpoint rather than
80
+ * Firestore: `pluginVersions` is server-only, because the docs carry
81
+ * publish internals a client has no business holding.
82
+ */ export function ListingReviewStatus(props) {
83
+ var _payload_revocationReason, _payload_revokedAtMs;
84
+ const { listingId, isPlugin } = props;
85
+ const { data: user } = useUser();
86
+ const [payload, setPayload] = useState(null);
87
+ const [failed, setFailed] = useState(false);
88
+ useEffect(()=>{
89
+ let cancelled = false;
90
+ if (!listingId || !isPlugin) return undefined;
91
+ void (async ()=>{
92
+ try {
93
+ const response = await authorizedFetch(user, `/api/marketplace/listing-versions?scope=publisher&listingId=${encodeURIComponent(listingId)}`);
94
+ if (!response.ok) {
95
+ if (!cancelled) setFailed(true);
96
+ return;
97
+ }
98
+ const data = await response.json();
99
+ if (!cancelled) setPayload(data);
100
+ } catch (unused) {
101
+ if (!cancelled) setFailed(true);
102
+ }
103
+ })();
104
+ return ()=>{
105
+ cancelled = true;
106
+ };
107
+ }, [
108
+ listingId,
109
+ isPlugin,
110
+ user
111
+ ]);
112
+ if (!isPlugin) return null;
113
+ if (failed) return null;
114
+ // Nothing rather than an empty shell: this card is only worth space once
115
+ // it has something to say, and a skeleton that never fills is worse than
116
+ // a card that appears.
117
+ if (!payload) return null;
118
+ const { versions, latestApprovedVersion, latestVersion } = payload;
119
+ const revocationReason = String((_payload_revocationReason = payload.revocationReason) != null ? _payload_revocationReason : '').trim();
120
+ const revokedAtMs = (_payload_revokedAtMs = payload.revokedAtMs) != null ? _payload_revokedAtMs : null;
121
+ const inReview = versions.filter((entry)=>entry.reviewState === 'pending' && !entry.grandfathered);
122
+ /**
123
+ * The version the card names as installing today, revoked (AGL-2328).
124
+ *
125
+ * `latestApprovedVersion` is a REVIEW verdict and the kill switch is not
126
+ * — nothing about revoking a version clears it — so the headline below
127
+ * went on saying "is approved and is what installs today" about a version
128
+ * the installer refuses with a 409. That is the sentence a publisher
129
+ * reads before opening a support ticket about installs that stopped
130
+ * working, so it is checked before any of the review arms.
131
+ */ const installsTodayRevoked = Boolean(latestApprovedVersion && versions.some((entry)=>entry.version === latestApprovedVersion && entry.revoked));
132
+ const behind = latestApprovedVersion && latestVersion && latestApprovedVersion !== latestVersion;
133
+ return /*#__PURE__*/ _jsx(CardDisplay, {
134
+ header: 'Review status',
135
+ help: pluginDocsHelp('publisherHandbook', {
136
+ anchor: '#review-what-happens-after-you-publish',
137
+ excerpt: 'What state each version is in, which one installs today, and why ' + 'anything was sent back.'
138
+ }),
139
+ contentGutterX: true,
140
+ contentGutterY: true,
141
+ children: /*#__PURE__*/ _jsxs(Stack, {
142
+ spacing: 2,
143
+ children: [
144
+ installsTodayRevoked ? /*#__PURE__*/ _jsx(Alert, {
145
+ severity: "error",
146
+ children: `v${latestApprovedVersion} was disabled by the platform. It is ` + 'still your latest approved version, but nobody can install ' + 'it and sites already running it render a placeholder. ' + 'Publishing a new version is what clears this — these bytes ' + 'cannot be re-enabled by editing them.'
147
+ }) : behind && inReview.length ? /*#__PURE__*/ _jsx(Alert, {
148
+ severity: "info",
149
+ children: `v${latestVersion} is in review. New installs get ` + `v${latestApprovedVersion} until it is approved, and anyone ` + 'already running it stays where they are.'
150
+ }) : behind ? /*#__PURE__*/ _jsx(Alert, {
151
+ severity: "warning",
152
+ children: `v${latestVersion} is not approved, so new installs get ` + `v${latestApprovedVersion}.`
153
+ }) : latestApprovedVersion ? /*#__PURE__*/ _jsx(Alert, {
154
+ severity: "success",
155
+ children: `v${latestApprovedVersion} is approved and is what installs today.`
156
+ }) : /*#__PURE__*/ _jsx(Alert, {
157
+ severity: "info",
158
+ children: 'Nothing is approved yet, so nobody else can install this. A ' + 'reviewer sees your submission in the queue. You can still ' + 'install it on your own sites to test it — that is the only ' + 'way these bytes run today.'
159
+ }),
160
+ /*#__PURE__*/ _jsx(Typography, {
161
+ variant: "caption",
162
+ color: "text.secondary",
163
+ children: 'While a version is in review you can still edit this listing — ' + 'name, description, README, screenshots and links — and it will ' + 'not send anything back to the queue, because approval is about ' + 'the bundle’s bytes. The bytes themselves are never edited: ' + 'changing them means publishing a new version.'
164
+ }),
165
+ /*#__PURE__*/ _jsx(Divider, {}),
166
+ /*#__PURE__*/ _jsxs(Stack, {
167
+ spacing: 2,
168
+ children: [
169
+ versions.map((entry)=>{
170
+ var _versions_;
171
+ const state = reviewLabel(entry);
172
+ const claimed = entry.attestation.length;
173
+ // Which set applied to THIS version: the first one published is
174
+ // not asked for a changelog, so measuring every version against
175
+ // the update set would report the first as permanently short.
176
+ const expected = requiredAttestationIds(entry.version !== ((_versions_ = versions[versions.length - 1]) == null ? void 0 : _versions_.version)).length;
177
+ return /*#__PURE__*/ _jsxs(Stack, {
178
+ spacing: 0.5,
179
+ children: [
180
+ /*#__PURE__*/ _jsxs(Stack, {
181
+ direction: "row",
182
+ spacing: 1,
183
+ sx: {
184
+ alignItems: 'center',
185
+ flexWrap: 'wrap'
186
+ },
187
+ children: [
188
+ /*#__PURE__*/ _jsx(Typography, {
189
+ variant: "subtitle2",
190
+ children: `v${entry.version}`
191
+ }),
192
+ /*#__PURE__*/ _jsx(Chip, {
193
+ size: "small",
194
+ color: state.color,
195
+ label: state.label
196
+ }),
197
+ entry.revoked ? /*#__PURE__*/ _jsx(Chip, {
198
+ size: "small",
199
+ color: "error",
200
+ label: "Disabled"
201
+ }) : null,
202
+ entry.version === latestApprovedVersion && !entry.revoked ? /*#__PURE__*/ _jsx(Chip, {
203
+ size: "small",
204
+ variant: "outlined",
205
+ label: "Installs today"
206
+ }) : null,
207
+ entry.signed ? /*#__PURE__*/ _jsx(Chip, {
208
+ size: "small",
209
+ variant: "outlined",
210
+ label: "Realm-trusted"
211
+ }) : null,
212
+ entry.activeInstalls ? /*#__PURE__*/ _jsx(Chip, {
213
+ size: "small",
214
+ variant: "outlined",
215
+ color: state.label === 'Approved' ? undefined : 'warning',
216
+ label: `${entry.activeInstalls} on this version`
217
+ }) : null
218
+ ]
219
+ }),
220
+ /*#__PURE__*/ _jsxs(Typography, {
221
+ variant: "caption",
222
+ color: "text.secondary",
223
+ children: [
224
+ state.detail,
225
+ entry.publishedAtMs ? ` Published ${new Date(entry.publishedAtMs).toLocaleDateString()}.` : ''
226
+ ]
227
+ }),
228
+ entry.activeInstalls && entry.reviewState !== 'approved' && !entry.grandfathered ? /*#__PURE__*/ _jsx(Alert, {
229
+ severity: "warning",
230
+ sx: {
231
+ mt: 0.5
232
+ },
233
+ children: `${entry.activeInstalls} of your installs ${entry.activeInstalls === 1 ? 'is' : 'are'} running this version, which no reviewer has approved. ` + 'Only your own organization can install it — but an ' + 'org-wide install covers every site in it, so if any ' + 'of them is live, unreviewed code is serving real ' + 'visitors.'
234
+ }) : null,
235
+ entry.rejectionReason ? /*#__PURE__*/ _jsx(Alert, {
236
+ severity: "error",
237
+ sx: {
238
+ mt: 0.5
239
+ },
240
+ children: entry.rejectionReason
241
+ }) : null,
242
+ entry.revoked ? /*#__PURE__*/ _jsxs(Alert, {
243
+ severity: "error",
244
+ sx: {
245
+ mt: 0.5
246
+ },
247
+ children: [
248
+ 'The platform disabled this version. Nobody can install ' + 'it, and sites already running it render a placeholder.',
249
+ revocationReason ? /*#__PURE__*/ _jsx(Typography, {
250
+ variant: "body2",
251
+ sx: {
252
+ mt: 1
253
+ },
254
+ children: `Reason: ${revocationReason}`
255
+ }) : null,
256
+ revokedAtMs ? /*#__PURE__*/ _jsx(Typography, {
257
+ variant: "caption",
258
+ sx: {
259
+ display: 'block',
260
+ mt: 0.5
261
+ },
262
+ children: `Disabled ${new Date(revokedAtMs).toLocaleDateString()}`
263
+ }) : null
264
+ ]
265
+ }) : null,
266
+ entry.changelog ? /*#__PURE__*/ _jsx(Typography, {
267
+ variant: "caption",
268
+ children: entry.changelog
269
+ }) : null,
270
+ /*#__PURE__*/ _jsx(Typography, {
271
+ variant: "caption",
272
+ color: "text.secondary",
273
+ children: claimed ? `You confirmed ${claimed}/${expected}: ` + attestationLabels(entry.attestation).map((label)=>label.replace(/\s+/g, ' ')).join('; ') : `Nothing was confirmed for these bytes — published ` + 'before the pre-publish checklist existed, or ' + 'republished since.'
274
+ }),
275
+ entry.sha256 ? /*#__PURE__*/ _jsx(Typography, {
276
+ variant: "caption",
277
+ color: "text.secondary",
278
+ sx: {
279
+ fontFamily: 'monospace'
280
+ },
281
+ children: `sha256 ${entry.sha256.slice(0, 12)}…`
282
+ }) : null
283
+ ]
284
+ }, entry.version);
285
+ }),
286
+ versions.length ? null : /*#__PURE__*/ _jsx(Typography, {
287
+ variant: "caption",
288
+ color: "text.secondary",
289
+ children: 'No versions published yet.'
290
+ })
291
+ ]
292
+ })
293
+ ]
294
+ })
295
+ });
296
+ }
297
+ ListingReviewStatus.displayName = 'ListingReviewStatus';
298
+ export default ListingReviewStatus;
299
+ /** Exported for the spec: the words each state is said in. */ export { reviewLabel, PUBLISHER_ATTESTATION };
300
+
301
+ //# sourceMappingURL=listing-review-status.component.js.map