@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,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
|