@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.
- package/package.json +15 -11
- package/src/lib/components/edit-listing-dialog.component.d.ts +20 -0
- package/src/lib/components/edit-listing-dialog.component.js +51 -0
- package/src/lib/components/edit-listing-dialog.component.js.map +1 -0
- package/src/lib/components/listing-detail-editor.component.d.ts +24 -0
- package/src/lib/components/listing-detail-editor.component.js +457 -0
- package/src/lib/components/listing-detail-editor.component.js.map +1 -0
- package/src/lib/components/listing-detail-page.component.d.ts +28 -0
- package/src/lib/components/listing-detail-page.component.js +151 -0
- package/src/lib/components/listing-detail-page.component.js.map +1 -0
- package/src/lib/components/listing-review-status.component.d.ts +67 -0
- package/src/lib/components/listing-review-status.component.js +301 -0
- package/src/lib/components/listing-review-status.component.js.map +1 -0
- package/src/lib/components/listing-verification-request.component.d.ts +40 -0
- package/src/lib/components/listing-verification-request.component.js +170 -0
- package/src/lib/components/listing-verification-request.component.js.map +1 -0
- package/src/lib/components/marketplace-hub.component.d.ts +43 -0
- package/src/lib/components/marketplace-hub.component.js +244 -0
- package/src/lib/components/marketplace-hub.component.js.map +1 -0
- package/src/lib/components/marketplace-reports-page.component.d.ts +23 -0
- package/src/lib/components/marketplace-reports-page.component.js +324 -0
- package/src/lib/components/marketplace-reports-page.component.js.map +1 -0
- package/src/lib/components/org-licences-panel.component.d.ts +73 -0
- package/src/lib/components/org-licences-panel.component.js +318 -0
- package/src/lib/components/org-licences-panel.component.js.map +1 -0
- package/src/lib/components/org-publish-panel.component.d.ts +45 -0
- package/src/lib/components/org-publish-panel.component.js +402 -0
- package/src/lib/components/org-publish-panel.component.js.map +1 -0
- package/src/lib/components/org-seller-panel.component.d.ts +28 -0
- package/src/lib/components/org-seller-panel.component.js +956 -0
- package/src/lib/components/org-seller-panel.component.js.map +1 -0
- package/src/lib/components/plugin-review-detail.component.d.ts +16 -0
- package/src/lib/components/plugin-review-detail.component.js +1438 -0
- package/src/lib/components/plugin-review-detail.component.js.map +1 -0
- package/src/lib/components/plugin-reviews-queue.component.d.ts +13 -0
- package/src/lib/components/plugin-reviews-queue.component.js +385 -0
- package/src/lib/components/plugin-reviews-queue.component.js.map +1 -0
- package/src/lib/components/plugin-reviews-surface.component.d.ts +19 -0
- package/src/lib/components/plugin-reviews-surface.component.js +46 -0
- package/src/lib/components/plugin-reviews-surface.component.js.map +1 -0
- package/src/lib/components/publish-plugin-form.component.d.ts +61 -0
- package/src/lib/components/publish-plugin-form.component.js +857 -0
- package/src/lib/components/publish-plugin-form.component.js.map +1 -0
- package/src/lib/components/publish-plugin-page.component.d.ts +29 -0
- package/src/lib/components/publish-plugin-page.component.js +92 -0
- package/src/lib/components/publish-plugin-page.component.js.map +1 -0
- package/src/lib/components/publisher-profile-page.component.d.ts +13 -0
- package/src/lib/components/publisher-profile-page.component.js +261 -0
- package/src/lib/components/publisher-profile-page.component.js.map +1 -0
- package/src/lib/constants/listing-media.d.ts +28 -0
- package/src/lib/constants/listing-media.js +28 -0
- package/src/lib/constants/listing-media.js.map +1 -0
- package/src/lib/model/listing-social-card.d.ts +94 -0
- package/src/lib/model/listing-social-card.js +177 -0
- package/src/lib/model/listing-social-card.js.map +1 -0
- package/src/lib/model/marketplace-paths.d.ts +54 -0
- package/src/lib/model/marketplace-paths.js +59 -0
- package/src/lib/model/marketplace-paths.js.map +1 -0
- package/src/lib/model/payout-readiness.d.ts +81 -0
- package/src/lib/model/payout-readiness.js +52 -0
- package/src/lib/model/payout-readiness.js.map +1 -0
- package/src/lib/model/plugin-rejection-categories.d.ts +66 -0
- package/src/lib/model/plugin-rejection-categories.js +124 -0
- package/src/lib/model/plugin-rejection-categories.js.map +1 -0
- package/src/lib/model/plugin-review-checklist.d.ts +62 -0
- package/src/lib/model/plugin-review-checklist.js +131 -0
- package/src/lib/model/plugin-review-checklist.js.map +1 -0
- package/src/lib/model/plugin-review-status.d.ts +104 -0
- package/src/lib/model/plugin-review-status.js +126 -0
- package/src/lib/model/plugin-review-status.js.map +1 -0
- package/src/lib/model/seller-ledger-totals.d.ts +100 -0
- package/src/lib/model/seller-ledger-totals.js +96 -0
- package/src/lib/model/seller-ledger-totals.js.map +1 -0
- package/src/lib/plugin.js +170 -40
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/admin-reports.d.ts +18 -0
- package/src/lib/server/admin-reports.js +280 -0
- package/src/lib/server/admin-reports.js.map +1 -0
- package/src/lib/server/admin-reviews.d.ts +18 -0
- package/src/lib/server/admin-reviews.js +1341 -0
- package/src/lib/server/admin-reviews.js.map +1 -0
- package/src/lib/server/listing-social-card.server.d.ts +24 -0
- package/src/lib/server/listing-social-card.server.js +65 -0
- package/src/lib/server/listing-social-card.server.js.map +1 -0
- package/src/lib/server/publisher-review-email.d.ts +54 -0
- package/src/lib/server/publisher-review-email.js +85 -0
- package/src/lib/server/publisher-review-email.js.map +1 -0
- package/src/lib/server/revoke-cache-drop.d.ts +59 -0
- package/src/lib/server/revoke-cache-drop.js +74 -0
- package/src/lib/server/revoke-cache-drop.js.map +1 -0
- package/src/lib/server.js +47 -0
- package/src/lib/server.js.map +1 -1
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
|
+
import { PLAN_ENTITLEMENTS, PLATFORM_BRAND_NAME, pluginDocsHelp, resolveMarketplaceFeePct } from "@aglyn/aglyn";
|
|
20
|
+
import { AppLink, CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
21
|
+
import { Alert, Box, Button, MenuItem, Stack, TextField, Typography } from "@mui/material";
|
|
22
|
+
import { getTenantEmail } from "@aglyn/shared-util-email";
|
|
23
|
+
import { collection, doc, limit, query, where } from "firebase/firestore";
|
|
24
|
+
import { useMemo, useState } from "react";
|
|
25
|
+
import { useFirestore, useFirestoreCollection, useFirestoreDoc } from "@aglyn/tenant-feature-instance";
|
|
26
|
+
import { publishPluginPath } from "../model/marketplace-paths.js";
|
|
27
|
+
import PublishArtifactDialog from "./publish-artifact-dialog.component.js";
|
|
28
|
+
const artifactName = (artifact)=>{
|
|
29
|
+
var _ref, _ref1, _ref2;
|
|
30
|
+
return (_ref = (_ref1 = (_ref2 = artifact == null ? void 0 : artifact.displayName) != null ? _ref2 : artifact == null ? void 0 : artifact.name) != null ? _ref1 : artifact == null ? void 0 : artifact.title) != null ? _ref : undefined;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Email templates are keyed by a fixed catalog key, and the stored doc carries
|
|
34
|
+
* only the design — the human name lives in the catalog.
|
|
35
|
+
*/ const emailLabel = (templateKey)=>{
|
|
36
|
+
var _ref;
|
|
37
|
+
var _getTenantEmail;
|
|
38
|
+
return (_ref = (_getTenantEmail = getTenantEmail(templateKey)) == null ? void 0 : _getTenantEmail.name) != null ? _ref : templateKey;
|
|
39
|
+
};
|
|
40
|
+
/** Per-kind select label, option text, and empty-state copy. */ const PICKERS = {
|
|
41
|
+
component: {
|
|
42
|
+
label: 'Component',
|
|
43
|
+
empty: 'This site has no reusable components to publish yet.',
|
|
44
|
+
optionLabel: (entry)=>{
|
|
45
|
+
var _artifactName;
|
|
46
|
+
return (_artifactName = artifactName(entry)) != null ? _artifactName : entry.$id;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
layout: {
|
|
50
|
+
label: 'Layout',
|
|
51
|
+
empty: 'This site has no layouts to publish yet.',
|
|
52
|
+
optionLabel: (entry)=>{
|
|
53
|
+
var _artifactName;
|
|
54
|
+
return (_artifactName = artifactName(entry)) != null ? _artifactName : entry.$id;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
datasetSchema: {
|
|
58
|
+
label: 'Dataset',
|
|
59
|
+
empty: 'This organization has no datasets to publish yet.',
|
|
60
|
+
optionLabel: (entry)=>{
|
|
61
|
+
var _artifactName;
|
|
62
|
+
return (_artifactName = artifactName(entry)) != null ? _artifactName : entry.$id;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
emailTemplate: {
|
|
66
|
+
label: 'Email',
|
|
67
|
+
empty: 'This site has no designed emails to publish yet.',
|
|
68
|
+
// The doc id IS the catalog key; the design carries no name of its own.
|
|
69
|
+
optionLabel: (entry)=>emailLabel(entry.$id)
|
|
70
|
+
},
|
|
71
|
+
// A campaign email is an ordinary screen document, so unlike the row above
|
|
72
|
+
// it carries its own name and there is no catalog to look one up in.
|
|
73
|
+
emailStarter: {
|
|
74
|
+
label: 'Email',
|
|
75
|
+
empty: 'This site has no saved campaign emails to publish yet.',
|
|
76
|
+
optionLabel: (entry)=>{
|
|
77
|
+
var _artifactName;
|
|
78
|
+
return (_artifactName = artifactName(entry)) != null ? _artifactName : entry.$id;
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
site: {
|
|
82
|
+
label: 'Site',
|
|
83
|
+
empty: '',
|
|
84
|
+
optionLabel: ()=>''
|
|
85
|
+
},
|
|
86
|
+
// A theme is a field on the site, so like `site` there is nothing to pick —
|
|
87
|
+
// choosing the source site has already chosen the theme.
|
|
88
|
+
theme: {
|
|
89
|
+
label: 'Theme',
|
|
90
|
+
empty: '',
|
|
91
|
+
optionLabel: ()=>''
|
|
92
|
+
},
|
|
93
|
+
// Plugins don't pick an existing artifact — they're uploaded (AGL-868).
|
|
94
|
+
plugin: {
|
|
95
|
+
label: 'Plugin',
|
|
96
|
+
empty: '',
|
|
97
|
+
optionLabel: ()=>''
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Org-level publish (AGL-776): pick a source site, then a component, a layout,
|
|
102
|
+
* or the whole site (as a template), and publish it under the org's publisher
|
|
103
|
+
* identity. This panel's whole job is choosing WHAT: it names the kind, the
|
|
104
|
+
* scope that holds it and the document, and hands that to the
|
|
105
|
+
* `hostArtifactPublish` zone (AGL-3080), whose widget owns the name/price
|
|
106
|
+
* form, the route and the POST. Plugins publish from their own bundle upload,
|
|
107
|
+
* not here.
|
|
108
|
+
*
|
|
109
|
+
* The per-site publish buttons (Components/Layouts/Setup pages) stay as
|
|
110
|
+
* in-context shortcuts; this is the one place that spans every site.
|
|
111
|
+
*/ export function OrgPublishPanel({ orgId, hosts, basePath, billingPath, org, orgReady }) {
|
|
112
|
+
var _hosts_, _hosts_find;
|
|
113
|
+
const firestore = useFirestore();
|
|
114
|
+
// The platform's cut, from the SAME helper the checkout deducts with
|
|
115
|
+
// (AGL-2078) rather than a restated constant — a second copy of a rate
|
|
116
|
+
// that moves money is a copy free to disagree with the one that bills.
|
|
117
|
+
const feePct = resolveMarketplaceFeePct(org);
|
|
118
|
+
const paidFeePct = PLAN_ENTITLEMENTS.starter.marketplaceFeePct;
|
|
119
|
+
const { data: profile } = useFirestoreDoc(()=>doc(firestore, 'publisherProfiles', orgId || '-none-'), [
|
|
120
|
+
firestore,
|
|
121
|
+
orgId
|
|
122
|
+
], {
|
|
123
|
+
idField: '$id'
|
|
124
|
+
});
|
|
125
|
+
const [sourceHostId, setSourceHostId] = useState('');
|
|
126
|
+
const hostId = sourceHostId || ((_hosts_ = hosts[0]) == null ? void 0 : _hosts_.id) || '';
|
|
127
|
+
const [kind, setKind] = useState('component');
|
|
128
|
+
const [artifactId, setArtifactId] = useState('');
|
|
129
|
+
const [target, setTarget] = useState(null);
|
|
130
|
+
// Held at null while there is no host, never addressed as `hosts/-none-`
|
|
131
|
+
// (AGL-1440): an org with ZERO sites has no `hosts[0]`, so the sentinel was
|
|
132
|
+
// not a loading window — it was three permanently rules-denied listens per
|
|
133
|
+
// panel mount, each reopened forever by the refusal loop. The hook issues
|
|
134
|
+
// nothing for a null query and the lists render their empty states.
|
|
135
|
+
const { data: componentDocs } = useFirestoreCollection(()=>hostId ? query(collection(firestore, 'hosts', hostId, 'components'), limit(100)) : null, [
|
|
136
|
+
firestore,
|
|
137
|
+
hostId
|
|
138
|
+
], {
|
|
139
|
+
idField: '$id'
|
|
140
|
+
});
|
|
141
|
+
const { data: layoutDocs } = useFirestoreCollection(()=>hostId ? query(collection(firestore, 'hosts', hostId, 'layouts'), limit(100)) : null, [
|
|
142
|
+
firestore,
|
|
143
|
+
hostId
|
|
144
|
+
], {
|
|
145
|
+
idField: '$id'
|
|
146
|
+
});
|
|
147
|
+
// Datasets are ORG-scoped (AGL-237), so unlike every other source here they
|
|
148
|
+
// don't depend on the selected site.
|
|
149
|
+
const { data: datasetDocs } = useFirestoreCollection(()=>orgId ? query(collection(firestore, 'orgs', orgId, 'datasets'), limit(100)) : null, [
|
|
150
|
+
firestore,
|
|
151
|
+
orgId
|
|
152
|
+
], {
|
|
153
|
+
idField: '$id'
|
|
154
|
+
});
|
|
155
|
+
const { data: emailDocs } = useFirestoreCollection(()=>hostId ? query(collection(firestore, 'hosts', hostId, 'emailTemplates'), limit(100)) : null, [
|
|
156
|
+
firestore,
|
|
157
|
+
hostId
|
|
158
|
+
], {
|
|
159
|
+
idField: '$id'
|
|
160
|
+
});
|
|
161
|
+
/**
|
|
162
|
+
* Campaign emails are `kind: 'email'` SCREENS, not entries in the
|
|
163
|
+
* transactional catalog above — a different collection, a different shape,
|
|
164
|
+
* and a different marketplace artifact type. The equality filter is on the
|
|
165
|
+
* query so a site with many pages does not read them all to find its emails.
|
|
166
|
+
*/ const emailScreenDocs = useFirestoreCollection(()=>hostId ? query(collection(firestore, 'hosts', hostId, 'screens'), where('kind', '==', 'email'), limit(100)) : null, [
|
|
167
|
+
firestore,
|
|
168
|
+
hostId
|
|
169
|
+
], {
|
|
170
|
+
idField: '$id'
|
|
171
|
+
}).data;
|
|
172
|
+
const components = useMemo(()=>(componentDocs != null ? componentDocs : []).filter((entry)=>!entry.deletedAt), [
|
|
173
|
+
componentDocs
|
|
174
|
+
]);
|
|
175
|
+
const layouts = useMemo(()=>(layoutDocs != null ? layoutDocs : []).filter((entry)=>!entry.deletedAt), [
|
|
176
|
+
layoutDocs
|
|
177
|
+
]);
|
|
178
|
+
const datasets = useMemo(()=>(datasetDocs != null ? datasetDocs : []).filter((entry)=>!entry.deletedAt), [
|
|
179
|
+
datasetDocs
|
|
180
|
+
]);
|
|
181
|
+
// Only emails that actually have a saved design are publishable — the doc
|
|
182
|
+
// exists with no `versionId` whenever the besigner was merely opened.
|
|
183
|
+
const emails = useMemo(()=>(emailDocs != null ? emailDocs : []).filter((entry)=>!entry.deletedAt && entry.versionId), [
|
|
184
|
+
emailDocs
|
|
185
|
+
]);
|
|
186
|
+
// Same rule, same reason: a screen with no `versionId` was opened and never
|
|
187
|
+
// saved, so there is no design to publish.
|
|
188
|
+
const emailScreens = useMemo(()=>(emailScreenDocs != null ? emailScreenDocs : []).filter((entry)=>!entry.deletedAt && entry.versionId), [
|
|
189
|
+
emailScreenDocs
|
|
190
|
+
]);
|
|
191
|
+
// Validate the pick against the current list rather than resetting it in an
|
|
192
|
+
// effect (a classic render-loop source — the reset runs a `setState` after
|
|
193
|
+
// every host/kind change, adding renders that a transient flurry can push
|
|
194
|
+
// past React's update-depth limit). A stale id from another site or
|
|
195
|
+
// artifact type simply reads as "nothing selected" this render, so we never
|
|
196
|
+
// publish it, and the MUI <Select> value stays in range every render.
|
|
197
|
+
const activeList = kind === 'component' ? components : kind === 'layout' ? layouts : kind === 'datasetSchema' ? datasets : kind === 'emailTemplate' ? emails : kind === 'emailStarter' ? emailScreens : [];
|
|
198
|
+
// One descriptor per source kind, so adding an artifact type is a row here
|
|
199
|
+
// rather than another arm of a nested ternary in the JSX below.
|
|
200
|
+
const picker = PICKERS[kind];
|
|
201
|
+
const selectedId = activeList.some((entry)=>entry.$id === artifactId) ? artifactId : '';
|
|
202
|
+
const hostLabel = (_hosts_find = hosts.find((host)=>host.id === hostId)) == null ? void 0 : _hosts_find.label;
|
|
203
|
+
/*
|
|
204
|
+
* WHAT IS BEING PUBLISHED, in this console's own words (AGL-3080).
|
|
205
|
+
*
|
|
206
|
+
* This used to build seven marketplace requests — an endpoint, a payload
|
|
207
|
+
* key and a noun each — which made a panel about the org's own artifacts a
|
|
208
|
+
* file that could not be right without being kept in step with a plugin's
|
|
209
|
+
* routes. It now says only what the thing IS and which scope holds it; the
|
|
210
|
+
* `hostArtifactPublish` zone's widget decides where it goes.
|
|
211
|
+
*
|
|
212
|
+
* ⚠️ Datasets are ORG-scoped and every other kind is a site's, which is why
|
|
213
|
+
* both scopes ride and neither is inferred from the other.
|
|
214
|
+
*/ const openPublish = ()=>{
|
|
215
|
+
const named = kind === 'site' ? hostLabel : kind === 'theme' ? hostLabel && `${hostLabel} theme` : kind === 'emailTemplate' ? emailLabel(selectedId) : artifactName(activeList.find((entry)=>entry.$id === selectedId));
|
|
216
|
+
setTarget(_extends({
|
|
217
|
+
kind,
|
|
218
|
+
hostId,
|
|
219
|
+
orgId
|
|
220
|
+
}, kind === 'site' || kind === 'theme' ? {} : {
|
|
221
|
+
artifactId: selectedId
|
|
222
|
+
}, named ? {
|
|
223
|
+
displayName: named
|
|
224
|
+
} : {}));
|
|
225
|
+
};
|
|
226
|
+
// Dataset schemas publish from the org, so they need no source site — an
|
|
227
|
+
// org with datasets but no sites yet can still publish one.
|
|
228
|
+
const canPublish = kind === 'datasetSchema' ? Boolean(orgId && selectedId) : Boolean(hostId && (kind === 'site' || kind === 'theme' || selectedId));
|
|
229
|
+
// A publisher profile (with a handle) is required server-side; guide the
|
|
230
|
+
// user there rather than letting the publish 412.
|
|
231
|
+
if (profile !== undefined && !(profile == null ? void 0 : profile.handle)) {
|
|
232
|
+
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
233
|
+
header: 'Publish to the marketplace',
|
|
234
|
+
// The SAME card, in the branch where it cannot be used — and until
|
|
235
|
+
// AGL-2130 the only branch with no help. A reader who arrives here is
|
|
236
|
+
// by definition the one who does not yet know how publishing works,
|
|
237
|
+
// so this is the branch that can least afford to drop the link.
|
|
238
|
+
help: pluginDocsHelp('publisherHandbook', {
|
|
239
|
+
anchor: '#where-to-publish-from',
|
|
240
|
+
excerpt: 'Publishing needs an organization publisher profile with a handle. ' + 'Set one up on the Profile tab first.'
|
|
241
|
+
}),
|
|
242
|
+
contentGutterX: true,
|
|
243
|
+
contentGutterY: true,
|
|
244
|
+
children: /*#__PURE__*/ _jsx(Alert, {
|
|
245
|
+
severity: "info",
|
|
246
|
+
children: 'Set up your organization’s publisher profile in the Profile tab ' + 'before publishing.'
|
|
247
|
+
})
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return /*#__PURE__*/ _jsxs(CardDisplay, {
|
|
251
|
+
header: 'Publish to the marketplace',
|
|
252
|
+
help: pluginDocsHelp('publisherHandbook', {
|
|
253
|
+
anchor: '#where-to-publish-from',
|
|
254
|
+
excerpt: 'Publish a component, layout, dataset schema, email template, or ' + 'whole site so other organizations can install it.'
|
|
255
|
+
}),
|
|
256
|
+
contentGutterX: true,
|
|
257
|
+
contentGutterY: true,
|
|
258
|
+
children: [
|
|
259
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
260
|
+
spacing: 2,
|
|
261
|
+
sx: {
|
|
262
|
+
maxWidth: 480
|
|
263
|
+
},
|
|
264
|
+
children: [
|
|
265
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
266
|
+
variant: "body2",
|
|
267
|
+
color: "text.secondary",
|
|
268
|
+
children: 'Publish a component, layout, dataset schema, email template, or ' + 'an entire site from your organization so other organizations ' + 'can install it. Your live site is unaffected.'
|
|
269
|
+
}),
|
|
270
|
+
orgReady ? /*#__PURE__*/ _jsxs(Alert, {
|
|
271
|
+
severity: feePct > paidFeePct ? 'warning' : 'info',
|
|
272
|
+
children: [
|
|
273
|
+
feePct > paidFeePct ? `${PLATFORM_BRAND_NAME} keeps ${feePct}% of each sale on your current plan. ` + `On a paid plan it is ${paidFeePct}%.` : `${PLATFORM_BRAND_NAME} keeps ${feePct}% of each sale. Payment processing ` + 'fees are charged separately by Stripe.',
|
|
274
|
+
feePct > paidFeePct && billingPath ? /*#__PURE__*/ _jsx(Box, {
|
|
275
|
+
sx: {
|
|
276
|
+
mt: 1
|
|
277
|
+
},
|
|
278
|
+
children: /*#__PURE__*/ _jsx(AppLink, {
|
|
279
|
+
href: billingPath,
|
|
280
|
+
children: 'See plans'
|
|
281
|
+
})
|
|
282
|
+
}) : null
|
|
283
|
+
]
|
|
284
|
+
}) : null,
|
|
285
|
+
hosts.length > 1 && kind !== 'datasetSchema' ? /*#__PURE__*/ _jsx(TextField, {
|
|
286
|
+
select: true,
|
|
287
|
+
size: "small",
|
|
288
|
+
label: "From site",
|
|
289
|
+
value: hostId,
|
|
290
|
+
onChange: (event)=>setSourceHostId(event.target.value),
|
|
291
|
+
children: hosts.map((host)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
292
|
+
value: host.id,
|
|
293
|
+
children: host.label
|
|
294
|
+
}, host.id))
|
|
295
|
+
}) : null,
|
|
296
|
+
/*#__PURE__*/ _jsxs(TextField, {
|
|
297
|
+
select: true,
|
|
298
|
+
size: "small",
|
|
299
|
+
label: "What to publish",
|
|
300
|
+
value: kind,
|
|
301
|
+
onChange: (event)=>setKind(event.target.value),
|
|
302
|
+
children: [
|
|
303
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
304
|
+
value: "component",
|
|
305
|
+
children: 'A component'
|
|
306
|
+
}),
|
|
307
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
308
|
+
value: "layout",
|
|
309
|
+
children: 'A layout'
|
|
310
|
+
}),
|
|
311
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
312
|
+
value: "datasetSchema",
|
|
313
|
+
children: 'A dataset schema'
|
|
314
|
+
}),
|
|
315
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
316
|
+
value: "emailTemplate",
|
|
317
|
+
children: 'An email template'
|
|
318
|
+
}),
|
|
319
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
320
|
+
value: "emailStarter",
|
|
321
|
+
children: 'A campaign email others can start from'
|
|
322
|
+
}),
|
|
323
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
324
|
+
value: "theme",
|
|
325
|
+
children: 'This site’s theme'
|
|
326
|
+
}),
|
|
327
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
328
|
+
value: "site",
|
|
329
|
+
children: 'This entire site (as a template)'
|
|
330
|
+
}),
|
|
331
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
332
|
+
value: "plugin",
|
|
333
|
+
children: 'A plugin (upload a bundle)'
|
|
334
|
+
})
|
|
335
|
+
]
|
|
336
|
+
}),
|
|
337
|
+
kind === 'plugin' ? /*#__PURE__*/ _jsx(Typography, {
|
|
338
|
+
variant: "body2",
|
|
339
|
+
color: "text.secondary",
|
|
340
|
+
children: 'Upload a self-contained plugin bundle and its manifest. It ' + 'publishes sandboxed and is signed after a reviewer verifies ' + 'it. Publish it to the marketplace, or keep it private to your ' + 'organization — private plugins take the same review path.'
|
|
341
|
+
}) : kind === 'site' ? /*#__PURE__*/ _jsx(Typography, {
|
|
342
|
+
variant: "body2",
|
|
343
|
+
color: "text.secondary",
|
|
344
|
+
children: 'Publishes this site’s current published screens and theme as an ' + 'installable starting point.'
|
|
345
|
+
}) : kind === 'theme' ? /*#__PURE__*/ _jsx(Typography, {
|
|
346
|
+
variant: "body2",
|
|
347
|
+
color: "text.secondary",
|
|
348
|
+
children: 'Publishes this site’s colors, typography, shape and component ' + 'styles on their own. Both light and dark schemes are required, ' + 'and text has to be readable against its background — you will ' + 'be told before anything is published if not.'
|
|
349
|
+
}) : activeList.length ? /*#__PURE__*/ _jsx(TextField, {
|
|
350
|
+
select: true,
|
|
351
|
+
size: "small",
|
|
352
|
+
label: picker.label,
|
|
353
|
+
value: selectedId,
|
|
354
|
+
onChange: (event)=>setArtifactId(event.target.value),
|
|
355
|
+
children: activeList.map((entry)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
356
|
+
value: entry.$id,
|
|
357
|
+
children: picker.optionLabel(entry)
|
|
358
|
+
}, entry.$id))
|
|
359
|
+
}) : /*#__PURE__*/ _jsx(Typography, {
|
|
360
|
+
variant: "body2",
|
|
361
|
+
color: "text.secondary",
|
|
362
|
+
children: picker.empty
|
|
363
|
+
}),
|
|
364
|
+
kind === 'emailTemplate' && activeList.length ? /*#__PURE__*/ _jsx(Typography, {
|
|
365
|
+
variant: "caption",
|
|
366
|
+
color: "text.secondary",
|
|
367
|
+
children: 'Installing an email template adds it as a draft version — it ' + 'never replaces the design a site is already sending.'
|
|
368
|
+
}) : null,
|
|
369
|
+
kind === 'emailStarter' && activeList.length ? /*#__PURE__*/ _jsx(Typography, {
|
|
370
|
+
variant: "caption",
|
|
371
|
+
color: "text.secondary",
|
|
372
|
+
children: 'A published email is copied into the sites that install it, so ' + 'later edits of yours never reach them. Images must come from ' + 'your media library or be inline, links must be https, and rich ' + 'text blocks are not published — an email you send is loaded ' + 'inside somebody else’s recipient’s mail client.'
|
|
373
|
+
}) : null,
|
|
374
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
375
|
+
children: kind === 'plugin' ? /*#__PURE__*/ _jsx(AppLink, {
|
|
376
|
+
href: publishPluginPath(basePath),
|
|
377
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
378
|
+
variant: "contained",
|
|
379
|
+
color: "primary",
|
|
380
|
+
children: 'Publish a plugin…'
|
|
381
|
+
})
|
|
382
|
+
}) : /*#__PURE__*/ _jsx(Button, {
|
|
383
|
+
variant: "contained",
|
|
384
|
+
color: "primary",
|
|
385
|
+
disabled: !canPublish,
|
|
386
|
+
onClick: openPublish,
|
|
387
|
+
children: 'Publish…'
|
|
388
|
+
})
|
|
389
|
+
})
|
|
390
|
+
]
|
|
391
|
+
}),
|
|
392
|
+
/*#__PURE__*/ _jsx(PublishArtifactDialog, {
|
|
393
|
+
artifact: target,
|
|
394
|
+
onClose: ()=>setTarget(null)
|
|
395
|
+
})
|
|
396
|
+
]
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
OrgPublishPanel.displayName = 'OrgPublishPanel';
|
|
400
|
+
export default OrgPublishPanel;
|
|
401
|
+
|
|
402
|
+
//# sourceMappingURL=org-publish-panel.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/org-publish-panel.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 PLAN_ENTITLEMENTS,\n PLATFORM_BRAND_NAME,\n pluginDocsHelp,\n resolveMarketplaceFeePct,\n type ConsolePublishableArtifact,\n} from '@aglyn/aglyn'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n Alert,\n Box,\n Button,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { getTenantEmail } from '@aglyn/shared-util-email'\nimport { collection, doc, limit, query, where } from 'firebase/firestore'\nimport { useMemo, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useFirestoreDoc,\n} from '@aglyn/tenant-feature-instance'\nimport { publishPluginPath } from '../model/marketplace-paths'\nimport PublishArtifactDialog from './publish-artifact-dialog.component'\n\ntype PublishKind =\n | 'component'\n | 'layout'\n | 'site'\n | 'datasetSchema'\n | 'emailTemplate'\n | 'emailStarter'\n | 'theme'\n | 'plugin'\n\nconst artifactName = (artifact: any): string | undefined =>\n artifact?.displayName ?? artifact?.name ?? artifact?.title ?? undefined\n\n/**\n * Email templates are keyed by a fixed catalog key, and the stored doc carries\n * only the design — the human name lives in the catalog.\n */\nconst emailLabel = (templateKey: string): string =>\n getTenantEmail(templateKey)?.name ?? templateKey\n\n/** Per-kind select label, option text, and empty-state copy. */\nconst PICKERS: Record<\n PublishKind,\n { label: string; empty: string; optionLabel: (entry: any) => string }\n> = {\n component: {\n label: 'Component',\n empty: 'This site has no reusable components to publish yet.',\n optionLabel: (entry) => artifactName(entry) ?? entry.$id,\n },\n layout: {\n label: 'Layout',\n empty: 'This site has no layouts to publish yet.',\n optionLabel: (entry) => artifactName(entry) ?? entry.$id,\n },\n datasetSchema: {\n label: 'Dataset',\n empty: 'This organization has no datasets to publish yet.',\n optionLabel: (entry) => artifactName(entry) ?? entry.$id,\n },\n emailTemplate: {\n label: 'Email',\n empty: 'This site has no designed emails to publish yet.',\n // The doc id IS the catalog key; the design carries no name of its own.\n optionLabel: (entry) => emailLabel(entry.$id),\n },\n // A campaign email is an ordinary screen document, so unlike the row above\n // it carries its own name and there is no catalog to look one up in.\n emailStarter: {\n label: 'Email',\n empty: 'This site has no saved campaign emails to publish yet.',\n optionLabel: (entry) => artifactName(entry) ?? entry.$id,\n },\n site: { label: 'Site', empty: '', optionLabel: () => '' },\n // A theme is a field on the site, so like `site` there is nothing to pick —\n // choosing the source site has already chosen the theme.\n theme: { label: 'Theme', empty: '', optionLabel: () => '' },\n // Plugins don't pick an existing artifact — they're uploaded (AGL-868).\n plugin: { label: 'Plugin', empty: '', optionLabel: () => '' },\n}\n\n/**\n * Org-level publish (AGL-776): pick a source site, then a component, a layout,\n * or the whole site (as a template), and publish it under the org's publisher\n * identity. This panel's whole job is choosing WHAT: it names the kind, the\n * scope that holds it and the document, and hands that to the\n * `hostArtifactPublish` zone (AGL-3080), whose widget owns the name/price\n * form, the route and the POST. Plugins publish from their own bundle upload,\n * not here.\n *\n * The per-site publish buttons (Components/Layouts/Setup pages) stay as\n * in-context shortcuts; this is the one place that spans every site.\n */\nexport function OrgPublishPanel({\n orgId,\n hosts,\n basePath,\n billingPath,\n org,\n orgReady,\n}: {\n orgId: string\n hosts: ReadonlyArray<{ id: string; label: string }>\n /**\n * Where the marketplace hub is mounted under this organization, from the\n * shell (AGL-3080). The publish-a-plugin page hangs beneath it.\n */\n basePath: string\n /**\n * Where this organization manages its plan, from the shell's org mount.\n * Absent on a deployment that bills nobody, and the fee notice then says\n * the rate without offering a way to change it.\n */\n billingPath?: string\n /**\n * The org billing doc the shell already loaded, and whether it has\n * settled. Read here rather than through the console's `useCurrentOrg`:\n * the fee shown is a claim about this publisher's plan, and\n * `resolveMarketplaceFeePct(undefined)` answers the FREE rate — so a\n * number rendered during load accuses a paying publisher of the higher\n * cut.\n */\n org?: Parameters<typeof resolveMarketplaceFeePct>[0]\n orgReady: boolean\n}) {\n const firestore = useFirestore()\n // The platform's cut, from the SAME helper the checkout deducts with\n // (AGL-2078) rather than a restated constant — a second copy of a rate\n // that moves money is a copy free to disagree with the one that bills.\n const feePct = resolveMarketplaceFeePct(org as never)\n const paidFeePct = PLAN_ENTITLEMENTS.starter.marketplaceFeePct\n const { data: profile } = useFirestoreDoc<any>(\n () => doc(firestore, 'publisherProfiles', orgId || '-none-'),\n [firestore, orgId],\n { idField: '$id' },\n )\n const [sourceHostId, setSourceHostId] = useState('')\n const hostId = sourceHostId || hosts[0]?.id || ''\n const [kind, setKind] = useState<PublishKind>('component')\n const [artifactId, setArtifactId] = useState('')\n const [target, setTarget] = useState<ConsolePublishableArtifact | null>(null)\n\n // Held at null while there is no host, never addressed as `hosts/-none-`\n // (AGL-1440): an org with ZERO sites has no `hosts[0]`, so the sentinel was\n // not a loading window — it was three permanently rules-denied listens per\n // panel mount, each reopened forever by the refusal loop. The hook issues\n // nothing for a null query and the lists render their empty states.\n const { data: componentDocs } = useFirestoreCollection<any>(\n () =>\n hostId\n ? query(\n collection(firestore, 'hosts', hostId, 'components'),\n limit(100),\n )\n : null,\n [firestore, hostId],\n { idField: '$id' },\n )\n const { data: layoutDocs } = useFirestoreCollection<any>(\n () =>\n hostId\n ? query(collection(firestore, 'hosts', hostId, 'layouts'), limit(100))\n : null,\n [firestore, hostId],\n { idField: '$id' },\n )\n // Datasets are ORG-scoped (AGL-237), so unlike every other source here they\n // don't depend on the selected site.\n const { data: datasetDocs } = useFirestoreCollection<any>(\n () =>\n orgId\n ? query(collection(firestore, 'orgs', orgId, 'datasets'), limit(100))\n : null,\n [firestore, orgId],\n { idField: '$id' },\n )\n const { data: emailDocs } = useFirestoreCollection<any>(\n () =>\n hostId\n ? query(\n collection(firestore, 'hosts', hostId, 'emailTemplates'),\n limit(100),\n )\n : null,\n [firestore, hostId],\n { idField: '$id' },\n )\n /**\n * Campaign emails are `kind: 'email'` SCREENS, not entries in the\n * transactional catalog above — a different collection, a different shape,\n * and a different marketplace artifact type. The equality filter is on the\n * query so a site with many pages does not read them all to find its emails.\n */\n const emailScreenDocs = useFirestoreCollection<any>(\n () =>\n hostId\n ? query(\n collection(firestore, 'hosts', hostId, 'screens'),\n where('kind', '==', 'email'),\n limit(100),\n )\n : null,\n [firestore, hostId],\n { idField: '$id' },\n ).data\n const components = useMemo(\n () => (componentDocs ?? []).filter((entry: any) => !entry.deletedAt),\n [componentDocs],\n )\n const layouts = useMemo(\n () => (layoutDocs ?? []).filter((entry: any) => !entry.deletedAt),\n [layoutDocs],\n )\n const datasets = useMemo(\n () => (datasetDocs ?? []).filter((entry: any) => !entry.deletedAt),\n [datasetDocs],\n )\n // Only emails that actually have a saved design are publishable — the doc\n // exists with no `versionId` whenever the besigner was merely opened.\n const emails = useMemo(\n () =>\n (emailDocs ?? []).filter(\n (entry: any) => !entry.deletedAt && entry.versionId,\n ),\n [emailDocs],\n )\n // Same rule, same reason: a screen with no `versionId` was opened and never\n // saved, so there is no design to publish.\n const emailScreens = useMemo(\n () =>\n (emailScreenDocs ?? []).filter(\n (entry: any) => !entry.deletedAt && entry.versionId,\n ),\n [emailScreenDocs],\n )\n\n // Validate the pick against the current list rather than resetting it in an\n // effect (a classic render-loop source — the reset runs a `setState` after\n // every host/kind change, adding renders that a transient flurry can push\n // past React's update-depth limit). A stale id from another site or\n // artifact type simply reads as \"nothing selected\" this render, so we never\n // publish it, and the MUI <Select> value stays in range every render.\n const activeList =\n kind === 'component'\n ? components\n : kind === 'layout'\n ? layouts\n : kind === 'datasetSchema'\n ? datasets\n : kind === 'emailTemplate'\n ? emails\n : kind === 'emailStarter'\n ? emailScreens\n : []\n // One descriptor per source kind, so adding an artifact type is a row here\n // rather than another arm of a nested ternary in the JSX below.\n const picker = PICKERS[kind]\n const selectedId = activeList.some((entry: any) => entry.$id === artifactId)\n ? artifactId\n : ''\n\n const hostLabel = hosts.find((host) => host.id === hostId)?.label\n\n /*\n * WHAT IS BEING PUBLISHED, in this console's own words (AGL-3080).\n *\n * This used to build seven marketplace requests — an endpoint, a payload\n * key and a noun each — which made a panel about the org's own artifacts a\n * file that could not be right without being kept in step with a plugin's\n * routes. It now says only what the thing IS and which scope holds it; the\n * `hostArtifactPublish` zone's widget decides where it goes.\n *\n * ⚠️ Datasets are ORG-scoped and every other kind is a site's, which is why\n * both scopes ride and neither is inferred from the other.\n */\n const openPublish = () => {\n const named =\n kind === 'site'\n ? hostLabel\n : kind === 'theme'\n ? hostLabel && `${hostLabel} theme`\n : kind === 'emailTemplate'\n ? emailLabel(selectedId)\n : artifactName(\n activeList.find((entry: any) => entry.$id === selectedId),\n )\n setTarget({\n kind,\n hostId,\n orgId,\n // Absent for a kind that IS the site — a whole site template, a theme —\n // where `hostId` above already names it.\n ...(kind === 'site' || kind === 'theme'\n ? {}\n : { artifactId: selectedId }),\n ...(named ? { displayName: named } : {}),\n })\n }\n\n // Dataset schemas publish from the org, so they need no source site — an\n // org with datasets but no sites yet can still publish one.\n const canPublish =\n kind === 'datasetSchema'\n ? Boolean(orgId && selectedId)\n : Boolean(hostId && (kind === 'site' || kind === 'theme' || selectedId))\n\n // A publisher profile (with a handle) is required server-side; guide the\n // user there rather than letting the publish 412.\n if (profile !== undefined && !profile?.handle) {\n return (\n <CardDisplay\n header={'Publish to the marketplace'}\n // The SAME card, in the branch where it cannot be used — and until\n // AGL-2130 the only branch with no help. A reader who arrives here is\n // by definition the one who does not yet know how publishing works,\n // so this is the branch that can least afford to drop the link.\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#where-to-publish-from',\n excerpt:\n 'Publishing needs an organization publisher profile with a handle. ' +\n 'Set one up on the Profile tab first.',\n })}\n contentGutterX\n contentGutterY\n >\n <Alert severity=\"info\">\n {'Set up your organization’s publisher profile in the Profile tab ' +\n 'before publishing.'}\n </Alert>\n </CardDisplay>\n )\n }\n\n return (\n <CardDisplay\n header={'Publish to the marketplace'}\n help={pluginDocsHelp('publisherHandbook', {\n anchor: '#where-to-publish-from',\n excerpt:\n 'Publish a component, layout, dataset schema, email template, or ' +\n 'whole site so other organizations can install it.',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2} sx={{ maxWidth: 480 }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Publish a component, layout, dataset schema, email template, or ' +\n 'an entire site from your organization so other organizations ' +\n 'can install it. Your live site is unaffected.'}\n </Typography>\n {/* What Aglyn keeps (AGL-2078). Until now this number appeared in\n exactly one place in the console — the STAFF override table —\n while `resolveMarketplaceFeePct` deducted it from every sale. A\n publisher chose a price and learned the cut from the payout.\n\n Rendered only once the org doc has arrived:\n `resolveMarketplaceFeePct(undefined)` returns the free-plan rate,\n so a number shown during load accuses a paying publisher of the\n higher cut — and the same helper prices a DEAD subscription as\n free-plan while `org.plan` still reads Pro, which is precisely\n the case a publisher has no other way to notice. */}\n {orgReady ? (\n <Alert severity={feePct > paidFeePct ? 'warning' : 'info'}>\n {feePct > paidFeePct\n ? `${PLATFORM_BRAND_NAME} keeps ${feePct}% of each sale on your current plan. ` +\n `On a paid plan it is ${paidFeePct}%.`\n : `${PLATFORM_BRAND_NAME} keeps ${feePct}% of each sale. Payment processing ` +\n 'fees are charged separately by Stripe.'}\n {feePct > paidFeePct && billingPath ? (\n <Box sx={{ mt: 1 }}>\n <AppLink href={billingPath}>{'See plans'}</AppLink>\n </Box>\n ) : null}\n </Alert>\n ) : null}\n {hosts.length > 1 && kind !== 'datasetSchema' ? (\n <TextField\n select\n size=\"small\"\n label=\"From site\"\n value={hostId}\n onChange={(event) => setSourceHostId(event.target.value)}\n >\n {hosts.map((host) => (\n <MenuItem key={host.id} value={host.id}>\n {host.label}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <TextField\n select\n size=\"small\"\n label=\"What to publish\"\n value={kind}\n onChange={(event) => setKind(event.target.value as PublishKind)}\n >\n <MenuItem value=\"component\">{'A component'}</MenuItem>\n <MenuItem value=\"layout\">{'A layout'}</MenuItem>\n <MenuItem value=\"datasetSchema\">{'A dataset schema'}</MenuItem>\n <MenuItem value=\"emailTemplate\">{'An email template'}</MenuItem>\n <MenuItem value=\"emailStarter\">\n {'A campaign email others can start from'}\n </MenuItem>\n <MenuItem value=\"theme\">{'This site’s theme'}</MenuItem>\n <MenuItem value=\"site\">{'This entire site (as a template)'}</MenuItem>\n <MenuItem value=\"plugin\">{'A plugin (upload a bundle)'}</MenuItem>\n </TextField>\n {kind === 'plugin' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Upload a self-contained plugin bundle and its manifest. It ' +\n 'publishes sandboxed and is signed after a reviewer verifies ' +\n 'it. Publish it to the marketplace, or keep it private to your ' +\n 'organization — private plugins take the same review path.'}\n </Typography>\n ) : kind === 'site' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Publishes this site’s current published screens and theme as an ' +\n 'installable starting point.'}\n </Typography>\n ) : kind === 'theme' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Publishes this site’s colors, typography, shape and component ' +\n 'styles on their own. Both light and dark schemes are required, ' +\n 'and text has to be readable against its background — you will ' +\n 'be told before anything is published if not.'}\n </Typography>\n ) : activeList.length ? (\n <TextField\n select\n size=\"small\"\n label={picker.label}\n value={selectedId}\n onChange={(event) => setArtifactId(event.target.value)}\n >\n {activeList.map((entry: any) => (\n <MenuItem key={entry.$id} value={entry.$id}>\n {picker.optionLabel(entry)}\n </MenuItem>\n ))}\n </TextField>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {picker.empty}\n </Typography>\n )}\n {kind === 'emailTemplate' && activeList.length ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Installing an email template adds it as a draft version — it ' +\n 'never replaces the design a site is already sending.'}\n </Typography>\n ) : null}\n {/* The publish rules, said before the publish rather than as a 422.\n They are narrower than the ones for a page, and the reason is worth\n a sentence: a design published here is mailed by other people, from\n a sending domain everyone shares. */}\n {kind === 'emailStarter' && activeList.length ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'A published email is copied into the sites that install it, so ' +\n 'later edits of yours never reach them. Images must come from ' +\n 'your media library or be inline, links must be https, and rich ' +\n 'text blocks are not published — an email you send is loaded ' +\n 'inside somebody else’s recipient’s mail client.'}\n </Typography>\n ) : null}\n <Box>\n {/* Publishing a plugin is a page now (AGL-1078), so this is a link\n — the destination has a URL, a draft that survives a reload,\n and room for the checklist. AppLink, never an href on Button:\n a raw href full-reloads the SPA. */}\n {kind === 'plugin' ? (\n <AppLink href={publishPluginPath(basePath)}>\n <Button variant=\"contained\" color=\"primary\">\n {'Publish a plugin…'}\n </Button>\n </AppLink>\n ) : (\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!canPublish}\n onClick={openPublish}\n >\n {'Publish…'}\n </Button>\n )}\n </Box>\n </Stack>\n {/* No `useSlotWidgets` gate on the button above, unlike the pages that\n offer a publish in passing: this panel is drawn inside the\n marketplace's own surface, so a workspace that cannot reach the\n zone's widget never reaches this panel either. */}\n {/* Rendered directly, not through the `hostArtifactPublish` zone. The\n zone exists so a page that offers a publish IN PASSING — a site's\n layouts list — can do it without importing this plugin. This panel\n IS this plugin, and a slot here would be the marketplace asking the\n shell to find the marketplace. */}\n <PublishArtifactDialog artifact={target} onClose={() => setTarget(null)} />\n </CardDisplay>\n )\n}\nOrgPublishPanel.displayName = 'OrgPublishPanel'\n\nexport default OrgPublishPanel\n"],"names":["PLAN_ENTITLEMENTS","PLATFORM_BRAND_NAME","pluginDocsHelp","resolveMarketplaceFeePct","AppLink","CardDisplay","Alert","Box","Button","MenuItem","Stack","TextField","Typography","getTenantEmail","collection","doc","limit","query","where","useMemo","useState","useFirestore","useFirestoreCollection","useFirestoreDoc","publishPluginPath","PublishArtifactDialog","artifactName","artifact","displayName","name","title","undefined","emailLabel","templateKey","PICKERS","component","label","empty","optionLabel","entry","$id","layout","datasetSchema","emailTemplate","emailStarter","site","theme","plugin","OrgPublishPanel","orgId","hosts","basePath","billingPath","org","orgReady","firestore","feePct","paidFeePct","starter","marketplaceFeePct","data","profile","idField","sourceHostId","setSourceHostId","hostId","id","kind","setKind","artifactId","setArtifactId","target","setTarget","componentDocs","layoutDocs","datasetDocs","emailDocs","emailScreenDocs","components","filter","deletedAt","layouts","datasets","emails","versionId","emailScreens","activeList","picker","selectedId","some","hostLabel","find","host","openPublish","named","canPublish","Boolean","handle","header","help","anchor","excerpt","contentGutterX","contentGutterY","severity","spacing","sx","maxWidth","variant","color","mt","href","length","select","size","value","onChange","event","map","disabled","onClick","onClose"],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
export type OrgSellerSection = 'profile' | 'listings' | 'payouts' | 'sales';
|
|
3
|
+
export interface OrgSellerPanelProps {
|
|
4
|
+
orgId: string;
|
|
5
|
+
/** Which seller card to render — one per Marketplace › seller tab. */
|
|
6
|
+
section: OrgSellerSection;
|
|
7
|
+
/**
|
|
8
|
+
* Where the marketplace hub is mounted under this organization, from the
|
|
9
|
+
* shell (AGL-3080) — the panel's links to a listing and to the publish
|
|
10
|
+
* form hang beneath it. It replaced an org slug and the console's route
|
|
11
|
+
* table, which a plugin may not import.
|
|
12
|
+
*/
|
|
13
|
+
basePath: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Seller area (AGL-44/798/801): the org's marketplace identity and its
|
|
17
|
+
* published listings, folded out of the retired `/[orgSlug]/marketplace` page.
|
|
18
|
+
* Each section (profile, listings, payouts, sales) is its own Marketplace
|
|
19
|
+
* ROUTE (AGL-801/693), so this renders exactly one card per the `section`
|
|
20
|
+
* prop. The shared Firestore hooks run regardless of which section is asked
|
|
21
|
+
* for — Firebase dedupes identical listeners, and only the section the reader
|
|
22
|
+
* navigated to is mounted at all, so the others never subscribe.
|
|
23
|
+
*/
|
|
24
|
+
export declare function OrgSellerPanel(props: OrgSellerPanelProps): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
25
|
+
export declare namespace OrgSellerPanel {
|
|
26
|
+
var displayName: string;
|
|
27
|
+
}
|
|
28
|
+
export default OrgSellerPanel;
|