@aglyn/plugins-marketplace 1.15.0 → 1.15.2
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/README.md +1 -0
- package/package.json +15 -14
- package/src/lib/components/editor-connections-provider.component.d.ts +37 -0
- package/src/lib/components/editor-connections-provider.component.js +161 -0
- package/src/lib/components/editor-connections-provider.component.js.map +1 -0
- package/src/lib/components/listing-connection-credentials-card.component.d.ts +33 -0
- package/src/lib/components/listing-connection-credentials-card.component.js +318 -0
- package/src/lib/components/listing-connection-credentials-card.component.js.map +1 -0
- package/src/lib/components/listing-detail-page.component.js +10 -2
- package/src/lib/components/listing-detail-page.component.js.map +1 -1
- package/src/lib/components/site-plugin-connections-card.component.d.ts +38 -0
- package/src/lib/components/site-plugin-connections-card.component.js +576 -0
- package/src/lib/components/site-plugin-connections-card.component.js.map +1 -0
- package/src/lib/declarations.console-server.d.ts +17 -0
- package/src/lib/declarations.console-server.js +41 -0
- package/src/lib/declarations.console-server.js.map +1 -0
- package/src/lib/declarations.js +11 -2
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/declarations.server.js +68 -1
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/hooks/use-plugin-connections-api.d.ts +89 -0
- package/src/lib/hooks/use-plugin-connections-api.js +132 -0
- package/src/lib/hooks/use-plugin-connections-api.js.map +1 -0
- package/src/lib/model/editor-connections.d.ts +46 -0
- package/src/lib/model/editor-connections.js +70 -0
- package/src/lib/model/editor-connections.js.map +1 -0
- package/src/lib/model/listing-price-floor.js +7 -4
- package/src/lib/model/listing-price-floor.js.map +1 -1
- package/src/lib/model/marketplace-publisher-permissions.d.ts +3 -1
- package/src/lib/model/marketplace-publisher-permissions.js +14 -0
- package/src/lib/model/marketplace-publisher-permissions.js.map +1 -1
- package/src/lib/model/marketplace-site-permissions.d.ts +9 -0
- package/src/lib/model/marketplace-site-permissions.js +29 -0
- package/src/lib/model/marketplace-site-permissions.js.map +1 -1
- package/src/lib/model/plugin-connection-card.d.ts +88 -0
- package/src/lib/model/plugin-connection-card.js +187 -0
- package/src/lib/model/plugin-connection-card.js.map +1 -0
- package/src/lib/model/plugin-connections.d.ts +172 -0
- package/src/lib/model/plugin-connections.js +119 -0
- package/src/lib/model/plugin-connections.js.map +1 -0
- package/src/lib/plugin.js +21 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/admin-reviews.js +18 -12
- package/src/lib/server/admin-reviews.js.map +1 -1
- package/src/lib/server/artifact-inventory.js +3 -1
- package/src/lib/server/artifact-inventory.js.map +1 -1
- package/src/lib/server/checkout.js +4 -0
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/connections/cleanup.d.ts +84 -0
- package/src/lib/server/connections/cleanup.js +116 -0
- package/src/lib/server/connections/cleanup.js.map +1 -0
- package/src/lib/server/connections/config.d.ts +42 -0
- package/src/lib/server/connections/config.js +48 -0
- package/src/lib/server/connections/config.js.map +1 -0
- package/src/lib/server/connections/connection-store.d.ts +173 -0
- package/src/lib/server/connections/connection-store.js +331 -0
- package/src/lib/server/connections/connection-store.js.map +1 -0
- package/src/lib/server/connections/credentials-store.d.ts +71 -0
- package/src/lib/server/connections/credentials-store.js +78 -0
- package/src/lib/server/connections/credentials-store.js.map +1 -0
- package/src/lib/server/connections/erasure.d.ts +25 -0
- package/src/lib/server/connections/erasure.js +72 -0
- package/src/lib/server/connections/erasure.js.map +1 -0
- package/src/lib/server/connections/installed-connection.d.ts +64 -0
- package/src/lib/server/connections/installed-connection.js +92 -0
- package/src/lib/server/connections/installed-connection.js.map +1 -0
- package/src/lib/server/connections/oauth-state.d.ts +129 -0
- package/src/lib/server/connections/oauth-state.js +252 -0
- package/src/lib/server/connections/oauth-state.js.map +1 -0
- package/src/lib/server/connections/provider-http.d.ts +99 -0
- package/src/lib/server/connections/provider-http.js +253 -0
- package/src/lib/server/connections/provider-http.js.map +1 -0
- package/src/lib/server/connections/record-announce.d.ts +20 -0
- package/src/lib/server/connections/record-announce.js +53 -0
- package/src/lib/server/connections/record-announce.js.map +1 -0
- package/src/lib/server/connections/record-mapping.d.ts +63 -0
- package/src/lib/server/connections/record-mapping.js +151 -0
- package/src/lib/server/connections/record-mapping.js.map +1 -0
- package/src/lib/server/connections/record-routes.d.ts +17 -0
- package/src/lib/server/connections/record-routes.js +96 -0
- package/src/lib/server/connections/record-routes.js.map +1 -0
- package/src/lib/server/connections/revoke.d.ts +29 -0
- package/src/lib/server/connections/revoke.js +52 -0
- package/src/lib/server/connections/revoke.js.map +1 -0
- package/src/lib/server/connections/routes.d.ts +77 -0
- package/src/lib/server/connections/routes.js +706 -0
- package/src/lib/server/connections/routes.js.map +1 -0
- package/src/lib/server/connections/site-records-platform.d.ts +24 -0
- package/src/lib/server/connections/site-records-platform.js +94 -0
- package/src/lib/server/connections/site-records-platform.js.map +1 -0
- package/src/lib/server/connections/site-records.d.ts +95 -0
- package/src/lib/server/connections/site-records.js +221 -0
- package/src/lib/server/connections/site-records.js.map +1 -0
- package/src/lib/server/connections/sync.d.ts +110 -0
- package/src/lib/server/connections/sync.js +414 -0
- package/src/lib/server/connections/sync.js.map +1 -0
- package/src/lib/server/install-plugin.js +56 -6
- package/src/lib/server/install-plugin.js.map +1 -1
- package/src/lib/server/publish-plugin.js +5 -2
- package/src/lib/server/publish-plugin.js.map +1 -1
- package/src/lib/server/revenue-source.js +6 -5
- package/src/lib/server/revenue-source.js.map +1 -1
- package/src/lib/server.js +60 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/memory-firestore.d.ts +34 -0
- package/src/lib/testing/memory-firestore.js +250 -0
- 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
|