@aglyn/plugins-marketplace 1.14.0 → 1.15.1

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 (107) hide show
  1. package/README.md +1 -0
  2. package/package.json +15 -14
  3. package/src/lib/components/editor-connections-provider.component.d.ts +37 -0
  4. package/src/lib/components/editor-connections-provider.component.js +161 -0
  5. package/src/lib/components/editor-connections-provider.component.js.map +1 -0
  6. package/src/lib/components/listing-connection-credentials-card.component.d.ts +33 -0
  7. package/src/lib/components/listing-connection-credentials-card.component.js +318 -0
  8. package/src/lib/components/listing-connection-credentials-card.component.js.map +1 -0
  9. package/src/lib/components/listing-detail-page.component.js +10 -2
  10. package/src/lib/components/listing-detail-page.component.js.map +1 -1
  11. package/src/lib/components/site-plugin-connections-card.component.d.ts +38 -0
  12. package/src/lib/components/site-plugin-connections-card.component.js +576 -0
  13. package/src/lib/components/site-plugin-connections-card.component.js.map +1 -0
  14. package/src/lib/declarations.console-server.d.ts +17 -0
  15. package/src/lib/declarations.console-server.js +41 -0
  16. package/src/lib/declarations.console-server.js.map +1 -0
  17. package/src/lib/declarations.js +11 -2
  18. package/src/lib/declarations.js.map +1 -1
  19. package/src/lib/declarations.server.js +68 -1
  20. package/src/lib/declarations.server.js.map +1 -1
  21. package/src/lib/hooks/use-plugin-connections-api.d.ts +89 -0
  22. package/src/lib/hooks/use-plugin-connections-api.js +132 -0
  23. package/src/lib/hooks/use-plugin-connections-api.js.map +1 -0
  24. package/src/lib/model/editor-connections.d.ts +46 -0
  25. package/src/lib/model/editor-connections.js +70 -0
  26. package/src/lib/model/editor-connections.js.map +1 -0
  27. package/src/lib/model/listing-price-floor.js +7 -4
  28. package/src/lib/model/listing-price-floor.js.map +1 -1
  29. package/src/lib/model/marketplace-publisher-permissions.d.ts +3 -1
  30. package/src/lib/model/marketplace-publisher-permissions.js +14 -0
  31. package/src/lib/model/marketplace-publisher-permissions.js.map +1 -1
  32. package/src/lib/model/marketplace-site-permissions.d.ts +9 -0
  33. package/src/lib/model/marketplace-site-permissions.js +29 -0
  34. package/src/lib/model/marketplace-site-permissions.js.map +1 -1
  35. package/src/lib/model/plugin-connection-card.d.ts +88 -0
  36. package/src/lib/model/plugin-connection-card.js +187 -0
  37. package/src/lib/model/plugin-connection-card.js.map +1 -0
  38. package/src/lib/model/plugin-connections.d.ts +172 -0
  39. package/src/lib/model/plugin-connections.js +119 -0
  40. package/src/lib/model/plugin-connections.js.map +1 -0
  41. package/src/lib/plugin.js +21 -1
  42. package/src/lib/plugin.js.map +1 -1
  43. package/src/lib/server/admin-reviews.js +18 -12
  44. package/src/lib/server/admin-reviews.js.map +1 -1
  45. package/src/lib/server/artifact-inventory.js +3 -1
  46. package/src/lib/server/artifact-inventory.js.map +1 -1
  47. package/src/lib/server/checkout.js +4 -0
  48. package/src/lib/server/checkout.js.map +1 -1
  49. package/src/lib/server/connections/cleanup.d.ts +84 -0
  50. package/src/lib/server/connections/cleanup.js +116 -0
  51. package/src/lib/server/connections/cleanup.js.map +1 -0
  52. package/src/lib/server/connections/config.d.ts +42 -0
  53. package/src/lib/server/connections/config.js +48 -0
  54. package/src/lib/server/connections/config.js.map +1 -0
  55. package/src/lib/server/connections/connection-store.d.ts +173 -0
  56. package/src/lib/server/connections/connection-store.js +331 -0
  57. package/src/lib/server/connections/connection-store.js.map +1 -0
  58. package/src/lib/server/connections/credentials-store.d.ts +71 -0
  59. package/src/lib/server/connections/credentials-store.js +78 -0
  60. package/src/lib/server/connections/credentials-store.js.map +1 -0
  61. package/src/lib/server/connections/erasure.d.ts +25 -0
  62. package/src/lib/server/connections/erasure.js +72 -0
  63. package/src/lib/server/connections/erasure.js.map +1 -0
  64. package/src/lib/server/connections/installed-connection.d.ts +64 -0
  65. package/src/lib/server/connections/installed-connection.js +92 -0
  66. package/src/lib/server/connections/installed-connection.js.map +1 -0
  67. package/src/lib/server/connections/oauth-state.d.ts +129 -0
  68. package/src/lib/server/connections/oauth-state.js +252 -0
  69. package/src/lib/server/connections/oauth-state.js.map +1 -0
  70. package/src/lib/server/connections/provider-http.d.ts +99 -0
  71. package/src/lib/server/connections/provider-http.js +253 -0
  72. package/src/lib/server/connections/provider-http.js.map +1 -0
  73. package/src/lib/server/connections/record-announce.d.ts +20 -0
  74. package/src/lib/server/connections/record-announce.js +53 -0
  75. package/src/lib/server/connections/record-announce.js.map +1 -0
  76. package/src/lib/server/connections/record-mapping.d.ts +63 -0
  77. package/src/lib/server/connections/record-mapping.js +151 -0
  78. package/src/lib/server/connections/record-mapping.js.map +1 -0
  79. package/src/lib/server/connections/record-routes.d.ts +17 -0
  80. package/src/lib/server/connections/record-routes.js +96 -0
  81. package/src/lib/server/connections/record-routes.js.map +1 -0
  82. package/src/lib/server/connections/revoke.d.ts +29 -0
  83. package/src/lib/server/connections/revoke.js +52 -0
  84. package/src/lib/server/connections/revoke.js.map +1 -0
  85. package/src/lib/server/connections/routes.d.ts +77 -0
  86. package/src/lib/server/connections/routes.js +706 -0
  87. package/src/lib/server/connections/routes.js.map +1 -0
  88. package/src/lib/server/connections/site-records-platform.d.ts +24 -0
  89. package/src/lib/server/connections/site-records-platform.js +94 -0
  90. package/src/lib/server/connections/site-records-platform.js.map +1 -0
  91. package/src/lib/server/connections/site-records.d.ts +95 -0
  92. package/src/lib/server/connections/site-records.js +221 -0
  93. package/src/lib/server/connections/site-records.js.map +1 -0
  94. package/src/lib/server/connections/sync.d.ts +110 -0
  95. package/src/lib/server/connections/sync.js +414 -0
  96. package/src/lib/server/connections/sync.js.map +1 -0
  97. package/src/lib/server/install-plugin.js +56 -6
  98. package/src/lib/server/install-plugin.js.map +1 -1
  99. package/src/lib/server/publish-plugin.js +5 -2
  100. package/src/lib/server/publish-plugin.js.map +1 -1
  101. package/src/lib/server/revenue-source.js +6 -5
  102. package/src/lib/server/revenue-source.js.map +1 -1
  103. package/src/lib/server.js +60 -0
  104. package/src/lib/server.js.map +1 -1
  105. package/src/lib/testing/memory-firestore.d.ts +34 -0
  106. package/src/lib/testing/memory-firestore.js +250 -0
  107. package/src/lib/testing/memory-firestore.js.map +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/listing-connection-credentials-card.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 { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { BlockedControl } from '@aglyn/shared-ui-jsx/components/blocked-control.component'\nimport { CopyField } from '@aglyn/shared-ui-jsx/components/copy-field.component'\nimport { StatusChip } from '@aglyn/shared-ui-jsx/components/status-chip.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Alert, Button, CircularProgress, Stack, TextField, Typography } from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport {\n PluginConnectionRouteError,\n usePluginConnectionCredentialsApi,\n type PluginConnectionCredentialsAnswer,\n type PluginConnectionCredentialsApi,\n} from '../hooks/use-plugin-connections-api'\nimport {\n formatConnectionTime,\n PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT,\n pluginConnectionRedirectUri,\n} from '../model/plugin-connection-card'\nimport {\n PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION,\n pluginConnectionsPermissionRefusal,\n type PluginConnectionCredentialsStatus,\n} from '../model/plugin-connections'\n\nexport interface ListingConnectionCredentialsCardProps {\n listingId: string\n /**\n * The viewer's resolved permissions, from the shell. Setting and clearing\n * the credentials need \"Set a listing's connection credentials\", as the\n * route does; without it the card stays, its buttons off and the\n * permission named. An absent key reads as allowed: the route still decides.\n */\n permissions?: Record<string, boolean | undefined>\n /** Test seam: the routes. The signed-in member's by default. */\n api?: PluginConnectionCredentialsApi\n /** Test seam: the console origin the redirect URI is built on. */\n origin?: string\n}\n\ntype Load =\n | { status: 'loading' }\n | { status: 'error'; message: string; refused: boolean }\n | { status: 'ready'; answer: PluginConnectionCredentialsAnswer }\n\nconst HELP = pluginDocsHelp('publisherHandbook', { anchor: '#connection-credentials' })\n\n/**\n * THE PUBLISHER'S OAUTH APP FOR EACH DECLARED CONNECTION (AGL-4196).\n *\n * A plugin that declares a connection signs site owners in with its\n * publisher's own OAuth app, and the platform holds that app's client ID and\n * secret sealed. This card sets, replaces and clears them. It is\n * write-only: the route never answers either value, so all it can ever show\n * is \"Set\" or \"Not set\", and the redirect URI the publisher registers at the\n * provider.\n *\n * Drawn for the listing's publisher only, and only for a listing whose latest\n * version declares a connection: the route lists none otherwise.\n */\nexport function ListingConnectionCredentialsCard(props: ListingConnectionCredentialsCardProps) {\n const { listingId, permissions } = props\n const routes = usePluginConnectionCredentialsApi()\n const api = props.api ?? routes\n const [load, setLoad] = useState<Load>({ status: 'loading' })\n const canSet = permissions?.[PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION] !== false\n const redirectUri = pluginConnectionRedirectUri(props.origin)\n\n const refresh = useCallback(async () => {\n try {\n setLoad({ status: 'ready', answer: await api.read(listingId) })\n } catch (error) {\n const refusal = error instanceof PluginConnectionRouteError ? error : null\n setLoad({\n status: 'error',\n message: refusal?.message ?? 'The connection credentials could not be read. Try again.',\n refused: refusal?.reason === 'permission',\n })\n }\n }, [api, listingId])\n\n useEffect(() => {\n if (listingId) void refresh()\n }, [listingId, refresh])\n\n if (load.status === 'loading') return null\n if (load.status === 'error') {\n return (\n <CardDisplay header={'Connection credentials'} help={HELP} contentGutterX contentGutterY>\n <Alert severity={load.refused ? 'info' : 'error'}>{load.message}</Alert>\n </CardDisplay>\n )\n }\n const { answer } = load\n if (!answer.credentials.length) return null\n\n return (\n <Stack spacing={3}>\n {answer.credentials.map((entry) => (\n <ConnectionCredentials\n key={entry.connectionId}\n listingId={listingId}\n entry={entry}\n configured={answer.configured}\n canSet={canSet}\n redirectUri={redirectUri}\n api={api}\n onAnswer={(next) => setLoad({ status: 'ready', answer: next })}\n />\n ))}\n </Stack>\n )\n}\nListingConnectionCredentialsCard.displayName = 'ListingConnectionCredentialsCard'\n\nexport default ListingConnectionCredentialsCard\n\ninterface ConnectionCredentialsProps {\n listingId: string\n entry: PluginConnectionCredentialsStatus\n configured: boolean\n canSet: boolean\n redirectUri: string\n api: PluginConnectionCredentialsApi\n onAnswer: (answer: PluginConnectionCredentialsAnswer) => void\n}\n\n/** One declared connection's OAuth app: set or not, and the form that replaces it. */\nfunction ConnectionCredentials(props: ConnectionCredentialsProps) {\n const { listingId, entry, configured, canSet, redirectUri, api, onAnswer } = props\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const [editing, setEditing] = useState(false)\n const [clientId, setClientId] = useState('')\n const [clientSecret, setClientSecret] = useState('')\n const [busy, setBusy] = useState<'save' | 'clear' | null>(null)\n const [error, setError] = useState<string | null>(null)\n const refusal = pluginConnectionsPermissionRefusal(PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION)\n const blockedReason = !canSet ? refusal : !configured ? PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT : null\n\n const close = () => {\n setEditing(false)\n setClientId('')\n setClientSecret('')\n setError(null)\n }\n\n const save = async () => {\n setBusy('save')\n setError(null)\n try {\n const answer = await api.save({ listingId, connectionId: entry.connectionId, clientId: clientId.trim(), clientSecret: clientSecret.trim() })\n close()\n onAnswer(answer)\n enqueueSnackbar(`${entry.label} credentials saved.`, { variant: 'success' })\n } catch (failure) {\n setError(failure instanceof Error ? failure.message : String(failure))\n } finally {\n setBusy(null)\n }\n }\n\n const clear = async () => {\n const ok = await Promise.resolve(\n confirm({\n title: `Clear the ${entry.label} credentials?`,\n description:\n 'Sites already connected stop syncing, and nobody can connect until you set new ones.',\n confirmationText: 'Clear',\n confirmationButtonProps: { color: 'warning', variant: 'contained' },\n }),\n )\n .then(() => true)\n .catch(() => false)\n if (!ok) return\n setBusy('clear')\n setError(null)\n try {\n onAnswer(await api.clear({ listingId, connectionId: entry.connectionId }))\n enqueueSnackbar(`${entry.label} credentials cleared.`, { variant: 'success' })\n } catch (failure) {\n setError(failure instanceof Error ? failure.message : String(failure))\n } finally {\n setBusy(null)\n }\n }\n\n const action = editing ? (\n <Stack direction=\"row\" spacing={1}>\n <Button color=\"inherit\" disabled={busy !== null} onClick={close}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={busy !== null || !clientId.trim() || !clientSecret.trim()}\n onClick={() => void save()}\n >\n {busy === 'save' ? 'Saving…' : 'Save'}\n </Button>\n </Stack>\n ) : (\n <Stack direction=\"row\" spacing={1}>\n <BlockedControl blocked={Boolean(blockedReason)} reason={blockedReason ?? ''}>\n <Button variant={entry.set ? 'outlined' : 'contained'} disabled={busy !== null} onClick={() => setEditing(true)}>\n {entry.set ? 'Replace' : 'Set credentials'}\n </Button>\n </BlockedControl>\n {entry.set ? (\n <BlockedControl blocked={!canSet} reason={refusal}>\n <Button color=\"warning\" disabled={busy !== null} onClick={() => void clear()}>\n {busy === 'clear' ? 'Clearing…' : 'Clear'}\n </Button>\n </BlockedControl>\n ) : null}\n </Stack>\n )\n\n return (\n <CardDisplay\n header={`Connection credentials: ${entry.label}`}\n help={HELP}\n contentGutterX\n contentGutterY\n HeaderProps={{ action }}\n >\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', gap: 1 }}>\n <StatusChip\n label={entry.set ? 'Set' : 'Not set'}\n tone={entry.set ? 'success' : 'neutral'}\n data-testid=\"plugin-connection-credentials-status\"\n />\n {entry.set && entry.updatedAtMs !== null ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Last changed ${formatConnectionTime(entry.updatedAtMs)}`}\n </Typography>\n ) : null}\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {entry.set\n ? 'Sites sign in to this service with your OAuth app. The client ID and secret are never shown again; replace them to change them.'\n : 'Until these are set, sites that install your plugin cannot connect this service.'}\n </Typography>\n {!configured ? <Alert severity=\"info\">{PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT}</Alert> : null}\n {!canSet ? <Alert severity=\"info\">{refusal}</Alert> : null}\n <CopyField\n label=\"Redirect URI\"\n value={redirectUri}\n helperText=\"Register this address at the provider as your OAuth app's redirect URI, exactly as shown.\"\n />\n {editing ? (\n <Stack spacing={2}>\n <TextField\n label=\"Client ID\"\n size=\"small\"\n value={clientId}\n autoComplete=\"off\"\n disabled={busy !== null}\n onChange={(event) => setClientId(event.target.value)}\n slotProps={{ htmlInput: { spellCheck: false, 'data-1p-ignore': true } }}\n />\n <TextField\n label=\"Client secret\"\n size=\"small\"\n type=\"password\"\n value={clientSecret}\n autoComplete=\"new-password\"\n disabled={busy !== null}\n onChange={(event) => setClientSecret(event.target.value)}\n helperText=\"Stored sealed. It cannot be read back, by you or anyone else.\"\n slotProps={{ htmlInput: { spellCheck: false, 'data-1p-ignore': true } }}\n />\n </Stack>\n ) : null}\n {busy === 'clear' ? <CircularProgress size={18} /> : null}\n {error ? (\n <Alert severity=\"error\" onClose={() => setError(null)}>\n {error}\n </Alert>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\n"],"names":["pluginDocsHelp","CardDisplay","useConfirmationContext","BlockedControl","CopyField","StatusChip","useSnackbar","Alert","Button","CircularProgress","Stack","TextField","Typography","useCallback","useEffect","useState","PluginConnectionRouteError","usePluginConnectionCredentialsApi","formatConnectionTime","PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT","pluginConnectionRedirectUri","PLUGIN_CONNECTIONS_CREDENTIALS_PERMISSION","pluginConnectionsPermissionRefusal","HELP","anchor","ListingConnectionCredentialsCard","props","listingId","permissions","routes","api","load","setLoad","status","canSet","redirectUri","origin","refresh","answer","read","error","refusal","message","refused","reason","header","help","contentGutterX","contentGutterY","severity","credentials","length","spacing","map","entry","ConnectionCredentials","configured","onAnswer","next","connectionId","displayName","enqueueSnackbar","confirm","editing","setEditing","clientId","setClientId","clientSecret","setClientSecret","busy","setBusy","setError","blockedReason","close","save","trim","label","variant","failure","Error","String","clear","ok","Promise","resolve","title","description","confirmationText","confirmationButtonProps","color","then","catch","action","direction","disabled","onClick","blocked","Boolean","set","HeaderProps","sx","alignItems","flexWrap","gap","tone","data-testid","updatedAtMs","value","helperText","size","autoComplete","onChange","event","target","slotProps","htmlInput","spellCheck","type","onClose"],"mappings":"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"}
@@ -21,13 +21,14 @@ import { BlockedControl } from "@aglyn/shared-ui-jsx/components/blocked-control.
21
21
  import { useFirestore, useFirestoreDoc, useUser } from "@aglyn/tenant-feature-instance";
22
22
  import { Alert, Button, Stack } from "@mui/material";
23
23
  import { doc } from "firebase/firestore";
24
- import { useMemo, useState } from "react";
24
+ import { lazy, Suspense, useMemo, useState } from "react";
25
25
  import ListingDetailEditor from "./listing-detail-editor.component.js";
26
26
  import ListingReviewStatus from "./listing-review-status.component.js";
27
27
  import ListingVerificationRequest from "./listing-verification-request.component.js";
28
28
  import { MarketplaceListingContent } from "./listing-content.component.js";
29
29
  import { publishPluginPath } from "../model/marketplace-paths.js";
30
30
  import { MARKETPLACE_MANAGE_LISTINGS_PERMISSION, marketplacePublisherPermissionRefusal } from "../model/marketplace-publisher-permissions.js";
31
+ /** The publisher's connection credentials: drawn only for an owner's plugin listing, so it loads then. */ const ListingConnectionCredentialsCard = /*#__PURE__*/ lazy(()=>import("./listing-connection-credentials-card.component.js"));
31
32
  /**
32
33
  * Org marketplace listing detail (AGL-772), at `{marketplace}/{listingId}`.
33
34
  *
@@ -144,7 +145,14 @@ import { MARKETPLACE_MANAGE_LISTINGS_PERMISSION, marketplacePublisherPermissionR
144
145
  user: user,
145
146
  isPlugin: isPlugin,
146
147
  permissions: permissions
147
- })
148
+ }),
149
+ isPlugin ? /*#__PURE__*/ _jsx(Suspense, {
150
+ fallback: null,
151
+ children: /*#__PURE__*/ _jsx(ListingConnectionCredentialsCard, {
152
+ listingId: listingId,
153
+ permissions: permissions
154
+ })
155
+ }) : null
148
156
  ]
149
157
  }) : null,
150
158
  !actingHost ? // Owners can still edit their listing with no site of their own —
@@ -1 +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 { BlockedControl } from '@aglyn/shared-ui-jsx/components/blocked-control.component'\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'\nimport {\n MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n marketplacePublisherPermissionRefusal,\n} from '../model/marketplace-publisher-permissions'\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 // Editing it asks \"Manage published listings\" (AGL-4047), as its save\n // route and the listing rules do. Off with the permission named, not\n // hidden; an absent key reads as allowed, and the route still decides.\n const canManageListings =\n permissions?.[MARKETPLACE_MANAGE_LISTINGS_PERMISSION] !== false\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\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ flexWrap: 'wrap' }}\n >\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 <BlockedControl\n blocked={!canManageListings}\n reason={marketplacePublisherPermissionRefusal(\n MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n )}\n >\n <Button\n variant=\"outlined\"\n color=\"primary\"\n onClick={() => setEditing(true)}\n >\n {'Edit listing'}\n </Button>\n </BlockedControl>\n </Stack>\n ) : null,\n [isOwner, editing, isPlugin, basePath, listingId, canManageListings],\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 && canManageListings && orgId && listing ? (\n <ListingDetailEditor\n orgId={orgId}\n listingId={listingId}\n listing={listing}\n user={user}\n onDone={() => setEditing(false)}\n />\n ) : (\n // One stack, one gap: the owner cards and the listing body share the\n // same edges and the same spacing between them.\n <Stack spacing={3}>\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\n listingId={listingId}\n isPlugin={isPlugin}\n permissions={permissions}\n />\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 permissions={permissions}\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 </Stack>\n )}\n </>\n )\n}\n\nListingDetailPage.displayName = 'ListingDetailPage'\n\nexport default ListingDetailPage\n"],"names":["PageHeaderActions","PageHeaderHelp","PageHeaderRecord","AppLink","BlockedControl","useFirestore","useFirestoreDoc","useUser","Alert","Button","Stack","doc","useMemo","useState","ListingDetailEditor","ListingReviewStatus","ListingVerificationRequest","MarketplaceListingContent","publishPluginPath","MARKETPLACE_MANAGE_LISTINGS_PERMISSION","marketplacePublisherPermissionRefusal","ListingDetailPage","props","listingId","orgId","basePath","actingHost","hosts","permissions","orgSlug","firestore","data","user","listing","idField","isOwner","Boolean","profileId","canManageListings","editing","setEditing","listingName","String","displayName","trim","isPlugin","artifactType","type","ownerActions","direction","spacing","useFlexGap","sx","flexWrap","href","variant","color","component","blocked","reason","onClick","title","topic","anchor","onDone","viewerOrgId","undefined","severity","hostId","orgScoped"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SACEA,iBAAiB,EACjBC,cAAc,EACdC,gBAAgB,QACX,eAAc;AACrB,SAASC,OAAO,QAAQ,uBAAsB;AAC9C,SAASC,cAAc,QAAQ,4DAA2D;AAC1F,SAASC,YAAY,EAAEC,eAAe,EAAEC,OAAO,QAAQ,iCAAgC;AACvF,SAASC,KAAK,EAAEC,MAAM,EAAEC,KAAK,QAAQ,gBAAe;AACpD,SAASC,GAAG,QAAQ,qBAAoB;AACxC,SAASC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACzC,OAAOC,yBAAyB,uCAAmC;AACnE,OAAOC,yBAAyB,uCAAmC;AACnE,OAAOC,gCAAgC,8CAA0C;AACjF,SAASC,yBAAyB,QAAQ,iCAA6B;AACvE,SAASC,iBAAiB,QAAQ,gCAA4B;AAC9D,SACEC,sCAAsC,EACtCC,qCAAqC,QAChC,gDAA4C;AAEnD;;;;;;;;;;CAUC,GACD,OAAO,SAASC,kBAAkBC,KASjC;;IACC,MAAM,EAAEC,SAAS,EAAEC,KAAK,EAAEC,QAAQ,EAAEC,UAAU,EAAEC,KAAK,EAAEC,WAAW,EAAEC,OAAO,EAAE,GAC3EP;IACF,MAAMQ,YAAYzB;IAClB,MAAM,EAAE0B,MAAMC,IAAI,EAAE,GAAGzB;IAEvB,oEAAoE;IACpE,wDAAwD;IACxD,MAAM,EAAEwB,MAAME,OAAO,EAAE,GAAG3B,gBACxB,IAAMK,IAAImB,WAAW,uBAAuBP,aAAa,cACzD;QAACO;QAAWP;KAAU,EACtB;QAAEW,SAAS;IAAM;IAEnB,oEAAoE;IACpE,MAAMC,UAAUC,QAAQZ,SAASS,CAAAA,2BAAAA,QAASI,SAAS,MAAKb;IACxD,sEAAsE;IACtE,qEAAqE;IACrE,uEAAuE;IACvE,MAAMc,oBACJV,CAAAA,+BAAAA,WAAa,CAACT,uCAAuC,MAAK;IAC5D,MAAM,CAACoB,SAASC,WAAW,GAAG3B,SAAS;IAEvC,0EAA0E;IAC1E,yEAAyE;IACzE,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,yEAAyE;IACzE,MAAM4B,cAAcC,eAAOT,2BAAAA,QAASU,WAAW,mBAAI,IAAIC,IAAI;IAC3D,MAAMC,WAAW,UAACZ,2BAAAA,QAASa,YAAY,oBAAIb,2BAAAA,QAASc,IAAI,MAAM;IAE9D;;;;;GAKC,GACD,MAAMC,eAAepC,QACnB,IACEuB,WAAW,CAACI,wBACV,MAAC7B;YACCuC,WAAU;YACVC,SAAS;YACTC,UAAU;YACVC,IAAI;gBAAEC,UAAU;YAAO;;gBAOtBR,yBACC,KAAC1C;oBAAQmD,MAAMpC,kBAAkBO,UAAUF;8BACzC,cAAA,KAACd;wBAAO8C,SAAQ;wBAAYC,OAAM;wBAAUC,WAAU;kCACnD;;qBAGH;8BACJ,KAACrD;oBACCsD,SAAS,CAACpB;oBACVqB,QAAQvC,sCACND;8BAGF,cAAA,KAACV;wBACC8C,SAAQ;wBACRC,OAAM;wBACNI,SAAS,IAAMpB,WAAW;kCAEzB;;;;aAIL,MACN;QAACL;QAASI;QAASM;QAAUpB;QAAUF;QAAWe;KAAkB;IAGtE,qBACE;;0BACE,KAACpC;gBAAiB2D,OAAOpB;;0BACzB,KAACxC;gBACC6D,OAAM;gBACNC,QAAO;;0BAMT,KAAC/D;0BAAmBgD;;YACnBT,WAAWJ,WAAWG,qBAAqBd,SAASS,wBACnD,KAACnB;gBACCU,OAAOA;gBACPD,WAAWA;gBACXU,SAASA;gBACTD,MAAMA;gBACNgC,QAAQ,IAAMxB,WAAW;iBAG3B,qEAAqE;YACrE,gDAAgD;0BAChD,MAAC9B;gBAAMwC,SAAS;;oBAObf,wBACC;;0CACE,KAACpB;gCACCQ,WAAWA;gCACXsB,UAAUA;gCACVjB,aAAaA;;0CAMf,KAACZ;gCACCO,WAAWA;gCACXU,SAASA;gCACTgC,WAAW,EAAEzC,gBAAAA,QAAS0C;gCACtBlC,MAAMA;gCACNa,UAAUA;gCACVjB,aAAaA;;;yBAGf;oBACH,CAACF,aACA,kEAAkE;oBAClE,qDAAqD;kCACrD,KAAClB;wBAAM2D,UAAS;kCACbhC,UACG,4DACA,kDACA,yDACA;uCAGN,KAAClB;wBACCmD,QAAQ1C;wBACRH,WAAWA;wBACXK,aAAaA;wBACbyC,SAAS;wBACTxC,SAASA;wBACTF,OAAOA;;;;;;AAOrB;AAEAN,kBAAkBsB,WAAW,GAAG;AAEhC,eAAetB,kBAAiB"}
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 { BlockedControl } from '@aglyn/shared-ui-jsx/components/blocked-control.component'\nimport { useFirestore, useFirestoreDoc, useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Stack } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { lazy, Suspense, 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'\nimport {\n MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n marketplacePublisherPermissionRefusal,\n} from '../model/marketplace-publisher-permissions'\n\n/** The publisher's connection credentials: drawn only for an owner's plugin listing, so it loads then. */\nconst ListingConnectionCredentialsCard = lazy(() => import('./listing-connection-credentials-card.component'))\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 // Editing it asks \"Manage published listings\" (AGL-4047), as its save\n // route and the listing rules do. Off with the permission named, not\n // hidden; an absent key reads as allowed, and the route still decides.\n const canManageListings =\n permissions?.[MARKETPLACE_MANAGE_LISTINGS_PERMISSION] !== false\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\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ flexWrap: 'wrap' }}\n >\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 <BlockedControl\n blocked={!canManageListings}\n reason={marketplacePublisherPermissionRefusal(\n MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n )}\n >\n <Button\n variant=\"outlined\"\n color=\"primary\"\n onClick={() => setEditing(true)}\n >\n {'Edit listing'}\n </Button>\n </BlockedControl>\n </Stack>\n ) : null,\n [isOwner, editing, isPlugin, basePath, listingId, canManageListings],\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 && canManageListings && orgId && listing ? (\n <ListingDetailEditor\n orgId={orgId}\n listingId={listingId}\n listing={listing}\n user={user}\n onDone={() => setEditing(false)}\n />\n ) : (\n // One stack, one gap: the owner cards and the listing body share the\n // same edges and the same spacing between them.\n <Stack spacing={3}>\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\n listingId={listingId}\n isPlugin={isPlugin}\n permissions={permissions}\n />\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 permissions={permissions}\n />\n {/* The publisher's OAuth app for each connection the latest\n version declares (AGL-4196). Draws nothing for a listing\n that declares none. */}\n {isPlugin ? (\n <Suspense fallback={null}>\n <ListingConnectionCredentialsCard\n listingId={listingId}\n permissions={permissions}\n />\n </Suspense>\n ) : null}\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 </Stack>\n )}\n </>\n )\n}\n\nListingDetailPage.displayName = 'ListingDetailPage'\n\nexport default ListingDetailPage\n"],"names":["PageHeaderActions","PageHeaderHelp","PageHeaderRecord","AppLink","BlockedControl","useFirestore","useFirestoreDoc","useUser","Alert","Button","Stack","doc","lazy","Suspense","useMemo","useState","ListingDetailEditor","ListingReviewStatus","ListingVerificationRequest","MarketplaceListingContent","publishPluginPath","MARKETPLACE_MANAGE_LISTINGS_PERMISSION","marketplacePublisherPermissionRefusal","ListingConnectionCredentialsCard","ListingDetailPage","props","listingId","orgId","basePath","actingHost","hosts","permissions","orgSlug","firestore","data","user","listing","idField","isOwner","Boolean","profileId","canManageListings","editing","setEditing","listingName","String","displayName","trim","isPlugin","artifactType","type","ownerActions","direction","spacing","useFlexGap","sx","flexWrap","href","variant","color","component","blocked","reason","onClick","title","topic","anchor","onDone","viewerOrgId","undefined","fallback","severity","hostId","orgScoped"],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { type PluginConnectionsApi } from '../hooks/use-plugin-connections-api';
2
+ /**
3
+ * What the `sitePluginConnections` zone hands each widget: the site's page
4
+ * for one installed plugin, and the pin whose manifest it runs.
5
+ */
6
+ export interface SitePluginConnectionsCardProps {
7
+ hostId: string;
8
+ /** Path slug for building `/[orgSlug]/…` links. */
9
+ orgSlug: string;
10
+ /** The installation's id: the listing the pin is keyed by. */
11
+ pluginRef: string;
12
+ /** The site's pin of the listing (else the workspace's): its version and manifest. */
13
+ pin: Readonly<Record<string, unknown>> | null | undefined;
14
+ /** Test seam: the routes. The signed-in member's by default. */
15
+ api?: PluginConnectionsApi;
16
+ /** Test seam: sends the browser to the provider's consent page. */
17
+ navigate?: (url: string) => void;
18
+ }
19
+ /**
20
+ * THE SITE'S CONNECTIONS FOR ONE INSTALLED PLUGIN (AGL-4196).
21
+ *
22
+ * A marketplace plugin may declare connections in its manifest: an account at
23
+ * another service the platform signs in to, the things the owner picks there
24
+ * (an account, then one of its locations), and a scheduled sync. The
25
+ * platform runs all of it, so this card is the same for every plugin: one
26
+ * card per declared connection, drawn from the pinned manifest and the
27
+ * status route.
28
+ *
29
+ * Every prerequisite stays on screen: a plan without connections, a
30
+ * publisher who has not set the connection up, a deployment that cannot run
31
+ * them yet and a member without the site key each keep the card, with its
32
+ * controls off and the reason said.
33
+ */
34
+ export declare function SitePluginConnectionsCard(props: SitePluginConnectionsCardProps): import("react").JSX.Element;
35
+ export declare namespace SitePluginConnectionsCard {
36
+ var displayName: string;
37
+ }
38
+ export default SitePluginConnectionsCard;