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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/package.json +15 -11
  2. package/src/lib/components/edit-listing-dialog.component.d.ts +20 -0
  3. package/src/lib/components/edit-listing-dialog.component.js +51 -0
  4. package/src/lib/components/edit-listing-dialog.component.js.map +1 -0
  5. package/src/lib/components/listing-detail-editor.component.d.ts +24 -0
  6. package/src/lib/components/listing-detail-editor.component.js +457 -0
  7. package/src/lib/components/listing-detail-editor.component.js.map +1 -0
  8. package/src/lib/components/listing-detail-page.component.d.ts +28 -0
  9. package/src/lib/components/listing-detail-page.component.js +151 -0
  10. package/src/lib/components/listing-detail-page.component.js.map +1 -0
  11. package/src/lib/components/listing-review-status.component.d.ts +67 -0
  12. package/src/lib/components/listing-review-status.component.js +301 -0
  13. package/src/lib/components/listing-review-status.component.js.map +1 -0
  14. package/src/lib/components/listing-verification-request.component.d.ts +40 -0
  15. package/src/lib/components/listing-verification-request.component.js +170 -0
  16. package/src/lib/components/listing-verification-request.component.js.map +1 -0
  17. package/src/lib/components/marketplace-hub.component.d.ts +43 -0
  18. package/src/lib/components/marketplace-hub.component.js +244 -0
  19. package/src/lib/components/marketplace-hub.component.js.map +1 -0
  20. package/src/lib/components/marketplace-reports-page.component.d.ts +23 -0
  21. package/src/lib/components/marketplace-reports-page.component.js +324 -0
  22. package/src/lib/components/marketplace-reports-page.component.js.map +1 -0
  23. package/src/lib/components/org-licences-panel.component.d.ts +73 -0
  24. package/src/lib/components/org-licences-panel.component.js +318 -0
  25. package/src/lib/components/org-licences-panel.component.js.map +1 -0
  26. package/src/lib/components/org-publish-panel.component.d.ts +45 -0
  27. package/src/lib/components/org-publish-panel.component.js +402 -0
  28. package/src/lib/components/org-publish-panel.component.js.map +1 -0
  29. package/src/lib/components/org-seller-panel.component.d.ts +28 -0
  30. package/src/lib/components/org-seller-panel.component.js +956 -0
  31. package/src/lib/components/org-seller-panel.component.js.map +1 -0
  32. package/src/lib/components/plugin-review-detail.component.d.ts +16 -0
  33. package/src/lib/components/plugin-review-detail.component.js +1438 -0
  34. package/src/lib/components/plugin-review-detail.component.js.map +1 -0
  35. package/src/lib/components/plugin-reviews-queue.component.d.ts +13 -0
  36. package/src/lib/components/plugin-reviews-queue.component.js +385 -0
  37. package/src/lib/components/plugin-reviews-queue.component.js.map +1 -0
  38. package/src/lib/components/plugin-reviews-surface.component.d.ts +19 -0
  39. package/src/lib/components/plugin-reviews-surface.component.js +46 -0
  40. package/src/lib/components/plugin-reviews-surface.component.js.map +1 -0
  41. package/src/lib/components/publish-plugin-form.component.d.ts +61 -0
  42. package/src/lib/components/publish-plugin-form.component.js +857 -0
  43. package/src/lib/components/publish-plugin-form.component.js.map +1 -0
  44. package/src/lib/components/publish-plugin-page.component.d.ts +29 -0
  45. package/src/lib/components/publish-plugin-page.component.js +92 -0
  46. package/src/lib/components/publish-plugin-page.component.js.map +1 -0
  47. package/src/lib/components/publisher-profile-page.component.d.ts +13 -0
  48. package/src/lib/components/publisher-profile-page.component.js +261 -0
  49. package/src/lib/components/publisher-profile-page.component.js.map +1 -0
  50. package/src/lib/constants/listing-media.d.ts +28 -0
  51. package/src/lib/constants/listing-media.js +28 -0
  52. package/src/lib/constants/listing-media.js.map +1 -0
  53. package/src/lib/model/listing-social-card.d.ts +94 -0
  54. package/src/lib/model/listing-social-card.js +177 -0
  55. package/src/lib/model/listing-social-card.js.map +1 -0
  56. package/src/lib/model/marketplace-paths.d.ts +54 -0
  57. package/src/lib/model/marketplace-paths.js +59 -0
  58. package/src/lib/model/marketplace-paths.js.map +1 -0
  59. package/src/lib/model/payout-readiness.d.ts +81 -0
  60. package/src/lib/model/payout-readiness.js +52 -0
  61. package/src/lib/model/payout-readiness.js.map +1 -0
  62. package/src/lib/model/plugin-rejection-categories.d.ts +66 -0
  63. package/src/lib/model/plugin-rejection-categories.js +124 -0
  64. package/src/lib/model/plugin-rejection-categories.js.map +1 -0
  65. package/src/lib/model/plugin-review-checklist.d.ts +62 -0
  66. package/src/lib/model/plugin-review-checklist.js +131 -0
  67. package/src/lib/model/plugin-review-checklist.js.map +1 -0
  68. package/src/lib/model/plugin-review-status.d.ts +104 -0
  69. package/src/lib/model/plugin-review-status.js +126 -0
  70. package/src/lib/model/plugin-review-status.js.map +1 -0
  71. package/src/lib/model/seller-ledger-totals.d.ts +100 -0
  72. package/src/lib/model/seller-ledger-totals.js +96 -0
  73. package/src/lib/model/seller-ledger-totals.js.map +1 -0
  74. package/src/lib/plugin.js +170 -40
  75. package/src/lib/plugin.js.map +1 -1
  76. package/src/lib/server/admin-reports.d.ts +18 -0
  77. package/src/lib/server/admin-reports.js +280 -0
  78. package/src/lib/server/admin-reports.js.map +1 -0
  79. package/src/lib/server/admin-reviews.d.ts +18 -0
  80. package/src/lib/server/admin-reviews.js +1341 -0
  81. package/src/lib/server/admin-reviews.js.map +1 -0
  82. package/src/lib/server/listing-social-card.server.d.ts +24 -0
  83. package/src/lib/server/listing-social-card.server.js +65 -0
  84. package/src/lib/server/listing-social-card.server.js.map +1 -0
  85. package/src/lib/server/publisher-review-email.d.ts +54 -0
  86. package/src/lib/server/publisher-review-email.js +85 -0
  87. package/src/lib/server/publisher-review-email.js.map +1 -0
  88. package/src/lib/server/revoke-cache-drop.d.ts +59 -0
  89. package/src/lib/server/revoke-cache-drop.js +74 -0
  90. package/src/lib/server/revoke-cache-drop.js.map +1 -0
  91. package/src/lib/server.js +47 -0
  92. package/src/lib/server.js.map +1 -1
@@ -0,0 +1,956 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
18
+ import { trackEvent } from "@aglyn/aglyn/app-utils/analytics-events";
19
+ import { mdiDotsVertical } from "@aglyn/shared-data-mdi";
20
+ import { AppLink, CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
21
+ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
22
+ import { Timestamp } from "@aglyn/shared-util-timestamp";
23
+ import { Alert, Avatar, Button, Chip, Divider, IconButton, Link, Menu, MenuItem, Stack, TextField, Typography } from "@mui/material";
24
+ import { PUBLISHER_AGREEMENT_POINTS, PUBLISHER_AGREEMENT_TITLE, PUBLISHER_AGREEMENT_VERSION, publisherAgreementPresentation, publisherAgreementState } from "@aglyn/aglyn/app-utils/publisher-agreement";
25
+ /*
26
+ THE LEGAL NAME, not `useBranding().branding.productName` (AGL-2351).
27
+
28
+ Every other customer-facing string in the console reads the org's resolved
29
+ product name, and doing that here would be false: the two sentences below say
30
+ which entity holds a sales-tax registration and remits against it. A
31
+ white-label agency holds neither. The value has to name the company that
32
+ actually stands behind the money — the same reasoning that keeps
33
+ `tx-return-webfile.ts` naming the entity rather than the product, and the
34
+ reason these two were left out of the AGL-2319 sweep instead of swept wrong.
35
+ */ import { PLATFORM_BRAND_LEGAL_NAME } from "@aglyn/aglyn/app-utils/platform-brand";
36
+ import { LISTING_LOGO_HINT } from "../constants/listing-media.js";
37
+ import { useRouter } from "next/navigation";
38
+ import { collection, doc, query, updateDoc, where } from "firebase/firestore";
39
+ import { useCallback, useEffect, useState } from "react";
40
+ import { useFirestore, useUser } from "@aglyn/tenant-feature-instance";
41
+ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
42
+ import { pluginDocsHelp, useMediaPicker } from "@aglyn/aglyn";
43
+ import { useFirestoreCollection } from "@aglyn/tenant-feature-instance";
44
+ import { useFirestoreDoc } from "@aglyn/tenant-feature-instance";
45
+ import EditListingDialog from "./edit-listing-dialog.component.js";
46
+ import { listingPath, publishPluginPath } from "../model/marketplace-paths.js";
47
+ import { payoutReadiness } from "../model/payout-readiness.js";
48
+ import { summarizeSellerLedger } from "../model/seller-ledger-totals.js";
49
+ const HANDLE_PATTERN = /^[a-z0-9][a-z0-9-]{2,29}$/;
50
+ /**
51
+ * Seller area (AGL-44/798/801): the org's marketplace identity and its
52
+ * published listings, folded out of the retired `/[orgSlug]/marketplace` page.
53
+ * Each section (profile, listings, payouts, sales) is its own Marketplace
54
+ * ROUTE (AGL-801/693), so this renders exactly one card per the `section`
55
+ * prop. The shared Firestore hooks run regardless of which section is asked
56
+ * for — Firebase dedupes identical listeners, and only the section the reader
57
+ * navigated to is mounted at all, so the others never subscribe.
58
+ */ export function OrgSellerPanel(props) {
59
+ var _ref, _ref1, _ref2, _profile_lastPayoutFailureCents, _profile_lastPayoutFailureReason;
60
+ var _profile_publisherAgreement, _profile_publisherAgreement1, _rowMenu_listing, _rowMenu_listing1, _rowMenu_listing2, _rowMenu_listing3, _rowMenu_listing4, _rowMenu_listing5, _rowMenu_listing6, _rowMenu_listing7;
61
+ const { orgId, section, basePath } = props;
62
+ const router = useRouter();
63
+ const firestore = useFirestore();
64
+ const { data: user } = useUser();
65
+ const { enqueueSnackbar } = useSnackbar();
66
+ /** Bumped by Retry to re-subscribe the publisher profile (AGL-1380). */ const [profileRetryNonce, setProfileRetryNonce] = useState(0);
67
+ const { data: profile, status: profileStatus } = useFirestoreDoc(// The org's marketplace identity (AGL-652), not the personal one. Held
68
+ // at null rather than addressed as `publisherProfiles/-none-`: that
69
+ // sentinel issued a guaranteed-denied read, and a denial now lands in
70
+ // `status: 'error'`, which means something.
71
+ ()=>orgId ? doc(firestore, 'publisherProfiles', orgId) : null, [
72
+ firestore,
73
+ orgId,
74
+ profileRetryNonce
75
+ ], {
76
+ idField: '$id'
77
+ });
78
+ /**
79
+ * Three outcomes, not two (AGL-1380). An absent publisher profile is the
80
+ * value BEFORE an answer as much as it is the value of a publisher who
81
+ * never connected Stripe, so reading payout state off `stripeChargesEnabled`
82
+ * alone told a publisher already taking payouts to "Connect a Stripe
83
+ * account" and offered them the setup button that starts onboarding again.
84
+ */ const payoutsState = !orgId || profileStatus === 'loading' ? 'pending' : profileStatus === 'error' ? 'error' : 'loaded';
85
+ /**
86
+ * TWO questions, not one (AGL-1997). This card said "Payouts are enabled"
87
+ * off `stripeChargesEnabled` — whether Stripe will let the account take
88
+ * MONEY — and never read `stripePayoutsEnabled`, whether the money can
89
+ * LEAVE. The decision itself lives in `payoutReadiness` so each of its six
90
+ * outcomes can be asserted without mounting this tree.
91
+ */ const readiness = payoutReadiness({
92
+ state: payoutsState,
93
+ chargesEnabled: profile == null ? void 0 : profile.stripeChargesEnabled,
94
+ payoutsEnabled: profile == null ? void 0 : profile.stripePayoutsEnabled,
95
+ accountLivemode: profile == null ? void 0 : profile.stripeAccountLivemode
96
+ });
97
+ /** Stripe will let this account take money — four of the seven outcomes. */ const chargesEnabled = readiness === 'ready' || readiness === 'blocked' || readiness === 'unknown' || readiness === 'unverified';
98
+ // Both held at null while the org is unknown, like the profile above
99
+ // (AGL-1440): AGL-1380 repaired the profile ref and left these two
100
+ // `where`-clause siblings on the sentinel. The purchases query is the one
101
+ // that matters — `marketplacePurchases` is buyer/seller-gated, and a
102
+ // rules-shaped LIST is evaluated against the QUERY, so the sentinel was
103
+ // denied wholesale and retried on the refusal cadence. The listings query
104
+ // is public and merely wasted the round trip.
105
+ const { data: listings } = useFirestoreCollection(()=>orgId ? query(collection(firestore, 'marketplaceListings'), // Listings are org-owned (AGL-652): `profileId` is the org id,
106
+ // not a uid — filtering by uid left this list permanently empty
107
+ // (AGL-781).
108
+ where('profileId', '==', orgId)) : null, [
109
+ firestore,
110
+ orgId
111
+ ], {
112
+ idField: '$id'
113
+ });
114
+ // Seller ledger (AGL-46): purchase records written by the Stripe webhook.
115
+ const { data: sales } = useFirestoreCollection(()=>orgId ? query(collection(firestore, 'marketplacePurchases'), // Sales belong to the ORG that published (AGL-652).
116
+ where('sellerOrgId', '==', orgId)) : null, [
117
+ firestore,
118
+ orgId
119
+ ], {
120
+ idField: '$id'
121
+ });
122
+ // What the publisher was actually paid (AGL-2158) — see
123
+ // `summarizeSellerLedger`, where the arithmetic and the reasoning live. This
124
+ // card used to render `amountCents − feeCents` as "net", which is
125
+ // `pretax + tax − fee`: the payout PLUS sales tax that was never the
126
+ // publisher's, summed over refunded sales as well as live ones.
127
+ const totals = summarizeSellerLedger(sales);
128
+ const [payoutsBusy, setPayoutsBusy] = useState(false);
129
+ /**
130
+ * A one-time Stripe Express dashboard link from the connect route. Not
131
+ * persisted: login links are single-use and short-lived, so a stored one is
132
+ * a button that fails the second time it is pressed.
133
+ */ const [dashboardUrl, setDashboardUrl] = useState(null);
134
+ const handlePayouts = useCallback(async ()=>{
135
+ setPayoutsBusy(true);
136
+ try {
137
+ const response = await authorizedFetch(user, '/api/marketplace/connect', {
138
+ method: 'POST',
139
+ headers: {
140
+ 'Content-Type': 'application/json'
141
+ },
142
+ // The acting org from the URL (AGL-861) — never let the server guess
143
+ // which of the user's orgs is being set up for payouts.
144
+ body: JSON.stringify({
145
+ orgId
146
+ })
147
+ });
148
+ const payload = await response.json();
149
+ if (response.status === 501) {
150
+ return void enqueueSnackbar('Payouts are not configured on this deployment', {
151
+ variant: 'info',
152
+ persist: false
153
+ });
154
+ }
155
+ if (!response.ok) {
156
+ var _ref;
157
+ return void enqueueSnackbar((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Payout setup failed', {
158
+ variant: 'error',
159
+ allowDuplicate: true
160
+ });
161
+ }
162
+ if (payload.chargesEnabled) {
163
+ // Publisher-payout half of the same activation metric (AGL-1561),
164
+ // gated on the transition for the same reason as the commerce card:
165
+ // the connect handler is re-entrant and reports `true` on every
166
+ // revisit. `chargesEnabled` is the pre-request state — the metric is
167
+ // about CONNECTING (AGL-1997 split the two flags; payout readiness
168
+ // can arrive days later and is not a second activation).
169
+ if (!chargesEnabled) trackEvent('stripe_connected', {});
170
+ // A WARNING THAT NAMES A PROBLEM MUST OFFER THE FIX.
171
+ //
172
+ // "Finish payout setup in Stripe" is the label this button carries in
173
+ // exactly this state, and it could not open Stripe: the route returned
174
+ // here with a status and no link, and this branch answered before the
175
+ // `payload.url` check below ever ran. An Express account has no
176
+ // password and no direct login, so a link minted by the platform is
177
+ // the publisher's only way in — without one their share accumulates in
178
+ // an account that cannot release it and nothing says why.
179
+ if (payload.payoutsEnabled === false && payload.url) {
180
+ return void window.location.assign(payload.url);
181
+ }
182
+ // Payouts are flowing, so the route answers with the Express dashboard
183
+ // instead: balance, payout schedule, and the reason a payout failed.
184
+ // Held rather than followed — this click was a status check.
185
+ if (payload.payoutsEnabled !== false && typeof payload.dashboardUrl === 'string') {
186
+ setDashboardUrl(payload.dashboardUrl);
187
+ }
188
+ // The same split the card renders (AGL-1997): the route answers both
189
+ // flags, so the toast must not announce payouts off the charges one.
190
+ return void enqueueSnackbar(payload.payoutsEnabled === false ? 'Connected — payouts are not released yet' : 'Payouts are enabled', {
191
+ variant: payload.payoutsEnabled === false ? 'warning' : 'success',
192
+ persist: false
193
+ });
194
+ }
195
+ if (payload.url) window.location.assign(payload.url);
196
+ } catch (error) {
197
+ console.error(error);
198
+ enqueueSnackbar('An error has occurred', {
199
+ variant: 'error',
200
+ allowDuplicate: true
201
+ });
202
+ } finally{
203
+ setPayoutsBusy(false);
204
+ }
205
+ }, [
206
+ orgId,
207
+ user,
208
+ chargesEnabled,
209
+ enqueueSnackbar
210
+ ]);
211
+ const [handle, setHandle] = useState('');
212
+ const [displayName, setDisplayName] = useState('');
213
+ const [bio, setBio] = useState('');
214
+ // Logo, support contact and social links (AGL-1009). `avatarUrl` and
215
+ // `website` existed server-side already — they just had no form input and
216
+ // no rendering, so they were dead fields until now.
217
+ const [avatarUrl, setAvatarUrl] = useState('');
218
+ /*
219
+ * The console's media library, opened through the picker context rather
220
+ * than by mounting its dialog (AGL-3080): the library is coupled to the
221
+ * org and the session, which a plugin has no way to reach, and the console
222
+ * provides `pickMedia` around every plugin page for exactly this. The URL
223
+ * comes back already resolved — CDN path over the raw download URL
224
+ * (AGL-1215) — so the panel no longer resolves one itself.
225
+ */ const { pickMedia } = useMediaPicker();
226
+ const chooseLogo = useCallback(async ()=>{
227
+ const picked = await (pickMedia == null ? void 0 : pickMedia({
228
+ kind: 'image'
229
+ }));
230
+ if (picked == null ? void 0 : picked.url) setAvatarUrl(picked.url);
231
+ }, [
232
+ pickMedia
233
+ ]);
234
+ const [website, setWebsite] = useState('');
235
+ const [supportEmail, setSupportEmail] = useState('');
236
+ const [supportUrl, setSupportUrl] = useState('');
237
+ const [githubUrl, setGithubUrl] = useState('');
238
+ const [xUrl, setXUrl] = useState('');
239
+ const [linkedinUrl, setLinkedinUrl] = useState('');
240
+ useEffect(()=>{
241
+ var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9;
242
+ setHandle((_ref = profile == null ? void 0 : profile.handle) != null ? _ref : '');
243
+ setDisplayName((_ref1 = profile == null ? void 0 : profile.displayName) != null ? _ref1 : '');
244
+ setBio((_ref2 = profile == null ? void 0 : profile.bio) != null ? _ref2 : '');
245
+ setAvatarUrl((_ref3 = profile == null ? void 0 : profile.avatarUrl) != null ? _ref3 : '');
246
+ setWebsite((_ref4 = profile == null ? void 0 : profile.website) != null ? _ref4 : '');
247
+ setSupportEmail((_ref5 = profile == null ? void 0 : profile.supportEmail) != null ? _ref5 : '');
248
+ setSupportUrl((_ref6 = profile == null ? void 0 : profile.supportUrl) != null ? _ref6 : '');
249
+ setGithubUrl((_ref7 = profile == null ? void 0 : profile.githubUrl) != null ? _ref7 : '');
250
+ setXUrl((_ref8 = profile == null ? void 0 : profile.xUrl) != null ? _ref8 : '');
251
+ setLinkedinUrl((_ref9 = profile == null ? void 0 : profile.linkedinUrl) != null ? _ref9 : '');
252
+ }, [
253
+ profile == null ? void 0 : profile.handle,
254
+ profile == null ? void 0 : profile.displayName,
255
+ profile == null ? void 0 : profile.bio,
256
+ profile == null ? void 0 : profile.avatarUrl,
257
+ profile == null ? void 0 : profile.website,
258
+ profile == null ? void 0 : profile.supportEmail,
259
+ profile == null ? void 0 : profile.supportUrl,
260
+ profile == null ? void 0 : profile.githubUrl,
261
+ profile == null ? void 0 : profile.xUrl,
262
+ profile == null ? void 0 : profile.linkedinUrl
263
+ ]);
264
+ const validHandle = HANDLE_PATTERN.test(handle);
265
+ const handleSave = useCallback(async ()=>{
266
+ if (!orgId || !validHandle || !displayName.trim()) return;
267
+ try {
268
+ // Server-owned (AGL-652): the handle must be claimed transactionally in
269
+ // publisherHandles, which a client write cannot do — two orgs racing
270
+ // for one handle would both succeed and one would silently lose its
271
+ // marketplace URL. The rules reject a client handle write outright.
272
+ const response = await authorizedFetch(user, '/api/marketplace/publisher-profile', {
273
+ method: 'POST',
274
+ headers: {
275
+ 'Content-Type': 'application/json'
276
+ },
277
+ body: JSON.stringify({
278
+ orgId,
279
+ handle,
280
+ displayName: displayName.trim(),
281
+ bio: bio.trim(),
282
+ // AGL-1009: validated server-side (https-only, fixed social
283
+ // hosts) — an empty string explicitly clears the stored field.
284
+ avatarUrl: avatarUrl.trim(),
285
+ website: website.trim(),
286
+ supportEmail: supportEmail.trim(),
287
+ supportUrl: supportUrl.trim(),
288
+ githubUrl: githubUrl.trim(),
289
+ xUrl: xUrl.trim(),
290
+ linkedinUrl: linkedinUrl.trim()
291
+ })
292
+ });
293
+ const payload = await response.json().catch(()=>null);
294
+ if (!response.ok) {
295
+ var _ref;
296
+ return enqueueSnackbar((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Could not save the profile', {
297
+ variant: 'warning',
298
+ persist: false
299
+ });
300
+ }
301
+ enqueueSnackbar('Profile saved', {
302
+ variant: 'success',
303
+ persist: false
304
+ });
305
+ } catch (error) {
306
+ console.error(error);
307
+ enqueueSnackbar('An error has occurred', {
308
+ variant: 'error',
309
+ allowDuplicate: true
310
+ });
311
+ }
312
+ }, [
313
+ orgId,
314
+ validHandle,
315
+ handle,
316
+ displayName,
317
+ bio,
318
+ avatarUrl,
319
+ website,
320
+ supportEmail,
321
+ supportUrl,
322
+ githubUrl,
323
+ xUrl,
324
+ linkedinUrl,
325
+ user,
326
+ enqueueSnackbar
327
+ ]);
328
+ // The publisher agreement (AGL-1077). `none` and `outdated` get different
329
+ // copy on purpose: "you never agreed" and "the terms moved" are different
330
+ // situations, and one message for both reads as a bug in whichever case it
331
+ // does not describe.
332
+ const agreementState = publisherAgreementState(profile == null ? void 0 : profile.publisherAgreement);
333
+ // Whether the document can be read at all (AGL-1660). While it has no
334
+ // published page there is no link to render and no acceptance to collect —
335
+ // the summary below is a summary OF something, and without the something it
336
+ // is just seven sentences we wrote.
337
+ const agreementPresentation = publisherAgreementPresentation(agreementState);
338
+ const acceptedOn = (()=>{
339
+ var _profile_publisherAgreement, _at_toDate;
340
+ const at = profile == null ? void 0 : (_profile_publisherAgreement = profile.publisherAgreement) == null ? void 0 : _profile_publisherAgreement.acceptedAt;
341
+ const date = at == null ? void 0 : (_at_toDate = at.toDate) == null ? void 0 : _at_toDate.call(at);
342
+ return date ? date.toLocaleDateString() : '';
343
+ })();
344
+ const [agreementBusy, setAgreementBusy] = useState(false);
345
+ const handleAcceptAgreement = useCallback(async ()=>{
346
+ if (!orgId) return;
347
+ setAgreementBusy(true);
348
+ try {
349
+ // Server-owned, like the handle: an acceptance the accepting party can
350
+ // write itself is not evidence of anything, and the rules block the
351
+ // field outright.
352
+ const response = await authorizedFetch(user, '/api/marketplace/publisher-profile', {
353
+ method: 'POST',
354
+ headers: {
355
+ 'Content-Type': 'application/json'
356
+ },
357
+ body: JSON.stringify({
358
+ action: 'accept-agreement',
359
+ orgId,
360
+ // Echoed back so the server can refuse an acceptance of anything
361
+ // other than the version in force — including one made against a
362
+ // page left open across a change.
363
+ version: PUBLISHER_AGREEMENT_VERSION
364
+ })
365
+ });
366
+ const payload = await response.json().catch(()=>null);
367
+ if (!response.ok) {
368
+ var _ref;
369
+ return void enqueueSnackbar((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Could not record the acceptance', {
370
+ variant: 'warning',
371
+ persist: false
372
+ });
373
+ }
374
+ enqueueSnackbar('Agreement accepted — you can publish now', {
375
+ variant: 'success',
376
+ persist: false
377
+ });
378
+ } catch (error) {
379
+ console.error(error);
380
+ enqueueSnackbar('An error has occurred', {
381
+ variant: 'error',
382
+ allowDuplicate: true
383
+ });
384
+ } finally{
385
+ setAgreementBusy(false);
386
+ }
387
+ }, [
388
+ orgId,
389
+ user,
390
+ enqueueSnackbar
391
+ ]);
392
+ // The listing open in the full editor (AGL-862), or null when closed.
393
+ const [editListing, setEditListing] = useState(null);
394
+ // Per-row overflow menu (AGL-999): the anchor plus the row it belongs to,
395
+ // so one <Menu> serves every row instead of one per listing.
396
+ const [rowMenu, setRowMenu] = useState(null);
397
+ const closeRowMenu = ()=>setRowMenu(null);
398
+ const listingHref = (listing)=>listingPath(basePath, listing.$id);
399
+ // Shipping a new version was invisible as a path (AGL-1008): the only door
400
+ // was the generic publish form, which reads entirely like first-time
401
+ // publishing, so updating looked like creating a second listing. Same
402
+ // form, pre-bound to the listing.
403
+ const newVersionHref = (listing)=>publishPluginPath(basePath, listing.$id);
404
+ // Private ⇄ public (AGL-968/994). Goes through the API rather than a client
405
+ // write: making a listing public is gated on it actually carrying the docs
406
+ // a stranger needs, and that check has to live somewhere a client can't
407
+ // skip. No re-review either way — the bytes are unchanged.
408
+ const [visibilityBusy, setVisibilityBusy] = useState('');
409
+ const handleVisibility = useCallback((listing)=>async ()=>{
410
+ const next = listing.visibility === 'private' ? 'public' : 'private';
411
+ setVisibilityBusy(listing.$id);
412
+ try {
413
+ const response = await authorizedFetch(user, '/api/marketplace/publish-plugin', {
414
+ method: 'POST',
415
+ headers: {
416
+ 'Content-Type': 'application/json'
417
+ },
418
+ body: JSON.stringify({
419
+ action: 'set-visibility',
420
+ listingId: listing.$id,
421
+ visibility: next
422
+ })
423
+ });
424
+ const payload = await response.json().catch(()=>({}));
425
+ if (!response.ok) {
426
+ var _ref;
427
+ return void enqueueSnackbar((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'Visibility change failed', {
428
+ variant: 'error',
429
+ allowDuplicate: true
430
+ });
431
+ }
432
+ enqueueSnackbar(next === 'private' ? 'Now private — only your sites can install it' : 'Published to the marketplace', {
433
+ variant: 'success',
434
+ persist: false
435
+ });
436
+ } catch (error) {
437
+ console.error(error);
438
+ enqueueSnackbar('An error has occurred', {
439
+ variant: 'error',
440
+ allowDuplicate: true
441
+ });
442
+ } finally{
443
+ setVisibilityBusy('');
444
+ }
445
+ }, [
446
+ user,
447
+ enqueueSnackbar
448
+ ]);
449
+ const handleUnpublish = useCallback((listing)=>async ()=>{
450
+ try {
451
+ await updateDoc(doc(firestore, 'marketplaceListings', listing.$id), {
452
+ deletedAt: listing.deletedAt ? null : Timestamp.now()
453
+ });
454
+ enqueueSnackbar(listing.deletedAt ? 'Republished' : 'Unpublished', {
455
+ variant: 'success',
456
+ persist: false
457
+ });
458
+ } catch (error) {
459
+ console.error(error);
460
+ enqueueSnackbar('An error has occurred', {
461
+ variant: 'error',
462
+ allowDuplicate: true
463
+ });
464
+ }
465
+ }, [
466
+ firestore,
467
+ enqueueSnackbar
468
+ ]);
469
+ const cards = {
470
+ profile: /*#__PURE__*/ _jsxs(Stack, {
471
+ spacing: 2,
472
+ children: [
473
+ /*#__PURE__*/ _jsx(CardDisplay, {
474
+ header: 'Public profile',
475
+ help: pluginDocsHelp('publisherHandbook', {
476
+ anchor: '#before-your-first-publish',
477
+ excerpt: 'Your public publisher identity — handle, name, and bio shown ' + 'on everything you publish to the marketplace.'
478
+ }),
479
+ contentGutterX: true,
480
+ contentGutterY: true,
481
+ children: /*#__PURE__*/ _jsxs(Stack, {
482
+ spacing: 2,
483
+ children: [
484
+ /*#__PURE__*/ _jsx(Typography, {
485
+ variant: "body2",
486
+ color: "text.secondary",
487
+ children: 'Shown on every component you publish to the marketplace. A ' + 'profile is required before publishing.'
488
+ }),
489
+ /*#__PURE__*/ _jsx(TextField, {
490
+ label: "Handle",
491
+ value: handle,
492
+ onChange: (event)=>setHandle(event.target.value.toLowerCase()),
493
+ size: "small",
494
+ error: Boolean(handle) && !validHandle,
495
+ helperText: Boolean(handle) && !validHandle ? '3–30 chars: lowercase letters, digits, dashes' : ' '
496
+ }),
497
+ /*#__PURE__*/ _jsx(TextField, {
498
+ label: "Display name",
499
+ value: displayName,
500
+ onChange: (event)=>setDisplayName(event.target.value),
501
+ size: "small"
502
+ }),
503
+ /*#__PURE__*/ _jsx(TextField, {
504
+ label: "Bio",
505
+ value: bio,
506
+ onChange: (event)=>setBio(event.target.value),
507
+ size: "small",
508
+ multiline: true,
509
+ minRows: 2
510
+ }),
511
+ /*#__PURE__*/ _jsx(Typography, {
512
+ variant: "caption",
513
+ color: "text.secondary",
514
+ component: "div",
515
+ children: LISTING_LOGO_HINT
516
+ }),
517
+ /*#__PURE__*/ _jsxs(Stack, {
518
+ direction: "row",
519
+ spacing: 2,
520
+ sx: {
521
+ alignItems: 'center'
522
+ },
523
+ children: [
524
+ /*#__PURE__*/ _jsx(Avatar, {
525
+ src: avatarUrl || undefined,
526
+ alt: displayName || 'Publisher logo',
527
+ sx: {
528
+ width: 56,
529
+ height: 56
530
+ },
531
+ variant: "rounded",
532
+ children: (displayName || handle || '?').slice(0, 1).toUpperCase()
533
+ }),
534
+ /*#__PURE__*/ _jsxs(Stack, {
535
+ direction: "row",
536
+ spacing: 1,
537
+ children: [
538
+ /*#__PURE__*/ _jsx(Button, {
539
+ size: "small",
540
+ onClick: chooseLogo,
541
+ children: avatarUrl ? 'Replace logo' : 'Choose logo from library'
542
+ }),
543
+ avatarUrl ? /*#__PURE__*/ _jsx(Button, {
544
+ size: "small",
545
+ color: "error",
546
+ onClick: ()=>setAvatarUrl(''),
547
+ children: 'Remove'
548
+ }) : null
549
+ ]
550
+ })
551
+ ]
552
+ }),
553
+ /*#__PURE__*/ _jsx(TextField, {
554
+ label: "Support email",
555
+ value: supportEmail,
556
+ onChange: (event)=>setSupportEmail(event.target.value),
557
+ size: "small",
558
+ type: "email"
559
+ }),
560
+ /*#__PURE__*/ _jsx(TextField, {
561
+ label: "Support URL",
562
+ value: supportUrl,
563
+ onChange: (event)=>setSupportUrl(event.target.value),
564
+ size: "small",
565
+ helperText: "Where buyers go for help with your plugins — https:// only"
566
+ }),
567
+ /*#__PURE__*/ _jsx(TextField, {
568
+ label: "Website",
569
+ value: website,
570
+ onChange: (event)=>setWebsite(event.target.value),
571
+ size: "small",
572
+ helperText: "Your own site, linked from your publisher page — https:// only"
573
+ }),
574
+ /*#__PURE__*/ _jsx(TextField, {
575
+ label: "GitHub",
576
+ value: githubUrl,
577
+ onChange: (event)=>setGithubUrl(event.target.value),
578
+ size: "small",
579
+ helperText: "https://github.com/…"
580
+ }),
581
+ /*#__PURE__*/ _jsx(TextField, {
582
+ label: "X",
583
+ value: xUrl,
584
+ onChange: (event)=>setXUrl(event.target.value),
585
+ size: "small",
586
+ helperText: "https://x.com/…"
587
+ }),
588
+ /*#__PURE__*/ _jsx(TextField, {
589
+ label: "LinkedIn",
590
+ value: linkedinUrl,
591
+ onChange: (event)=>setLinkedinUrl(event.target.value),
592
+ size: "small",
593
+ helperText: "https://linkedin.com/…"
594
+ }),
595
+ /*#__PURE__*/ _jsx(Button, {
596
+ variant: "contained",
597
+ color: "primary",
598
+ disabled: !validHandle || !displayName.trim(),
599
+ onClick: handleSave,
600
+ children: 'Save profile'
601
+ })
602
+ ]
603
+ })
604
+ }),
605
+ /*#__PURE__*/ _jsx(CardDisplay, {
606
+ header: PUBLISHER_AGREEMENT_TITLE,
607
+ help: pluginDocsHelp('publisherHandbook', {
608
+ anchor: '#the-publisher-agreement',
609
+ excerpt: 'The terms your organization publishes under — accepted once, ' + 're-asked when they change.'
610
+ }),
611
+ contentGutterX: true,
612
+ contentGutterY: true,
613
+ children: /*#__PURE__*/ _jsxs(Stack, {
614
+ spacing: 2,
615
+ children: [
616
+ agreementState === 'current' ? /*#__PURE__*/ _jsx(Alert, {
617
+ severity: "success",
618
+ children: `Accepted — version ${profile == null ? void 0 : (_profile_publisherAgreement = profile.publisherAgreement) == null ? void 0 : _profile_publisherAgreement.version}` + (acceptedOn ? ` on ${acceptedOn}` : '')
619
+ }) : null,
620
+ agreementPresentation.unavailableNotice ? /*#__PURE__*/ _jsx(Alert, {
621
+ severity: "warning",
622
+ children: agreementPresentation.unavailableNotice
623
+ }) : agreementState === 'current' ? null : /*#__PURE__*/ _jsx(Alert, {
624
+ severity: agreementState === 'outdated' ? 'warning' : 'info',
625
+ children: agreementState === 'outdated' ? 'These terms have changed since your organization accepted ' + `them (you accepted ${profile == null ? void 0 : (_profile_publisherAgreement1 = profile.publisherAgreement) == null ? void 0 : _profile_publisherAgreement1.version}). ` + 'Publishing is paused until someone who can bind the ' + 'organization accepts the current version.' : 'Your organization has not accepted these terms. You can ' + 'set up a profile without them, but you cannot publish.'
626
+ }),
627
+ /*#__PURE__*/ _jsx(Typography, {
628
+ variant: "body2",
629
+ color: "text.secondary",
630
+ children: 'In summary — this is not the agreement, it is what tends to ' + 'surprise people later:'
631
+ }),
632
+ /*#__PURE__*/ _jsx(Stack, {
633
+ spacing: 1,
634
+ children: PUBLISHER_AGREEMENT_POINTS.map((point)=>/*#__PURE__*/ _jsxs(Stack, {
635
+ spacing: 0.25,
636
+ children: [
637
+ /*#__PURE__*/ _jsx(Typography, {
638
+ variant: "body2",
639
+ children: point.label
640
+ }),
641
+ /*#__PURE__*/ _jsx(Typography, {
642
+ variant: "caption",
643
+ color: "text.secondary",
644
+ children: point.detail
645
+ })
646
+ ]
647
+ }, point.id))
648
+ }),
649
+ agreementPresentation.documentUrl ? /*#__PURE__*/ _jsxs(Typography, {
650
+ variant: "body2",
651
+ children: [
652
+ /*#__PURE__*/ _jsx(Link, {
653
+ href: agreementPresentation.documentUrl,
654
+ target: "_blank",
655
+ rel: "noopener noreferrer",
656
+ underline: "always",
657
+ children: `Read the full ${PUBLISHER_AGREEMENT_TITLE}`
658
+ }),
659
+ ` (version ${PUBLISHER_AGREEMENT_VERSION})`
660
+ ]
661
+ }) : /*#__PURE__*/ _jsx(Typography, {
662
+ variant: "body2",
663
+ color: "text.secondary",
664
+ children: `Version ${PUBLISHER_AGREEMENT_VERSION} has no published page ` + 'yet, so there is no full text to link to.'
665
+ }),
666
+ agreementPresentation.canAccept ? /*#__PURE__*/ _jsx(Button, {
667
+ variant: "contained",
668
+ color: "primary",
669
+ disabled: agreementBusy,
670
+ onClick: ()=>void handleAcceptAgreement(),
671
+ sx: {
672
+ alignSelf: 'flex-start'
673
+ },
674
+ children: agreementBusy ? 'Recording…' : agreementState === 'outdated' ? 'Accept the current version' : 'Accept on behalf of this organization'
675
+ }) : null,
676
+ agreementPresentation.canAccept ? /*#__PURE__*/ _jsx(Typography, {
677
+ variant: "caption",
678
+ color: "text.secondary",
679
+ children: 'Accepting binds the organization, not you personally — only ' + 'an owner or admin can do it, and we record who did and when.'
680
+ }) : null
681
+ ]
682
+ })
683
+ })
684
+ ]
685
+ }),
686
+ listings: /*#__PURE__*/ _jsxs(CardDisplay, {
687
+ header: 'Your listings',
688
+ help: pluginDocsHelp('publisherHandbook', {
689
+ anchor: '#authoring-your-listing',
690
+ excerpt: 'Everything you have published, with preview images and ' + 'per-listing unpublish/republish.'
691
+ }),
692
+ contentGutterX: true,
693
+ contentGutterY: true,
694
+ children: [
695
+ (listings != null ? listings : []).length === 0 ? /*#__PURE__*/ _jsx(Typography, {
696
+ variant: "body2",
697
+ color: "text.secondary",
698
+ children: 'Publish a reusable component from a site’s Setup page to list ' + 'it here.'
699
+ }) : /*#__PURE__*/ _jsxs(Stack, {
700
+ spacing: 1,
701
+ children: [
702
+ (listings != null ? listings : []).some((listing)=>listing.visibility === 'private') ? /*#__PURE__*/ _jsx(Typography, {
703
+ variant: "caption",
704
+ color: "text.secondary",
705
+ children: 'Private plugins never appear in the marketplace — open one ' + 'with View to install it on your sites.'
706
+ }) : null,
707
+ (listings != null ? listings : []).map((listing)=>{
708
+ var _listing_priceUsd;
709
+ return /*#__PURE__*/ _jsxs(Stack, {
710
+ direction: "row",
711
+ spacing: 1,
712
+ sx: {
713
+ alignItems: 'center',
714
+ // The whole row is the way in (AGL-999) — the name and its
715
+ // metadata are what you aim at, so they should behave like
716
+ // the link they read as.
717
+ borderRadius: 1,
718
+ mx: -1,
719
+ px: 1,
720
+ '&:hover': {
721
+ bgcolor: 'action.hover'
722
+ }
723
+ },
724
+ children: [
725
+ /*#__PURE__*/ _jsxs(Stack, {
726
+ onClick: ()=>router.push(listingHref(listing)),
727
+ sx: {
728
+ flex: 1,
729
+ minWidth: 0,
730
+ py: 0.75,
731
+ cursor: 'pointer'
732
+ },
733
+ children: [
734
+ /*#__PURE__*/ _jsxs(Stack, {
735
+ direction: "row",
736
+ spacing: 1,
737
+ sx: {
738
+ alignItems: 'center',
739
+ minWidth: 0
740
+ },
741
+ children: [
742
+ /*#__PURE__*/ _jsx(Typography, {
743
+ variant: "body2",
744
+ noWrap: true,
745
+ children: listing.displayName
746
+ }),
747
+ listing.visibility === 'private' ? /*#__PURE__*/ _jsx(Chip, {
748
+ size: "small",
749
+ variant: "outlined",
750
+ label: "Private"
751
+ }) : null
752
+ ]
753
+ }),
754
+ /*#__PURE__*/ _jsx(Typography, {
755
+ variant: "caption",
756
+ color: "text.secondary",
757
+ children: `v${listing.latestVersion}` + (Number((_listing_priceUsd = listing.priceUsd) != null ? _listing_priceUsd : 0) > 0 ? ` · $${listing.priceUsd}` : ' · free') + (listing.previewImageUrl ? ' · has preview' : '') + (listing.deletedAt ? ' · unpublished' : '')
758
+ })
759
+ ]
760
+ }),
761
+ /*#__PURE__*/ _jsx(AppLink, {
762
+ href: listingHref(listing),
763
+ children: /*#__PURE__*/ _jsx(Button, {
764
+ size: "small",
765
+ color: "primary",
766
+ component: "span",
767
+ children: 'View'
768
+ })
769
+ }),
770
+ /*#__PURE__*/ _jsx(IconButton, {
771
+ size: "small",
772
+ "aria-label": `More actions for ${listing.displayName}`,
773
+ onClick: (event)=>setRowMenu({
774
+ anchor: event.currentTarget,
775
+ listing
776
+ }),
777
+ children: /*#__PURE__*/ _jsx(MdiIcon, {
778
+ path: mdiDotsVertical.path,
779
+ fontSize: "small"
780
+ })
781
+ })
782
+ ]
783
+ }, listing.$id);
784
+ })
785
+ ]
786
+ }),
787
+ /*#__PURE__*/ _jsxs(Menu, {
788
+ anchorEl: (_ref = rowMenu == null ? void 0 : rowMenu.anchor) != null ? _ref : null,
789
+ open: Boolean(rowMenu),
790
+ onClose: closeRowMenu,
791
+ children: [
792
+ ((_ref1 = rowMenu == null ? void 0 : (_rowMenu_listing = rowMenu.listing) == null ? void 0 : _rowMenu_listing.artifactType) != null ? _ref1 : rowMenu == null ? void 0 : (_rowMenu_listing1 = rowMenu.listing) == null ? void 0 : _rowMenu_listing1.type) === 'plugin' ? /*#__PURE__*/ _jsx(MenuItem, {
793
+ onClick: ()=>{
794
+ const listing = rowMenu == null ? void 0 : rowMenu.listing;
795
+ closeRowMenu();
796
+ if (listing) router.push(newVersionHref(listing));
797
+ },
798
+ children: 'Publish new version…'
799
+ }) : null,
800
+ /*#__PURE__*/ _jsx(MenuItem, {
801
+ onClick: ()=>{
802
+ var _ref;
803
+ setEditListing((_ref = rowMenu == null ? void 0 : rowMenu.listing) != null ? _ref : null);
804
+ closeRowMenu();
805
+ },
806
+ children: 'Edit listing'
807
+ }),
808
+ ((_ref2 = rowMenu == null ? void 0 : (_rowMenu_listing2 = rowMenu.listing) == null ? void 0 : _rowMenu_listing2.artifactType) != null ? _ref2 : rowMenu == null ? void 0 : (_rowMenu_listing3 = rowMenu.listing) == null ? void 0 : _rowMenu_listing3.type) === 'plugin' ? /*#__PURE__*/ _jsx(MenuItem, {
809
+ disabled: visibilityBusy === (rowMenu == null ? void 0 : (_rowMenu_listing4 = rowMenu.listing) == null ? void 0 : _rowMenu_listing4.$id),
810
+ onClick: ()=>{
811
+ const listing = rowMenu == null ? void 0 : rowMenu.listing;
812
+ closeRowMenu();
813
+ if (listing) void handleVisibility(listing)();
814
+ },
815
+ children: (rowMenu == null ? void 0 : (_rowMenu_listing5 = rowMenu.listing) == null ? void 0 : _rowMenu_listing5.visibility) === 'private' ? 'Make public…' : 'Make private'
816
+ }) : null,
817
+ /*#__PURE__*/ _jsx(Divider, {}),
818
+ /*#__PURE__*/ _jsx(MenuItem, {
819
+ sx: {
820
+ color: (rowMenu == null ? void 0 : (_rowMenu_listing6 = rowMenu.listing) == null ? void 0 : _rowMenu_listing6.deletedAt) ? undefined : 'error.main'
821
+ },
822
+ onClick: ()=>{
823
+ const listing = rowMenu == null ? void 0 : rowMenu.listing;
824
+ closeRowMenu();
825
+ if (listing) void handleUnpublish(listing)();
826
+ },
827
+ children: (rowMenu == null ? void 0 : (_rowMenu_listing7 = rowMenu.listing) == null ? void 0 : _rowMenu_listing7.deletedAt) ? 'Republish' : 'Unpublish'
828
+ })
829
+ ]
830
+ }),
831
+ /*#__PURE__*/ _jsx(EditListingDialog, {
832
+ orgId: orgId,
833
+ listing: editListing,
834
+ open: Boolean(editListing),
835
+ onClose: ()=>setEditListing(null),
836
+ user: user
837
+ })
838
+ ]
839
+ }),
840
+ payouts: /*#__PURE__*/ _jsx(CardDisplay, {
841
+ header: 'Payouts',
842
+ help: pluginDocsHelp('publisherHandbook', {
843
+ anchor: '#getting-paid',
844
+ excerpt: 'Connect Stripe to receive payouts for paid listings. Platform ' + 'fee: 20% per sale, 30% on the Free plan.'
845
+ }),
846
+ contentGutterX: true,
847
+ contentGutterY: true,
848
+ children: /*#__PURE__*/ _jsx(Stack, {
849
+ spacing: 2,
850
+ children: payoutsState === 'error' ? // Neither sentence is safe here: "Payouts are enabled" and
851
+ // "Connect a Stripe account" both state a payout arrangement we
852
+ // failed to read, and the button under them acts on it.
853
+ /*#__PURE__*/ _jsx(Alert, {
854
+ severity: "error",
855
+ action: /*#__PURE__*/ _jsx(Button, {
856
+ color: "inherit",
857
+ size: "small",
858
+ onClick: ()=>setProfileRetryNonce((n)=>n + 1),
859
+ children: 'Retry'
860
+ }),
861
+ children: 'We couldn’t load your payout setup. This says nothing about ' + 'whether payouts are on — nothing has changed.'
862
+ }) : payoutsState === 'pending' ? /*#__PURE__*/ _jsx(Typography, {
863
+ variant: "body2",
864
+ color: "text.secondary",
865
+ children: 'Checking your payout setup…'
866
+ }) : /*#__PURE__*/ _jsxs(_Fragment, {
867
+ children: [
868
+ readiness === 'blocked' ? /*#__PURE__*/ _jsx(Alert, {
869
+ severity: "warning",
870
+ children: 'Your Stripe account can take payments, but payouts are ' + 'not enabled yet — sales would stay in Stripe rather ' + 'than reaching your bank. Finish verification in Stripe ' + 'to release them.'
871
+ }) : null,
872
+ readiness === 'unverified' ? /*#__PURE__*/ _jsx(Alert, {
873
+ severity: "error",
874
+ children: 'This Stripe account has not been verified for payments ' + 'here, so sales will be refused. Reconnect it to ' + 'confirm — if it was set up against Stripe test mode, ' + 'you will need to connect a live account.'
875
+ }) : null,
876
+ /*#__PURE__*/ _jsx(Typography, {
877
+ variant: "body2",
878
+ color: "text.secondary",
879
+ children: readiness === 'ready' ? 'Payouts are enabled — paid listings transfer to your ' + 'Stripe account automatically (platform fee 20%, 30% on ' + 'the Free plan).' : readiness === 'unknown' ? // known, like the error branch above.
880
+ 'Your Stripe account is connected. We haven’t checked ' + 'whether payouts are released yet — recheck to ' + 'confirm. The platform fee is 20% per sale (30% on ' + 'the Free plan).' : readiness === 'blocked' || readiness === 'unverified' ? 'The platform fee is 20% per sale (30% on the Free ' + 'plan).' : 'Connect a Stripe account to sell components. The ' + 'platform fee is 20% per sale (30% on the Free plan).'
881
+ }),
882
+ /*#__PURE__*/ _jsx(Button, {
883
+ variant: chargesEnabled ? 'outlined' : 'contained',
884
+ color: "primary",
885
+ disabled: payoutsBusy,
886
+ onClick: handlePayouts,
887
+ children: payoutsBusy ? 'Opening Stripe…' : readiness === 'ready' ? 'Payouts enabled — recheck status' : readiness === 'blocked' ? 'Finish payout setup in Stripe' : readiness === 'unverified' ? 'Reconnect Stripe to verify' : readiness === 'unknown' ? 'Recheck payout status' : 'Set up payouts'
888
+ }),
889
+ dashboardUrl ? /*#__PURE__*/ _jsx(Button, {
890
+ variant: "text",
891
+ color: "primary",
892
+ href: dashboardUrl,
893
+ target: "_blank",
894
+ rel: "noopener noreferrer",
895
+ children: 'View payouts in Stripe'
896
+ }) : null
897
+ ]
898
+ })
899
+ })
900
+ }),
901
+ sales: /*#__PURE__*/ _jsx(CardDisplay, {
902
+ header: 'Sales',
903
+ help: pluginDocsHelp('publishAPlugin', {
904
+ anchor: '#paid-listings',
905
+ excerpt: 'Sent to your Stripe account is your share after the platform ' + `fee — sales tax is collected on ${PLATFORM_BRAND_LEGAL_NAME}’s ` + 'registration and is never yours.'
906
+ }),
907
+ contentGutterX: true,
908
+ contentGutterY: true,
909
+ children: (sales != null ? sales : []).length === 0 ? /*#__PURE__*/ _jsx(Typography, {
910
+ variant: "body2",
911
+ color: "text.secondary",
912
+ children: 'No sales yet. Paid listings appear here with what buyers paid ' + 'and what was paid out to you.'
913
+ }) : /*#__PURE__*/ _jsxs(Stack, {
914
+ spacing: 0.5,
915
+ children: [
916
+ /*#__PURE__*/ _jsxs(Typography, {
917
+ variant: "body2",
918
+ children: [
919
+ `${totals.paidCount} sale${totals.paidCount === 1 ? '' : 's'}`,
920
+ totals.refundedCount > 0 ? ` · ${totals.refundedCount} refunded or charged back` : ''
921
+ ]
922
+ }),
923
+ /*#__PURE__*/ _jsx(Typography, {
924
+ variant: "body2",
925
+ children: `Sent to your Stripe account $${(totals.sentToStripeCents / 100).toFixed(2)}`
926
+ }),
927
+ (profile == null ? void 0 : profile.lastPayoutFailureAtMs) ? /*#__PURE__*/ _jsx(Alert, {
928
+ severity: "warning",
929
+ variant: "outlined",
930
+ children: `A payout of $${(Number((_profile_lastPayoutFailureCents = profile.lastPayoutFailureCents) != null ? _profile_lastPayoutFailureCents : 0) / 100).toFixed(2)} did not reach your bank on ${new Date(Number(profile.lastPayoutFailureAtMs)).toLocaleDateString()} — ${String((_profile_lastPayoutFailureReason = profile.lastPayoutFailureReason) != null ? _profile_lastPayoutFailureReason : 'Stripe did not say why')} The money is still in your Stripe account. Open Stripe to fix your payout details.`
931
+ }) : null,
932
+ /*#__PURE__*/ _jsx(Typography, {
933
+ variant: "body2",
934
+ color: "text.secondary",
935
+ children: `Buyers paid $${(totals.buyersPaidCents / 100).toFixed(2)} · ` + `sales tax $${(totals.salesTaxCents / 100).toFixed(2)} · ` + `platform fee $${(totals.platformFeeCents / 100).toFixed(2)}`
936
+ }),
937
+ totals.refundedCount > 0 ? /*#__PURE__*/ _jsx(Typography, {
938
+ variant: "body2",
939
+ color: "text.secondary",
940
+ children: `$${(totals.returnedCents / 100).toFixed(2)} returned on refunded ` + 'and charged-back sales, and excluded above'
941
+ }) : null,
942
+ /*#__PURE__*/ _jsx(Typography, {
943
+ variant: "caption",
944
+ color: "text.secondary",
945
+ children: 'Sent to your Stripe account is the pre-tax price less the ' + 'platform fee. Sales tax is collected and remitted by ' + `${PLATFORM_BRAND_LEGAL_NAME} under its marketplace-provider ` + 'registration and was never part of your share. Reaching ' + 'your Stripe balance is not the same as reaching your bank — ' + 'a failed payout is called out above.'
946
+ })
947
+ ]
948
+ })
949
+ })
950
+ };
951
+ return cards[section];
952
+ }
953
+ OrgSellerPanel.displayName = 'OrgSellerPanel';
954
+ export default OrgSellerPanel;
955
+
956
+ //# sourceMappingURL=org-seller-panel.component.js.map