@aglyn/plugins-marketplace 1.15.0 → 1.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +1 -0
  2. package/package.json +15 -14
  3. package/src/lib/components/editor-connections-provider.component.d.ts +37 -0
  4. package/src/lib/components/editor-connections-provider.component.js +161 -0
  5. package/src/lib/components/editor-connections-provider.component.js.map +1 -0
  6. package/src/lib/components/listing-connection-credentials-card.component.d.ts +33 -0
  7. package/src/lib/components/listing-connection-credentials-card.component.js +318 -0
  8. package/src/lib/components/listing-connection-credentials-card.component.js.map +1 -0
  9. package/src/lib/components/listing-detail-page.component.js +10 -2
  10. package/src/lib/components/listing-detail-page.component.js.map +1 -1
  11. package/src/lib/components/site-plugin-connections-card.component.d.ts +38 -0
  12. package/src/lib/components/site-plugin-connections-card.component.js +576 -0
  13. package/src/lib/components/site-plugin-connections-card.component.js.map +1 -0
  14. package/src/lib/declarations.console-server.d.ts +17 -0
  15. package/src/lib/declarations.console-server.js +41 -0
  16. package/src/lib/declarations.console-server.js.map +1 -0
  17. package/src/lib/declarations.js +11 -2
  18. package/src/lib/declarations.js.map +1 -1
  19. package/src/lib/declarations.server.js +68 -1
  20. package/src/lib/declarations.server.js.map +1 -1
  21. package/src/lib/hooks/use-plugin-connections-api.d.ts +89 -0
  22. package/src/lib/hooks/use-plugin-connections-api.js +132 -0
  23. package/src/lib/hooks/use-plugin-connections-api.js.map +1 -0
  24. package/src/lib/model/editor-connections.d.ts +46 -0
  25. package/src/lib/model/editor-connections.js +70 -0
  26. package/src/lib/model/editor-connections.js.map +1 -0
  27. package/src/lib/model/listing-price-floor.js +7 -4
  28. package/src/lib/model/listing-price-floor.js.map +1 -1
  29. package/src/lib/model/marketplace-publisher-permissions.d.ts +3 -1
  30. package/src/lib/model/marketplace-publisher-permissions.js +14 -0
  31. package/src/lib/model/marketplace-publisher-permissions.js.map +1 -1
  32. package/src/lib/model/marketplace-site-permissions.d.ts +9 -0
  33. package/src/lib/model/marketplace-site-permissions.js +29 -0
  34. package/src/lib/model/marketplace-site-permissions.js.map +1 -1
  35. package/src/lib/model/plugin-connection-card.d.ts +88 -0
  36. package/src/lib/model/plugin-connection-card.js +187 -0
  37. package/src/lib/model/plugin-connection-card.js.map +1 -0
  38. package/src/lib/model/plugin-connections.d.ts +172 -0
  39. package/src/lib/model/plugin-connections.js +119 -0
  40. package/src/lib/model/plugin-connections.js.map +1 -0
  41. package/src/lib/plugin.js +21 -1
  42. package/src/lib/plugin.js.map +1 -1
  43. package/src/lib/server/admin-reviews.js +18 -12
  44. package/src/lib/server/admin-reviews.js.map +1 -1
  45. package/src/lib/server/artifact-inventory.js +3 -1
  46. package/src/lib/server/artifact-inventory.js.map +1 -1
  47. package/src/lib/server/checkout.js +4 -0
  48. package/src/lib/server/checkout.js.map +1 -1
  49. package/src/lib/server/connections/cleanup.d.ts +84 -0
  50. package/src/lib/server/connections/cleanup.js +116 -0
  51. package/src/lib/server/connections/cleanup.js.map +1 -0
  52. package/src/lib/server/connections/config.d.ts +42 -0
  53. package/src/lib/server/connections/config.js +48 -0
  54. package/src/lib/server/connections/config.js.map +1 -0
  55. package/src/lib/server/connections/connection-store.d.ts +173 -0
  56. package/src/lib/server/connections/connection-store.js +331 -0
  57. package/src/lib/server/connections/connection-store.js.map +1 -0
  58. package/src/lib/server/connections/credentials-store.d.ts +71 -0
  59. package/src/lib/server/connections/credentials-store.js +78 -0
  60. package/src/lib/server/connections/credentials-store.js.map +1 -0
  61. package/src/lib/server/connections/erasure.d.ts +25 -0
  62. package/src/lib/server/connections/erasure.js +72 -0
  63. package/src/lib/server/connections/erasure.js.map +1 -0
  64. package/src/lib/server/connections/installed-connection.d.ts +64 -0
  65. package/src/lib/server/connections/installed-connection.js +92 -0
  66. package/src/lib/server/connections/installed-connection.js.map +1 -0
  67. package/src/lib/server/connections/oauth-state.d.ts +129 -0
  68. package/src/lib/server/connections/oauth-state.js +252 -0
  69. package/src/lib/server/connections/oauth-state.js.map +1 -0
  70. package/src/lib/server/connections/provider-http.d.ts +99 -0
  71. package/src/lib/server/connections/provider-http.js +253 -0
  72. package/src/lib/server/connections/provider-http.js.map +1 -0
  73. package/src/lib/server/connections/record-announce.d.ts +20 -0
  74. package/src/lib/server/connections/record-announce.js +53 -0
  75. package/src/lib/server/connections/record-announce.js.map +1 -0
  76. package/src/lib/server/connections/record-mapping.d.ts +63 -0
  77. package/src/lib/server/connections/record-mapping.js +151 -0
  78. package/src/lib/server/connections/record-mapping.js.map +1 -0
  79. package/src/lib/server/connections/record-routes.d.ts +17 -0
  80. package/src/lib/server/connections/record-routes.js +96 -0
  81. package/src/lib/server/connections/record-routes.js.map +1 -0
  82. package/src/lib/server/connections/revoke.d.ts +29 -0
  83. package/src/lib/server/connections/revoke.js +52 -0
  84. package/src/lib/server/connections/revoke.js.map +1 -0
  85. package/src/lib/server/connections/routes.d.ts +77 -0
  86. package/src/lib/server/connections/routes.js +706 -0
  87. package/src/lib/server/connections/routes.js.map +1 -0
  88. package/src/lib/server/connections/site-records-platform.d.ts +24 -0
  89. package/src/lib/server/connections/site-records-platform.js +94 -0
  90. package/src/lib/server/connections/site-records-platform.js.map +1 -0
  91. package/src/lib/server/connections/site-records.d.ts +95 -0
  92. package/src/lib/server/connections/site-records.js +221 -0
  93. package/src/lib/server/connections/site-records.js.map +1 -0
  94. package/src/lib/server/connections/sync.d.ts +110 -0
  95. package/src/lib/server/connections/sync.js +414 -0
  96. package/src/lib/server/connections/sync.js.map +1 -0
  97. package/src/lib/server/install-plugin.js +56 -6
  98. package/src/lib/server/install-plugin.js.map +1 -1
  99. package/src/lib/server/publish-plugin.js +5 -2
  100. package/src/lib/server/publish-plugin.js.map +1 -1
  101. package/src/lib/server/revenue-source.js +6 -5
  102. package/src/lib/server/revenue-source.js.map +1 -1
  103. package/src/lib/server.js +60 -0
  104. package/src/lib/server.js.map +1 -1
  105. package/src/lib/testing/memory-firestore.d.ts +34 -0
  106. package/src/lib/testing/memory-firestore.js +250 -0
  107. package/src/lib/testing/memory-firestore.js.map +1 -0
@@ -0,0 +1,576 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
+ import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
20
+ import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
21
+ import { BlockedControl } from "@aglyn/shared-ui-jsx/components/blocked-control.component";
22
+ import { PlanUpsellNotice } from "@aglyn/shared-ui-jsx/components/plan-upsell-notice.component";
23
+ import { PrerequisiteNotice } from "@aglyn/shared-ui-jsx/components/prerequisite-notice.component";
24
+ import { StatusChip } from "@aglyn/shared-ui-jsx/components/status-chip.component";
25
+ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
26
+ import { useSiteKeys } from "@aglyn/tenant-feature-instance/hooks/use-site-keys";
27
+ import { Alert, Button, CircularProgress, MenuItem, Stack, TextField, Typography } from "@mui/material";
28
+ import { useCallback, useEffect, useRef, useState } from "react";
29
+ import { PluginConnectionRouteError, usePluginConnectionsApi } from "../hooks/use-plugin-connections-api.js";
30
+ import { declaredPluginConnections, declaredSyncIntervalHours, formatConnectionTime, nextResourceToChoose, PLUGIN_CONNECTION_PHASE_CHIP, PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT, pluginConnectionNextSyncAtMs, pluginConnectionNotSetUpText, pluginConnectionPhase, pluginConnectionResultMessage, pluginConnectionSharesText, pluginConnectionTooSoonText, readPluginConnectionReturn } from "../model/plugin-connection-card.js";
31
+ import { PLUGIN_CONNECTIONS_PLAN_REFUSAL, PLUGIN_CONNECTIONS_SITE_PERMISSION, PLUGIN_CONNECTIONS_SITE_ROLE_PRESET, pluginConnectionsPermissionRefusal } from "../model/plugin-connections.js";
32
+ const leavePage = (url)=>window.location.assign(url);
33
+ const HELP = pluginDocsHelp('installYourFirstPlugin', {
34
+ anchor: '#plugin-connections',
35
+ excerpt: 'Some plugins show information from another service, such as your business listing. Connect it once and the plugin keeps it up to date on its own.'
36
+ });
37
+ /**
38
+ * THE SITE'S CONNECTIONS FOR ONE INSTALLED PLUGIN (AGL-4196).
39
+ *
40
+ * A marketplace plugin may declare connections in its manifest: an account at
41
+ * another service the platform signs in to, the things the owner picks there
42
+ * (an account, then one of its locations), and a scheduled sync. The
43
+ * platform runs all of it, so this card is the same for every plugin: one
44
+ * card per declared connection, drawn from the pinned manifest and the
45
+ * status route.
46
+ *
47
+ * Every prerequisite stays on screen: a plan without connections, a
48
+ * publisher who has not set the connection up, a deployment that cannot run
49
+ * them yet and a member without the site key each keep the card, with its
50
+ * controls off and the reason said.
51
+ */ export function SitePluginConnectionsCard(props) {
52
+ var _props_api, _props_navigate;
53
+ const { hostId, orgSlug, pluginRef, pin } = props;
54
+ const routes = usePluginConnectionsApi(hostId);
55
+ const api = (_props_api = props.api) != null ? _props_api : routes;
56
+ const navigate = (_props_navigate = props.navigate) != null ? _props_navigate : leavePage;
57
+ const { enqueueSnackbar } = useSnackbar();
58
+ const { blocked } = useSiteKeys(hostId);
59
+ const declared = declaredPluginConnections(pin == null ? void 0 : pin['manifest']);
60
+ const hasDeclared = declared.length > 0;
61
+ const [load, setLoad] = useState({
62
+ status: 'loading'
63
+ });
64
+ const returned = useRef(false);
65
+ const refresh = useCallback(async ()=>{
66
+ try {
67
+ setLoad({
68
+ status: 'ready',
69
+ answer: await api.status(pluginRef)
70
+ });
71
+ } catch (error) {
72
+ var _ref;
73
+ const refusal = error instanceof PluginConnectionRouteError ? error : null;
74
+ setLoad({
75
+ status: 'error',
76
+ message: (_ref = refusal == null ? void 0 : refusal.message) != null ? _ref : 'The connections could not be read. Try again.',
77
+ refused: (refusal == null ? void 0 : refusal.reason) === 'permission'
78
+ });
79
+ }
80
+ }, [
81
+ api,
82
+ pluginRef
83
+ ]);
84
+ useEffect(()=>{
85
+ if (!hasDeclared || !hostId) return;
86
+ if (!returned.current && typeof window !== 'undefined') {
87
+ returned.current = true;
88
+ const back = readPluginConnectionReturn(window.location.href, pluginRef);
89
+ if (back) {
90
+ var _ref;
91
+ var _declared_find;
92
+ window.history.replaceState(window.history.state, '', back.cleanUrl);
93
+ const label = (_ref = (_declared_find = declared.find((one)=>one.id === back.connectionId)) == null ? void 0 : _declared_find.label) != null ? _ref : 'The service';
94
+ const { variant, message } = pluginConnectionResultMessage(back.result, label);
95
+ enqueueSnackbar(message, {
96
+ variant
97
+ });
98
+ }
99
+ }
100
+ void refresh();
101
+ // `declared` is re-derived each render from the same pin; its ids are what matter.
102
+ // eslint-disable-next-line react-hooks/exhaustive-deps
103
+ }, [
104
+ hasDeclared,
105
+ hostId,
106
+ pluginRef,
107
+ refresh
108
+ ]);
109
+ if (!hasDeclared) return null;
110
+ const keyRefused = blocked(PLUGIN_CONNECTIONS_SITE_PERMISSION, PLUGIN_CONNECTIONS_SITE_ROLE_PRESET);
111
+ const permissionText = pluginConnectionsPermissionRefusal(PLUGIN_CONNECTIONS_SITE_PERMISSION);
112
+ if (keyRefused || load.status === 'error' && load.refused) {
113
+ return /*#__PURE__*/ _jsx(Stack, {
114
+ spacing: 3,
115
+ children: declared.map((connection)=>/*#__PURE__*/ _jsx(CardDisplay, {
116
+ header: connection.label,
117
+ subheader: connection.description,
118
+ help: HELP,
119
+ contentGutterX: true,
120
+ contentGutterY: true,
121
+ HeaderProps: {
122
+ action: /*#__PURE__*/ _jsx(BlockedControl, {
123
+ blocked: true,
124
+ reason: permissionText,
125
+ children: /*#__PURE__*/ _jsx(Button, {
126
+ variant: "contained",
127
+ children: 'Connect'
128
+ })
129
+ })
130
+ },
131
+ children: /*#__PURE__*/ _jsx(Alert, {
132
+ severity: "info",
133
+ children: permissionText
134
+ })
135
+ }, connection.id))
136
+ });
137
+ }
138
+ if (load.status !== 'ready') {
139
+ return /*#__PURE__*/ _jsx(Stack, {
140
+ spacing: 3,
141
+ children: declared.map((connection)=>/*#__PURE__*/ _jsx(CardDisplay, {
142
+ header: connection.label,
143
+ subheader: connection.description,
144
+ help: HELP,
145
+ contentGutterX: true,
146
+ contentGutterY: true,
147
+ HeaderProps: load.status === 'error' ? {
148
+ action: /*#__PURE__*/ _jsx(Button, {
149
+ variant: "outlined",
150
+ onClick: ()=>void refresh(),
151
+ children: 'Try again'
152
+ })
153
+ } : undefined,
154
+ children: load.status === 'error' ? /*#__PURE__*/ _jsx(Alert, {
155
+ severity: "error",
156
+ children: load.message
157
+ }) : /*#__PURE__*/ _jsxs(Stack, {
158
+ direction: "row",
159
+ spacing: 1,
160
+ sx: {
161
+ alignItems: 'center'
162
+ },
163
+ role: "status",
164
+ children: [
165
+ /*#__PURE__*/ _jsx(CircularProgress, {
166
+ size: 18
167
+ }),
168
+ /*#__PURE__*/ _jsx(Typography, {
169
+ variant: "body2",
170
+ color: "text.secondary",
171
+ children: 'Checking the connection…'
172
+ })
173
+ ]
174
+ })
175
+ }, connection.id))
176
+ });
177
+ }
178
+ const { answer } = load;
179
+ const replace = (entry, connection)=>setLoad({
180
+ status: 'ready',
181
+ answer: _extends({}, answer, {
182
+ connections: answer.connections.map((one)=>one.listingId === entry.listingId && one.connectionId === entry.connectionId ? _extends({}, one, {
183
+ connection
184
+ }) : one)
185
+ })
186
+ });
187
+ return /*#__PURE__*/ _jsx(Stack, {
188
+ spacing: 3,
189
+ children: answer.connections.map((entry)=>{
190
+ var _ref;
191
+ return /*#__PURE__*/ _jsx(SitePluginConnection, {
192
+ entry: entry,
193
+ configured: answer.configured,
194
+ planAllows: answer.planAllows && entry.planAllows,
195
+ upgradeHref: orgSlug ? `/${orgSlug}/billing#plans` : undefined,
196
+ pluginName: String((_ref = pin == null ? void 0 : pin['displayName']) != null ? _ref : '') || 'this plugin',
197
+ intervalHours: declaredSyncIntervalHours(pin == null ? void 0 : pin['manifest'], entry.connectionId),
198
+ api: api,
199
+ navigate: navigate,
200
+ onConnection: (connection)=>replace(entry, connection),
201
+ onStale: refresh
202
+ }, `${entry.listingId}/${entry.connectionId}`);
203
+ })
204
+ });
205
+ }
206
+ SitePluginConnectionsCard.displayName = 'SitePluginConnectionsCard';
207
+ export default SitePluginConnectionsCard;
208
+ /** One declared connection on the site: its state, what was chosen, and what can be done next. */ function SitePluginConnection(props) {
209
+ var _entry_resources_changing, _ref, _ref1;
210
+ var _view_choices_resourceId;
211
+ const { entry, configured, planAllows, upgradeHref, pluginName, intervalHours, api, navigate, onConnection, onStale } = props;
212
+ const { enqueueSnackbar } = useSnackbar();
213
+ const { confirm } = useConfirmationContext();
214
+ const [busy, setBusy] = useState(null);
215
+ const [error, setError] = useState(null);
216
+ const [wait, setWait] = useState(null);
217
+ const [changing, setChanging] = useState(null);
218
+ const [items, setItems] = useState({
219
+ status: 'idle'
220
+ });
221
+ const [picked, setPicked] = useState('');
222
+ const phase = pluginConnectionPhase(entry, configured);
223
+ // A pinned version that declares no sync has nothing for Sync now to run, and no sync times to show.
224
+ const declaresSync = intervalHours !== null;
225
+ const view = entry.connection;
226
+ const label = entry.label;
227
+ const ids = {
228
+ listingId: entry.listingId,
229
+ connectionId: entry.connectionId
230
+ };
231
+ const resource = changing !== null ? (_entry_resources_changing = entry.resources[changing]) != null ? _entry_resources_changing : null : phase === 'choosing' ? nextResourceToChoose(entry) : null;
232
+ const resourceId = (_ref = resource == null ? void 0 : resource.id) != null ? _ref : '';
233
+ const currentChoice = resourceId ? (_ref1 = view == null ? void 0 : (_view_choices_resourceId = view.choices[resourceId]) == null ? void 0 : _view_choices_resourceId.id) != null ? _ref1 : '' : '';
234
+ useEffect(()=>{
235
+ if (!resourceId || !planAllows) {
236
+ setItems({
237
+ status: 'idle'
238
+ });
239
+ return;
240
+ }
241
+ let live = true;
242
+ setItems({
243
+ status: 'loading'
244
+ });
245
+ setPicked(currentChoice);
246
+ api.resources(_extends({}, ids, {
247
+ resource: resourceId
248
+ })).then((answer)=>{
249
+ if (!live) return;
250
+ setItems({
251
+ status: 'ready',
252
+ items: answer.items
253
+ });
254
+ setPicked((was)=>was || (answer.items.length === 1 ? answer.items[0].id : ''));
255
+ }).catch((failure)=>{
256
+ if (!live) return;
257
+ setItems({
258
+ status: 'error',
259
+ message: failure instanceof Error ? failure.message : String(failure)
260
+ });
261
+ if (failure instanceof PluginConnectionRouteError && failure.reason === 'reconnect') void onStale();
262
+ });
263
+ return ()=>{
264
+ live = false;
265
+ };
266
+ // The connection's ids and the resource are the request; the rest is display.
267
+ // eslint-disable-next-line react-hooks/exhaustive-deps
268
+ }, [
269
+ api,
270
+ entry.listingId,
271
+ entry.connectionId,
272
+ resourceId,
273
+ planAllows
274
+ ]);
275
+ const run = async (kind, work)=>{
276
+ setBusy(kind);
277
+ setError(null);
278
+ setWait(null);
279
+ try {
280
+ await work();
281
+ } catch (failure) {
282
+ if (failure instanceof PluginConnectionRouteError && failure.reason === 'too-soon') {
283
+ // Not a failure: the last sync is under a minute old. Say how long, and show what it brought.
284
+ const retryAfterMs = Number(failure.body['retryAfterMs']);
285
+ setWait(pluginConnectionTooSoonText(label, Number.isFinite(retryAfterMs) ? retryAfterMs : null));
286
+ const connection = failure.body['connection'];
287
+ if (connection && typeof connection === 'object') onConnection(connection);
288
+ return;
289
+ }
290
+ const message = failure instanceof Error ? failure.message : String(failure);
291
+ setError(message);
292
+ if (failure instanceof PluginConnectionRouteError && (failure.reason === 'reconnect' || failure.reason === 'not-connected' || failure.reason === 'choose-first')) {
293
+ void onStale();
294
+ }
295
+ } finally{
296
+ setBusy(null);
297
+ }
298
+ };
299
+ const connect = ()=>run('connect', async ()=>{
300
+ const { authorizeUrl } = await api.connectStart(_extends({}, ids, {
301
+ returnTo: window.location.href
302
+ }));
303
+ navigate(authorizeUrl);
304
+ });
305
+ const choose = ()=>run('choose', async ()=>{
306
+ if (!resource || !picked) return;
307
+ const { connection } = await api.choose(_extends({}, ids, {
308
+ resource: resource.id,
309
+ id: picked
310
+ }));
311
+ onConnection(connection);
312
+ if (changing !== null) {
313
+ const next = changing + 1;
314
+ setChanging(next < entry.resources.length ? next : null);
315
+ }
316
+ if (connection == null ? void 0 : connection.ready) enqueueSnackbar(`${label} is set up. It syncs on its own from now on.`, {
317
+ variant: 'success'
318
+ });
319
+ });
320
+ const sync = ()=>run('sync', async ()=>{
321
+ const { connection, synced } = await api.sync(ids);
322
+ onConnection(connection);
323
+ enqueueSnackbar(synced ? `${label} is up to date.` : `${label} did not sync. See why below.`, {
324
+ variant: synced ? 'success' : 'warning'
325
+ });
326
+ });
327
+ const disconnect = async ()=>{
328
+ const ok = await Promise.resolve(confirm({
329
+ title: `Disconnect ${label}?`,
330
+ description: `${pluginName} stops receiving updates from ${label} on this site, and what it last brought in is removed from the site. You can connect again at any time.`,
331
+ confirmationText: 'Disconnect',
332
+ confirmationButtonProps: {
333
+ color: 'warning',
334
+ variant: 'contained'
335
+ }
336
+ })).then(()=>true).catch(()=>false);
337
+ if (!ok) return;
338
+ await run('disconnect', async ()=>{
339
+ await api.disconnect(ids);
340
+ setChanging(null);
341
+ onConnection(null);
342
+ enqueueSnackbar(`${label} is disconnected.`, {
343
+ variant: 'success'
344
+ });
345
+ });
346
+ };
347
+ const planReason = PLUGIN_CONNECTIONS_PLAN_REFUSAL;
348
+ const blockedBy = phase === 'not-configured' ? PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT : phase === 'not-set-up' ? pluginConnectionNotSetUpText(label) : !planAllows ? planReason : null;
349
+ const guarded = (reason, control)=>/*#__PURE__*/ _jsx(BlockedControl, {
350
+ blocked: Boolean(reason),
351
+ reason: reason != null ? reason : '',
352
+ children: control
353
+ });
354
+ const disconnectButton = /*#__PURE__*/ _jsx(Button, {
355
+ color: "warning",
356
+ disabled: busy !== null,
357
+ onClick: ()=>void disconnect(),
358
+ children: busy === 'disconnect' ? 'Disconnecting…' : 'Disconnect'
359
+ });
360
+ const actions = [];
361
+ if (resource) {
362
+ actions.push(guarded(!planAllows ? planReason : null, /*#__PURE__*/ _jsx(Button, {
363
+ variant: "contained",
364
+ disabled: busy !== null || !picked || items.status !== 'ready',
365
+ onClick: ()=>void choose(),
366
+ children: busy === 'choose' ? 'Saving…' : `Use this ${resource.label.toLowerCase()}`
367
+ }, "use")));
368
+ if (changing !== null) {
369
+ actions.push(/*#__PURE__*/ _jsx(Button, {
370
+ color: "inherit",
371
+ disabled: busy !== null,
372
+ onClick: ()=>setChanging(null),
373
+ children: 'Cancel'
374
+ }, "cancel"));
375
+ }
376
+ actions.push(disconnectButton);
377
+ } else if (phase === 'not-configured' || phase === 'not-set-up' || phase === 'not-connected') {
378
+ actions.push(guarded(blockedBy, /*#__PURE__*/ _jsx(Button, {
379
+ variant: "contained",
380
+ disabled: busy !== null,
381
+ onClick: ()=>void connect(),
382
+ children: busy === 'connect' ? 'Opening…' : 'Connect'
383
+ }, "connect")));
384
+ } else if (phase === 'reconnect') {
385
+ actions.push(guarded(!planAllows ? planReason : !entry.credentialsSet ? pluginConnectionNotSetUpText(label) : null, /*#__PURE__*/ _jsx(Button, {
386
+ variant: "contained",
387
+ disabled: busy !== null,
388
+ onClick: ()=>void connect(),
389
+ children: busy === 'connect' ? 'Opening…' : 'Reconnect'
390
+ }, "reconnect")));
391
+ actions.push(disconnectButton);
392
+ } else {
393
+ if (declaresSync) {
394
+ actions.push(guarded(!planAllows ? planReason : null, /*#__PURE__*/ _jsx(Button, {
395
+ variant: phase === 'error' ? 'contained' : 'outlined',
396
+ disabled: busy !== null,
397
+ onClick: ()=>void sync(),
398
+ children: busy === 'sync' ? 'Syncing…' : 'Sync now'
399
+ }, "sync")));
400
+ }
401
+ if (entry.resources.length) {
402
+ actions.push(guarded(!planAllows ? planReason : null, /*#__PURE__*/ _jsx(Button, {
403
+ variant: "outlined",
404
+ disabled: busy !== null,
405
+ onClick: ()=>setChanging(0),
406
+ children: 'Change'
407
+ }, "change")));
408
+ }
409
+ actions.push(disconnectButton);
410
+ }
411
+ const chip = PLUGIN_CONNECTION_PHASE_CHIP[phase];
412
+ const shares = pluginConnectionSharesText(entry.hosts);
413
+ const nextSync = pluginConnectionNextSyncAtMs(view, intervalHours);
414
+ const chosen = entry.resources.filter((one)=>view == null ? void 0 : view.choices[one.id]);
415
+ return /*#__PURE__*/ _jsx(CardDisplay, {
416
+ header: label,
417
+ subheader: entry.description,
418
+ help: HELP,
419
+ contentGutterX: true,
420
+ contentGutterY: true,
421
+ HeaderProps: {
422
+ action: /*#__PURE__*/ _jsx(Stack, {
423
+ direction: "row",
424
+ spacing: 1,
425
+ useFlexGap: true,
426
+ sx: {
427
+ flexWrap: 'wrap',
428
+ justifyContent: 'flex-end'
429
+ },
430
+ children: actions.map((action, index)=>/*#__PURE__*/ _jsx("span", {
431
+ children: action
432
+ }, index))
433
+ })
434
+ },
435
+ children: /*#__PURE__*/ _jsxs(Stack, {
436
+ spacing: 1.5,
437
+ children: [
438
+ /*#__PURE__*/ _jsx(Stack, {
439
+ direction: "row",
440
+ spacing: 1,
441
+ children: /*#__PURE__*/ _jsx(StatusChip, {
442
+ label: chip.label,
443
+ tone: chip.tone,
444
+ "data-testid": "plugin-connection-status"
445
+ })
446
+ }),
447
+ phase === 'not-configured' ? /*#__PURE__*/ _jsx(Alert, {
448
+ severity: "info",
449
+ children: PLUGIN_CONNECTIONS_NOT_CONFIGURED_TEXT
450
+ }) : null,
451
+ phase === 'not-set-up' ? /*#__PURE__*/ _jsx(PrerequisiteNotice, {
452
+ children: pluginConnectionNotSetUpText(label)
453
+ }) : null,
454
+ !planAllows && phase !== 'not-configured' && phase !== 'not-set-up' ? /*#__PURE__*/ _jsx(PlanUpsellNotice, {
455
+ planLabel: undefined,
456
+ upgradeHref: upgradeHref,
457
+ children: view ? `${planReason} You can still disconnect it.` : planReason
458
+ }) : null,
459
+ phase === 'not-connected' ? /*#__PURE__*/ _jsx(Typography, {
460
+ variant: "body2",
461
+ color: "text.secondary",
462
+ children: `Connect ${label} so ${pluginName} can use it on this site. You sign in at ${label} and choose what to share; this site never sees your password.`
463
+ }) : null,
464
+ phase === 'reconnect' ? /*#__PURE__*/ _jsx(Alert, {
465
+ severity: "warning",
466
+ children: `${label} no longer accepts this connection. Nothing syncs until you reconnect.`
467
+ }) : null,
468
+ phase === 'error' ? /*#__PURE__*/ _jsx(Alert, {
469
+ severity: "error",
470
+ children: (view == null ? void 0 : view.lastError) ? `The last sync did not work: ${view.lastError}. It tries again on schedule, or you can sync now.` : 'The last sync did not work. It tries again on schedule, or you can sync now.'
471
+ }) : null,
472
+ resource ? /*#__PURE__*/ _jsxs(Stack, {
473
+ spacing: 1,
474
+ children: [
475
+ /*#__PURE__*/ _jsx(Typography, {
476
+ variant: "body2",
477
+ color: "text.secondary",
478
+ children: changing === null ? `Choose the ${resource.label.toLowerCase()} ${pluginName} should use. Nothing syncs until you do.` : `Choose the ${resource.label.toLowerCase()} ${pluginName} should use.`
479
+ }),
480
+ items.status === 'loading' ? /*#__PURE__*/ _jsxs(Stack, {
481
+ direction: "row",
482
+ spacing: 1,
483
+ sx: {
484
+ alignItems: 'center'
485
+ },
486
+ role: "status",
487
+ children: [
488
+ /*#__PURE__*/ _jsx(CircularProgress, {
489
+ size: 18
490
+ }),
491
+ /*#__PURE__*/ _jsx(Typography, {
492
+ variant: "body2",
493
+ color: "text.secondary",
494
+ children: `Asking ${label}…`
495
+ })
496
+ ]
497
+ }) : null,
498
+ items.status === 'error' ? /*#__PURE__*/ _jsx(Alert, {
499
+ severity: "error",
500
+ children: items.message
501
+ }) : null,
502
+ items.status === 'ready' && !items.items.length ? /*#__PURE__*/ _jsx(Alert, {
503
+ severity: "info",
504
+ children: `The ${label} account you connected has nothing to choose here. Connect a different account, or add one at ${label} first.`
505
+ }) : null,
506
+ items.status === 'ready' && items.items.length ? /*#__PURE__*/ _jsx(TextField, {
507
+ select: true,
508
+ size: "small",
509
+ label: resource.label,
510
+ value: picked,
511
+ disabled: busy !== null || !planAllows,
512
+ onChange: (event)=>setPicked(event.target.value),
513
+ sx: {
514
+ maxWidth: 480
515
+ },
516
+ children: items.items.map((item)=>/*#__PURE__*/ _jsx(MenuItem, {
517
+ value: item.id,
518
+ children: item.label
519
+ }, item.id))
520
+ }) : null
521
+ ]
522
+ }) : null,
523
+ view && chosen.length && changing === null ? /*#__PURE__*/ _jsx(Stack, {
524
+ spacing: 0.5,
525
+ children: chosen.map((one)=>{
526
+ var _view_choices_one_id;
527
+ return /*#__PURE__*/ _jsxs(Typography, {
528
+ variant: "body2",
529
+ children: [
530
+ /*#__PURE__*/ _jsx(Typography, {
531
+ component: "span",
532
+ variant: "body2",
533
+ color: "text.secondary",
534
+ children: `${one.label}: `
535
+ }),
536
+ (_view_choices_one_id = view.choices[one.id]) == null ? void 0 : _view_choices_one_id.label
537
+ ]
538
+ }, one.id);
539
+ })
540
+ }) : null,
541
+ view && declaresSync && (phase === 'connected' || phase === 'error') ? /*#__PURE__*/ _jsxs(Stack, {
542
+ spacing: 0.5,
543
+ children: [
544
+ /*#__PURE__*/ _jsx(Typography, {
545
+ variant: "body2",
546
+ color: "text.secondary",
547
+ children: view.lastSyncAtMs !== null ? `Last synced ${formatConnectionTime(view.lastSyncAtMs)}` : 'Not synced yet'
548
+ }),
549
+ nextSync !== null ? /*#__PURE__*/ _jsx(Typography, {
550
+ variant: "body2",
551
+ color: "text.secondary",
552
+ children: nextSync === 'soon' ? 'Next sync: within the hour' : `Next sync ${formatConnectionTime(nextSync)}`
553
+ }) : null
554
+ ]
555
+ }) : null,
556
+ shares ? /*#__PURE__*/ _jsx(Typography, {
557
+ variant: "caption",
558
+ color: "text.secondary",
559
+ children: shares
560
+ }) : null,
561
+ wait ? /*#__PURE__*/ _jsx(Alert, {
562
+ severity: "info",
563
+ onClose: ()=>setWait(null),
564
+ children: wait
565
+ }) : null,
566
+ error ? /*#__PURE__*/ _jsx(Alert, {
567
+ severity: "error",
568
+ onClose: ()=>setError(null),
569
+ children: error
570
+ }) : null
571
+ ]
572
+ })
573
+ });
574
+ }
575
+
576
+ //# sourceMappingURL=site-plugin-connections-card.component.js.map